mirror of https://github.com/containers/podman.git
374 lines
10 KiB
Go
374 lines
10 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)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, report)
|
|
}
|
|
|
|
func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*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
|
|
}
|
|
|
|
// 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: false,
|
|
Attachable: false,
|
|
Ingress: false,
|
|
ConfigFrom: dockerNetwork.ConfigReference{},
|
|
ConfigOnly: false,
|
|
Containers: containerEndpoints,
|
|
Options: nil,
|
|
Labels: nil,
|
|
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
|
|
}
|
|
|
|
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))
|
|
logrus.Errorf("netNames: %q", strings.Join(netNames, ", "))
|
|
for _, name := range netNames {
|
|
report, err := getNetworkResourceByName(name, runtime)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
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,
|
|
}
|
|
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)
|
|
config, err := runtime.GetConfig()
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
name := utils.GetName(r)
|
|
exists, err := network.Exists(config, name)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
if !exists {
|
|
utils.Error(w, "network not found", http.StatusNotFound, define.ErrNoSuchNetwork)
|
|
return
|
|
}
|
|
if err := network.RemoveNetwork(config, name); err != nil {
|
|
utils.InternalServerError(w, 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")
|
|
}
|