From 4a981c490be01547c17fb0b1fc4d1c3bfbf3551e Mon Sep 17 00:00:00 2001 From: Carlo Lobrano Date: Fri, 17 Jun 2022 09:54:22 +0200 Subject: allow filter networks by dangling status add the ability to filter networks by their dangling status via: `network ls --filter dangling=true/false` Fixes: #14595 Signed-off-by: Carlo Lobrano --- docs/source/markdown/podman-network-ls.1.md | 3 ++ pkg/domain/infra/abi/network.go | 84 +++++++++++++++++++++-------- test/apiv2/35-networks.at | 4 +- test/e2e/network_test.go | 20 +++++++ 4 files changed, 86 insertions(+), 25 deletions(-) diff --git a/docs/source/markdown/podman-network-ls.1.md b/docs/source/markdown/podman-network-ls.1.md index b341083f9..3c696d404 100644 --- a/docs/source/markdown/podman-network-ls.1.md +++ b/docs/source/markdown/podman-network-ls.1.md @@ -25,6 +25,7 @@ Supported filters: | label | Filter by network with (or without, in the case of label!=[...] is used) the specified labels. | | name | Filter by network name (accepts `regex`). | | until | Filter by networks created before given timestamp. | +| dangling | Filter by networks with no containers attached. | The `driver` filter accepts values: `bridge`, `macvlan`, `ipvlan`. @@ -33,6 +34,8 @@ The `label` *filter* accepts two formats. One is the `label`=*key* or `label`=*k The `until` *filter* can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time. +The `dangling` *filter* accepts values `true` or `false`. + #### **--format**=*format* Change the default output format. This can be of a supported type like 'json' diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index 47f7917f4..43047c936 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -2,6 +2,7 @@ package abi import ( "context" + "strconv" "github.com/containers/common/libnetwork/types" netutil "github.com/containers/common/libnetwork/util" @@ -12,10 +13,39 @@ import ( ) func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]types.Network, error) { + // dangling filter is not provided by netutil + var wantDangling bool + + val, filterDangling := options.Filters["dangling"] + if filterDangling { + switch len(val) { + case 0: + return nil, errors.Errorf("got no values for filter key \"dangling\"") + case 1: + var err error + wantDangling, err = strconv.ParseBool(val[0]) + if err != nil { + return nil, errors.Errorf("invalid dangling filter value \"%v\"", val[0]) + } + delete(options.Filters, "dangling") + default: + return nil, errors.Errorf("got more than one value for filter key \"dangling\"") + } + } + filters, err := netutil.GenerateNetworkFilters(options.Filters) if err != nil { return nil, err } + + if filterDangling { + danglingFilterFunc, err := ic.createDanglingFilterFunc(wantDangling) + if err != nil { + return nil, err + } + + filters = append(filters, danglingFilterFunc) + } nets, err := ic.Libpod.Network().NetworkList(filters...) return nets, err } @@ -144,6 +174,33 @@ func (ic *ContainerEngine) NetworkExists(ctx context.Context, networkname string // Network prune removes unused cni networks func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.NetworkPruneOptions) ([]*entities.NetworkPruneReport, error) { + // get all filters + filters, err := netutil.GenerateNetworkPruneFilters(options.Filters) + if err != nil { + return nil, err + } + danglingFilterFunc, err := ic.createDanglingFilterFunc(true) + if err != nil { + return nil, err + } + filters = append(filters, danglingFilterFunc) + nets, err := ic.Libpod.Network().NetworkList(filters...) + if err != nil { + return nil, err + } + + pruneReport := make([]*entities.NetworkPruneReport, 0, len(nets)) + for _, net := range nets { + pruneReport = append(pruneReport, &entities.NetworkPruneReport{ + Name: net.Name, + Error: ic.Libpod.Network().NetworkRemove(net.Name), + }) + } + return pruneReport, nil +} + +// danglingFilter function is special and not implemented in libnetwork filters +func (ic *ContainerEngine) createDanglingFilterFunc(wantDangling bool) (types.FilterFunc, error) { cons, err := ic.Libpod.GetAllContainers() if err != nil { return nil, err @@ -163,31 +220,12 @@ func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.Ne // ignore the default network, this one cannot be deleted networksToKeep[ic.Libpod.GetDefaultNetworkName()] = true - // get all filters - filters, err := netutil.GenerateNetworkPruneFilters(options.Filters) - if err != nil { - return nil, err - } - danglingFilterFunc := func(net types.Network) bool { + return func(net types.Network) bool { for network := range networksToKeep { if network == net.Name { - return false + return !wantDangling } } - return true - } - filters = append(filters, danglingFilterFunc) - nets, err := ic.Libpod.Network().NetworkList(filters...) - if err != nil { - return nil, err - } - - pruneReport := make([]*entities.NetworkPruneReport, 0, len(nets)) - for _, net := range nets { - pruneReport = append(pruneReport, &entities.NetworkPruneReport{ - Name: net.Name, - Error: ic.Libpod.Network().NetworkRemove(net.Name), - }) - } - return pruneReport, nil + return wantDangling + }, nil } diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at index 4aad4563d..fcff26521 100644 --- a/test/apiv2/35-networks.at +++ b/test/apiv2/35-networks.at @@ -78,8 +78,8 @@ t GET networks?filters="{\"id\":[\"$network1_id\"]}" 200 \ .[0].Name=network1 \ .[0].Id=$network1_id # invalid filter -t GET networks?filters='{"dangling":["1"]}' 500 \ - .cause='invalid filter "dangling"' +t GET networks?filters='{"dangling":["true","0"]}' 500 \ + .cause="got more than one value for filter key \"dangling\"" # (#9293 with no networks the endpoint should return empty array instead of null) t GET networks?filters='{"name":["doesnotexists"]}' 200 \ "[]" diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index 715455521..2fdd62f7e 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -163,6 +163,26 @@ var _ = Describe("Podman network", func() { Expect(session.OutputToString()).To(Not(ContainSubstring(name))) }) + It("podman network list --filter dangling", func() { + name, path := generateNetworkConfig(podmanTest) + defer removeConf(path) + + session := podmanTest.Podman([]string{"network", "ls", "--filter", "dangling=true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring(name)) + + session = podmanTest.Podman([]string{"network", "ls", "--filter", "dangling=false"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).NotTo(ContainSubstring(name)) + + session = podmanTest.Podman([]string{"network", "ls", "--filter", "dangling=foo"}) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError()) + Expect(session.ErrorToString()).To(ContainSubstring(`invalid dangling filter value "foo"`)) + }) + It("podman network ID test", func() { net := "networkIDTest" // the network id should be the sha256 hash of the network name -- cgit v1.2.3-54-g00ecf