Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for dangling filter to volumes #6756

Merged
merged 1 commit into from Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 11 additions & 1 deletion libpod/volume.go
Expand Up @@ -137,7 +137,7 @@ func (v *Volume) Config() (*VolumeConfig, error) {

// VolumeInUse goes through the container dependencies of a volume
// and checks if the volume is being used by any container.
func (v *Volume) VolumesInUse() ([]string, error) {
func (v *Volume) VolumeInUse() ([]string, error) {
v.lock.Lock()
defer v.lock.Unlock()

Expand All @@ -146,3 +146,13 @@ func (v *Volume) VolumesInUse() ([]string, error) {
}
return v.runtime.state.VolumeInUse(v)
}

// IsDangling returns whether this volume is dangling (unused by any
// containers).
func (v *Volume) IsDangling() (bool, error) {
ctrs, err := v.VolumeInUse()
if err != nil {
return false, err
}
return len(ctrs) == 0, nil
}
23 changes: 23 additions & 0 deletions pkg/domain/filters/volumes.go
Expand Up @@ -61,6 +61,29 @@ func GenerateVolumeFilters(filters map[string][]string) ([]libpod.VolumeFilter,
}
return false
})
case "dangling":
danglingVal := val
invert := false
switch strings.ToLower(danglingVal) {
case "true", "1":
// Do nothing
case "false", "0":
// Dangling=false requires that we
// invert the result of IsDangling.
invert = true
default:
return nil, errors.Errorf("%q is not a valid value for the \"dangling\" filter - must be true or false", danglingVal)
}
vf = append(vf, func(v *libpod.Volume) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docker API spec for this filter is a bit ambiguous IMO. Since it's a filter defined as an element of a map[string][]string, it's sounds like it's possible to specify it repeatedly and the spec doesn't disallow that by mutually excluding the possible boolean values. This code does exactly that but the interesting consequence is that the way we build our filter set is exclusive, not inclusive; meaning that if we specify {"dangling": ["true", "false"]}, we'd expect to get no results based on the code as it stands; but it's not clear if this is what a user might expect or what the API spec demands.

This actually has a more interesting consequence (that might be enlightening for if any structural changes need to be made) in the name filter where the exclusive filtering causes us to not be able to match multiple volumes if we specify all of their names.

e.g.

sh-5.0$ podman volume ls
DRIVER      VOLUME NAME
local       bar
local       foo
sh-5.0$ python -c 'import urllib.parse; import json; print(urllib.parse.quote(json.dumps({"name": ["foo", "bar"]})))'
%7B%22name%22%3A%20%5B%22foo%22%2C%20%22bar%22%5D%7D
sh-5.0$ curl --unix-socket /tmp/podman.sock -H "Content-Type: application/json" 'http://unixsocket/v1.40/volumes?filters=%7B%22name%22%3A%20%5B%22foo%22%2C%20%22bar%22%5D%7D' | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    29  100    29    0     0   9666      0 --:--:-- --:--:-- --:--:--  9666
{
  "Volumes": [],
  "Warnings": []
}
sh-5.0$ curl --unix-socket /tmp/podman.sock -H "Content-Type: application/json" 'http://unixsocket/v1.40/volumes?filters=%7B%22name%22%3A%20%5B%22foo%22%5D%7D' | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   218  100   218    0     0  72666      0 --:--:-- --:--:-- --:--:--  106k
{
  "Volumes": [
    {
      "CreatedAt": "2020-06-25T10:29:50+10:00",
      "Driver": "local",
      "Labels": {},
      "Mountpoint": "/home/user/.local/share/containers/storage/volumes/foo/_data",
      "Name": "foo",
      "Options": {},
      "Scope": "local"
    }
  ],
  "Warnings": []
}
sh-5.0$ curl --unix-socket /tmp/podman.sock -H "Content-Type: application/json" 'http://unixsocket/v1.40/volumes?filters=%7B%22name%22%3A%20%5B%22bar%22%5D%7D' | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   218  100   218    0     0  72666      0 --:--:-- --:--:-- --:--:--  106k
{
  "Volumes": [
    {
      "CreatedAt": "2020-06-25T10:29:52+10:00",
      "Driver": "local",
      "Labels": {},
      "Mountpoint": "/home/user/.local/share/containers/storage/volumes/bar/_data",
      "Name": "bar",
      "Options": {},
      "Scope": "local"
    }
  ],
  "Warnings": []
}

I'll make an issue to follow that up since it's not really specifically relevant to this change. In the meantime, perhaps we could amend this changeset to explode if we hit both sides of the case statement, even though the Docker API doesn't forbid that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably only want to generate one filter for each filter type, and within that filter check if the value given is any one of the user-provided inputs. Should not be a big change.

Agree that we should not allow both true and false filters to be passed; honestly, I'd expect to error if more than one dangling= filter was passed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I briefly tested this against a docker on another machine and it looks like they support multiple filters and combine them inclusively. So you can (weirdly) provide dangling=["true", "false"] to get all volumes.

dangling, err := v.IsDangling()
if err != nil {
return false
}
if invert {
return !dangling
}
return dangling
})
default:
return nil, errors.Errorf("%q is in an invalid volume filter", filter)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/infra/abi/system.go
Expand Up @@ -330,7 +330,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System
if err != nil {
return nil, err
}
inUse, err := v.VolumesInUse()
inUse, err := v.VolumeInUse()
if err != nil {
return nil, err
}
Expand Down
27 changes: 27 additions & 0 deletions test/e2e/volume_ls_test.go
@@ -1,6 +1,7 @@
package integration

import (
"fmt"
"os"

. "github.com/containers/libpod/test/utils"
Expand Down Expand Up @@ -82,4 +83,30 @@ var _ = Describe("Podman volume ls", func() {
Expect(len(session.OutputToStringArray())).To(Equal(2))
Expect(session.OutputToStringArray()[1]).To(ContainSubstring(volName))
})

It("podman volume ls with --filter dangling", func() {
volName1 := "volume1"
session := podmanTest.Podman([]string{"volume", "create", volName1})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))

volName2 := "volume2"
session2 := podmanTest.Podman([]string{"volume", "create", volName2})
session2.WaitWithDefaultTimeout()
Expect(session2.ExitCode()).To(Equal(0))

ctr := podmanTest.Podman([]string{"create", "-v", fmt.Sprintf("%s:/test", volName2), ALPINE, "sh"})
ctr.WaitWithDefaultTimeout()
Expect(ctr.ExitCode()).To(Equal(0))

lsNoDangling := podmanTest.Podman([]string{"volume", "ls", "--filter", "dangling=false", "--quiet"})
lsNoDangling.WaitWithDefaultTimeout()
Expect(lsNoDangling.ExitCode()).To(Equal(0))
Expect(lsNoDangling.OutputToString()).To(ContainSubstring(volName2))

lsDangling := podmanTest.Podman([]string{"volume", "ls", "--filter", "dangling=true", "--quiet"})
lsDangling.WaitWithDefaultTimeout()
Expect(lsDangling.ExitCode()).To(Equal(0))
Expect(lsDangling.OutputToString()).To(ContainSubstring(volName1))
})
})