podman network label support

Add label support for podman network create. Use the `args`
field in the cni config file to store the podman labels.
Use `podman_labels` as key name and store the labels as
map[string]string.

For reference: https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config
https://github.com/containernetworking/cni/blob/spec-v0.4.0/SPEC.md#network-configuration

Example snippet:

```
...
"args": {
	"podman_labels": {
		"key1":"value1",
		"key2":"value2"
	}
}
...
```

Make podman network list support several filters. Supported filters are name,
plugin, driver and label. Filters with different keys work exclusive. Several label
filters work exclusive and the other filter keys are working inclusive.

Also adjust the compat api to support labels in network create and list.

Breaking changes:

- podman network ls -f shortform is used for --filter instead --format
This matches docker and other podman commands (container ps, volume ps)

- libpod network list endpoint filter parameter is removed. Instead the
filters paramter should be used as json encoded map[string][]string.

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
This commit is contained in:
Paul Holzinger
2020-11-27 18:02:27 +01:00
parent ad2439264d
commit 8494bcb866
15 changed files with 285 additions and 96 deletions

View File

@ -6,9 +6,11 @@ import (
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -27,6 +29,7 @@ var (
var (
networkCreateOptions entities.NetworkCreateOptions
labels []string
)
func networkCreateFlags(cmd *cobra.Command) {
@ -50,6 +53,10 @@ func networkCreateFlags(cmd *cobra.Command) {
flags.StringVar(&networkCreateOptions.MacVLAN, macvlanFlagName, "", "create a Macvlan connection based on this device")
_ = cmd.RegisterFlagCompletionFunc(macvlanFlagName, completion.AutocompleteNone)
labelFlagName := "label"
flags.StringArrayVar(&labels, labelFlagName, nil, "set metadata on a network")
_ = cmd.RegisterFlagCompletionFunc(labelFlagName, completion.AutocompleteNone)
// TODO not supported yet
// flags.StringVar(&networkCreateOptions.IPamDriver, "ipam-driver", "", "IP Address Management Driver")
@ -81,6 +88,11 @@ func networkCreate(cmd *cobra.Command, args []string) error {
}
name = args[0]
}
var err error
networkCreateOptions.Labels, err = parse.GetAllLabels([]string{}, labels)
if err != nil {
return errors.Wrap(err, "failed to parse labels")
}
response, err := registry.ContainerEngine().NetworkCreate(registry.Context(), name, networkCreateOptions)
if err != nil {
return err

View File

@ -16,6 +16,7 @@ import (
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@ -35,17 +36,18 @@ var (
var (
networkListOptions entities.NetworkListOptions
filters []string
)
func networkListFlags(flags *pflag.FlagSet) {
formatFlagName := "format"
flags.StringVarP(&networkListOptions.Format, formatFlagName, "f", "", "Pretty-print networks to JSON or using a Go template")
flags.StringVar(&networkListOptions.Format, formatFlagName, "", "Pretty-print networks to JSON or using a Go template")
_ = networklistCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat)
flags.BoolVarP(&networkListOptions.Quiet, "quiet", "q", false, "display only names")
filterFlagName := "filter"
flags.StringVarP(&networkListOptions.Filter, filterFlagName, "", "", "Provide filter values (e.g. 'name=podman')")
flags.StringArrayVarP(&filters, filterFlagName, "f", nil, "Provide filter values (e.g. 'name=podman')")
_ = networklistCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteNetworkFilters)
}
@ -61,14 +63,14 @@ func init() {
}
func networkList(cmd *cobra.Command, args []string) error {
// validate the filter pattern.
if len(networkListOptions.Filter) > 0 {
tokens := strings.Split(networkListOptions.Filter, "=")
if len(tokens) != 2 {
return fmt.Errorf("invalid filter syntax : %s", networkListOptions.Filter)
networkListOptions.Filters = make(map[string][]string)
for _, f := range filters {
split := strings.SplitN(f, "=", 2)
if len(split) == 1 {
return errors.Errorf("invalid filter %q", f)
}
networkListOptions.Filters[split[0]] = append(networkListOptions.Filters[split[0]], split[1])
}
responses, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions)
if err != nil {
return err
@ -93,6 +95,7 @@ func networkList(cmd *cobra.Command, args []string) error {
"CNIVersion": "version",
"Version": "version",
"Plugins": "plugins",
"Labels": "labels",
})
renderHeaders := true
row := "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n"
@ -144,3 +147,11 @@ func (n ListPrintReports) Version() string {
func (n ListPrintReports) Plugins() string {
return network.GetCNIPlugins(n.NetworkConfigList)
}
func (n ListPrintReports) Labels() string {
list := make([]string, 0, len(n.NetworkListReport.Labels))
for k, v := range n.NetworkListReport.Labels {
list = append(list, k+"="+v)
}
return strings.Join(list, ",")
}

View File

@ -40,6 +40,10 @@ Restrict external access of this network
Allocate container IP from a range. The range must be a complete subnet and in CIDR notation. The *ip-range* option
must be used with a *subnet* option.
#### **--label**
Set metadata for a network (e.g., --label mykey=value).
#### **--macvlan**
Create a *Macvlan* based connection rather than a classic bridge. You must pass an interface name from the host for the

View File

@ -14,13 +14,25 @@ Displays a list of existing podman networks. This command is not available for r
The `quiet` option will restrict the output to only the network names.
#### **--format**, **-f**
#### **--format**
Pretty-print networks to JSON or using a Go template.
#### **--filter**
#### **--filter**, **-f**
Provide filter values (e.g. 'name=podman').
Filter output based on conditions given.
Multiple filters can be given with multiple uses of the --filter flag.
Filters with the same key work inclusive with the only exception being
`label` which is exclusive. Filters with different keys always work exclusive.
Valid filters are listed below:
| **Filter** | **Description** |
| ---------- | ------------------------------------------------------------------------------------- |
| name | [Name] Network name (accepts regex) |
| label | [Key] or [Key=Value] Label assigned to a network |
| plugin | [Plugin] CNI plugins included in a network (e.g `bridge`,`portmap`,`firewall`,`tuning`,`dnsname`,`macvlan`) |
| driver | [Driver] Only `bridge` is supported |
## EXAMPLE

View File

@ -169,7 +169,7 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon
}
// create CNI plugin configuration
ncList := NewNcList(name, version.Current())
ncList := NewNcList(name, version.Current(), options.Labels)
var plugins []CNIPlugins
// TODO need to iron out the role of isDefaultGW and IPMasq
bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig)
@ -223,7 +223,7 @@ func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeCo
return "", err
}
}
ncList := NewNcList(name, version.Current())
ncList := NewNcList(name, version.Current(), options.Labels)
macvlan := NewMacVLANPlugin(options.MacVLAN)
plugins = append(plugins, macvlan)
ncList["plugins"] = plugins

View File

@ -12,6 +12,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v2/libpod/define"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// GetCNIConfDir get CNI configuration directory
@ -86,6 +87,35 @@ func GetCNIPlugins(list *libcni.NetworkConfigList) string {
return strings.Join(plugins, ",")
}
// GetNetworkLabels returns a list of labels as a string
func GetNetworkLabels(list *libcni.NetworkConfigList) NcLabels {
cniJSON := make(map[string]interface{})
err := json.Unmarshal(list.Bytes, &cniJSON)
if err != nil {
logrus.Errorf("failed to unmarshal network config %v %v", cniJSON["name"], err)
return nil
}
if args, ok := cniJSON["args"]; ok {
if key, ok := args.(map[string]interface{}); ok {
if labels, ok := key[PodmanLabelKey]; ok {
if labels, ok := labels.(map[string]interface{}); ok {
result := make(NcLabels, len(labels))
for k, v := range labels {
if v, ok := v.(string); ok {
result[k] = v
} else {
logrus.Errorf("network config %v invalid label value type %T should be string", cniJSON["name"], labels)
}
}
return result
}
logrus.Errorf("network config %v invalid label type %T should be map[string]string", cniJSON["name"], labels)
}
}
}
return nil
}
// GetNetworksFromFilesystem gets all the networks from the cni configuration
// files
func GetNetworksFromFilesystem(config *config.Config) ([]*allocator.Net, error) {

View File

@ -4,6 +4,11 @@ import (
"net"
"os"
"path/filepath"
"strings"
"github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v2/pkg/util"
"github.com/pkg/errors"
)
const (
@ -14,12 +19,24 @@ const (
// NcList describes a generic map
type NcList map[string]interface{}
// NcArgs describes the cni args field
type NcArgs map[string]NcLabels
// NcLabels describes the label map
type NcLabels map[string]string
// PodmanLabelKey key used to store the podman network label in a cni config
const PodmanLabelKey = "podman_labels"
// NewNcList creates a generic map of values with string
// keys and adds in version and network name
func NewNcList(name, version string) NcList {
func NewNcList(name, version string, labels NcLabels) NcList {
n := NcList{}
n["cniVersion"] = version
n["name"] = name
if len(labels) > 0 {
n["args"] = NcArgs{PodmanLabelKey: labels}
}
return n
}
@ -159,3 +176,64 @@ func NewMacVLANPlugin(device string) MacVLANConfig {
}
return m
}
// IfPassesFilter filters NetworkListReport and returns true if the filter match the given config
func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]string) (bool, error) {
result := true
for key, filterValues := range filters {
result = false
switch strings.ToLower(key) {
case "name":
// matches one name, regex allowed
result = util.StringMatchRegexSlice(netconf.Name, filterValues)
case "plugin":
// match one plugin
plugins := GetCNIPlugins(netconf)
for _, val := range filterValues {
if strings.Contains(plugins, val) {
result = true
break
}
}
case "label":
// matches all labels
labels := GetNetworkLabels(netconf)
outer:
for _, filterValue := range filterValues {
filterArray := strings.SplitN(filterValue, "=", 2)
filterKey := filterArray[0]
if len(filterArray) > 1 {
filterValue = filterArray[1]
} else {
filterValue = ""
}
for labelKey, labelValue := range labels {
if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
result = true
continue outer
}
}
result = false
}
case "driver":
// matches only for the DefaultNetworkDriver
for _, filterValue := range filterValues {
plugins := GetCNIPlugins(netconf)
if filterValue == DefaultNetworkDriver &&
strings.Contains(plugins, DefaultNetworkDriver) {
result = true
}
}
// TODO: add dangling filter
// TODO TODO: add id filter if we support ids
default:
return false, errors.Errorf("invalid filter %q", key)
}
}
return result, nil
}

View File

@ -50,7 +50,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.NetworkNotFound(w, name, err)
return
}
report, err := getNetworkResourceByName(name, runtime)
report, err := getNetworkResourceByName(name, runtime, nil)
if err != nil {
utils.InternalServerError(w, err)
return
@ -58,7 +58,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, report)
}
func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.NetworkResource, error) {
func getNetworkResourceByName(name string, runtime *libpod.Runtime, filters map[string][]string) (*types.NetworkResource, error) {
var (
ipamConfigs []dockerNetwork.IPAMConfig
)
@ -85,6 +85,16 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
if err != nil {
return nil, err
}
if len(filters) > 0 {
ok, err := network.IfPassesFilter(conf, filters)
if err != nil {
return nil, err
}
if !ok {
// do not return the config if we did not match the filter
return nil, nil
}
}
// No Bridge plugin means we bail
bridge, err := genericPluginsToBridge(conf.Plugins, network.DefaultNetworkDriver)
@ -129,14 +139,14 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
Options: nil,
Config: ipamConfigs,
},
Internal: false,
Internal: !bridge.IsGW,
Attachable: false,
Ingress: false,
ConfigFrom: dockerNetwork.ConfigReference{},
ConfigOnly: false,
Containers: containerEndpoints,
Options: nil,
Labels: nil,
Labels: network.GetNetworkLabels(conf),
Peers: nil,
Services: nil,
}
@ -180,42 +190,24 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
return
}
filterNames, nameFilterExists := query.Filters["name"]
// TODO remove when filters are implemented
if (!nameFilterExists && len(query.Filters) > 0) || len(query.Filters) > 1 {
utils.InternalServerError(w, errors.New("only the name filter for listing networks is implemented"))
return
}
netNames, err := network.GetNetworkNamesFromFileSystem(config)
if err != nil {
utils.InternalServerError(w, err)
return
}
// filter by name
if nameFilterExists {
names := []string{}
for _, name := range netNames {
for _, filter := range filterNames {
if strings.Contains(name, filter) {
names = append(names, name)
break
}
}
}
netNames = names
}
reports := make([]*types.NetworkResource, 0, len(netNames))
var reports []*types.NetworkResource
logrus.Errorf("netNames: %q", strings.Join(netNames, ", "))
for _, name := range netNames {
report, err := getNetworkResourceByName(name, runtime)
report, err := getNetworkResourceByName(name, runtime, query.Filters)
if err != nil {
utils.InternalServerError(w, err)
return
}
if report != nil {
reports = append(reports, report)
}
}
utils.WriteResponse(w, http.StatusOK, reports)
}
@ -245,6 +237,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
ncOptions := entities.NetworkCreateOptions{
Driver: network.DefaultNetworkDriver,
Internal: networkCreate.Internal,
Labels: networkCreate.Labels,
}
if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil {
if len(networkCreate.IPAM.Config) > 1 {

View File

@ -48,7 +48,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Filter string `schema:"filter"`
Filters map[string][]string `schema:"filters"`
}{
// override any golang type defaults
}
@ -59,7 +59,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
}
options := entities.NetworkListOptions{
Filter: query.Filter,
Filters: query.Filters,
}
ic := abi.ContainerEngine{Libpod: runtime}
reports, err := ic.NetworkList(r.Context(), options)

View File

@ -65,7 +65,11 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// - in: query
// name: filters
// type: string
// description: JSON encoded value of the filters (a map[string][]string) to process on the networks list. Only the name filter is supported.
// description: |
// JSON encoded value of the filters (a map[string][]string) to process on the network list. Currently available filters:
// - name=[name] Matches network name (accepts regex).
// - driver=[driver] Only bridge is supported.
// - label=[key] or label=[key=value] Matches networks based on the presence of a label alone or a label and a value.
// produces:
// - application/json
// responses:
@ -216,9 +220,14 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// description: Display summary of network configurations
// parameters:
// - in: query
// name: filter
// name: filters
// type: string
// description: Provide filter values (e.g. 'name=podman')
// description: |
// JSON encoded value of the filters (a map[string][]string) to process on the network list. Available filters:
// - name=[name] Matches network name (accepts regex).
// - driver=[driver] Only bridge is supported.
// - label=[key] or label=[key=value] Matches networks based on the presence of a label alone or a label and a value.
// - plugin=[plugin] Matches CNI plugins included in a network (e.g `bridge`,`portmap`,`firewall`,`tuning`,`dnsname`,`macvlan`)
// produces:
// - application/json
// responses:

View File

@ -2,6 +2,7 @@ package network
import (
"context"
"encoding/json"
"net/http"
"net/url"
"strconv"
@ -79,8 +80,12 @@ func List(ctx context.Context, options entities.NetworkListOptions) ([]*entities
return nil, err
}
params := url.Values{}
if options.Filter != "" {
params.Set("filter", options.Filter)
if options.Filters != nil {
b, err := json.Marshal(options.Filters)
if err != nil {
return nil, err
}
params.Set("filters", string(b))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", params, nil)
if err != nil {

View File

@ -10,12 +10,13 @@ import (
type NetworkListOptions struct {
Format string
Quiet bool
Filter string
Filters map[string][]string
}
// NetworkListReport describes the results from listing networks
type NetworkListReport struct {
*libcni.NetworkConfigList
Labels map[string]string
}
// NetworkInspectReport describes the results from inspect networks
@ -39,6 +40,7 @@ type NetworkCreateOptions struct {
Driver string
Gateway net.IP
Internal bool
Labels map[string]string
MacVLAN string
Range net.IPNet
Subnet net.IPNet

View File

@ -2,10 +2,7 @@ package abi
import (
"context"
"fmt"
"strings"
"github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/domain/entities"
@ -26,18 +23,16 @@ func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.Net
return nil, err
}
var tokens []string
// tokenize the networkListOptions.Filter in key=value.
if len(options.Filter) > 0 {
tokens = strings.Split(options.Filter, "=")
if len(tokens) != 2 {
return nil, fmt.Errorf("invalid filter syntax : %s", options.Filter)
}
}
for _, n := range networks {
if ifPassesFilterTest(n, tokens) {
reports = append(reports, &entities.NetworkListReport{NetworkConfigList: n})
ok, err := network.IfPassesFilter(n, options.Filters)
if err != nil {
return nil, err
}
if ok {
reports = append(reports, &entities.NetworkListReport{
NetworkConfigList: n,
Labels: network.GetNetworkLabels(n),
})
}
}
return reports, nil
@ -117,28 +112,6 @@ func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, optio
return network.Create(name, options, runtimeConfig)
}
func ifPassesFilterTest(netconf *libcni.NetworkConfigList, filter []string) bool {
result := false
if len(filter) == 0 {
// No filter, so pass
return true
}
switch strings.ToLower(filter[0]) {
case "name":
if filter[1] == netconf.Name {
result = true
}
case "plugin":
plugins := network.GetCNIPlugins(netconf)
if strings.Contains(plugins, filter[1]) {
result = true
}
default:
result = false
}
return result
}
// NetworkDisconnect removes a container from a given network
func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname string, options entities.NetworkDisconnectOptions) error {
return ic.Libpod.DisconnectContainerFromNetwork(options.Container, networkname, options.Force)

View File

@ -9,8 +9,8 @@ t GET networks/non-existing-network 404 \
t POST libpod/networks/create?name=network1 '' 200 \
.Filename~.*/network1\\.conflist
# --data '{"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}}'
t POST libpod/networks/create?name=network2 '"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}' 200 \
# --data '{"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]},"Labels":{"abc":"val"}}'
t POST libpod/networks/create?name=network2 '"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]},"Labels":{"abc":"val"}' 200 \
.Filename~.*/network2\\.conflist
# test for empty mask
@ -22,7 +22,8 @@ t POST libpod/networks/create '"Subnet":{"IP":"10.10.1.0","Mask":[0,255,255,0]}'
# network list
t GET libpod/networks/json 200
t GET libpod/networks/json?filter=name=network1 200 \
# filters={"name":["network1"]}
t GET libpod/networks/json?filters=%7B%22name%22%3A%5B%22network1%22%5D%7D 200 \
length=1 \
.[0].Name=network1
t GET networks 200
@ -34,12 +35,12 @@ length=2
#filters={"name":["network"]}
t GET networks?filters=%7B%22name%22%3A%5B%22network%22%5D%7D 200 \
length=2
# invalid filter filters={"label":"abc"}
t GET networks?filters=%7B%22label%22%3A%5B%22abc%22%5D%7D 500 \
.cause="only the name filter for listing networks is implemented"
# invalid filter filters={"label":"abc","name":["network"]}
t GET networks?filters=%7B%22label%22%3A%22abc%22%2C%22name%22%3A%5B%22network%22%5D%7D 500 \
.cause="only the name filter for listing networks is implemented"
# filters={"label":["abc"]}
t GET networks?filters=%7B%22label%22%3A%5B%22abc%22%5D%7D 200 \
length=1
# invalid filter filters={"id":["abc"]}
t GET networks?filters=%7B%22id%22%3A%5B%22abc%22%5D%7D 500 \
.cause='invalid filter "id"'
# clean the network
t DELETE libpod/networks/network1 200 \

View File

@ -66,6 +66,65 @@ var _ = Describe("Podman network", func() {
Expect(session.LineInOutputContains(name)).To(BeTrue())
})
It("podman network list --filter plugin and name", func() {
name, path := generateNetworkConfig(podmanTest)
defer removeConf(path)
session := podmanTest.Podman([]string{"network", "ls", "--filter", "plugin=bridge", "--filter", "name=" + name})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(name))
})
It("podman network list --filter two names", func() {
name1, path1 := generateNetworkConfig(podmanTest)
defer removeConf(path1)
name2, path2 := generateNetworkConfig(podmanTest)
defer removeConf(path2)
session := podmanTest.Podman([]string{"network", "ls", "--filter", "name=" + name1, "--filter", "name=" + name2})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(name1))
Expect(session.OutputToString()).To(ContainSubstring(name2))
})
It("podman network list --filter labels", func() {
net1 := "labelnet" + stringid.GenerateNonCryptoID()
label1 := "testlabel1=abc"
label2 := "abcdef"
session := podmanTest.Podman([]string{"network", "create", "--label", label1, net1})
session.WaitWithDefaultTimeout()
defer podmanTest.removeCNINetwork(net1)
Expect(session.ExitCode()).To(BeZero())
net2 := "labelnet" + stringid.GenerateNonCryptoID()
session = podmanTest.Podman([]string{"network", "create", "--label", label1, "--label", label2, net2})
session.WaitWithDefaultTimeout()
defer podmanTest.removeCNINetwork(net2)
Expect(session.ExitCode()).To(BeZero())
session = podmanTest.Podman([]string{"network", "ls", "--filter", "label=" + label1})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(net1))
Expect(session.OutputToString()).To(ContainSubstring(net2))
session = podmanTest.Podman([]string{"network", "ls", "--filter", "label=" + label1, "--filter", "label=" + label2})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).ToNot(ContainSubstring(net1))
Expect(session.OutputToString()).To(ContainSubstring(net2))
})
It("podman network list --filter invalid value", func() {
session := podmanTest.Podman([]string{"network", "ls", "--filter", "namr=ab"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError())
Expect(session.ErrorToString()).To(ContainSubstring(`invalid filter "namr"`))
})
It("podman network list --filter failure", func() {
name, path := generateNetworkConfig(podmanTest)
defer removeConf(path)