Files
podman/pkg/api/handlers/compat/networks.go
Paul Holzinger 8494bcb866 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>
2020-11-28 18:35:43 +01:00

386 lines
11 KiB
Go

package compat
import (
"encoding/json"
"net"
"net/http"
"os"
"strings"
"syscall"
"time"
"github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi"
"github.com/docker/docker/api/types"
dockerNetwork "github.com/docker/docker/api/types/network"
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
func InspectNetwork(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
// FYI scope and version are currently unused but are described by the API
// Leaving this for if/when we have to enable these
// query := struct {
// scope string
// verbose bool
// }{
// // override any golang type defaults
// }
// decoder := r.Context().Value("decoder").(*schema.Decoder)
// if err := decoder.Decode(&query, r.URL.Query()); err != nil {
// utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
// return
// }
config, err := runtime.GetConfig()
if err != nil {
utils.InternalServerError(w, err)
return
}
name := utils.GetName(r)
_, err = network.InspectNetwork(config, name)
if err != nil {
utils.NetworkNotFound(w, name, err)
return
}
report, err := getNetworkResourceByName(name, runtime, nil)
if err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusOK, report)
}
func getNetworkResourceByName(name string, runtime *libpod.Runtime, filters map[string][]string) (*types.NetworkResource, error) {
var (
ipamConfigs []dockerNetwork.IPAMConfig
)
config, err := runtime.GetConfig()
if err != nil {
return nil, err
}
containerEndpoints := map[string]types.EndpointResource{}
// Get the network path so we can get created time
networkConfigPath, err := network.GetCNIConfigPathByName(config, name)
if err != nil {
return nil, err
}
f, err := os.Stat(networkConfigPath)
if err != nil {
return nil, err
}
stat := f.Sys().(*syscall.Stat_t)
cons, err := runtime.GetAllContainers()
if err != nil {
return nil, err
}
conf, err := libcni.ConfListFromFile(networkConfigPath)
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)
if err != nil {
return nil, err
}
for _, outer := range bridge.IPAM.Ranges {
for _, n := range outer {
ipamConfig := dockerNetwork.IPAMConfig{
Subnet: n.Subnet,
Gateway: n.Gateway,
}
ipamConfigs = append(ipamConfigs, ipamConfig)
}
}
for _, con := range cons {
data, err := con.Inspect(false)
if err != nil {
return nil, err
}
if netData, ok := data.NetworkSettings.Networks[name]; ok {
containerEndpoint := types.EndpointResource{
Name: netData.NetworkID,
EndpointID: netData.EndpointID,
MacAddress: netData.MacAddress,
IPv4Address: netData.IPAddress,
IPv6Address: netData.GlobalIPv6Address,
}
containerEndpoints[con.ID()] = containerEndpoint
}
}
report := types.NetworkResource{
Name: name,
ID: name,
Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert
Scope: "",
Driver: network.DefaultNetworkDriver,
EnableIPv6: false,
IPAM: dockerNetwork.IPAM{
Driver: "default",
Options: nil,
Config: ipamConfigs,
},
Internal: !bridge.IsGW,
Attachable: false,
Ingress: false,
ConfigFrom: dockerNetwork.ConfigReference{},
ConfigOnly: false,
Containers: containerEndpoints,
Options: nil,
Labels: network.GetNetworkLabels(conf),
Peers: nil,
Services: nil,
}
return &report, nil
}
func genericPluginsToBridge(plugins []*libcni.NetworkConfig, pluginType string) (network.HostLocalBridge, error) {
var bridge network.HostLocalBridge
generic, err := findPluginByName(plugins, pluginType)
if err != nil {
return bridge, err
}
err = json.Unmarshal(generic, &bridge)
return bridge, err
}
func findPluginByName(plugins []*libcni.NetworkConfig, pluginType string) ([]byte, error) {
for _, p := range plugins {
if pluginType == p.Network.Type {
return p.Bytes, nil
}
}
return nil, errors.New("unable to find bridge plugin")
}
func ListNetworks(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Filters map[string][]string `schema:"filters"`
}{
// override any golang type defaults
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
config, err := runtime.GetConfig()
if err != nil {
utils.InternalServerError(w, err)
return
}
netNames, err := network.GetNetworkNamesFromFileSystem(config)
if err != nil {
utils.InternalServerError(w, err)
return
}
var reports []*types.NetworkResource
logrus.Errorf("netNames: %q", strings.Join(netNames, ", "))
for _, name := range netNames {
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)
}
func CreateNetwork(w http.ResponseWriter, r *http.Request) {
var (
name string
networkCreate types.NetworkCreateRequest
)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
if err := json.NewDecoder(r.Body).Decode(&networkCreate); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
if len(networkCreate.Name) > 0 {
name = networkCreate.Name
}
if len(networkCreate.Driver) < 1 {
networkCreate.Driver = network.DefaultNetworkDriver
}
// At present I think we should just support the bridge driver
// and allow demand to make us consider more
if networkCreate.Driver != network.DefaultNetworkDriver {
utils.InternalServerError(w, errors.New("network create only supports the bridge driver"))
return
}
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 {
utils.InternalServerError(w, errors.New("compat network create can only support one IPAM config"))
return
}
if len(networkCreate.IPAM.Config[0].Subnet) > 0 {
_, subnet, err := net.ParseCIDR(networkCreate.IPAM.Config[0].Subnet)
if err != nil {
utils.InternalServerError(w, err)
return
}
ncOptions.Subnet = *subnet
}
if len(networkCreate.IPAM.Config[0].Gateway) > 0 {
ncOptions.Gateway = net.ParseIP(networkCreate.IPAM.Config[0].Gateway)
}
if len(networkCreate.IPAM.Config[0].IPRange) > 0 {
_, IPRange, err := net.ParseCIDR(networkCreate.IPAM.Config[0].IPRange)
if err != nil {
utils.InternalServerError(w, err)
return
}
ncOptions.Range = *IPRange
}
}
ce := abi.ContainerEngine{Libpod: runtime}
if _, err := ce.NetworkCreate(r.Context(), name, ncOptions); err != nil {
utils.InternalServerError(w, err)
return
}
body := struct {
Id string
Warning []string
}{
Id: name,
}
utils.WriteResponse(w, http.StatusCreated, body)
}
func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
ic := abi.ContainerEngine{Libpod: runtime}
query := struct {
Force bool `schema:"force"`
}{
// This is where you can override the golang default value for one of fields
}
decoder := r.Context().Value("decoder").(*schema.Decoder)
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
options := entities.NetworkRmOptions{
Force: query.Force,
}
name := utils.GetName(r)
reports, err := ic.NetworkRm(r.Context(), []string{name}, options)
if err != nil {
utils.Error(w, "remove Network failed", http.StatusInternalServerError, err)
return
}
if len(reports) == 0 {
utils.Error(w, "remove Network failed", http.StatusInternalServerError, errors.Errorf("internal error"))
return
}
report := reports[0]
if report.Err != nil {
if errors.Cause(report.Err) == define.ErrNoSuchNetwork {
utils.Error(w, "network not found", http.StatusNotFound, define.ErrNoSuchNetwork)
return
}
utils.InternalServerError(w, report.Err)
return
}
utils.WriteResponse(w, http.StatusNoContent, "")
}
// Connect adds a container to a network
func Connect(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
var (
aliases []string
netConnect types.NetworkConnect
)
if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
name := utils.GetName(r)
if netConnect.EndpointConfig != nil {
if netConnect.EndpointConfig.Aliases != nil {
aliases = netConnect.EndpointConfig.Aliases
}
}
err := runtime.ConnectContainerToNetwork(netConnect.Container, name, aliases)
if err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
utils.ContainerNotFound(w, netConnect.Container, err)
return
}
if errors.Cause(err) == define.ErrNoSuchNetwork {
utils.Error(w, "network not found", http.StatusNotFound, err)
return
}
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return
}
utils.WriteResponse(w, http.StatusOK, "OK")
}
// Disconnect removes a container from a network
func Disconnect(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
var netDisconnect types.NetworkDisconnect
if err := json.NewDecoder(r.Body).Decode(&netDisconnect); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
name := utils.GetName(r)
err := runtime.DisconnectContainerFromNetwork(netDisconnect.Container, name, netDisconnect.Force)
if err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
utils.Error(w, "container not found", http.StatusNotFound, err)
return
}
if errors.Cause(err) == define.ErrNoSuchNetwork {
utils.Error(w, "network not found", http.StatusNotFound, err)
return
}
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return
}
utils.WriteResponse(w, http.StatusOK, "OK")
}