Vendor container-libs main

Mostly for libnetwork changes in c/common

Signed-off-by: Ashley Cui <acui@redhat.com>
This commit is contained in:
Ashley Cui
2026-04-28 10:22:31 -04:00
parent 241e59b9af
commit 09103756cc
17 changed files with 312 additions and 369 deletions

8
go.mod
View File

@@ -46,7 +46,7 @@ require (
github.com/moby/sys/user v0.4.0
github.com/moby/term v0.5.2
github.com/nxadm/tail v1.4.11
github.com/onsi/ginkgo/v2 v2.28.1
github.com/onsi/ginkgo/v2 v2.28.2
github.com/onsi/gomega v1.39.1
github.com/opencontainers/cgroups v0.0.6
github.com/opencontainers/go-digest v1.0.0
@@ -64,9 +64,9 @@ require (
github.com/vbauerster/mpb/v8 v8.12.0
github.com/vishvananda/netlink v1.3.1
go.podman.io/buildah v1.42.1-0.20260421143840-0acb6b8cca85
go.podman.io/common v0.67.2-0.20260427104901-081c2519fc6a
go.podman.io/image/v5 v5.39.3-0.20260427104901-081c2519fc6a
go.podman.io/storage v1.62.1-0.20260427104901-081c2519fc6a
go.podman.io/common v0.67.2-0.20260428163628-e3b0c9aa788d
go.podman.io/image/v5 v5.39.3-0.20260428163628-e3b0c9aa788d
go.podman.io/storage v1.62.1-0.20260428163628-e3b0c9aa788d
golang.org/x/crypto v0.50.0
golang.org/x/net v0.53.0
golang.org/x/sync v0.20.0

16
go.sum
View File

@@ -281,8 +281,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps=
github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
github.com/opencontainers/cgroups v0.0.6 h1:tfZFWTIIGaUUFImTyuTg+Mr5x8XRiSdZESgEBW7UxuI=
@@ -431,12 +431,12 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.podman.io/buildah v1.42.1-0.20260421143840-0acb6b8cca85 h1:rVKRagobPO6kGHOg2NhGNs6xWVgZctiOn4tmxa3cytA=
go.podman.io/buildah v1.42.1-0.20260421143840-0acb6b8cca85/go.mod h1:JjFirF1zlILz55ZkVYYhLRnted7mPlmoS2w2ihYw8iw=
go.podman.io/common v0.67.2-0.20260427104901-081c2519fc6a h1:cWMyBIJJe5g67dRtnzstL/tneqk2zUFDOrWHuX88tfg=
go.podman.io/common v0.67.2-0.20260427104901-081c2519fc6a/go.mod h1:dxkgre+xzxpAUuDsXBeAgTcNR4jN8d/00oHYt8QcNHs=
go.podman.io/image/v5 v5.39.3-0.20260427104901-081c2519fc6a h1:8tXwkOe3xho4XKZcsGu8PPMZi80bwQ/IR0Blr8dH69I=
go.podman.io/image/v5 v5.39.3-0.20260427104901-081c2519fc6a/go.mod h1:sAszAH18v4KPpzPZdav9ovjEBcIDkmqdhbJV9SmnzfE=
go.podman.io/storage v1.62.1-0.20260427104901-081c2519fc6a h1:i1Imee48BwEq66Kkwb2TAUnwDTOeGNLkUsXyjcdYHeA=
go.podman.io/storage v1.62.1-0.20260427104901-081c2519fc6a/go.mod h1:13aOBf6782/fbAzH7QNEqlVzFu+X4sS4MxDM/VdJGZU=
go.podman.io/common v0.67.2-0.20260428163628-e3b0c9aa788d h1:/oR7Ke8lxnw/pTcQ3mcjKLPI0gn0xc/wGPKXGpq+/gM=
go.podman.io/common v0.67.2-0.20260428163628-e3b0c9aa788d/go.mod h1:3Dn8ywd1MInft7FIPMBcLOvVWgAhiLFCwsNxCTc9QhM=
go.podman.io/image/v5 v5.39.3-0.20260428163628-e3b0c9aa788d h1:mm8baBK0FvElAxfI6Z9knY0PcnjX4wVvlI+/H4lg1kI=
go.podman.io/image/v5 v5.39.3-0.20260428163628-e3b0c9aa788d/go.mod h1:c1ged4R93jqNgA1E1Quywv65YAZvPZm4bOEqFMOi1OE=
go.podman.io/storage v1.62.1-0.20260428163628-e3b0c9aa788d h1:V1Tk7mksAafNAjdDEdJ8IFKkKceYoWXDuOqO5RJw/OI=
go.podman.io/storage v1.62.1-0.20260428163628-e3b0c9aa788d/go.mod h1:13aOBf6782/fbAzH7QNEqlVzFu+X4sS4MxDM/VdJGZU=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=

View File

@@ -1,3 +1,12 @@
## 2.28.2
- Add ArtifactDir() to support Go 1.26 testing.TB interface [f3a36b6]
- Implement shell completion [94151c8]
- Add asan CLI option mirroring msan implementation [4d21dbb]
- Bump uri from 1.0.3 to 1.0.4 in /docs (#1630) [c102161]
- fix aspect ratio [9619647]
- update logos [5779304]
## 2.28.1
Update all dependencies. This auto-updated the required version of Go to 1.24, consistent with the fact that Go 1.23 has been out of support for almost six months.

View File

@@ -120,6 +120,6 @@ Sponsors commit to a [sponsorship](https://github.com/sponsors/onsi) for a year.
<p style="font-size:21px; color:black;">Browser testing via
<a href="https://www.testmu.ai/" target="_blank">
<img src="https://www.testmu.ai/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
<img src="https://assets.testmu.ai/resources/images/logos/white-logo.png" style="vertical-align: middle;" width="250" />
</a>
</p>

View File

@@ -1,9 +1,13 @@
package command
import (
"bufio"
"fmt"
"io"
"maps"
"os"
"path/filepath"
"slices"
"strings"
"github.com/onsi/ginkgo/v2/formatter"
@@ -158,6 +162,166 @@ func (p Program) handleHelpRequestsAndExit(writer io.Writer, args []string) {
}
}
type completionOptions = struct {
Complete bool
Install bool
}
func (p *Program) BuildCompletionCommand() Command {
opts := completionOptions{}
flags, err := types.NewGinkgoFlagSet(
types.GinkgoFlags{
{Name: "complete", KeyPath: "Complete", Usage: "Generate completion for arguments after --"},
{Name: "install", KeyPath: "Install", Usage: "Install shell completion script into $XDG_DATA_HOME, ~/.local/share"},
},
&opts,
types.GinkgoFlagSections{},
)
if err != nil {
panic(err)
}
return Command{
Name: "completion",
Usage: "ginkgo completion <FLAGS> <SHELL> [-- <COMPLETE>]",
Flags: flags,
ShortDoc: "Generate shell completion",
Documentation: `To use install completion script for your shell (bash, fish, zsh).
Or load completion code by: {{bold}}source <(ginkgo completion <SHELL>){{/}}.`,
Command: func(args []string, completeArgs []string) {
p.handleCompletionAndExit(args, completeArgs, opts)
},
}
}
func (p Program) generateShellCompletionScript(shell string) (scriptPath string, script string) {
switch shell {
case "bash":
scriptPath = fmt.Sprintf("bash-completion/completions/%s", p.Name)
script = fmt.Sprintf(`__%s_complete_bash() {
mapfile -t COMPREPLY < <("${COMP_WORDS[0]}" completion --complete bash -- "${COMP_WORDS[@]:1:COMP_CWORD}")
}
complete -o bashdefault -o default -F __%[1]s_complete_bash %[1]s
`, p.Name)
case "fish":
scriptPath = fmt.Sprintf("fish/vendor_completions.d/%s.fish", p.Name)
script = fmt.Sprintf(`function __fish_%[1]s_complete
set -l args (commandline -opc) (commandline -ct)
set -e args[1]
%[1]s completion --complete fish -- $args
end
complete -c %[1]s -a "(__fish_%[1]s_complete)"
`, p.Name)
case "zsh":
scriptPath = fmt.Sprintf("zsh/site-functions/_%s", p.Name)
script = fmt.Sprintf(`#compdef %[1]s
_%[1]s() {
local -a completions
completions=(${(f)"$("${words[1]}" completion --complete zsh -- "${words[@]:1:$((CURRENT-1))}")"})
if (( ${#completions[@]} )); then
_describe 'completions' completions
else
_default
fi
}
compdef _%[1]s %[1]s
if [ "$funcstack[1]" = "_%[1]s" ]; then
_%[1]s
fi
`, p.Name)
case "":
AbortWithUsage("Shell is not specified")
default:
AbortWith("Shell %q is not supported yet. Choose: bash, fish, zsh", shell)
}
return scriptPath, script
}
func (p Program) handleCompletionAndExit(args, completeArgs []string, opts completionOptions) {
writer := p.OutWriter
if writer == nil {
writer = os.Stdout
}
buffer := bufio.NewWriter(writer)
defer buffer.Flush()
var shell string
if len(args) > 0 {
shell = args[0]
}
if !opts.Complete {
scriptPath, script := p.generateShellCompletionScript(shell)
if opts.Install {
dataHomeDir := os.Getenv("XDG_DATA_HOME")
if dataHomeDir == "" {
userHomeDir, err := os.UserHomeDir()
AbortIfError("Failed to find home", err)
dataHomeDir = filepath.Join(userHomeDir, ".local/share")
}
scriptPath = filepath.Join(dataHomeDir, scriptPath)
fmt.Fprintf(buffer, "Installing completion script: %v\n", scriptPath)
err := os.WriteFile(scriptPath, []byte(script), 0644)
AbortIfError("Failed to install completion script", err)
} else {
buffer.Write([]byte(script))
}
Abort(AbortDetails{})
}
var lastArg string
var result map[string]string
if len(completeArgs) > 0 {
lastArg = completeArgs[len(completeArgs)-1]
}
if delim := slices.Index(completeArgs, "--"); delim >= 0 && delim != len(completeArgs)-1 {
// No completion for pass-through arguments after "--"
} else if len(lastArg) > 0 && lastArg[0] == '-' {
// Complete flags
cmd := &p.DefaultCommand
for i := range p.Commands {
if p.Commands[i].Name == completeArgs[0] {
cmd = &p.Commands[i]
break
}
}
result = cmd.Flags.Completion(lastArg)
} else if len(completeArgs) <= 1 {
// Complete commands
result = make(map[string]string, len(p.Commands)+1)
for _, cmd := range append(p.Commands, p.DefaultCommand) {
if strings.HasPrefix(cmd.Name, lastArg) {
result[cmd.Name] = cmd.Usage
}
}
}
width := 0
for suggest := range result {
width = max(width, len(suggest))
}
for _, suggest := range slices.Sorted(maps.Keys(result)) {
usage := result[suggest]
switch {
case shell == "bash" && usage != "" && len(result) > 1:
fmt.Fprintf(buffer, "%*s (%s)\n", -width-2, suggest, usage)
case shell == "fish":
fmt.Fprintf(buffer, "%s\t%s\n", suggest, usage)
case shell == "zsh":
fmt.Fprintf(buffer, "%s:%s\n", suggest, usage)
default:
fmt.Fprintln(buffer, suggest)
}
}
Abort(AbortDetails{})
}
func (p Program) EmitUsage(writer io.Writer) {
fmt.Fprintln(writer, formatter.F(p.Heading))
fmt.Fprintln(writer, formatter.F("{{gray}}%s{{/}}", strings.Repeat("-", len(p.Heading))))

View File

@@ -41,6 +41,7 @@ func main() {
{Name: "nodot", Deprecation: types.Deprecations.Nodot()},
},
}
program.Commands = append(program.Commands, program.BuildCompletionCommand())
program.RunAndExit(os.Args)
}

View File

@@ -72,6 +72,7 @@ type GinkgoTInterface interface {
TempDir() string
Attr(key, value string)
Output() io.Writer
ArtifactDir() string
}
/*
@@ -196,3 +197,6 @@ func (g *GinkgoTBWrapper) Attr(key, value string) {
func (g *GinkgoTBWrapper) Output() io.Writer {
return g.GinkgoT.Output()
}
func (g *GinkgoTBWrapper) ArtifactDir() string {
return g.GinkgoT.ArtifactDir()
}

View File

@@ -181,6 +181,15 @@ func (t *ginkgoTestingTProxy) TempDir() string {
return tmpDir
}
func (t *ginkgoTestingTProxy) ArtifactDir() string {
artifactDir, err := os.MkdirTemp("", "ginkgo")
if err != nil {
t.fail(fmt.Sprintf("Failed to create artifact directory: %v", err), 1)
return ""
}
return artifactDir
}
// FullGinkgoTInterface
func (t *ginkgoTestingTProxy) AddReportEntryVisibilityAlways(name string, args ...any) {
finalArgs := []any{internal.Offset(1), types.ReportEntryVisibilityAlways}

View File

@@ -215,6 +215,7 @@ type GoFlagsConfig struct {
N bool
ModFile string
ModCacheRW bool
ASan bool
MSan bool
PkgDir string
Tags string
@@ -570,6 +571,8 @@ var GoBuildFlags = GinkgoFlags{
Usage: "leave newly-created directories in the module cache read-write instead of making them read-only."},
{KeyPath: "Go.ModFile", Name: "modfile", UsageArgument: "file", SectionKey: "go-build",
Usage: `in module aware mode, read (and possibly write) an alternate go.mod file instead of the one in the module root directory. A file named go.mod must still be present in order to determine the module root directory, but it is not accessed. When -modfile is specified, an alternate go.sum file is also used: its path is derived from the -modfile flag by trimming the ".mod" extension and appending ".sum".`},
{KeyPath: "Go.ASan", Name: "asan", SectionKey: "go-build",
Usage: "enable interoperation with address sanitizer."},
{KeyPath: "Go.MSan", Name: "msan", SectionKey: "go-build",
Usage: "enable interoperation with memory sanitizer. Supported only on linux/amd64, linux/arm64 and only with Clang/LLVM as the host C compiler. On linux/arm64, pie build mode will be used."},
{KeyPath: "Go.N", Name: "n", SectionKey: "go-build",

View File

@@ -212,6 +212,24 @@ func (f GinkgoFlagSet) IsZero() bool {
return f.flagSet == nil
}
func (f GinkgoFlagSet) Completion(arg string) map[string]string {
if f.IsZero() {
return nil
}
prefix := strings.TrimLeft(arg, "-")
dash := arg[:len(arg)-len(prefix)]
if len(dash) < 1 || len(dash) > 3 {
return nil
}
result := make(map[string]string, len(f.flags))
for _, flag := range f.flags {
if flag.Name != "" && strings.HasPrefix(flag.Name, prefix) {
result[dash+flag.Name] = flag.Usage
}
}
return result
}
func (f GinkgoFlagSet) WasSet(name string) bool {
found := false
f.flagSet.Visit(func(f *flag.Flag) {

View File

@@ -1,3 +1,3 @@
package types
const VERSION = "2.28.1"
const VERSION = "2.28.2"

View File

@@ -1,57 +0,0 @@
package util
import (
"fmt"
"github.com/sirupsen/logrus"
"go.podman.io/common/libnetwork/types"
)
func CommonNetworkCreate(n NetUtil, network *types.Network) error {
if network.Labels == nil {
network.Labels = map[string]string{}
}
if network.Options == nil {
network.Options = map[string]string{}
}
if network.IPAMOptions == nil {
network.IPAMOptions = map[string]string{}
}
var name string
var err error
// validate the name when given
if network.Name != "" {
if !types.NameRegex.MatchString(network.Name) {
return fmt.Errorf("network name %s invalid: %w", network.Name, types.ErrInvalidName)
}
if _, err := n.Network(network.Name); err == nil {
return fmt.Errorf("network name %s already used: %w", network.Name, types.ErrNetworkExists)
}
} else {
name, err = GetFreeDeviceName(n)
if err != nil {
return err
}
network.Name = name
// also use the name as interface name when we create a bridge network
if network.Driver == types.BridgeNetworkDriver && network.NetworkInterface == "" {
network.NetworkInterface = name
}
}
// Validate interface name if specified
if network.NetworkInterface != "" {
if err := ValidateInterfaceName(network.NetworkInterface); err != nil {
return fmt.Errorf("network interface name %s invalid: %w", network.NetworkInterface, err)
}
}
return nil
}
func IpamNoneDisableDNS(network *types.Network) {
if network.IPAMOptions[types.Driver] == types.NoneIPAMDriver {
logrus.Debugf("dns disabled for network %q because ipam driver is set to none", network.Name)
network.DNSEnabled = false
}
}

View File

@@ -10,11 +10,11 @@ import (
"os"
"path/filepath"
"slices"
"strconv"
"time"
"strings"
internalutil "go.podman.io/common/libnetwork/internal/util"
"go.podman.io/common/libnetwork/types"
"go.podman.io/common/pkg/config"
"go.podman.io/storage/pkg/stringid"
)
@@ -100,13 +100,14 @@ func (n *netavarkNetwork) NetworkCreate(net types.Network, options *types.Networ
if err != nil {
return types.Network{}, err
}
if options != nil && options.IgnoreIfExists {
if network, ok := n.networks[net.Name]; ok {
return *network, nil
}
}
network, err := n.networkCreate(&net, false)
if err != nil {
if options != nil && options.IgnoreIfExists && errors.Is(err, types.ErrNetworkExists) {
if network, ok := n.networks[net.Name]; ok {
return *network, nil
}
}
return types.Network{}, err
}
// add the new network to the map
@@ -139,251 +140,86 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo
return nil, errors.New("failed to create random network ID")
}
}
if newNetwork.Name == "" {
name, err := internalutil.GetFreeDeviceName(n)
if err != nil {
return nil, err
}
newNetwork.Name = name
}
err := internalutil.CommonNetworkCreate(n, newNetwork)
usedSubnets, err := internalutil.GetUsedSubnets(n)
if err != nil {
return nil, err
}
err = validateIPAMDriver(newNetwork)
usedNames := make(map[string]string, len(n.networks))
for name, network := range n.networks {
usedNames[name] = network.ID
}
type Used struct {
Interfaces []string `json:"interfaces"`
Names map[string]string `json:"names"`
Subnets []types.IPNet `json:"subnets"`
}
type ConfigOpts struct {
SubnetPools []config.SubnetPool `json:"subnet_pools"`
DefaultInterfaceName string `json:"default_interface_name"`
CheckUsedSubnets bool `json:"check_used_subnets"`
}
type CreateConfigOptions struct {
Network types.Network `json:"network"`
Used Used `json:"used"`
Options ConfigOpts `json:"options"`
}
subnets := make([]types.IPNet, len(usedSubnets))
for i, subnet := range usedSubnets {
subnets[i] = types.IPNet{IPNet: *subnet}
}
opts := CreateConfigOptions{
Network: *newNetwork,
Used: Used{
Interfaces: internalutil.GetBridgeInterfaceNames(n),
Names: usedNames,
Subnets: subnets,
},
Options: ConfigOpts{
SubnetPools: n.defaultsubnetPools,
DefaultInterfaceName: n.DefaultInterfaceName(),
CheckUsedSubnets: !defaultNet,
},
}
var needsPlugin bool
if !slices.Contains(builtinDrivers, newNetwork.Driver) {
needsPlugin = true
}
var result *types.Network
err = n.execNetavark([]string{"create"}, needsPlugin, &opts, &result)
if err != nil {
if strings.Contains(err.Error(), "network already exists") {
return nil, fmt.Errorf("%w%.0w", err, types.ErrNetworkExists)
}
return nil, err
}
// Only get the used networks for validation if we do not create the default network.
// The default network should not be validated against used subnets, we have to ensure
// that this network can always be created even when a subnet is already used on the host.
// This could happen if you run a container on this net, then the network interface will be
// created on the host and "block" this subnet from being used again.
// Therefore the next podman command tries to create the default net again and it would
// fail because it thinks the network is used on the host.
var usedNetworks []*net.IPNet
if !defaultNet && newNetwork.Driver == types.BridgeNetworkDriver {
usedNetworks, err = internalutil.GetUsedSubnets(n)
if err != nil {
return nil, err
}
}
switch newNetwork.Driver {
case types.BridgeNetworkDriver:
internalutil.MapDockerBridgeDriverOptions(newNetwork)
checkBridgeConflict := true
// validate the given options,
for key, value := range newNetwork.Options {
switch key {
case types.MTUOption:
_, err = internalutil.ParseMTU(value)
if err != nil {
return nil, err
}
case types.VLANOption:
_, err = internalutil.ParseVlan(value)
if err != nil {
return nil, err
}
// Unset used networks here to ensure that when using vlan networks
// we do not error if the subnet is already in use on the host.
// https://github.com/containers/podman/issues/25736
usedNetworks = nil
// If there is no vlan there should be no other config with the same bridge.
// However with vlan we want to allow that so that you can have different
// configs on the same bridge but different vlans
// https://github.com/containers/common/issues/2095
checkBridgeConflict = false
case types.IsolateOption:
val, err := internalutil.ParseIsolate(value)
if err != nil {
return nil, err
}
newNetwork.Options[types.IsolateOption] = val
case types.MetricOption:
_, err := strconv.ParseUint(value, 10, 32)
if err != nil {
return nil, err
}
case types.NoDefaultRoute:
val, err := strconv.ParseBool(value)
if err != nil {
return nil, err
}
// rust only support "true" or "false" while go can parse 1 and 0 as well so we need to change it
newNetwork.Options[types.NoDefaultRoute] = strconv.FormatBool(val)
case types.VRFOption:
if len(value) == 0 {
return nil, errors.New("invalid vrf name")
}
case types.ModeOption:
switch value {
case types.BridgeModeManaged:
case types.BridgeModeUnmanaged:
// Unset used networks here to ensure that when using unmanaged networks
// we do not error if the subnet is already in use on the host.
// https://github.com/containers/common/issues/2322
usedNetworks = nil
// Also make sure we don't error if the bridge name is already used as well.
checkBridgeConflict = false
default:
return nil, fmt.Errorf("unknown bridge mode %q", value)
}
default:
return nil, fmt.Errorf("unsupported bridge network option %s", key)
}
}
err = internalutil.CreateBridge(n, newNetwork, usedNetworks, n.defaultsubnetPools, checkBridgeConflict)
if err != nil {
return nil, err
}
case types.MacVLANNetworkDriver, types.IPVLANNetworkDriver:
err = createIpvlanOrMacvlan(newNetwork)
if err != nil {
return nil, err
}
default:
net, err := n.createPlugin(newNetwork)
if err != nil {
return nil, err
}
newNetwork = net
}
// when we do not have ipam we must disable dns
internalutil.IpamNoneDisableDNS(newNetwork)
// process NetworkDNSServers
if len(newNetwork.NetworkDNSServers) > 0 && !newNetwork.DNSEnabled {
return nil, fmt.Errorf("cannot set NetworkDNSServers if DNS is not enabled for the network: %w", types.ErrInvalidArg)
}
// validate ip address
for _, dnsServer := range newNetwork.NetworkDNSServers {
if net.ParseIP(dnsServer) == nil {
return nil, fmt.Errorf("unable to parse ip %s specified in NetworkDNSServers: %w", dnsServer, types.ErrInvalidArg)
}
}
// add gateway when not internal or dns enabled
addGateway := !newNetwork.Internal || newNetwork.DNSEnabled
err = internalutil.ValidateSubnets(newNetwork, addGateway, usedNetworks)
if err != nil {
// normalize network fields
if err := parseNetwork(result); err != nil {
return nil, err
}
// validate routes
err = internalutil.ValidateRoutes(newNetwork.Routes)
if err != nil {
return nil, err
}
newNetwork.Created = time.Now()
if !defaultNet {
err = n.commitNetwork(newNetwork)
err := n.commitNetwork(result)
if err != nil {
return nil, err
}
}
return newNetwork, nil
}
// ipvlan shares the same mac address so supporting DHCP is not really possible.
var errIpvlanNoDHCP = errors.New("ipam driver dhcp is not supported with ipvlan")
func createIpvlanOrMacvlan(network *types.Network) error {
if network.NetworkInterface != "" {
interfaceNames, err := internalutil.GetLiveNetworkNames()
if err != nil {
return err
}
if !slices.Contains(interfaceNames, network.NetworkInterface) {
return fmt.Errorf("parent interface %s does not exist", network.NetworkInterface)
}
}
driver := network.Driver
isMacVlan := driver != types.IPVLANNetworkDriver
// always turn dns off with macvlan, it is not implemented in netavark
// and makes little sense to support with macvlan
// see https://github.com/containers/netavark/pull/467
network.DNSEnabled = false
// we already validated the drivers before so we just have to set the default here
switch network.IPAMOptions[types.Driver] {
case "":
if len(network.Subnets) == 0 {
// if no subnets and no driver choose dhcp
network.IPAMOptions[types.Driver] = types.DHCPIPAMDriver
if !isMacVlan {
return errIpvlanNoDHCP
}
} else {
network.IPAMOptions[types.Driver] = types.HostLocalIPAMDriver
}
case types.HostLocalIPAMDriver:
if len(network.Subnets) == 0 {
return fmt.Errorf("%s driver needs at least one subnet specified when the host-local ipam driver is set", driver)
}
case types.DHCPIPAMDriver:
if !isMacVlan {
return errIpvlanNoDHCP
}
if len(network.Subnets) > 0 {
return errors.New("ipam driver dhcp set but subnets are set")
}
}
// validate the given options, we do not need them but just check to make sure they are valid
for key, value := range network.Options {
switch key {
case types.ModeOption:
if isMacVlan {
if !slices.Contains(types.ValidMacVLANModes, value) {
return fmt.Errorf("unknown macvlan mode %q", value)
}
} else {
if !slices.Contains(types.ValidIPVLANModes, value) {
return fmt.Errorf("unknown ipvlan mode %q", value)
}
}
case types.MetricOption:
_, err := strconv.ParseUint(value, 10, 32)
if err != nil {
return err
}
case types.MTUOption:
_, err := internalutil.ParseMTU(value)
if err != nil {
return err
}
case types.NoDefaultRoute:
val, err := strconv.ParseBool(value)
if err != nil {
return err
}
// rust only support "true" or "false" while go can parse 1 and 0 as well so we need to change it
network.Options[types.NoDefaultRoute] = strconv.FormatBool(val)
case types.BclimOption:
if isMacVlan {
_, err := strconv.ParseInt(value, 10, 32)
if err != nil {
return fmt.Errorf("failed to parse %q option: %w", key, err)
}
// do not fallthrough for macvlan
break
}
// bclim is only valid for macvlan not ipvlan so fallthrough to error case
fallthrough
default:
return fmt.Errorf("unsupported %s network option %s", driver, key)
}
}
return nil
return result, nil
}
// NetworkRemove will remove the Network with the given name or ID.
@@ -461,45 +297,6 @@ func (n *netavarkNetwork) NetworkInspect(nameOrID string) (types.Network, error)
return *network, nil
}
func validateIPAMDriver(n *types.Network) error {
ipamDriver := n.IPAMOptions[types.Driver]
switch ipamDriver {
case "", types.HostLocalIPAMDriver, types.DHCPIPAMDriver:
case types.NoneIPAMDriver:
if len(n.Subnets) > 0 {
return errors.New("none ipam driver is set but subnets are given")
}
default:
return fmt.Errorf("unsupported ipam driver %q", ipamDriver)
}
return nil
}
var errInvalidPluginResult = errors.New("invalid plugin result")
func (n *netavarkNetwork) createPlugin(net *types.Network) (*types.Network, error) {
path, err := getPlugin(net.Driver, n.pluginDirs)
if err != nil {
return nil, err
}
result := new(types.Network)
err = n.execPlugin(path, []string{"create"}, net, result)
if err != nil {
return nil, fmt.Errorf("plugin %s failed: %w", path, err)
}
// now make sure that neither the name, ID, driver were changed by the plugin
if net.Name != result.Name {
return nil, fmt.Errorf("%w: changed network name", errInvalidPluginResult)
}
if net.ID != result.ID {
return nil, fmt.Errorf("%w: changed network ID", errInvalidPluginResult)
}
if net.Driver != result.Driver {
return nil, fmt.Errorf("%w: changed network driver", errInvalidPluginResult)
}
return result, nil
}
func getAllPlugins(dirs []string) []string {
var plugins []string
for _, dir := range dirs {
@@ -515,14 +312,3 @@ func getAllPlugins(dirs []string) []string {
}
return plugins
}
func getPlugin(name string, dirs []string) (string, error) {
for _, dir := range dirs {
fullpath := filepath.Join(dir, name)
st, err := os.Stat(fullpath)
if err == nil && st.Mode().IsRegular() {
return fullpath, nil
}
}
return "", fmt.Errorf("failed to find driver or plugin %q", name)
}

View File

@@ -101,10 +101,6 @@ func (n *netavarkNetwork) execNetavark(args []string, needPlugin bool, stdin, re
return n.execBinary(n.netavarkBinary, append(n.getCommonNetavarkOptions(needPlugin), args...), stdin, result, env)
}
func (n *netavarkNetwork) execPlugin(path string, args []string, stdin, result any) error {
return n.execBinary(path, args, stdin, result, nil)
}
func (n *netavarkNetwork) execBinary(path string, args []string, stdin, result any, env []string) error {
stdinR, stdinW, err := os.Pipe()
if err != nil {

View File

@@ -296,17 +296,27 @@ func parseNetwork(network *types.Network) error {
}
func (n *netavarkNetwork) createDefaultNetwork() (*types.Network, error) {
net := types.Network{
network := &types.Network{
Name: n.defaultNetwork,
NetworkInterface: defaultBridgeName + "0",
// Important do not change this ID
ID: "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
Driver: types.BridgeNetworkDriver,
ID: "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
Driver: types.BridgeNetworkDriver,
Created: time.Now(),
Subnets: []types.Subnet{
{Subnet: n.defaultSubnet},
},
IPAMOptions: map[string]string{
"driver": types.HostLocalIPAMDriver,
},
}
return n.networkCreate(&net, true)
// Normalize network fields (initializes nil maps, adds gateway, validates, etc.)
if err := parseNetwork(network); err != nil {
return nil, err
}
return network, nil
}
// getNetwork will lookup a network by name or ID. It returns an

View File

@@ -635,10 +635,10 @@ type NetworkConfig struct {
type SubnetPool struct {
// Base is a bigger subnet which will be used to allocate a subnet with
// the given size.
Base *types.IPNet `toml:"base,omitempty"`
Base *types.IPNet `toml:"base,omitempty" json:"base,omitempty"`
// Size is the CIDR for the new subnet. It must be equal or small
// than the CIDR from the base subnet.
Size int `toml:"size,omitempty"`
Size int `toml:"size,omitempty" json:"size,omitempty"`
}
// SecretConfig represents the "secret" TOML config table.

8
vendor/modules.txt vendored
View File

@@ -443,7 +443,7 @@ github.com/nxadm/tail/ratelimiter
github.com/nxadm/tail/util
github.com/nxadm/tail/watch
github.com/nxadm/tail/winfile
# github.com/onsi/ginkgo/v2 v2.28.1
# github.com/onsi/ginkgo/v2 v2.28.2
## explicit; go 1.24.0
github.com/onsi/ginkgo/v2
github.com/onsi/ginkgo/v2/config
@@ -736,7 +736,7 @@ go.podman.io/buildah/pkg/sshagent
go.podman.io/buildah/pkg/util
go.podman.io/buildah/pkg/volumes
go.podman.io/buildah/util
# go.podman.io/common v0.67.2-0.20260427104901-081c2519fc6a
# go.podman.io/common v0.67.2-0.20260428163628-e3b0c9aa788d
## explicit; go 1.25.6
go.podman.io/common/internal
go.podman.io/common/libimage
@@ -802,7 +802,7 @@ go.podman.io/common/pkg/umask
go.podman.io/common/pkg/util
go.podman.io/common/pkg/version
go.podman.io/common/version
# go.podman.io/image/v5 v5.39.3-0.20260427104901-081c2519fc6a
# go.podman.io/image/v5 v5.39.3-0.20260428163628-e3b0c9aa788d
## explicit; go 1.25.6
go.podman.io/image/v5/copy
go.podman.io/image/v5/directory
@@ -879,7 +879,7 @@ go.podman.io/image/v5/transports
go.podman.io/image/v5/transports/alltransports
go.podman.io/image/v5/types
go.podman.io/image/v5/version
# go.podman.io/storage v1.62.1-0.20260427104901-081c2519fc6a
# go.podman.io/storage v1.62.1-0.20260428163628-e3b0c9aa788d
## explicit; go 1.25.0
go.podman.io/storage
go.podman.io/storage/drivers