mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00

Docker uses "bridge" as default network name so some tools expect this to work with network list or inspect. To fix this we change "bridge" to the podman default ("podman") name. Fixes #14983 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
478 lines
14 KiB
Go
478 lines
14 KiB
Go
package compat
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
|
|
nettypes "github.com/containers/common/libnetwork/types"
|
|
netutil "github.com/containers/common/libnetwork/util"
|
|
"github.com/containers/podman/v4/libpod"
|
|
"github.com/containers/podman/v4/libpod/define"
|
|
"github.com/containers/podman/v4/pkg/api/handlers/utils"
|
|
api "github.com/containers/podman/v4/pkg/api/types"
|
|
"github.com/containers/podman/v4/pkg/domain/entities"
|
|
"github.com/containers/podman/v4/pkg/domain/infra/abi"
|
|
"github.com/containers/podman/v4/pkg/util"
|
|
"github.com/docker/docker/api/types"
|
|
|
|
dockerNetwork "github.com/docker/docker/api/types/network"
|
|
"github.com/gorilla/schema"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func normalizeNetworkName(rt *libpod.Runtime, name string) (string, bool) {
|
|
if name == nettypes.BridgeNetworkDriver {
|
|
return rt.Network().DefaultNetworkName(), true
|
|
}
|
|
return name, false
|
|
}
|
|
|
|
func InspectNetwork(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
|
|
// scope is only used to see if the user passes any illegal value, verbose is not used but implemented
|
|
// for compatibility purposes only.
|
|
query := struct {
|
|
scope string `schema:"scope"`
|
|
verbose bool `schema:"verbose"`
|
|
}{
|
|
scope: "local",
|
|
}
|
|
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
|
|
return
|
|
}
|
|
|
|
if query.scope != "local" {
|
|
utils.Error(w, http.StatusBadRequest, define.ErrInvalidArg)
|
|
return
|
|
}
|
|
name, changed := normalizeNetworkName(runtime, utils.GetName(r))
|
|
net, err := runtime.Network().NetworkInspect(name)
|
|
if err != nil {
|
|
utils.NetworkNotFound(w, name, err)
|
|
return
|
|
}
|
|
report, err := convertLibpodNetworktoDockerNetwork(runtime, &net, changed)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, report)
|
|
}
|
|
|
|
func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, network *nettypes.Network, changeDefaultName bool) (*types.NetworkResource, error) {
|
|
cons, err := runtime.GetAllContainers()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
containerEndpoints := make(map[string]types.EndpointResource, len(cons))
|
|
for _, con := range cons {
|
|
data, err := con.Inspect(false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if netData, ok := data.NetworkSettings.Networks[network.Name]; ok {
|
|
ipv4Address := ""
|
|
if netData.IPAddress != "" {
|
|
ipv4Address = fmt.Sprintf("%s/%d", netData.IPAddress, netData.IPPrefixLen)
|
|
}
|
|
ipv6Address := ""
|
|
if netData.GlobalIPv6Address != "" {
|
|
ipv6Address = fmt.Sprintf("%s/%d", netData.GlobalIPv6Address, netData.GlobalIPv6PrefixLen)
|
|
}
|
|
containerEndpoint := types.EndpointResource{
|
|
Name: con.Name(),
|
|
EndpointID: netData.EndpointID,
|
|
MacAddress: netData.MacAddress,
|
|
IPv4Address: ipv4Address,
|
|
IPv6Address: ipv6Address,
|
|
}
|
|
containerEndpoints[con.ID()] = containerEndpoint
|
|
}
|
|
}
|
|
ipamConfigs := make([]dockerNetwork.IPAMConfig, 0, len(network.Subnets))
|
|
for _, sub := range network.Subnets {
|
|
ipamConfig := dockerNetwork.IPAMConfig{
|
|
Subnet: sub.Subnet.String(),
|
|
Gateway: sub.Gateway.String(),
|
|
// TODO add range
|
|
}
|
|
ipamConfigs = append(ipamConfigs, ipamConfig)
|
|
}
|
|
ipamDriver := network.IPAMOptions["driver"]
|
|
if ipamDriver == nettypes.HostLocalIPAMDriver {
|
|
ipamDriver = "default"
|
|
}
|
|
ipam := dockerNetwork.IPAM{
|
|
Driver: ipamDriver,
|
|
Options: network.IPAMOptions,
|
|
Config: ipamConfigs,
|
|
}
|
|
|
|
name := network.Name
|
|
if changeDefaultName && name == runtime.Network().DefaultNetworkName() {
|
|
name = nettypes.BridgeNetworkDriver
|
|
}
|
|
report := types.NetworkResource{
|
|
Name: name,
|
|
ID: network.ID,
|
|
Driver: network.Driver,
|
|
Created: network.Created,
|
|
Internal: network.Internal,
|
|
EnableIPv6: network.IPv6Enabled,
|
|
Labels: network.Labels,
|
|
Options: network.Options,
|
|
IPAM: ipam,
|
|
Scope: "local",
|
|
Attachable: false,
|
|
Ingress: false,
|
|
ConfigFrom: dockerNetwork.ConfigReference{},
|
|
ConfigOnly: false,
|
|
Containers: containerEndpoints,
|
|
Peers: nil,
|
|
Services: nil,
|
|
}
|
|
return &report, nil
|
|
}
|
|
|
|
func ListNetworks(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
filterMap, err := util.PrepareFilters(r)
|
|
if err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
|
|
return
|
|
}
|
|
|
|
options := entities.NetworkListOptions{
|
|
Filters: *filterMap,
|
|
}
|
|
|
|
ic := abi.ContainerEngine{Libpod: runtime}
|
|
nets, err := ic.NetworkList(r.Context(), options)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
reports := make([]*types.NetworkResource, 0, len(nets))
|
|
for _, net := range nets {
|
|
report, err := convertLibpodNetworktoDockerNetwork(runtime, &net, true)
|
|
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 (
|
|
networkCreate types.NetworkCreateRequest
|
|
network nettypes.Network
|
|
responseWarning string
|
|
)
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
if err := json.NewDecoder(r.Body).Decode(&networkCreate); err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err))
|
|
return
|
|
}
|
|
|
|
network.Name = networkCreate.Name
|
|
if networkCreate.Driver == "" {
|
|
networkCreate.Driver = nettypes.DefaultNetworkDriver
|
|
}
|
|
network.Driver = networkCreate.Driver
|
|
network.Labels = networkCreate.Labels
|
|
network.Internal = networkCreate.Internal
|
|
network.IPv6Enabled = networkCreate.EnableIPv6
|
|
|
|
network.Options = make(map[string]string)
|
|
|
|
// TODO: we should consider making this constants in c/common/libnetwork/types
|
|
for opt, optVal := range networkCreate.Options {
|
|
switch opt {
|
|
case "mtu":
|
|
fallthrough
|
|
case "com.docker.network.driver.mtu":
|
|
if network.Driver == nettypes.BridgeNetworkDriver {
|
|
network.Options["mtu"] = optVal
|
|
}
|
|
case "icc":
|
|
fallthrough
|
|
case "com.docker.network.bridge.enable_icc":
|
|
// TODO: needs to be implemented
|
|
if network.Driver == nettypes.BridgeNetworkDriver {
|
|
responseWarning = "com.docker.network.bridge.enable_icc is not currently implemented"
|
|
}
|
|
case "com.docker.network.bridge.name":
|
|
if network.Driver == nettypes.BridgeNetworkDriver {
|
|
network.NetworkInterface = optVal
|
|
}
|
|
case "mode":
|
|
if network.Driver == nettypes.MacVLANNetworkDriver || network.Driver == nettypes.IPVLANNetworkDriver {
|
|
network.Options[opt] = optVal
|
|
}
|
|
case "parent":
|
|
if network.Driver == nettypes.MacVLANNetworkDriver || network.Driver == nettypes.IPVLANNetworkDriver {
|
|
network.NetworkInterface = optVal
|
|
}
|
|
default:
|
|
responseWarning = "\"" + opt + ": " + optVal + "\" is not a recognized option"
|
|
}
|
|
}
|
|
|
|
// dns is only enabled for the bridge driver
|
|
if network.Driver == nettypes.BridgeNetworkDriver {
|
|
network.DNSEnabled = true
|
|
}
|
|
|
|
if networkCreate.IPAM != nil && len(networkCreate.IPAM.Config) > 0 {
|
|
for _, conf := range networkCreate.IPAM.Config {
|
|
s := nettypes.Subnet{}
|
|
if len(conf.Subnet) > 0 {
|
|
var err error
|
|
subnet, err := nettypes.ParseCIDR(conf.Subnet)
|
|
if err != nil {
|
|
utils.InternalServerError(w, fmt.Errorf("failed to parse subnet: %w", err))
|
|
return
|
|
}
|
|
s.Subnet = subnet
|
|
}
|
|
if len(conf.Gateway) > 0 {
|
|
gw := net.ParseIP(conf.Gateway)
|
|
if gw == nil {
|
|
utils.InternalServerError(w, fmt.Errorf("failed to parse gateway ip %s", conf.Gateway))
|
|
return
|
|
}
|
|
s.Gateway = gw
|
|
}
|
|
if len(conf.IPRange) > 0 {
|
|
_, net, err := net.ParseCIDR(conf.IPRange)
|
|
if err != nil {
|
|
utils.InternalServerError(w, fmt.Errorf("failed to parse ip range: %w", err))
|
|
return
|
|
}
|
|
startIP, err := netutil.FirstIPInSubnet(net)
|
|
if err != nil {
|
|
utils.InternalServerError(w, fmt.Errorf("failed to get first ip in range: %w", err))
|
|
return
|
|
}
|
|
lastIP, err := netutil.LastIPInSubnet(net)
|
|
if err != nil {
|
|
utils.InternalServerError(w, fmt.Errorf("failed to get last ip in range: %w", err))
|
|
return
|
|
}
|
|
s.LeaseRange = &nettypes.LeaseRange{
|
|
StartIP: startIP,
|
|
EndIP: lastIP,
|
|
}
|
|
}
|
|
network.Subnets = append(network.Subnets, s)
|
|
}
|
|
// FIXME can we use the IPAM driver and options?
|
|
}
|
|
|
|
ic := abi.ContainerEngine{Libpod: runtime}
|
|
newNetwork, err := ic.NetworkCreate(r.Context(), network)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
body := struct {
|
|
ID string `json:"Id"`
|
|
Warning string `json:"Warning"`
|
|
}{
|
|
ID: newNetwork.ID,
|
|
Warning: responseWarning,
|
|
}
|
|
utils.WriteResponse(w, http.StatusCreated, body)
|
|
}
|
|
|
|
func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
ic := abi.ContainerEngine{Libpod: runtime}
|
|
|
|
query := struct {
|
|
Force bool `schema:"force"`
|
|
Timeout *uint `schema:"timeout"`
|
|
}{
|
|
// This is where you can override the golang default value for one of fields
|
|
}
|
|
|
|
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
|
|
return
|
|
}
|
|
|
|
options := entities.NetworkRmOptions{
|
|
Force: query.Force,
|
|
Timeout: query.Timeout,
|
|
}
|
|
|
|
name, _ := normalizeNetworkName(runtime, utils.GetName(r))
|
|
reports, err := ic.NetworkRm(r.Context(), []string{name}, options)
|
|
if err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
if len(reports) == 0 {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("internal error"))
|
|
return
|
|
}
|
|
report := reports[0]
|
|
if report.Err != nil {
|
|
if errors.Is(report.Err, define.ErrNoSuchNetwork) {
|
|
utils.Error(w, http.StatusNotFound, define.ErrNoSuchNetwork)
|
|
return
|
|
}
|
|
utils.InternalServerError(w, report.Err)
|
|
return
|
|
}
|
|
|
|
utils.WriteResponse(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
// Connect adds a container to a network
|
|
func Connect(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
|
|
var netConnect types.NetworkConnect
|
|
if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err))
|
|
return
|
|
}
|
|
|
|
netOpts := nettypes.PerNetworkOptions{}
|
|
|
|
name, _ := normalizeNetworkName(runtime, utils.GetName(r))
|
|
if netConnect.EndpointConfig != nil {
|
|
if netConnect.EndpointConfig.Aliases != nil {
|
|
netOpts.Aliases = netConnect.EndpointConfig.Aliases
|
|
}
|
|
|
|
// if IP address is provided
|
|
if len(netConnect.EndpointConfig.IPAddress) > 0 {
|
|
staticIP := net.ParseIP(netConnect.EndpointConfig.IPAddress)
|
|
if staticIP == nil {
|
|
utils.Error(w, http.StatusInternalServerError,
|
|
fmt.Errorf("failed to parse the ip address %q", netConnect.EndpointConfig.IPAddress))
|
|
return
|
|
}
|
|
netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
|
|
}
|
|
|
|
if netConnect.EndpointConfig.IPAMConfig != nil {
|
|
// if IPAMConfig.IPv4Address is provided
|
|
if len(netConnect.EndpointConfig.IPAMConfig.IPv4Address) > 0 {
|
|
staticIP := net.ParseIP(netConnect.EndpointConfig.IPAMConfig.IPv4Address)
|
|
if staticIP == nil {
|
|
utils.Error(w, http.StatusInternalServerError,
|
|
fmt.Errorf("failed to parse the ipv4 address %q", netConnect.EndpointConfig.IPAMConfig.IPv4Address))
|
|
return
|
|
}
|
|
netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
|
|
}
|
|
// if IPAMConfig.IPv6Address is provided
|
|
if len(netConnect.EndpointConfig.IPAMConfig.IPv6Address) > 0 {
|
|
staticIP := net.ParseIP(netConnect.EndpointConfig.IPAMConfig.IPv6Address)
|
|
if staticIP == nil {
|
|
utils.Error(w, http.StatusInternalServerError,
|
|
fmt.Errorf("failed to parse the ipv6 address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address))
|
|
return
|
|
}
|
|
netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
|
|
}
|
|
}
|
|
// If MAC address is provided
|
|
if len(netConnect.EndpointConfig.MacAddress) > 0 {
|
|
staticMac, err := net.ParseMAC(netConnect.EndpointConfig.MacAddress)
|
|
if err != nil {
|
|
utils.Error(w, http.StatusInternalServerError,
|
|
fmt.Errorf("failed to parse the mac address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address))
|
|
return
|
|
}
|
|
netOpts.StaticMAC = nettypes.HardwareAddr(staticMac)
|
|
}
|
|
}
|
|
err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netOpts)
|
|
if err != nil {
|
|
if errors.Is(err, define.ErrNoSuchCtr) {
|
|
utils.ContainerNotFound(w, netConnect.Container, err)
|
|
return
|
|
}
|
|
if errors.Is(err, define.ErrNoSuchNetwork) {
|
|
utils.Error(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
utils.Error(w, 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(api.RuntimeKey).(*libpod.Runtime)
|
|
|
|
var netDisconnect types.NetworkDisconnect
|
|
if err := json.NewDecoder(r.Body).Decode(&netDisconnect); err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err))
|
|
return
|
|
}
|
|
|
|
name, _ := normalizeNetworkName(runtime, utils.GetName(r))
|
|
err := runtime.DisconnectContainerFromNetwork(netDisconnect.Container, name, netDisconnect.Force)
|
|
if err != nil {
|
|
if errors.Is(err, define.ErrNoSuchCtr) {
|
|
utils.Error(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
if errors.Is(err, define.ErrNoSuchNetwork) {
|
|
utils.Error(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
utils.Error(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, "OK")
|
|
}
|
|
|
|
// Prune removes unused networks
|
|
func Prune(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
filterMap, err := util.PrepareFilters(r)
|
|
if err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err))
|
|
return
|
|
}
|
|
|
|
ic := abi.ContainerEngine{Libpod: runtime}
|
|
pruneOptions := entities.NetworkPruneOptions{
|
|
Filters: *filterMap,
|
|
}
|
|
pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions)
|
|
if err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
type response struct {
|
|
NetworksDeleted []string
|
|
}
|
|
prunedNetworks := []string{}
|
|
for _, pr := range pruneReports {
|
|
if pr.Error != nil {
|
|
logrus.Error(pr.Error)
|
|
continue
|
|
}
|
|
prunedNetworks = append(prunedNetworks, pr.Name)
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, response{NetworksDeleted: prunedNetworks})
|
|
}
|