podman-build/pkg/domain/infra/abi/network.go
2025-10-11 12:30:35 +09:00

308 lines
9.8 KiB
Go

//go:build !remote
package abi
import (
"context"
"errors"
"fmt"
"slices"
"strconv"
"github.com/containers/common/libnetwork/pasta"
"github.com/containers/common/libnetwork/slirp4netns"
"github.com/containers/common/libnetwork/types"
netutil "github.com/containers/common/libnetwork/util"
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/libpod/events"
"github.com/containers/podman/v5/pkg/domain/entities"
)
func (ic *ContainerEngine) NetworkUpdate(ctx context.Context, netName string, options entities.NetworkUpdateOptions) error {
var networkUpdateOptions types.NetworkUpdateOptions
networkUpdateOptions.AddDNSServers = options.AddDNSServers
networkUpdateOptions.RemoveDNSServers = options.RemoveDNSServers
err := ic.Libpod.Network().NetworkUpdate(netName, networkUpdateOptions)
if err != nil {
return err
}
return nil
}
func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]types.Network, error) {
// dangling filter is not provided by netutil
var wantDangling bool
val, filterDangling := options.Filters["dangling"]
if filterDangling {
switch len(val) {
case 0:
return nil, fmt.Errorf("got no values for filter key \"dangling\"")
case 1:
var err error
wantDangling, err = strconv.ParseBool(val[0])
if err != nil {
return nil, fmt.Errorf("invalid dangling filter value \"%v\"", val[0])
}
delete(options.Filters, "dangling")
default:
return nil, fmt.Errorf("got more than one value for filter key \"dangling\"")
}
}
filters, err := netutil.GenerateNetworkFilters(options.Filters)
if err != nil {
return nil, err
}
if filterDangling {
danglingFilterFunc, err := ic.createDanglingFilterFunc(wantDangling)
if err != nil {
return nil, err
}
filters = append(filters, danglingFilterFunc)
}
nets, err := ic.Libpod.Network().NetworkList(filters...)
return nets, err
}
func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]entities.NetworkInspectReport, []error, error) {
var errs []error
statuses, err := ic.GetContainerNetStatuses()
if err != nil {
return nil, nil, fmt.Errorf("failed to get network status for containers: %w", err)
}
networks := make([]entities.NetworkInspectReport, 0, len(namesOrIds))
for _, name := range namesOrIds {
net, err := ic.Libpod.Network().NetworkInspect(name)
if err != nil {
if errors.Is(err, define.ErrNoSuchNetwork) {
errs = append(errs, fmt.Errorf("network %s: %w", name, err))
continue
} else {
return nil, nil, fmt.Errorf("inspecting network %s: %w", name, err)
}
}
containerMap := make(map[string]entities.NetworkContainerInfo)
for _, st := range statuses {
// Make sure to only show the info for the correct network
if sb, ok := st.Status[net.Name]; ok {
containerMap[st.ID] = entities.NetworkContainerInfo{
Name: st.Name,
Interfaces: sb.Interfaces,
}
}
}
netReport := entities.NetworkInspectReport{
Network: net,
Containers: containerMap,
}
networks = append(networks, netReport)
}
return networks, errs, nil
}
func (ic *ContainerEngine) NetworkReload(ctx context.Context, names []string, options entities.NetworkReloadOptions) ([]*entities.NetworkReloadReport, error) {
containers, err := getContainers(ic.Libpod, getContainersOptions{all: options.All, latest: options.Latest, names: names})
if err != nil {
return nil, err
}
reports := make([]*entities.NetworkReloadReport, 0, len(containers))
for _, ctr := range containers {
report := new(entities.NetworkReloadReport)
report.Id = ctr.ID()
report.Err = ctr.ReloadNetwork()
// ignore errors for invalid ctr state and network mode when --all is used
if options.All && (errors.Is(report.Err, define.ErrCtrStateInvalid) ||
errors.Is(report.Err, define.ErrNetworkModeInvalid)) {
continue
}
reports = append(reports, report)
}
return reports, nil
}
func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, options entities.NetworkRmOptions) ([]*entities.NetworkRmReport, error) {
reports := make([]*entities.NetworkRmReport, 0, len(namesOrIds))
for _, name := range namesOrIds {
report := entities.NetworkRmReport{Name: name}
containers, err := ic.Libpod.GetAllContainers()
if err != nil {
return reports, err
}
// We need to iterate containers looking to see if they belong to the given network
for _, c := range containers {
networks, err := c.Networks()
// if container vanished or network does not exist, go to next container
if errors.Is(err, define.ErrNoSuchNetwork) || errors.Is(err, define.ErrNoSuchCtr) {
continue
}
if err != nil {
return reports, err
}
if slices.Contains(networks, name) {
// if user passes force, we nuke containers and pods
if !options.Force {
// Without the force option, we return an error
return reports, fmt.Errorf("%q has associated containers with it. Use -f to forcibly delete containers and pods: %w", name, define.ErrNetworkInUse)
}
if c.IsInfra() {
// if we have an infra container we need to remove the pod
pod, err := ic.Libpod.GetPod(c.PodID())
if err != nil {
return reports, err
}
if _, err := ic.Libpod.RemovePod(ctx, pod, true, true, options.Timeout); err != nil {
return reports, err
}
} else if err := ic.Libpod.RemoveContainer(ctx, c, true, true, options.Timeout); err != nil && !errors.Is(err, define.ErrNoSuchCtr) {
return reports, err
}
}
}
net, err := ic.Libpod.Network().NetworkInspect(name)
if err != nil && !errors.Is(err, define.ErrNoSuchNetwork) {
return reports, err
}
if err := ic.Libpod.Network().NetworkRemove(name); err != nil {
report.Err = err
}
if len(net.Name) != 0 {
ic.Libpod.NewNetworkEvent(events.Remove, net.Name, net.ID, net.Driver)
}
reports = append(reports, &report)
}
return reports, nil
}
func (ic *ContainerEngine) NetworkCreate(ctx context.Context, network types.Network, createOptions *types.NetworkCreateOptions) (*types.Network, error) {
if slices.Contains([]string{"none", "host", "bridge", "private", slirp4netns.BinaryName, pasta.BinaryName, "container", "ns", "default"}, network.Name) {
return nil, fmt.Errorf("cannot create network with name %q because it conflicts with a valid network mode", network.Name)
}
network, err := ic.Libpod.Network().NetworkCreate(network, createOptions)
if err != nil {
return nil, err
}
ic.Libpod.NewNetworkEvent(events.Create, network.Name, network.ID, network.Driver)
return &network, nil
}
// NetworkDisconnect removes a container from a given network
func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname string, options entities.NetworkDisconnectOptions) error {
return ic.Libpod.DisconnectContainerFromNetwork(options.Container, networkname, options.Force)
}
func (ic *ContainerEngine) NetworkConnect(ctx context.Context, networkname string, options entities.NetworkConnectOptions) error {
return ic.Libpod.ConnectContainerToNetwork(options.Container, networkname, options.PerNetworkOptions)
}
// NetworkExists checks if the given network exists
func (ic *ContainerEngine) NetworkExists(ctx context.Context, networkname string) (*entities.BoolReport, error) {
_, err := ic.Libpod.Network().NetworkInspect(networkname)
exists := true
// if err is ErrNoSuchNetwork do not return it
if errors.Is(err, define.ErrNoSuchNetwork) {
exists = false
} else if err != nil {
return nil, err
}
return &entities.BoolReport{
Value: exists,
}, nil
}
// Network prune removes unused networks
func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.NetworkPruneOptions) ([]*entities.NetworkPruneReport, error) {
// get all filters
filters, err := netutil.GenerateNetworkPruneFilters(options.Filters)
if err != nil {
return nil, err
}
danglingFilterFunc, err := ic.createDanglingFilterFunc(true)
if err != nil {
return nil, err
}
filters = append(filters, danglingFilterFunc)
nets, err := ic.Libpod.Network().NetworkList(filters...)
if err != nil {
return nil, err
}
pruneReport := make([]*entities.NetworkPruneReport, 0, len(nets))
for _, net := range nets {
pruneReport = append(pruneReport, &entities.NetworkPruneReport{
Name: net.Name,
Error: ic.Libpod.Network().NetworkRemove(net.Name),
})
}
return pruneReport, nil
}
// danglingFilter function is special and not implemented in libnetwork filters
func (ic *ContainerEngine) createDanglingFilterFunc(wantDangling bool) (types.FilterFunc, error) {
cons, err := ic.Libpod.GetAllContainers()
if err != nil {
return nil, err
}
// Gather up all the non-default networks that the
// containers want
networksToKeep := make(map[string]bool)
for _, c := range cons {
nets, err := c.Networks()
if err != nil {
return nil, err
}
for _, n := range nets {
networksToKeep[n] = true
}
}
// ignore the default network, this one cannot be deleted
networksToKeep[ic.Libpod.GetDefaultNetworkName()] = true
return func(net types.Network) bool {
for network := range networksToKeep {
if network == net.Name {
return !wantDangling
}
}
return wantDangling
}, nil
}
type ContainerNetStatus struct {
// Name of the container
Name string
// ID of the container
ID string
// Status contains the net status, the key is the network name
Status map[string]types.StatusBlock
}
func (ic *ContainerEngine) GetContainerNetStatuses() ([]ContainerNetStatus, error) {
cons, err := ic.Libpod.GetAllContainers()
if err != nil {
return nil, err
}
statuses := make([]ContainerNetStatus, 0, len(cons))
for _, con := range cons {
status, err := con.GetNetworkStatus()
if err != nil {
if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) {
continue
}
return nil, err
}
statuses = append(statuses, ContainerNetStatus{
ID: con.ID(),
Name: con.Name(),
Status: status,
})
}
return statuses, nil
}