Files
podman/pkg/api/handlers/compat/networks.go
Paul Holzinger c31a5c0d9c Add support for network ids
The network ID is not stored. It is just the sha256 hash from
the network name. There is a risk of a potential hash collision.
However it's very unlikely and even if we hit this it will
complain that more than network with this ID exists.

The main benefit is that the compat api can have proper
network ID support. Also this adds the support for
`podman network ls --format "{{.ID}}"` and `--filter id=<ID>`.

It also ensures that we can do network rm <ID> and network
inspect <ID>.

Since we use a hash this commit is backwards compatible even for
already existing networks.

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
2020-12-02 10:27:00 +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 := getNetworkResourceByNameOrID(name, runtime, nil)
if err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusOK, report)
}
func getNetworkResourceByNameOrID(nameOrID 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.GetCNIConfigPathByNameOrID(config, nameOrID)
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[conf.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: conf.Name,
ID: network.GetNetworkID(conf.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 := getNetworkResourceByNameOrID(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")
}