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/common/pkg/completion"
"github.com/containers/podman/v2/cmd/podman/common" "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/cmd/podman/registry"
"github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -27,6 +29,7 @@ var (
var ( var (
networkCreateOptions entities.NetworkCreateOptions networkCreateOptions entities.NetworkCreateOptions
labels []string
) )
func networkCreateFlags(cmd *cobra.Command) { 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") flags.StringVar(&networkCreateOptions.MacVLAN, macvlanFlagName, "", "create a Macvlan connection based on this device")
_ = cmd.RegisterFlagCompletionFunc(macvlanFlagName, completion.AutocompleteNone) _ = 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 // TODO not supported yet
// flags.StringVar(&networkCreateOptions.IPamDriver, "ipam-driver", "", "IP Address Management Driver") // 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] 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) response, err := registry.ContainerEngine().NetworkCreate(registry.Context(), name, networkCreateOptions)
if err != nil { if err != nil {
return err return err

View File

@ -16,6 +16,7 @@ import (
"github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/libpod/network" "github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
@ -35,17 +36,18 @@ var (
var ( var (
networkListOptions entities.NetworkListOptions networkListOptions entities.NetworkListOptions
filters []string
) )
func networkListFlags(flags *pflag.FlagSet) { func networkListFlags(flags *pflag.FlagSet) {
formatFlagName := "format" 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) _ = networklistCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat)
flags.BoolVarP(&networkListOptions.Quiet, "quiet", "q", false, "display only names") flags.BoolVarP(&networkListOptions.Quiet, "quiet", "q", false, "display only names")
filterFlagName := "filter" 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) _ = networklistCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteNetworkFilters)
} }
@ -61,14 +63,14 @@ func init() {
} }
func networkList(cmd *cobra.Command, args []string) error { func networkList(cmd *cobra.Command, args []string) error {
// validate the filter pattern. networkListOptions.Filters = make(map[string][]string)
if len(networkListOptions.Filter) > 0 { for _, f := range filters {
tokens := strings.Split(networkListOptions.Filter, "=") split := strings.SplitN(f, "=", 2)
if len(tokens) != 2 { if len(split) == 1 {
return fmt.Errorf("invalid filter syntax : %s", networkListOptions.Filter) 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) responses, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions)
if err != nil { if err != nil {
return err return err
@ -93,6 +95,7 @@ func networkList(cmd *cobra.Command, args []string) error {
"CNIVersion": "version", "CNIVersion": "version",
"Version": "version", "Version": "version",
"Plugins": "plugins", "Plugins": "plugins",
"Labels": "labels",
}) })
renderHeaders := true renderHeaders := true
row := "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n" row := "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n"
@ -144,3 +147,11 @@ func (n ListPrintReports) Version() string {
func (n ListPrintReports) Plugins() string { func (n ListPrintReports) Plugins() string {
return network.GetCNIPlugins(n.NetworkConfigList) 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 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. must be used with a *subnet* option.
#### **--label**
Set metadata for a network (e.g., --label mykey=value).
#### **--macvlan** #### **--macvlan**
Create a *Macvlan* based connection rather than a classic bridge. You must pass an interface name from the host for the 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. 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. 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 ## EXAMPLE

View File

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

View File

@ -12,6 +12,7 @@ import (
"github.com/containers/common/pkg/config" "github.com/containers/common/pkg/config"
"github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/define"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
) )
// GetCNIConfDir get CNI configuration directory // GetCNIConfDir get CNI configuration directory
@ -86,6 +87,35 @@ func GetCNIPlugins(list *libcni.NetworkConfigList) string {
return strings.Join(plugins, ",") 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 // GetNetworksFromFilesystem gets all the networks from the cni configuration
// files // files
func GetNetworksFromFilesystem(config *config.Config) ([]*allocator.Net, error) { func GetNetworksFromFilesystem(config *config.Config) ([]*allocator.Net, error) {

View File

@ -4,6 +4,11 @@ import (
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v2/pkg/util"
"github.com/pkg/errors"
) )
const ( const (
@ -14,12 +19,24 @@ const (
// NcList describes a generic map // NcList describes a generic map
type NcList map[string]interface{} 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 // NewNcList creates a generic map of values with string
// keys and adds in version and network name // 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 := NcList{}
n["cniVersion"] = version n["cniVersion"] = version
n["name"] = name n["name"] = name
if len(labels) > 0 {
n["args"] = NcArgs{PodmanLabelKey: labels}
}
return n return n
} }
@ -159,3 +176,64 @@ func NewMacVLANPlugin(device string) MacVLANConfig {
} }
return m 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) utils.NetworkNotFound(w, name, err)
return return
} }
report, err := getNetworkResourceByName(name, runtime) report, err := getNetworkResourceByName(name, runtime, nil)
if err != nil { if err != nil {
utils.InternalServerError(w, err) utils.InternalServerError(w, err)
return return
@ -58,7 +58,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, report) 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 ( var (
ipamConfigs []dockerNetwork.IPAMConfig ipamConfigs []dockerNetwork.IPAMConfig
) )
@ -85,6 +85,16 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
if err != nil { if err != nil {
return nil, err 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 // No Bridge plugin means we bail
bridge, err := genericPluginsToBridge(conf.Plugins, network.DefaultNetworkDriver) bridge, err := genericPluginsToBridge(conf.Plugins, network.DefaultNetworkDriver)
@ -129,14 +139,14 @@ func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.Netw
Options: nil, Options: nil,
Config: ipamConfigs, Config: ipamConfigs,
}, },
Internal: false, Internal: !bridge.IsGW,
Attachable: false, Attachable: false,
Ingress: false, Ingress: false,
ConfigFrom: dockerNetwork.ConfigReference{}, ConfigFrom: dockerNetwork.ConfigReference{},
ConfigOnly: false, ConfigOnly: false,
Containers: containerEndpoints, Containers: containerEndpoints,
Options: nil, Options: nil,
Labels: nil, Labels: network.GetNetworkLabels(conf),
Peers: nil, Peers: nil,
Services: nil, Services: nil,
} }
@ -180,41 +190,23 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
return 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) netNames, err := network.GetNetworkNamesFromFileSystem(config)
if err != nil { if err != nil {
utils.InternalServerError(w, err) utils.InternalServerError(w, err)
return return
} }
// filter by name var reports []*types.NetworkResource
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))
logrus.Errorf("netNames: %q", strings.Join(netNames, ", ")) logrus.Errorf("netNames: %q", strings.Join(netNames, ", "))
for _, name := range netNames { for _, name := range netNames {
report, err := getNetworkResourceByName(name, runtime) report, err := getNetworkResourceByName(name, runtime, query.Filters)
if err != nil { if err != nil {
utils.InternalServerError(w, err) utils.InternalServerError(w, err)
return return
} }
reports = append(reports, report) if report != nil {
reports = append(reports, report)
}
} }
utils.WriteResponse(w, http.StatusOK, reports) utils.WriteResponse(w, http.StatusOK, reports)
} }
@ -245,6 +237,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
ncOptions := entities.NetworkCreateOptions{ ncOptions := entities.NetworkCreateOptions{
Driver: network.DefaultNetworkDriver, Driver: network.DefaultNetworkDriver,
Internal: networkCreate.Internal, Internal: networkCreate.Internal,
Labels: networkCreate.Labels,
} }
if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil { if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil {
if len(networkCreate.IPAM.Config) > 1 { 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) runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder) decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct { query := struct {
Filter string `schema:"filter"` Filters map[string][]string `schema:"filters"`
}{ }{
// override any golang type defaults // override any golang type defaults
} }
@ -59,7 +59,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
} }
options := entities.NetworkListOptions{ options := entities.NetworkListOptions{
Filter: query.Filter, Filters: query.Filters,
} }
ic := abi.ContainerEngine{Libpod: runtime} ic := abi.ContainerEngine{Libpod: runtime}
reports, err := ic.NetworkList(r.Context(), options) reports, err := ic.NetworkList(r.Context(), options)

View File

@ -65,7 +65,11 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// - in: query // - in: query
// name: filters // name: filters
// type: string // 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: // produces:
// - application/json // - application/json
// responses: // responses:
@ -216,9 +220,14 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// description: Display summary of network configurations // description: Display summary of network configurations
// parameters: // parameters:
// - in: query // - in: query
// name: filter // name: filters
// type: string // 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: // produces:
// - application/json // - application/json
// responses: // responses:

View File

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

View File

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

View File

@ -2,10 +2,7 @@ package abi
import ( import (
"context" "context"
"fmt"
"strings"
"github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/network" "github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/domain/entities" "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 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 { for _, n := range networks {
if ifPassesFilterTest(n, tokens) { ok, err := network.IfPassesFilter(n, options.Filters)
reports = append(reports, &entities.NetworkListReport{NetworkConfigList: n}) if err != nil {
return nil, err
}
if ok {
reports = append(reports, &entities.NetworkListReport{
NetworkConfigList: n,
Labels: network.GetNetworkLabels(n),
})
} }
} }
return reports, nil return reports, nil
@ -117,28 +112,6 @@ func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, optio
return network.Create(name, options, runtimeConfig) 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 // NetworkDisconnect removes a container from a given network
func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname string, options entities.NetworkDisconnectOptions) error { func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname string, options entities.NetworkDisconnectOptions) error {
return ic.Libpod.DisconnectContainerFromNetwork(options.Container, networkname, options.Force) 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 \ t POST libpod/networks/create?name=network1 '' 200 \
.Filename~.*/network1\\.conflist .Filename~.*/network1\\.conflist
# --data '{"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}}' # --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]}' 200 \ 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 .Filename~.*/network2\\.conflist
# test for empty mask # 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 # network list
t GET libpod/networks/json 200 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 \ length=1 \
.[0].Name=network1 .[0].Name=network1
t GET networks 200 t GET networks 200
@ -34,12 +35,12 @@ length=2
#filters={"name":["network"]} #filters={"name":["network"]}
t GET networks?filters=%7B%22name%22%3A%5B%22network%22%5D%7D 200 \ t GET networks?filters=%7B%22name%22%3A%5B%22network%22%5D%7D 200 \
length=2 length=2
# invalid filter filters={"label":"abc"} # filters={"label":["abc"]}
t GET networks?filters=%7B%22label%22%3A%5B%22abc%22%5D%7D 500 \ t GET networks?filters=%7B%22label%22%3A%5B%22abc%22%5D%7D 200 \
.cause="only the name filter for listing networks is implemented" length=1
# invalid filter filters={"label":"abc","name":["network"]} # invalid filter filters={"id":["abc"]}
t GET networks?filters=%7B%22label%22%3A%22abc%22%2C%22name%22%3A%5B%22network%22%5D%7D 500 \ t GET networks?filters=%7B%22id%22%3A%5B%22abc%22%5D%7D 500 \
.cause="only the name filter for listing networks is implemented" .cause='invalid filter "id"'
# clean the network # clean the network
t DELETE libpod/networks/network1 200 \ t DELETE libpod/networks/network1 200 \

View File

@ -66,6 +66,65 @@ var _ = Describe("Podman network", func() {
Expect(session.LineInOutputContains(name)).To(BeTrue()) 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() { It("podman network list --filter failure", func() {
name, path := generateNetworkConfig(podmanTest) name, path := generateNetworkConfig(podmanTest)
defer removeConf(path) defer removeConf(path)