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 <c.lobrano@gmail.com>
This commit is contained in:
Carlo Lobrano
2022-06-17 09:54:22 +02:00
parent 78ecdad5f8
commit 4a981c490b
4 changed files with 86 additions and 25 deletions

View File

@ -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 machines 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'

View File

@ -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
}

View File

@ -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 \
"[]"

View File

@ -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