diff --git a/cli/command/builder/client_test.go b/cli/command/builder/client_test.go index 2568fca500af..f48914bdb76c 100644 --- a/cli/command/builder/client_test.go +++ b/cli/command/builder/client_test.go @@ -3,18 +3,17 @@ package builder import ( "context" - "github.com/moby/moby/api/types/build" "github.com/moby/moby/client" ) type fakeClient struct { client.Client - builderPruneFunc func(ctx context.Context, opts client.BuildCachePruneOptions) (*build.CachePruneReport, error) + builderPruneFunc func(ctx context.Context, opts client.BuildCachePruneOptions) (client.BuildCachePruneResult, error) } -func (c *fakeClient) BuildCachePrune(ctx context.Context, opts client.BuildCachePruneOptions) (*build.CachePruneReport, error) { +func (c *fakeClient) BuildCachePrune(ctx context.Context, opts client.BuildCachePruneOptions) (client.BuildCachePruneResult, error) { if c.builderPruneFunc != nil { return c.builderPruneFunc(ctx, opts) } - return nil, nil + return client.BuildCachePruneResult{}, nil } diff --git a/cli/command/builder/prune.go b/cli/command/builder/prune.go index 1619ef928364..962112e92e74 100644 --- a/cli/command/builder/prune.go +++ b/cli/command/builder/prune.go @@ -87,7 +87,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) } } - report, err := dockerCli.Client().BuildCachePrune(ctx, client.BuildCachePruneOptions{ + resp, err := dockerCli.Client().BuildCachePrune(ctx, client.BuildCachePruneOptions{ All: options.all, ReservedSpace: options.reservedSpace.Value(), Filters: pruneFilters, @@ -95,7 +95,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) if err != nil { return 0, "", err } - + report := resp.Report if len(report.CachesDeleted) > 0 { var sb strings.Builder sb.WriteString("Deleted build cache objects:\n") diff --git a/cli/command/builder/prune_test.go b/cli/command/builder/prune_test.go index c586c237c745..884523197e2b 100644 --- a/cli/command/builder/prune_test.go +++ b/cli/command/builder/prune_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/docker/cli/internal/test" - "github.com/moby/moby/api/types/build" "github.com/moby/moby/client" ) @@ -16,8 +15,8 @@ func TestBuilderPromptTermination(t *testing.T) { t.Cleanup(cancel) cli := test.NewFakeCli(&fakeClient{ - builderPruneFunc: func(ctx context.Context, opts client.BuildCachePruneOptions) (*build.CachePruneReport, error) { - return nil, errors.New("fakeClient builderPruneFunc should not be called") + builderPruneFunc: func(ctx context.Context, opts client.BuildCachePruneOptions) (client.BuildCachePruneResult, error) { + return client.BuildCachePruneResult{}, errors.New("fakeClient builderPruneFunc should not be called") }, }) cmd := newPruneCommand(cli) diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index cff4e499496a..c2b029f30ebf 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net" + "net/netip" "os" "path" "path/filepath" @@ -431,11 +432,42 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con return nil, err } - ports, portBindings, err := nat.ParsePortSpecs(convertedOpts) + ports, natPortBindings, err := nat.ParsePortSpecs(convertedOpts) if err != nil { return nil, err } + portBindings := make(map[network.Port][]network.PortBinding) + for port, bindings := range natPortBindings { + p, err := network.ParsePort(port.String()) + if err != nil { + return nil, err + } + portBindings[p] = []network.PortBinding{} + for _, b := range bindings { + var hostIP netip.Addr + if b.HostIP.String() != "" { + hostIP, err = netip.ParseAddr(b.HostIP.String()) + if err != nil { + return nil, err + } + } + portBindings[p] = append(portBindings[p], network.PortBinding{ + HostIP: hostIP, + HostPort: b.HostPort, + }) + } + } + + exposedPorts := network.PortSet{} + for port := range ports { + p, err := network.ParsePort(port.String()) + if err != nil { + return nil, err + } + exposedPorts[p] = struct{}{} + } + // Merge in exposed ports to the map of published ports for _, e := range copts.expose.GetSlice() { if strings.Contains(e, ":") { @@ -626,7 +658,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con config := &container.Config{ Hostname: copts.hostname, Domainname: copts.domainname, - ExposedPorts: ports, + ExposedPorts: exposedPorts, User: copts.user, Tty: copts.tty, OpenStdin: copts.stdin, @@ -662,7 +694,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con // but pre created containers can still have those nil values. // See https://github.com/docker/docker/pull/17779 // for a more detailed explanation on why we don't want that. - DNS: copts.dns.GetAllOrEmpty(), + DNS: toNetipAddrSlice(copts.dns.GetAllOrEmpty()), DNSSearch: copts.dnsSearch.GetAllOrEmpty(), DNSOptions: copts.dnsOptions.GetAllOrEmpty(), ExtraHosts: copts.extraHosts.GetSlice(), @@ -805,10 +837,10 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption if len(n.Links) > 0 && copts.links.Len() > 0 { return invalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links")) } - if n.IPv4Address != "" && copts.ipv4Address != "" { + if n.IPv4Address.IsValid() && copts.ipv4Address != "" { return invalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address")) } - if n.IPv6Address != "" && copts.ipv6Address != "" { + if n.IPv6Address.IsValid() && copts.ipv6Address != "" { return invalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address")) } if n.MacAddress != "" && copts.macAddress != "" { @@ -828,17 +860,24 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption copy(n.Links, copts.links.GetSlice()) } if copts.ipv4Address != "" { - n.IPv4Address = copts.ipv4Address + var err error + n.IPv4Address, err = netip.ParseAddr(copts.ipv4Address) + if err != nil { + return err + } } if copts.ipv6Address != "" { - n.IPv6Address = copts.ipv6Address + var err error + n.IPv6Address, err = netip.ParseAddr(copts.ipv6Address) + if err != nil { + return err + } } if copts.macAddress != "" { n.MacAddress = copts.macAddress } if copts.linkLocalIPs.Len() > 0 { - n.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len()) - copy(n.LinkLocalIPs, copts.linkLocalIPs.GetSlice()) + n.LinkLocalIPs = toNetipAddrSlice(copts.linkLocalIPs.GetSlice()) } return nil } @@ -867,7 +906,7 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*network.Endpoint if len(ep.Links) > 0 { epConfig.Links = ep.Links } - if ep.IPv4Address != "" || ep.IPv6Address != "" || len(ep.LinkLocalIPs) > 0 { + if ep.IPv4Address.IsValid() || ep.IPv6Address.IsValid() || len(ep.LinkLocalIPs) > 0 { epConfig.IPAMConfig = &network.EndpointIPAMConfig{ IPv4Address: ep.IPv4Address, IPv6Address: ep.IPv6Address, @@ -1131,3 +1170,15 @@ func validateAttach(val string) (string, error) { } return val, errors.New("valid streams are STDIN, STDOUT and STDERR") } + +func toNetipAddrSlice(ips []string) []netip.Addr { + netips := make([]netip.Addr, 0, len(ips)) + for _, ip := range ips { + addr, err := netip.ParseAddr(ip) + if err != nil { + continue + } + netips = append(netips, addr) + } + return netips +} diff --git a/cli/command/container/opts_test.go b/cli/command/container/opts_test.go index 874cbb903c60..ea43e398ba67 100644 --- a/cli/command/container/opts_test.go +++ b/cli/command/container/opts_test.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "net/netip" "os" "runtime" "strings" @@ -11,7 +12,7 @@ import ( "time" "github.com/moby/moby/api/types/container" - networktypes "github.com/moby/moby/api/types/network" + "github.com/moby/moby/api/types/network" "github.com/spf13/pflag" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" @@ -42,7 +43,7 @@ func TestValidateAttach(t *testing.T) { } } -func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { +func parseRun(args []string) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) { flags, copts := setupRunFlags() if err := flags.Parse(args); err != nil { return nil, nil, nil, err @@ -63,7 +64,7 @@ func setupRunFlags() (*pflag.FlagSet, *containerOptions) { return flags, copts } -func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig) { +func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig, *network.NetworkingConfig) { t.Helper() config, hostConfig, nwConfig, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash")) assert.NilError(t, err) @@ -447,12 +448,12 @@ func TestParseWithExpose(t *testing.T) { } }) t.Run("valid", func(t *testing.T) { - tests := map[string][]container.PortRangeProto{ - "8080/tcp": {"8080/tcp"}, - "8080/udp": {"8080/udp"}, - "8080/ncp": {"8080/ncp"}, - "8080-8080/udp": {"8080/udp"}, - "8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"}, + tests := map[string][]network.Port{ + "8080/tcp": {network.MustParsePort("8080/tcp")}, + "8080/udp": {network.MustParsePort("8080/udp")}, + "8080/ncp": {network.MustParsePort("8080/ncp")}, + "8080-8080/udp": {network.MustParsePort("8080/udp")}, + "8080-8082/tcp": {network.MustParsePort("8080/tcp"), network.MustParsePort("8081/tcp"), network.MustParsePort("8082/tcp")}, } for expose, exposedPorts := range tests { t.Run(expose, func(t *testing.T) { @@ -471,7 +472,7 @@ func TestParseWithExpose(t *testing.T) { config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"}) assert.NilError(t, err) assert.Check(t, is.Len(config.ExposedPorts, 2)) - ports := []container.PortRangeProto{"80/tcp", "81/tcp"} + ports := []network.Port{network.MustParsePort("80/tcp"), network.MustParsePort("81/tcp")} for _, port := range ports { _, ok := config.ExposedPorts[port] assert.Check(t, ok, "missing port %q in exposed ports", port) @@ -573,7 +574,7 @@ func TestParseNetworkConfig(t *testing.T) { tests := []struct { name string flags []string - expected map[string]*networktypes.EndpointSettings + expected map[string]*network.EndpointSettings expectedCfg container.Config expectedHostCfg container.HostConfig expectedErr string @@ -581,13 +582,13 @@ func TestParseNetworkConfig(t *testing.T) { { name: "single-network-legacy", flags: []string{"--network", "net1"}, - expected: map[string]*networktypes.EndpointSettings{}, + expected: map[string]*network.EndpointSettings{}, expectedHostCfg: container.HostConfig{NetworkMode: "net1"}, }, { name: "single-network-advanced", flags: []string{"--network", "name=net1"}, - expected: map[string]*networktypes.EndpointSettings{}, + expected: map[string]*network.EndpointSettings{}, expectedHostCfg: container.HostConfig{NetworkMode: "net1"}, }, { @@ -603,12 +604,12 @@ func TestParseNetworkConfig(t *testing.T) { "--network-alias", "web1", "--network-alias", "web2", }, - expected: map[string]*networktypes.EndpointSettings{ + expected: map[string]*network.EndpointSettings{ "net1": { - IPAMConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "172.20.88.22", - IPv6Address: "2001:db8::8822", - LinkLocalIPs: []string{"169.254.2.2", "fe80::169:254:2:2"}, + IPAMConfig: &network.EndpointIPAMConfig{ + IPv4Address: netip.MustParseAddr("172.20.88.22"), + IPv6Address: netip.MustParseAddr("2001:db8::8822"), + LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.2.2"), netip.MustParseAddr("fe80::169:254:2:2")}, }, Links: []string{"foo:bar", "bar:baz"}, Aliases: []string{"web1", "web2"}, @@ -632,13 +633,13 @@ func TestParseNetworkConfig(t *testing.T) { "--network", "name=net3,alias=web3,driver-opt=field3=value3,ip=172.20.88.22,ip6=2001:db8::8822", "--network", "name=net4,mac-address=02:32:1c:23:00:04,link-local-ip=169.254.169.254", }, - expected: map[string]*networktypes.EndpointSettings{ + expected: map[string]*network.EndpointSettings{ "net1": { DriverOpts: map[string]string{"field1": "value1"}, - IPAMConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "172.20.88.22", - IPv6Address: "2001:db8::8822", - LinkLocalIPs: []string{"169.254.2.2", "fe80::169:254:2:2"}, + IPAMConfig: &network.EndpointIPAMConfig{ + IPv4Address: netip.MustParseAddr("172.20.88.22"), + IPv6Address: netip.MustParseAddr("2001:db8::8822"), + LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.2.2"), netip.MustParseAddr("fe80::169:254:2:2")}, }, Links: []string{"foo:bar", "bar:baz"}, Aliases: []string{"web1", "web2"}, @@ -646,16 +647,16 @@ func TestParseNetworkConfig(t *testing.T) { "net2": {}, "net3": { DriverOpts: map[string]string{"field3": "value3"}, - IPAMConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "172.20.88.22", - IPv6Address: "2001:db8::8822", + IPAMConfig: &network.EndpointIPAMConfig{ + IPv4Address: netip.MustParseAddr("172.20.88.22"), + IPv6Address: netip.MustParseAddr("2001:db8::8822"), }, Aliases: []string{"web3"}, }, "net4": { MacAddress: "02:32:1c:23:00:04", - IPAMConfig: &networktypes.EndpointIPAMConfig{ - LinkLocalIPs: []string{"169.254.169.254"}, + IPAMConfig: &network.EndpointIPAMConfig{ + LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.169.254")}, }, }, }, @@ -664,15 +665,15 @@ func TestParseNetworkConfig(t *testing.T) { { name: "single-network-advanced-with-options", flags: []string{"--network", "name=net1,alias=web1,alias=web2,driver-opt=field1=value1,driver-opt=field2=value2,ip=172.20.88.22,ip6=2001:db8::8822,mac-address=02:32:1c:23:00:04"}, - expected: map[string]*networktypes.EndpointSettings{ + expected: map[string]*network.EndpointSettings{ "net1": { DriverOpts: map[string]string{ "field1": "value1", "field2": "value2", }, - IPAMConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "172.20.88.22", - IPv6Address: "2001:db8::8822", + IPAMConfig: &network.EndpointIPAMConfig{ + IPv4Address: netip.MustParseAddr("172.20.88.22"), + IPv6Address: netip.MustParseAddr("2001:db8::8822"), }, Aliases: []string{"web1", "web2"}, MacAddress: "02:32:1c:23:00:04", @@ -684,13 +685,13 @@ func TestParseNetworkConfig(t *testing.T) { { name: "multiple-networks", flags: []string{"--network", "net1", "--network", "name=net2"}, - expected: map[string]*networktypes.EndpointSettings{"net1": {}, "net2": {}}, + expected: map[string]*network.EndpointSettings{"net1": {}, "net2": {}}, expectedHostCfg: container.HostConfig{NetworkMode: "net1"}, }, { name: "advanced-options-with-standalone-mac-address-flag", flags: []string{"--network=name=net1,alias=foobar", "--mac-address", "52:0f:f3:dc:50:10"}, - expected: map[string]*networktypes.EndpointSettings{ + expected: map[string]*network.EndpointSettings{ "net1": { Aliases: []string{"foobar"}, MacAddress: "52:0f:f3:dc:50:10", diff --git a/cli/command/container/port.go b/cli/command/container/port.go index 1df09417df9a..a10268b3bdf6 100644 --- a/cli/command/container/port.go +++ b/cli/command/container/port.go @@ -5,14 +5,13 @@ import ( "fmt" "net" "sort" - "strconv" "strings" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" "github.com/fvbommel/sortorder" - "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" "github.com/spf13/cobra" ) @@ -60,24 +59,21 @@ func runPort(ctx context.Context, dockerCli command.Cli, opts *portOptions) erro var out []string if opts.port != "" { - port, proto, _ := strings.Cut(opts.port, "/") - if proto == "" { - proto = "tcp" + port, err := network.ParsePort(opts.port) + if err != nil { + return err } - if _, err = strconv.ParseUint(port, 10, 16); err != nil { - return fmt.Errorf("invalid port (%s): %w", port, err) - } - frontends, exists := c.NetworkSettings.Ports[container.PortRangeProto(port+"/"+proto)] + frontends, exists := c.NetworkSettings.Ports[port] if !exists || len(frontends) == 0 { return fmt.Errorf("no public port '%s' published for %s", opts.port, opts.container) } for _, frontend := range frontends { - out = append(out, net.JoinHostPort(frontend.HostIP, frontend.HostPort)) + out = append(out, net.JoinHostPort(frontend.HostIP.String(), frontend.HostPort)) } } else { for from, frontends := range c.NetworkSettings.Ports { for _, frontend := range frontends { - out = append(out, fmt.Sprintf("%s -> %s", from, net.JoinHostPort(frontend.HostIP, frontend.HostPort))) + out = append(out, fmt.Sprintf("%s -> %s", from, net.JoinHostPort(frontend.HostIP.String(), frontend.HostPort))) } } } diff --git a/cli/command/container/port_test.go b/cli/command/container/port_test.go index e1a7e317ca2f..e1760af08bfa 100644 --- a/cli/command/container/port_test.go +++ b/cli/command/container/port_test.go @@ -2,10 +2,12 @@ package container import ( "io" + "net/netip" "testing" "github.com/docker/cli/internal/test" "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" "gotest.tools/v3/assert" "gotest.tools/v3/golden" ) @@ -46,20 +48,20 @@ func TestNewPortCommandOutput(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ inspectFunc: func(string) (container.InspectResponse, error) { ci := container.InspectResponse{NetworkSettings: &container.NetworkSettings{}} - ci.NetworkSettings.Ports = container.PortMap{ - "80/tcp": make([]container.PortBinding, len(tc.ips)), - "443/tcp": make([]container.PortBinding, len(tc.ips)), - "443/udp": make([]container.PortBinding, len(tc.ips)), + ci.NetworkSettings.Ports = network.PortMap{ + network.MustParsePort("80/tcp"): make([]network.PortBinding, len(tc.ips)), + network.MustParsePort("443/tcp"): make([]network.PortBinding, len(tc.ips)), + network.MustParsePort("443/udp"): make([]network.PortBinding, len(tc.ips)), } for i, ip := range tc.ips { - ci.NetworkSettings.Ports["80/tcp"][i] = container.PortBinding{ - HostIP: ip, HostPort: "3456", + ci.NetworkSettings.Ports[network.MustParsePort("80/tcp")][i] = network.PortBinding{ + HostIP: netip.MustParseAddr(ip), HostPort: "3456", } - ci.NetworkSettings.Ports["443/tcp"][i] = container.PortBinding{ - HostIP: ip, HostPort: "4567", + ci.NetworkSettings.Ports[network.MustParsePort("443/tcp")][i] = network.PortBinding{ + HostIP: netip.MustParseAddr(ip), HostPort: "4567", } - ci.NetworkSettings.Ports["443/udp"][i] = container.PortBinding{ - HostIP: ip, HostPort: "5678", + ci.NetworkSettings.Ports[network.MustParsePort("443/udp")][i] = network.PortBinding{ + HostIP: netip.MustParseAddr(ip), HostPort: "5678", } } return ci, nil diff --git a/cli/command/formatter/container.go b/cli/command/formatter/container.go index 60dfb91b42f2..9ce7fbf68f06 100644 --- a/cli/command/formatter/container.go +++ b/cli/command/formatter/container.go @@ -360,13 +360,13 @@ func DisplayablePorts(ports []container.PortSummary) string { for _, port := range ports { current := port.PrivatePort portKey := port.Type - if port.IP != "" { + if !port.IP.IsUnspecified() { if port.PublicPort != current { - hAddrPort := net.JoinHostPort(port.IP, strconv.Itoa(int(port.PublicPort))) + hAddrPort := net.JoinHostPort(port.IP.String(), strconv.Itoa(int(port.PublicPort))) hostMappings = append(hostMappings, fmt.Sprintf("%s->%d/%s", hAddrPort, port.PrivatePort, port.Type)) continue } - portKey = port.IP + "/" + port.Type + portKey = port.IP.String() + "/" + port.Type } group := groupMap[portKey] @@ -416,7 +416,7 @@ func comparePorts(i, j container.PortSummary) bool { } if i.IP != j.IP { - return i.IP < j.IP + return i.IP.String() < j.IP.String() } if i.PublicPort != j.PublicPort { diff --git a/cli/command/formatter/container_test.go b/cli/command/formatter/container_test.go index 594ced3b47d0..67c76cff97aa 100644 --- a/cli/command/formatter/container_test.go +++ b/cli/command/formatter/container_test.go @@ -7,6 +7,7 @@ import ( "bytes" "encoding/json" "fmt" + "net/netip" "strings" "testing" "time" @@ -660,7 +661,7 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "0.0.0.0", + IP: netip.MustParseAddr("0.0.0.0"), PrivatePort: 9988, Type: "tcp", }, @@ -670,7 +671,7 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "::", + IP: netip.MustParseAddr("::"), PrivatePort: 9988, Type: "tcp", }, @@ -690,7 +691,7 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "4.3.2.1", + IP: netip.MustParseAddr("4.3.2.1"), PrivatePort: 9988, PublicPort: 8899, Type: "tcp", @@ -701,7 +702,7 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "::1", + IP: netip.MustParseAddr("::1"), PrivatePort: 9988, PublicPort: 8899, Type: "tcp", @@ -712,7 +713,7 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "4.3.2.1", + IP: netip.MustParseAddr("4.3.2.1"), PrivatePort: 9988, PublicPort: 9988, Type: "tcp", @@ -723,7 +724,7 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "::1", + IP: netip.MustParseAddr("::1"), PrivatePort: 9988, PublicPort: 9988, Type: "tcp", @@ -746,12 +747,12 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "1.2.3.4", + IP: netip.MustParseAddr("1.2.3.4"), PublicPort: 9998, PrivatePort: 9998, Type: "udp", }, { - IP: "1.2.3.4", + IP: netip.MustParseAddr("1.2.3.4"), PublicPort: 9999, PrivatePort: 9999, Type: "udp", @@ -762,12 +763,12 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "::1", + IP: netip.MustParseAddr("::1"), PublicPort: 9998, PrivatePort: 9998, Type: "udp", }, { - IP: "::1", + IP: netip.MustParseAddr("::1"), PublicPort: 9999, PrivatePort: 9999, Type: "udp", @@ -778,12 +779,12 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "1.2.3.4", + IP: netip.MustParseAddr("1.2.3.4"), PublicPort: 8887, PrivatePort: 9998, Type: "udp", }, { - IP: "1.2.3.4", + IP: netip.MustParseAddr("1.2.3.4"), PublicPort: 8888, PrivatePort: 9999, Type: "udp", @@ -794,12 +795,12 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "::1", + IP: netip.MustParseAddr("::1"), PublicPort: 8887, PrivatePort: 9998, Type: "udp", }, { - IP: "::1", + IP: netip.MustParseAddr("::1"), PublicPort: 8888, PrivatePort: 9999, Type: "udp", @@ -822,7 +823,7 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "1.2.3.4", + IP: netip.MustParseAddr("1.2.3.4"), PrivatePort: 6677, PublicPort: 7766, Type: "tcp", @@ -837,22 +838,22 @@ func TestDisplayablePorts(t *testing.T) { { ports: []container.PortSummary{ { - IP: "1.2.3.4", + IP: netip.MustParseAddr("1.2.3.4"), PrivatePort: 9988, PublicPort: 8899, Type: "udp", }, { - IP: "1.2.3.4", + IP: netip.MustParseAddr("1.2.3.4"), PrivatePort: 9988, PublicPort: 8899, Type: "tcp", }, { - IP: "4.3.2.1", + IP: netip.MustParseAddr("4.3.2.1"), PrivatePort: 2233, PublicPort: 3322, Type: "tcp", }, { - IP: "::1", + IP: netip.MustParseAddr("::1"), PrivatePort: 2233, PublicPort: 3322, Type: "tcp", @@ -867,12 +868,12 @@ func TestDisplayablePorts(t *testing.T) { PublicPort: 8899, Type: "udp", }, { - IP: "1.2.3.4", + IP: netip.MustParseAddr("1.2.3.4"), PrivatePort: 6677, PublicPort: 7766, Type: "tcp", }, { - IP: "4.3.2.1", + IP: netip.MustParseAddr("4.3.2.1"), PrivatePort: 2233, PublicPort: 3322, Type: "tcp", @@ -895,42 +896,42 @@ func TestDisplayablePorts(t *testing.T) { PrivatePort: 1024, Type: "udp", }, { - IP: "1.1.1.1", + IP: netip.MustParseAddr("1.1.1.1"), PublicPort: 80, PrivatePort: 1024, Type: "tcp", }, { - IP: "1.1.1.1", + IP: netip.MustParseAddr("1.1.1.1"), PublicPort: 80, PrivatePort: 1024, Type: "udp", }, { - IP: "1.1.1.1", + IP: netip.MustParseAddr("1.1.1.1"), PublicPort: 1024, PrivatePort: 80, Type: "tcp", }, { - IP: "1.1.1.1", + IP: netip.MustParseAddr("1.1.1.1"), PublicPort: 1024, PrivatePort: 80, Type: "udp", }, { - IP: "2.1.1.1", + IP: netip.MustParseAddr("2.1.1.1"), PublicPort: 80, PrivatePort: 1024, Type: "tcp", }, { - IP: "2.1.1.1", + IP: netip.MustParseAddr("2.1.1.1"), PublicPort: 80, PrivatePort: 1024, Type: "udp", }, { - IP: "2.1.1.1", + IP: netip.MustParseAddr("2.1.1.1"), PublicPort: 1024, PrivatePort: 80, Type: "tcp", }, { - IP: "2.1.1.1", + IP: netip.MustParseAddr("2.1.1.1"), PublicPort: 1024, PrivatePort: 80, Type: "udp", diff --git a/cli/command/network/connect.go b/cli/command/network/connect.go index dc9d96ae50f6..211087b5d16e 100644 --- a/cli/command/network/connect.go +++ b/cli/command/network/connect.go @@ -3,6 +3,8 @@ package network import ( "context" "errors" + "net" + "net/netip" "strings" "github.com/docker/cli/cli" @@ -17,11 +19,11 @@ import ( type connectOptions struct { network string container string - ipaddress string - ipv6address string + ipaddress net.IP // TODO(thaJeztah): we need a flag-type to handle netip.Addr directly + ipv6address net.IP // TODO(thaJeztah): we need a flag-type to handle netip.Addr directly links opts.ListOpts aliases []string - linklocalips []string + linklocalips []net.IP // TODO(thaJeztah): we need a flag-type to handle []netip.Addr directly driverOpts []string gwPriority int } @@ -51,11 +53,11 @@ func newConnectCommand(dockerCLI command.Cli) *cobra.Command { } flags := cmd.Flags() - flags.StringVar(&options.ipaddress, "ip", "", `IPv4 address (e.g., "172.30.100.104")`) - flags.StringVar(&options.ipv6address, "ip6", "", `IPv6 address (e.g., "2001:db8::33")`) + flags.IPVar(&options.ipaddress, "ip", nil, `IPv4 address (e.g., "172.30.100.104")`) + flags.IPVar(&options.ipv6address, "ip6", nil, `IPv6 address (e.g., "2001:db8::33")`) flags.Var(&options.links, "link", "Add link to another container") flags.StringSliceVar(&options.aliases, "alias", []string{}, "Add network-scoped alias for the container") - flags.StringSliceVar(&options.linklocalips, "link-local-ip", []string{}, "Add a link-local address for the container") + flags.IPSliceVar(&options.linklocalips, "link-local-ip", nil, "Add a link-local address for the container") flags.StringSliceVar(&options.driverOpts, "driver-opt", []string{}, "driver options for the network") flags.IntVar(&options.gwPriority, "gw-priority", 0, "Highest gw-priority provides the default gateway. Accepts positive and negative values.") return cmd @@ -69,9 +71,9 @@ func runConnect(ctx context.Context, apiClient client.NetworkAPIClient, options return apiClient.NetworkConnect(ctx, options.network, options.container, &network.EndpointSettings{ IPAMConfig: &network.EndpointIPAMConfig{ - IPv4Address: options.ipaddress, - IPv6Address: options.ipv6address, - LinkLocalIPs: options.linklocalips, + IPv4Address: toNetipAddr(options.ipaddress), + IPv6Address: toNetipAddr(options.ipv6address), + LinkLocalIPs: toNetipAddrSlice(options.linklocalips), }, Links: options.links.GetSlice(), Aliases: options.aliases, @@ -93,3 +95,48 @@ func convertDriverOpt(options []string) (map[string]string, error) { } return driverOpt, nil } + +func toNetipAddrSlice(ips []net.IP) []netip.Addr { + netips := make([]netip.Addr, 0, len(ips)) + for _, ip := range ips { + netips = append(netips, toNetipAddr(ip)) + } + return netips +} + +func toNetipAddr(ip net.IP) netip.Addr { + if len(ip) == 0 { + return netip.Addr{} + } + if ip4 := ip.To4(); ip4 != nil { + a, _ := netip.AddrFromSlice(ip4) + return a + } + if ip16 := ip.To16(); ip16 != nil { + a, _ := netip.AddrFromSlice(ip16) + return a + } + return netip.Addr{} +} + +func ipNetToPrefix(n net.IPNet) netip.Prefix { + if n.IP == nil { + return netip.Prefix{} + } + + ip := n.IP.To4() + if ip == nil { + ip = n.IP.To16() + } + if ip == nil { + return netip.Prefix{} + } + + addr, ok := netip.AddrFromSlice(ip) + if !ok { + return netip.Prefix{} + } + + ones, _ := n.Mask.Size() + return netip.PrefixFrom(addr, ones) +} diff --git a/cli/command/network/connect_test.go b/cli/command/network/connect_test.go index f1176a031ed4..81777c5a7fce 100644 --- a/cli/command/network/connect_test.go +++ b/cli/command/network/connect_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "io" + "net/netip" "testing" "github.com/docker/cli/internal/test" @@ -46,9 +47,9 @@ func TestNetworkConnectErrors(t *testing.T) { func TestNetworkConnectWithFlags(t *testing.T) { expectedConfig := &network.EndpointSettings{ IPAMConfig: &network.EndpointIPAMConfig{ - IPv4Address: "192.168.4.1", - IPv6Address: "fdef:f401:8da0:1234::5678", - LinkLocalIPs: []string{"169.254.42.42"}, + IPv4Address: netip.MustParseAddr("192.168.4.1"), + IPv6Address: netip.MustParseAddr("fdef:f401:8da0:1234::5678"), + LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.42.42")}, }, Links: []string{"otherctr"}, Aliases: []string{"poor-yorick"}, diff --git a/cli/command/network/create.go b/cli/command/network/create.go index e0798584d42a..482c16859785 100644 --- a/cli/command/network/create.go +++ b/cli/command/network/create.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net" + "net/netip" "strings" "github.com/docker/cli/cli" @@ -34,9 +35,9 @@ type createOptions struct { type ipamOptions struct { driver string - subnets []string - ipRanges []string - gateways []string + subnets []string // TODO(thaJeztah): change to []net.IPNet? This won't accept a bare address (without "/xxx"); we need a flag-type to handle []netip.Prefix directly + ipRanges []net.IPNet // TODO(thaJeztah): we need a flag-type to handle []netip.Prefix directly + gateways []net.IP // TODO(thaJeztah): we need a flag-type to handle []netip.Addr directly auxAddresses opts.MapOpts driverOpts opts.MapOpts } @@ -92,8 +93,8 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { flags.StringVar(&options.ipam.driver, "ipam-driver", "default", "IP Address Management Driver") flags.StringSliceVar(&options.ipam.subnets, "subnet", []string{}, "Subnet in CIDR format that represents a network segment") - flags.StringSliceVar(&options.ipam.ipRanges, "ip-range", []string{}, "Allocate container ip from a sub-range") - flags.StringSliceVar(&options.ipam.gateways, "gateway", []string{}, "IPv4 or IPv6 Gateway for the master subnet") + flags.IPNetSliceVar(&options.ipam.ipRanges, "ip-range", nil, "Allocate container ip from a sub-range") + flags.IPSliceVar(&options.ipam.gateways, "gateway", nil, "IPv4 or IPv6 Gateway for the master subnet") flags.Var(&options.ipam.auxAddresses, "aux-address", "Auxiliary IPv4 or IPv6 addresses used by Network driver") flags.Var(&options.ipam.driverOpts, "ipam-opt", "Set IPAM driver specific options") @@ -149,6 +150,7 @@ func createIPAMConfig(options ipamOptions) (*network.IPAM, error) { // Populate non-overlapping subnets into consolidation map for _, s := range options.subnets { + // TODO(thaJeztah): is all this validation needed on the CLI-side? for k := range iData { ok1, err := subnetMatches(s, k) if err != nil { @@ -162,28 +164,32 @@ func createIPAMConfig(options ipamOptions) (*network.IPAM, error) { return nil, errors.New("multiple overlapping subnet configuration is not supported") } } - iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}} + sn, err := parsePrefixOrAddr(s) + if err != nil { + return nil, err + } + iData[s] = &network.IPAMConfig{Subnet: sn, AuxAddress: map[string]netip.Addr{}} } // Validate and add valid ip ranges for _, r := range options.ipRanges { + // TODO(thaJeztah): is all this validation needed on the CLI-side? match := false for _, s := range options.subnets { - if _, _, err := net.ParseCIDR(r); err != nil { - return nil, err - } - ok, err := subnetMatches(s, r) + ok, err := subnetMatches(s, r.String()) if err != nil { return nil, err } if !ok { continue } - if iData[s].IPRange != "" { + + // Using "IsValid" to check if a valid IPRange was already set. + if iData[s].IPRange.IsValid() { return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s) } d := iData[s] - d.IPRange = r + d.IPRange = ipNetToPrefix(r) match = true } if !match { @@ -195,18 +201,19 @@ func createIPAMConfig(options ipamOptions) (*network.IPAM, error) { for _, g := range options.gateways { match := false for _, s := range options.subnets { - ok, err := subnetMatches(s, g) + // TODO(thaJeztah): is all this validation needed on the CLI-side? + ok, err := subnetMatches(s, g.String()) if err != nil { return nil, err } if !ok { continue } - if iData[s].Gateway != "" { + if iData[s].Gateway.IsValid() { return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s) } d := iData[s] - d.Gateway = g + d.Gateway = toNetipAddr(g) match = true } if !match { @@ -216,16 +223,20 @@ func createIPAMConfig(options ipamOptions) (*network.IPAM, error) { // Validate and add aux-addresses for key, aa := range options.auxAddresses.GetAll() { + auxAddr, err := netip.ParseAddr(aa) + if err != nil { + return nil, err + } match := false for _, s := range options.subnets { - ok, err := subnetMatches(s, aa) + ok, err := subnetMatches(s, auxAddr.String()) if err != nil { return nil, err } if !ok { continue } - iData[s].AuxAddress[key] = aa + iData[s].AuxAddress[key] = auxAddr match = true } if !match { @@ -264,3 +275,20 @@ func subnetMatches(subnet, data string) (bool, error) { return s.Contains(ip), nil } + +// parsePrefixOrAddr parses s as a subnet in CIDR notation (e.g. "10.0.0.0/24"). +// If s does not include a prefix length, it is interpreted as a single-address +// subnet using the full address width (/32 for IPv4 or /128 for IPv6). +// +// It returns the resulting netip.Prefix or an error if the input is invalid. +func parsePrefixOrAddr(s string) (netip.Prefix, error) { + pfx, err := netip.ParsePrefix(s) + if err != nil { + addr, err := netip.ParseAddr(s) + if err != nil { + return netip.Prefix{}, fmt.Errorf("invalid address: %w", err) + } + pfx = netip.PrefixFrom(addr, addr.BitLen()) + } + return pfx, nil +} diff --git a/cli/command/network/create_test.go b/cli/command/network/create_test.go index a5d8c102ce00..8f9aed02405a 100644 --- a/cli/command/network/create_test.go +++ b/cli/command/network/create_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "io" + "net/netip" "strings" "testing" @@ -175,10 +176,10 @@ func TestNetworkCreateWithFlags(t *testing.T) { expectedDriver := "foo" expectedOpts := []network.IPAMConfig{ { - Subnet: "192.168.4.0/24", - IPRange: "192.168.4.0/24", - Gateway: "192.168.4.1/24", - AuxAddress: map[string]string{}, + Subnet: netip.MustParsePrefix("192.168.4.0/24"), + IPRange: netip.MustParsePrefix("192.168.4.0/24"), + Gateway: netip.MustParseAddr("192.168.4.1"), + AuxAddress: map[string]netip.Addr{}, }, } cli := test.NewFakeCli(&fakeClient{ diff --git a/cli/command/service/formatter.go b/cli/command/service/formatter.go index ba00600a995c..778536a0d69a 100644 --- a/cli/command/service/formatter.go +++ b/cli/command/service/formatter.go @@ -735,7 +735,7 @@ type portRange struct { pEnd uint32 tStart uint32 tEnd uint32 - protocol swarm.PortConfigProtocol + protocol network.IPProtocol } func (pr portRange) String() string { diff --git a/cli/command/service/inspect_test.go b/cli/command/service/inspect_test.go index fa09a64ce6c6..607850ca4e2d 100644 --- a/cli/command/service/inspect_test.go +++ b/cli/command/service/inspect_test.go @@ -6,6 +6,7 @@ package service import ( "bytes" "encoding/json" + "net/netip" "strings" "testing" "time" @@ -27,7 +28,7 @@ func formatServiceInspect(t *testing.T, format formatter.Format, now time.Time) Mode: "vip", Ports: []swarm.PortConfig{ { - Protocol: swarm.PortConfigProtocolTCP, + Protocol: network.TCP, TargetPort: 5000, }, }, @@ -108,7 +109,7 @@ func formatServiceInspect(t *testing.T, format formatter.Format, now time.Time) Spec: *endpointSpec, Ports: []swarm.PortConfig{ { - Protocol: swarm.PortConfigProtocolTCP, + Protocol: network.TCP, TargetPort: 5000, PublishedPort: 30000, }, @@ -116,7 +117,7 @@ func formatServiceInspect(t *testing.T, format formatter.Format, now time.Time) VirtualIPs: []swarm.EndpointVirtualIP{ { NetworkID: "6o4107cj2jx9tihgb0jyts6pj", - Addr: "10.255.0.4/16", + Addr: netip.MustParseAddr("10.255.0.4/16"), }, }, }, diff --git a/cli/command/service/opts.go b/cli/command/service/opts.go index 2dc89fffcb92..d276b2c10042 100644 --- a/cli/command/service/opts.go +++ b/cli/command/service/opts.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "net/netip" "sort" "strconv" "strings" @@ -762,7 +763,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N Mounts: options.mounts.Value(), Init: &options.init, DNSConfig: &swarm.DNSConfig{ - Nameservers: options.dns.GetSlice(), + Nameservers: toNetipAddrSlice(options.dns.GetSlice()), Search: options.dnsSearch.GetSlice(), Options: options.dnsOption.GetSlice(), }, @@ -1073,3 +1074,15 @@ const ( flagUlimitRemove = "ulimit-rm" flagOomScoreAdj = "oom-score-adj" ) + +func toNetipAddrSlice(ips []string) []netip.Addr { + netips := make([]netip.Addr, 0, len(ips)) + for _, ip := range ips { + addr, err := netip.ParseAddr(ip) + if err != nil { + continue + } + netips = append(netips, addr) + } + return netips +} diff --git a/cli/command/service/update.go b/cli/command/service/update.go index 15e33419fa77..5856b45f528e 100644 --- a/cli/command/service/update.go +++ b/cli/command/service/update.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "net/netip" + "slices" "sort" "strings" "time" @@ -15,6 +17,7 @@ import ( "github.com/docker/cli/opts/swarmopts" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/mount" + "github.com/moby/moby/api/types/network" "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" @@ -999,9 +1002,9 @@ func updateGroups(flags *pflag.FlagSet, groups *[]string) error { return nil } -func removeDuplicates(entries []string) []string { - hit := map[string]bool{} - newEntries := []string{} +func removeDuplicates(entries []netip.Addr) []netip.Addr { + hit := map[netip.Addr]bool{} + var newEntries []netip.Addr for _, v := range entries { if !hit[v] { newEntries = append(newEntries, v) @@ -1017,24 +1020,39 @@ func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error { nameservers := (*config).Nameservers if flags.Changed(flagDNSAdd) { values := flags.Lookup(flagDNSAdd).Value.(*opts.ListOpts).GetSlice() - nameservers = append(nameservers, values...) + var ips []netip.Addr + for _, ip := range values { + a, err := netip.ParseAddr(ip) + if err != nil { + return err + } + ips = append(ips, a) + } + nameservers = append(nameservers, ips...) } - nameservers = removeDuplicates(nameservers) + // Remove duplicates + slices.SortFunc(newConfig.Nameservers, func(a, b netip.Addr) int { + return a.Compare(b) + }) + nameservers = slices.Compact(nameservers) + toRemove := buildToRemoveSet(flags, flagDNSRemove) for _, nameserver := range nameservers { - if _, exists := toRemove[nameserver]; !exists { + if _, exists := toRemove[nameserver.String()]; !exists { newConfig.Nameservers = append(newConfig.Nameservers, nameserver) } } // Sort so that result is predictable. - sort.Strings(newConfig.Nameservers) + slices.SortFunc(newConfig.Nameservers, func(a, b netip.Addr) int { + return a.Compare(b) + }) search := (*config).Search if flags.Changed(flagDNSSearchAdd) { values := flags.Lookup(flagDNSSearchAdd).Value.(*opts.ListOpts).GetSlice() search = append(search, values...) } - search = removeDuplicates(search) + search = slices.Compact(search) toRemove = buildToRemoveSet(flags, flagDNSSearchRemove) for _, entry := range search { if _, exists := toRemove[entry]; !exists { @@ -1049,9 +1067,16 @@ func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error { values := flags.Lookup(flagDNSOptionAdd).Value.(*opts.ListOpts).GetSlice() options = append(options, values...) } - options = removeDuplicates(options) + options = slices.Compact(options) toRemove = buildToRemoveSet(flags, flagDNSOptionRemove) for _, option := range options { + a, err := netip.ParseAddr(option) + if err != nil { + return err + } + var ips []netip.Addr + ips = append(ips, a) + if _, exists := toRemove[option]; !exists { newConfig.Options = append(newConfig.Options, option) } @@ -1120,16 +1145,16 @@ portLoop: return nil } -func equalProtocol(prot1, prot2 swarm.PortConfigProtocol) bool { +func equalProtocol(prot1, prot2 network.IPProtocol) bool { return prot1 == prot2 || - (prot1 == swarm.PortConfigProtocol("") && prot2 == swarm.PortConfigProtocolTCP) || - (prot2 == swarm.PortConfigProtocol("") && prot1 == swarm.PortConfigProtocolTCP) + (prot1 == "" && prot2 == network.TCP) || + (prot2 == "" && prot1 == network.TCP) } func equalPublishMode(mode1, mode2 swarm.PortConfigPublishMode) bool { return mode1 == mode2 || - (mode1 == swarm.PortConfigPublishMode("") && mode2 == swarm.PortConfigPublishModeIngress) || - (mode2 == swarm.PortConfigPublishMode("") && mode1 == swarm.PortConfigPublishModeIngress) + (mode1 == "" && mode2 == swarm.PortConfigPublishModeIngress) || + (mode2 == "" && mode1 == swarm.PortConfigPublishModeIngress) } func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error { @@ -1232,7 +1257,9 @@ func updateHosts(flags *pflag.FlagSet, hosts *[]string) error { values := convertExtraHostsToSwarmHosts(flags.Lookup(flagHostAdd).Value.(*opts.ListOpts).GetSlice()) newHosts = append(newHosts, values...) } - *hosts = removeDuplicates(newHosts) + + slices.Sort(newHosts) + *hosts = slices.Compact(newHosts) return nil } diff --git a/cli/command/service/update_test.go b/cli/command/service/update_test.go index 542c83adb5b6..c795b121ccf8 100644 --- a/cli/command/service/update_test.go +++ b/cli/command/service/update_test.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "net/netip" "reflect" "sort" "testing" @@ -213,7 +214,7 @@ func TestUpdateDNSConfig(t *testing.T) { flags.Set("dns-option-rm", "timeout:3") config := &swarm.DNSConfig{ - Nameservers: []string{"3.3.3.3", "5.5.5.5"}, + Nameservers: []netip.Addr{netip.MustParseAddr("3.3.3.3"), netip.MustParseAddr("5.5.5.5")}, Search: []string{"localdomain"}, Options: []string{"timeout:3"}, } @@ -272,7 +273,7 @@ func TestUpdatePorts(t *testing.T) { flags.Set("publish-rm", "333/udp") portConfigs := []swarm.PortConfig{ - {TargetPort: 333, Protocol: swarm.PortConfigProtocolUDP}, + {TargetPort: 333, Protocol: network.UDP}, {TargetPort: 555}, } @@ -295,7 +296,7 @@ func TestUpdatePortsDuplicate(t *testing.T) { { TargetPort: 80, PublishedPort: 80, - Protocol: swarm.PortConfigProtocolTCP, + Protocol: network.TCP, PublishMode: swarm.PortConfigPublishModeIngress, }, } @@ -488,7 +489,7 @@ func TestUpdatePortsRmWithProtocol(t *testing.T) { { TargetPort: 80, PublishedPort: 8080, - Protocol: swarm.PortConfigProtocolTCP, + Protocol: network.TCP, PublishMode: swarm.PortConfigPublishModeIngress, }, } diff --git a/cli/command/stack/services_test.go b/cli/command/stack/services_test.go index 51d2b8d38e20..0fe5b5fc8e0a 100644 --- a/cli/command/stack/services_test.go +++ b/cli/command/stack/services_test.go @@ -8,6 +8,7 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test/builders" + "github.com/moby/moby/api/types/network" "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/client" "gotest.tools/v3/assert" @@ -164,7 +165,7 @@ func TestStackServicesWithoutFormat(t *testing.T) { PublishMode: swarm.PortConfigPublishModeIngress, PublishedPort: 0, TargetPort: 3232, - Protocol: swarm.PortConfigProtocolTCP, + Protocol: network.TCP, }), )}, nil }, diff --git a/cli/command/swarm/init.go b/cli/command/swarm/init.go index b19b1beb27c9..7fa4fe20fb1d 100644 --- a/cli/command/swarm/init.go +++ b/cli/command/swarm/init.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "net/netip" "strings" "github.com/docker/cli/cli" @@ -67,9 +68,22 @@ func newInitCommand(dockerCLI command.Cli) *cobra.Command { func runInit(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, opts initOptions) error { apiClient := dockerCLI.Client() - defaultAddrPool := make([]string, 0, len(opts.defaultAddrPools)) + // TODO(thaJeztah): should we change opts.defaultAddrPools to be the right type? What formats does it accept? + defaultAddrPool := make([]netip.Prefix, 0, len(opts.defaultAddrPools)) for _, p := range opts.defaultAddrPools { - defaultAddrPool = append(defaultAddrPool, p.String()) + if p.IP == nil || len(p.IP) == 0 { + continue + } + ip := p.IP.To4() + if ip == nil { + ip = p.IP.To16() + } + addr, ok := netip.AddrFromSlice(ip) + if !ok { + return fmt.Errorf("invalid IP address: %s", p.IP) + } + ones, _ := p.Mask.Size() + defaultAddrPool = append(defaultAddrPool, netip.PrefixFrom(addr, ones)) } req := swarm.InitRequest{ ListenAddr: opts.listenAddr.String(), diff --git a/cli/command/system/info.go b/cli/command/system/info.go index 0fcca3d95ef9..989359eea9bf 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -372,9 +372,8 @@ func prettyPrintServerInfo(streams command.Streams, info *dockerInfo) []error { } } - for _, registryConfig := range info.RegistryConfig.InsecureRegistryCIDRs { - mask, _ := registryConfig.Mask.Size() - fprintf(output, " %s/%d\n", registryConfig.IP.String(), mask) + for _, cidr := range info.RegistryConfig.InsecureRegistryCIDRs { + fprintf(output, " %s\n", cidr) } } @@ -429,7 +428,7 @@ func printSwarmInfo(output io.Writer, info system.Info) { var strAddrPool strings.Builder if info.Swarm.Cluster.DefaultAddrPool != nil { for _, p := range info.Swarm.Cluster.DefaultAddrPool { - strAddrPool.WriteString(p + " ") + strAddrPool.WriteString(p.String() + " ") } fprintln(output, " Default Address Pool:", strAddrPool.String()) fprintln(output, " SubnetSize:", info.Swarm.Cluster.SubnetSize) diff --git a/cli/command/system/info_test.go b/cli/command/system/info_test.go index 90222a61699f..5c8fbd9d9b3f 100644 --- a/cli/command/system/info_test.go +++ b/cli/command/system/info_test.go @@ -3,7 +3,7 @@ package system import ( "encoding/base64" "errors" - "net" + "net/netip" "testing" "time" @@ -69,11 +69,8 @@ var sampleInfoNoSwarm = system.Info{ Architecture: "x86_64", IndexServerAddress: "https://index.docker.io/v1/", RegistryConfig: ®istrytypes.ServiceConfig{ - InsecureRegistryCIDRs: []*registrytypes.NetIPNet{ - { - IP: net.ParseIP("127.0.0.0"), - Mask: net.IPv4Mask(255, 0, 0, 0), - }, + InsecureRegistryCIDRs: []netip.Prefix{ + netip.PrefixFrom(netip.MustParseAddr("127.0.0.0"), 8), }, IndexConfigs: map[string]*registrytypes.IndexInfo{ "docker.io": { @@ -119,7 +116,7 @@ var sampleInfoNoSwarm = system.Info{ SecurityOptions: []string{"name=apparmor", "name=seccomp,profile=default"}, DefaultAddressPools: []system.NetworkAddressPool{ { - Base: "10.123.0.0/16", + Base: netip.MustParsePrefix("10.123.0.0/16"), Size: 24, }, }, diff --git a/cli/compose/convert/compose.go b/cli/compose/convert/compose.go index d2122f8b26b3..74c9c6a6e381 100644 --- a/cli/compose/convert/compose.go +++ b/cli/compose/convert/compose.go @@ -1,6 +1,8 @@ package convert import ( + "fmt" + "net/netip" "os" "strings" @@ -84,8 +86,9 @@ func Networks(namespace Namespace, networks networkMap, servicesNetworks map[str Driver: nw.Ipam.Driver, } for _, ipamConfig := range nw.Ipam.Config { + sn, _ := parsePrefixOrAddr(ipamConfig.Subnet) // TODO(thaJeztah): change Subnet field to netip.Prefix (but this would break "address only" formats. createOpts.IPAM.Config = append(createOpts.IPAM.Config, network.IPAMConfig{ - Subnet: ipamConfig.Subnet, + Subnet: sn, }) } } @@ -199,3 +202,20 @@ func fileObjectConfig(namespace Namespace, name string, obj composetypes.FileObj Data: data, }, nil } + +// parsePrefixOrAddr parses s as a subnet in CIDR notation (e.g. "10.0.0.0/24"). +// If s does not include a prefix length, it is interpreted as a single-address +// subnet using the full address width (/32 for IPv4 or /128 for IPv6). +// +// It returns the resulting netip.Prefix or an error if the input is invalid. +func parsePrefixOrAddr(s string) (netip.Prefix, error) { + pfx, err := netip.ParsePrefix(s) + if err != nil { + addr, err := netip.ParseAddr(s) + if err != nil { + return netip.Prefix{}, fmt.Errorf("invalid address: %w", err) + } + pfx = netip.PrefixFrom(addr, addr.BitLen()) + } + return pfx, nil +} diff --git a/cli/compose/convert/compose_test.go b/cli/compose/convert/compose_test.go index f3d697b6725d..2055847c5d86 100644 --- a/cli/compose/convert/compose_test.go +++ b/cli/compose/convert/compose_test.go @@ -1,6 +1,7 @@ package convert import ( + "net/netip" "testing" composetypes "github.com/docker/cli/cli/compose/types" @@ -89,7 +90,7 @@ func TestNetworks(t *testing.T) { Driver: "driver", Config: []network.IPAMConfig{ { - Subnet: "10.0.0.0", + Subnet: netip.MustParsePrefix("10.0.0.0"), }, }, }, diff --git a/cli/compose/convert/service.go b/cli/compose/convert/service.go index 9714cb372f47..731b2195cca0 100644 --- a/cli/compose/convert/service.go +++ b/cli/compose/convert/service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net/netip" "os" "sort" "strings" @@ -13,6 +14,7 @@ import ( composetypes "github.com/docker/cli/cli/compose/types" "github.com/docker/cli/opts" "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" @@ -96,7 +98,7 @@ func Service( return swarm.ServiceSpec{}, err } - dnsConfig := convertDNSConfig(service.DNS, service.DNSSearch) + dnsConfig := convertDNSConfig(service.DNS, service.DNSSearch) // TODO(thaJeztah): change service.DNS to a []netip.Addr var privileges swarm.Privileges privileges.CredentialSpec, err = convertCredentialSpec( @@ -578,7 +580,7 @@ func convertEndpointSpec(endpointMode string, source []composetypes.ServicePortC portConfigs := []swarm.PortConfig{} for _, port := range source { portConfig := swarm.PortConfig{ - Protocol: swarm.PortConfigProtocol(port.Protocol), + Protocol: network.IPProtocol(port.Protocol), TargetPort: port.Target, PublishedPort: port.Published, PublishMode: swarm.PortConfigPublishMode(port.Mode), @@ -641,15 +643,27 @@ func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error) } func convertDNSConfig(dns []string, dnsSearch []string) *swarm.DNSConfig { - if dns != nil || dnsSearch != nil { + if len(dns) > 0 || len(dnsSearch) > 0 { return &swarm.DNSConfig{ - Nameservers: dns, + Nameservers: toNetipAddrSlice(dns), Search: dnsSearch, } } return nil } +func toNetipAddrSlice(ips []string) []netip.Addr { + netips := make([]netip.Addr, 0, len(ips)) + for _, ip := range ips { + addr, err := netip.ParseAddr(ip) + if err != nil { + continue + } + netips = append(netips, addr) + } + return netips +} + func convertCredentialSpec(namespace Namespace, spec composetypes.CredentialSpecConfig, refs []*swarm.ConfigReference) (*swarm.CredentialSpec, error) { var o []string diff --git a/cli/compose/convert/service_test.go b/cli/compose/convert/service_test.go index 82db561577e1..5f50205ef069 100644 --- a/cli/compose/convert/service_test.go +++ b/cli/compose/convert/service_test.go @@ -3,6 +3,7 @@ package convert import ( "context" "errors" + "net/netip" "os" "strings" "testing" @@ -300,14 +301,15 @@ func TestConvertDNSConfigEmpty(t *testing.T) { } var ( - nameservers = []string{"8.8.8.8", "9.9.9.9"} - search = []string{"dc1.example.com", "dc2.example.com"} + nameservers = []string{"8.8.8.8", "9.9.9.9"} + expectedNameservers = []netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("9.9.9.9")} + search = []string{"dc1.example.com", "dc2.example.com"} ) func TestConvertDNSConfigAll(t *testing.T) { dnsConfig := convertDNSConfig(nameservers, search) assert.Check(t, is.DeepEqual(&swarm.DNSConfig{ - Nameservers: nameservers, + Nameservers: expectedNameservers, Search: search, }, dnsConfig)) } @@ -315,7 +317,7 @@ func TestConvertDNSConfigAll(t *testing.T) { func TestConvertDNSConfigNameservers(t *testing.T) { dnsConfig := convertDNSConfig(nameservers, nil) assert.Check(t, is.DeepEqual(&swarm.DNSConfig{ - Nameservers: nameservers, + Nameservers: expectedNameservers, Search: nil, }, dnsConfig)) } diff --git a/cli/compose/loader/loader.go b/cli/compose/loader/loader.go index 953bdd4f3d23..d1c59fe85947 100644 --- a/cli/compose/loader/loader.go +++ b/cli/compose/loader/loader.go @@ -25,7 +25,6 @@ import ( "github.com/docker/go-units" "github.com/go-viper/mapstructure/v2" "github.com/google/shlex" - "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/versions" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" @@ -925,15 +924,17 @@ func toServicePortConfigs(value string) ([]any, error) { return nil, err } // We need to sort the key of the ports to make sure it is consistent - keys := []string{} + keys := make([]string, 0, len(ports)) + portMap := make(map[string]nat.Port, len(ports)) for port := range ports { - keys = append(keys, string(port)) + key := port.String() + portMap[key] = port + keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { - // Reuse ConvertPortToPortConfig so that it is consistent - portConfig, err := swarmopts.ConvertPortToPortConfig(container.PortRangeProto(key), portBindings) + portConfig, err := swarmopts.ConvertPortToPortConfig(portMap[key], portBindings) if err != nil { return nil, err } diff --git a/internal/test/builders/container.go b/internal/test/builders/container.go index 45e98682373f..6c5f0324b9f7 100644 --- a/internal/test/builders/container.go +++ b/internal/test/builders/container.go @@ -1,6 +1,7 @@ package builders import ( + "net/netip" "time" "github.com/moby/moby/api/types/container" @@ -73,7 +74,7 @@ func WithSize(size int64) func(*container.Summary) { // IP sets the ip of the port func IP(ip string) func(*container.PortSummary) { return func(p *container.PortSummary) { - p.IP = ip + p.IP, _ = netip.ParseAddr(ip) } } diff --git a/opts/network.go b/opts/network.go index 43b3a09d4151..489ef8be3971 100644 --- a/opts/network.go +++ b/opts/network.go @@ -4,6 +4,7 @@ import ( "encoding/csv" "errors" "fmt" + "net/netip" "regexp" "strconv" "strings" @@ -26,9 +27,9 @@ type NetworkAttachmentOpts struct { Aliases []string DriverOpts map[string]string Links []string // TODO add support for links in the csv notation of `--network` - IPv4Address string - IPv6Address string - LinkLocalIPs []string + IPv4Address netip.Addr + IPv6Address netip.Addr + LinkLocalIPs []netip.Addr MacAddress string GwPriority int } @@ -70,13 +71,23 @@ func (n *NetworkOpt) Set(value string) error { //nolint:gocyclo case networkOptAlias: netOpt.Aliases = append(netOpt.Aliases, val) case networkOptIPv4Address: - netOpt.IPv4Address = val + netOpt.IPv4Address, err = netip.ParseAddr(val) + if err != nil { + return err + } case networkOptIPv6Address: - netOpt.IPv6Address = val + netOpt.IPv6Address, err = netip.ParseAddr(val) + if err != nil { + return err + } case networkOptMacAddress: netOpt.MacAddress = val case networkOptLinkLocalIP: - netOpt.LinkLocalIPs = append(netOpt.LinkLocalIPs, val) + a, err := netip.ParseAddr(val) + if err != nil { + return err + } + netOpt.LinkLocalIPs = append(netOpt.LinkLocalIPs, a) case driverOpt: key, val, err = parseDriverOpt(val) if err != nil { diff --git a/opts/network_test.go b/opts/network_test.go index ffdcbf26cd80..65a1245adb3f 100644 --- a/opts/network_test.go +++ b/opts/network_test.go @@ -1,8 +1,10 @@ package opts import ( + "net/netip" "testing" + "github.com/google/go-cmp/cmp/cmpopts" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) @@ -64,8 +66,8 @@ func TestNetworkOptAdvancedSyntax(t *testing.T) { { Target: "docknet1", Aliases: []string{}, - IPv4Address: "172.20.88.22", - IPv6Address: "2001:db8::8822", + IPv4Address: netip.MustParseAddr("172.20.88.22"), + IPv6Address: netip.MustParseAddr("2001:db8::8822"), }, }, }, @@ -94,7 +96,7 @@ func TestNetworkOptAdvancedSyntax(t *testing.T) { { Target: "docknet1", Aliases: []string{}, - LinkLocalIPs: []string{"169.254.169.254", "169.254.10.10"}, + LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.169.254"), netip.MustParseAddr("169.254.10.10")}, }, }, }, @@ -127,7 +129,7 @@ func TestNetworkOptAdvancedSyntax(t *testing.T) { t.Run(tc.value, func(t *testing.T) { var network NetworkOpt assert.NilError(t, network.Set(tc.value)) - assert.Check(t, is.DeepEqual(tc.expected, network.Value())) + assert.Check(t, is.DeepEqual(tc.expected, network.Value(), cmpopts.EquateComparable())) }) } } diff --git a/opts/swarmopts/port.go b/opts/swarmopts/port.go index cab64f0ab52d..e973744107d3 100644 --- a/opts/swarmopts/port.go +++ b/opts/swarmopts/port.go @@ -10,7 +10,7 @@ import ( "strings" "github.com/docker/go-connections/nat" - "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" "github.com/moby/moby/api/types/swarm" "github.com/sirupsen/logrus" ) @@ -43,7 +43,7 @@ func (p *PortOpt) Set(value string) error { } pConfig := swarm.PortConfig{ - Protocol: swarm.PortConfigProtocolTCP, + Protocol: network.TCP, PublishMode: swarm.PortConfigPublishModeIngress, } for _, field := range fields { @@ -54,9 +54,9 @@ func (p *PortOpt) Set(value string) error { } switch key { case portOptProtocol: - switch swarm.PortConfigProtocol(val) { - case swarm.PortConfigProtocolTCP, swarm.PortConfigProtocolUDP, swarm.PortConfigProtocolSCTP: - pConfig.Protocol = swarm.PortConfigProtocol(val) + switch proto := network.IPProtocol(val); proto { + case network.TCP, network.UDP, network.SCTP: + pConfig.Protocol = proto default: return fmt.Errorf("invalid protocol value '%s'", val) } @@ -107,7 +107,7 @@ func (p *PortOpt) Set(value string) error { } for _, portBindings := range portBindingMap { for _, portBinding := range portBindings { - if portBinding.HostIP != "" { + if !portBinding.HostIP.IsValid() { return errors.New("hostip is not supported") } } @@ -148,30 +148,28 @@ func (p *PortOpt) Value() []swarm.PortConfig { // ConvertPortToPortConfig converts ports to the swarm type func ConvertPortToPortConfig( - portRangeProto container.PortRangeProto, - portBindings map[container.PortRangeProto][]container.PortBinding, + portProto nat.Port, + portBindings map[nat.Port][]nat.PortBinding, ) ([]swarm.PortConfig, error) { - proto, port := nat.SplitProtoPort(string(portRangeProto)) - portInt, _ := strconv.ParseUint(port, 10, 16) - proto = strings.ToLower(proto) + proto, portInt := portProto.Proto(), portProto.Int() ports := make([]swarm.PortConfig, 0, len(portBindings)) - for _, binding := range portBindings[portRangeProto] { - if p := net.ParseIP(binding.HostIP); p != nil && !p.IsUnspecified() { + for _, binding := range portBindings[portProto] { + if !binding.HostIP.IsUnspecified() { // TODO(thaJeztah): use context-logger, so that this output can be suppressed (in tests). - logrus.Warnf("ignoring IP-address (%s:%s) service will listen on '0.0.0.0'", net.JoinHostPort(binding.HostIP, binding.HostPort), portRangeProto) + logrus.Warnf("ignoring IP-address (%s:%s) service will listen on '0.0.0.0'", net.JoinHostPort(binding.HostIP.String(), binding.HostPort), portProto) } startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort) if err != nil && binding.HostPort != "" { - return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port) + return nil, fmt.Errorf("invalid hostport binding (%s) for port (%d)", binding.HostPort, portProto.Int()) } for i := startHostPort; i <= endHostPort; i++ { ports = append(ports, swarm.PortConfig{ // TODO Name: ? - Protocol: swarm.PortConfigProtocol(proto), + Protocol: proto, TargetPort: uint32(portInt), PublishedPort: uint32(i), PublishMode: swarm.PortConfigPublishModeIngress, diff --git a/opts/swarmopts/port_test.go b/opts/swarmopts/port_test.go index ad1838b60a91..baf4aa11e3eb 100644 --- a/opts/swarmopts/port_test.go +++ b/opts/swarmopts/port_test.go @@ -2,10 +2,11 @@ package swarmopts import ( "bytes" + "net/netip" "os" "testing" - "github.com/moby/moby/api/types/container" + "github.com/docker/go-connections/nat" "github.com/moby/moby/api/types/swarm" "github.com/sirupsen/logrus" "gotest.tools/v3/assert" @@ -347,9 +348,10 @@ func TestConvertPortToPortConfigWithIP(t *testing.T) { var b bytes.Buffer logrus.SetOutput(&b) for _, tc := range testCases { + port, _ := nat.NewPort("tcp", "80") t.Run(tc.value, func(t *testing.T) { - _, err := ConvertPortToPortConfig("80/tcp", map[container.PortRangeProto][]container.PortBinding{ - "80/tcp": {{HostIP: tc.value, HostPort: "2345"}}, + _, err := ConvertPortToPortConfig(port, map[nat.Port][]nat.PortBinding{ + port: {{HostIP: netip.MustParseAddr(tc.value), HostPort: "2345"}}, }) assert.NilError(t, err) if tc.expectedWarning == "" { diff --git a/vendor.mod b/vendor.mod index c7084ef208c3..04373e58bfd1 100644 --- a/vendor.mod +++ b/vendor.mod @@ -28,8 +28,8 @@ require ( github.com/google/uuid v1.6.0 github.com/mattn/go-runewidth v0.0.17 github.com/moby/go-archive v0.1.0 - github.com/moby/moby/api v1.52.0-beta.1.0.20250930082920-4ca8aedf929f // master - github.com/moby/moby/client v0.1.0-beta.0.0.20250930082920-4ca8aedf929f // master + github.com/moby/moby/api v1.52.0-beta.1.0.20251007000938-19e498ea6522 // master + github.com/moby/moby/client v0.1.0-beta.0.0.20251006143509-694e30abff1c // master github.com/moby/patternmatcher v0.6.0 github.com/moby/swarmkit/v2 v2.1.0 github.com/moby/sys/atomicwriter v0.1.0 @@ -108,3 +108,5 @@ require ( google.golang.org/grpc v1.72.2 // indirect google.golang.org/protobuf v1.36.9 // indirect ) + +replace github.com/docker/go-connections => github.com/austinvazquez/go-connections v0.0.0-20251007024048-9df23d402fa0 diff --git a/vendor.sum b/vendor.sum index e0a6e9613cbf..4110025d4626 100644 --- a/vendor.sum +++ b/vendor.sum @@ -6,12 +6,15 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.4.21/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d h1:hi6J4K6DKrR4/ljxn6SF6nURyu785wKMuQcjt7H3VCQ= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/austinvazquez/go-connections v0.0.0-20251007024048-9df23d402fa0 h1:1WZw/lsRIr5xjkJqoeME7hHQiwAstxjnhBIe1XQR+4A= +github.com/austinvazquez/go-connections v0.0.0-20251007024048-9df23d402fa0/go.mod h1:SL1DIMpPBAZkcbSo7B2PXUpO0tIVxbdiZ8GX5SH3vQM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -61,9 +64,6 @@ github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4= github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= @@ -110,6 +110,7 @@ github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519 github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY= github.com/google/certificate-transparency-go v1.1.4/go.mod h1:D6lvbfwckhNrbM9WVl1EVeMOyzC19mpIjMOI4nxBHtQ= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -170,10 +171,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= -github.com/moby/moby/api v1.52.0-beta.1.0.20250930082920-4ca8aedf929f h1:2KUOgolCU2LE+IbKEyrFK478EwZiI4mpeKO8nvztkno= -github.com/moby/moby/api v1.52.0-beta.1.0.20250930082920-4ca8aedf929f/go.mod h1:8sBV0soUREiudtow4vqJGOxa4GyHI5vLQmvgKdHq5Ok= -github.com/moby/moby/client v0.1.0-beta.0.0.20250930082920-4ca8aedf929f h1:H28/bSN4nOtXzD3iBu7SOMGNZh4FTEpFiZMNLWyiQDI= -github.com/moby/moby/client v0.1.0-beta.0.0.20250930082920-4ca8aedf929f/go.mod h1:o5CkJu0RlmnLWRZRaEd7fL6wo0Ggr8Hw/UvgqdIUBuI= +github.com/moby/moby/api v1.52.0-beta.1.0.20251007000938-19e498ea6522 h1:bLoNdDBj+gro5RVsCOg5rQQ5MZQrrtxUuYMllhCtkaU= +github.com/moby/moby/api v1.52.0-beta.1.0.20251007000938-19e498ea6522/go.mod h1:/ou52HkRydg4+odrUR3vFsGgjIyHvprrpEQEkweL10s= +github.com/moby/moby/client v0.1.0-beta.0.0.20251006143509-694e30abff1c h1:r6qSAHeiGgN3kEHCF+fTu4kIMMITXZNk3KKN6JCo1oM= +github.com/moby/moby/client v0.1.0-beta.0.0.20251006143509-694e30abff1c/go.mod h1:o5CkJu0RlmnLWRZRaEd7fL6wo0Ggr8Hw/UvgqdIUBuI= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/swarmkit/v2 v2.1.0 h1:u+cJ5hSyF3HnzsyI+NtegYxdIPQIuibk7IbpXNxuISM= @@ -211,6 +212,7 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= @@ -247,10 +249,13 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -292,6 +297,7 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc h1:zkGwegkOW709y0oiAraH/3D8njopUR/pARHv4tZZ6pw= github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc/go.mod h1:FM4U1E3NzlNMRnSUTU3P1UdukWhYGifqEsjk9fn7BCk= github.com/zmap/zlint/v3 v3.1.0 h1:WjVytZo79m/L1+/Mlphl09WBob6YTGljN5IGWZFpAv0= @@ -329,10 +335,15 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -340,6 +351,11 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -348,6 +364,9 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -359,16 +378,33 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= @@ -377,6 +413,9 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/vendor/github.com/docker/go-connections/nat/nat.go b/vendor/github.com/docker/go-connections/nat/nat.go index 1ffe0355dc15..94304706d2ad 100644 --- a/vendor/github.com/docker/go-connections/nat/nat.go +++ b/vendor/github.com/docker/go-connections/nat/nat.go @@ -5,41 +5,42 @@ import ( "errors" "fmt" "net" + "net/netip" "strconv" "strings" + + "github.com/moby/moby/api/types/network" ) // PortBinding represents a binding between a Host IP address and a Host Port -type PortBinding struct { - // HostIP is the host IP Address - HostIP string `json:"HostIp"` - // HostPort is the host port number - HostPort string -} +// +// Deprecated: Use [network.PortBinding] instead. +type PortBinding = network.PortBinding // PortMap is a collection of PortBinding indexed by Port -type PortMap map[Port][]PortBinding +// +// Deprecated: Use [network.PortMap] instead. +type PortMap = network.PortMap // PortSet is a collection of structs indexed by Port -type PortSet map[Port]struct{} +// +// Deprecated: Use [network.PortSet] instead. +type PortSet = network.PortSet // Port is a string containing port number and protocol in the format "80/tcp" -type Port string +// +// Deprecated: Use [network.Port] or [network.PortRange] accordingly instead. +type Port struct { + network.PortRange +} // NewPort creates a new instance of a Port given a protocol and port number or port range -func NewPort(proto, port string) (Port, error) { - // Check for parsing issues on "port" now so we can avoid having - // to check it later on. - - portStartInt, portEndInt, err := ParsePortRangeToInt(port) +func NewPort(proto, portOrRange string) (Port, error) { + pr, err := network.ParsePortRange(portOrRange + "/" + proto) if err != nil { - return "", err + return Port{}, err } - - if portStartInt == portEndInt { - return Port(fmt.Sprintf("%d/%s", portStartInt, proto)), nil - } - return Port(fmt.Sprintf("%d-%d/%s", portStartInt, portEndInt, proto)), nil + return Port{pr}, nil } // ParsePort parses the port number string and returns an int @@ -47,49 +48,36 @@ func ParsePort(rawPort string) (int, error) { if rawPort == "" { return 0, nil } - port, err := strconv.ParseUint(rawPort, 10, 16) + port, err := parsePortNumber(rawPort) if err != nil { - return 0, fmt.Errorf("invalid port '%s': %w", rawPort, errors.Unwrap(err)) + return 0, fmt.Errorf("invalid port '%s': %w", rawPort, err) } - return int(port), nil + return port, nil } // ParsePortRangeToInt parses the port range string and returns start/end ints -func ParsePortRangeToInt(rawPort string) (int, int, error) { +func ParsePortRangeToInt(rawPort string) (startPort, endPort int, _ error) { if rawPort == "" { + // TODO(thaJeztah): consider making this an error; this was kept to keep existing behavior. return 0, 0, nil } - start, end, err := ParsePortRange(rawPort) - if err != nil { - return 0, 0, err - } - return int(start), int(end), nil -} - -// Proto returns the protocol of a Port -func (p Port) Proto() string { - proto, _ := SplitProtoPort(string(p)) - return proto + return parsePortRange(rawPort) } // Port returns the port number of a Port func (p Port) Port() string { - _, port := SplitProtoPort(string(p)) - return port + return fmt.Sprintf("%d", p.Start()) } -// Int returns the port number of a Port as an int +// Int returns the port number of a Port as an int. It assumes [Port] +// is valid, and returns 0 otherwise. func (p Port) Int() int { - portStr := p.Port() - // We don't need to check for an error because we're going to - // assume that any error would have been found, and reported, in NewPort() - port, _ := ParsePort(portStr) - return port + return int(p.Start()) } // Range returns the start/end port numbers of a Port range as ints func (p Port) Range() (int, int, error) { - return ParsePortRangeToInt(p.Port()) + return int(p.Start()), int(p.End()), nil } // SplitProtoPort splits a port(range) and protocol, formatted as "/[]" @@ -150,7 +138,13 @@ type PortMapping struct { } func (p *PortMapping) String() string { - return net.JoinHostPort(p.Binding.HostIP, p.Binding.HostPort+":"+string(p.Port)) + var host string + + if p.Binding.HostIP != netip.IPv4Unspecified() { + host = p.Binding.HostIP.String() + } + + return net.JoinHostPort(host, p.Binding.HostPort+":"+p.Port.String()) } func splitParts(rawport string) (hostIP, hostPort, containerPort string) { @@ -173,6 +167,10 @@ func splitParts(rawport string) (hostIP, hostPort, containerPort string) { func ParsePortSpec(rawPort string) ([]PortMapping, error) { ip, hostPort, containerPort := splitParts(rawPort) proto, containerPort := SplitProtoPort(containerPort) + if containerPort == "" { + return nil, fmt.Errorf("no port specified: %s", rawPort) + } + proto = strings.ToLower(proto) if err := validateProto(proto); err != nil { return nil, err @@ -189,18 +187,15 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) { if ip != "" && net.ParseIP(ip) == nil { return nil, errors.New("invalid IP address: " + ip) } - if containerPort == "" { - return nil, fmt.Errorf("no port specified: %s", rawPort) - } - startPort, endPort, err := ParsePortRange(containerPort) + startPort, endPort, err := parsePortRange(containerPort) if err != nil { return nil, errors.New("invalid containerPort: " + containerPort) } - var startHostPort, endHostPort uint64 + var startHostPort, endHostPort int if hostPort != "" { - startHostPort, endHostPort, err = ParsePortRange(hostPort) + startHostPort, endHostPort, err = parsePortRange(hostPort) if err != nil { return nil, errors.New("invalid hostPort: " + hostPort) } @@ -217,20 +212,38 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) { count := endPort - startPort + 1 ports := make([]PortMapping, 0, count) - for i := uint64(0); i < count; i++ { - cPort := Port(strconv.FormatUint(startPort+i, 10) + "/" + proto) + for i := 0; i < count; i++ { hPort := "" if hostPort != "" { - hPort = strconv.FormatUint(startHostPort+i, 10) + hPort = strconv.Itoa(startHostPort + i) // Set hostPort to a range only if there is a single container port // and a dynamic host port. if count == 1 && startHostPort != endHostPort { - hPort += "-" + strconv.FormatUint(endHostPort, 10) + hPort += "-" + strconv.Itoa(endHostPort) } } + port, err := network.ParsePortRange(fmt.Sprintf("%d/%s", startPort+i, proto)) + if err != nil { + return nil, err + } + + var addr netip.Addr + if strings.Count(ip, ":") >= 2 { + addr = netip.IPv6Unspecified() + } else { + addr = netip.IPv4Unspecified() + } + + if ip != "" { + addr, err = netip.ParseAddr(ip) + if err != nil { + return nil, err + } + } + ports = append(ports, PortMapping{ - Port: cPort, - Binding: PortBinding{HostIP: ip, HostPort: hPort}, + Port: Port{port}, + Binding: PortBinding{HostIP: addr, HostPort: hPort}, }) } return ports, nil diff --git a/vendor/github.com/docker/go-connections/nat/parse.go b/vendor/github.com/docker/go-connections/nat/parse.go index 64affa2a904c..f6f86bd04fe3 100644 --- a/vendor/github.com/docker/go-connections/nat/parse.go +++ b/vendor/github.com/docker/go-connections/nat/parse.go @@ -2,32 +2,59 @@ package nat import ( "errors" + "fmt" "strconv" "strings" ) -// ParsePortRange parses and validates the specified string as a port-range (8000-9000) -func ParsePortRange(ports string) (uint64, uint64, error) { +// ParsePortRange parses and validates the specified string as a port range (e.g., "8000-9000"). +func ParsePortRange(ports string) (startPort, endPort uint64, _ error) { + start, end, err := parsePortRange(ports) + return uint64(start), uint64(end), err +} + +// parsePortRange parses and validates the specified string as a port range (e.g., "8000-9000"). +func parsePortRange(ports string) (startPort, endPort int, _ error) { if ports == "" { return 0, 0, errors.New("empty string specified for ports") } - if !strings.Contains(ports, "-") { - start, err := strconv.ParseUint(ports, 10, 16) - end := start - return start, end, err + start, end, ok := strings.Cut(ports, "-") + + startPort, err := parsePortNumber(start) + if err != nil { + return 0, 0, fmt.Errorf("invalid start port '%s': %w", start, err) + } + if !ok || start == end { + return startPort, startPort, nil } - parts := strings.Split(ports, "-") - start, err := strconv.ParseUint(parts[0], 10, 16) + endPort, err = parsePortNumber(end) if err != nil { - return 0, 0, err + return 0, 0, fmt.Errorf("invalid end port '%s': %w", end, err) } - end, err := strconv.ParseUint(parts[1], 10, 16) + if endPort < startPort { + return 0, 0, errors.New("invalid port range: " + ports) + } + return startPort, endPort, nil +} + +// parsePortNumber parses rawPort into an int, unwrapping strconv errors +// and returning a single "out of range" error for any value outside 0–65535. +func parsePortNumber(rawPort string) (int, error) { + if rawPort == "" { + return 0, errors.New("value is empty") + } + port, err := strconv.ParseInt(rawPort, 10, 0) if err != nil { - return 0, 0, err + var numErr *strconv.NumError + if errors.As(err, &numErr) { + err = numErr.Err + } + return 0, err } - if end < start { - return 0, 0, errors.New("invalid range specified for port: " + ports) + if port < 0 || port > 65535 { + return 0, errors.New("value out of range (0–65535)") } - return start, end, nil + + return int(port), nil } diff --git a/vendor/github.com/docker/go-connections/nat/sort.go b/vendor/github.com/docker/go-connections/nat/sort.go index b6eed145e1cc..45ba991f3c9d 100644 --- a/vendor/github.com/docker/go-connections/nat/sort.go +++ b/vendor/github.com/docker/go-connections/nat/sort.go @@ -34,8 +34,10 @@ func Sort(ports []Port, predicate func(i, j Port) bool) { } type portMapEntry struct { - port Port - binding PortBinding + port Port + binding *PortBinding + portInt int + portProto string } type portMapSorter []portMapEntry @@ -48,23 +50,37 @@ func (s portMapSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // 2. larger port // 3. port with tcp protocol func (s portMapSorter) Less(i, j int) bool { - pi, pj := s[i].port, s[j].port - hpi, hpj := toInt(s[i].binding.HostPort), toInt(s[j].binding.HostPort) - return hpi > hpj || pi.Int() > pj.Int() || (pi.Int() == pj.Int() && strings.ToLower(pi.Proto()) == "tcp") + pi, pj := s[i].portInt, s[j].portInt + var hpi, hpj int + if s[i].binding != nil { + hpi = toInt(s[i].binding.HostPort) + } + if s[j].binding != nil { + hpj = toInt(s[j].binding.HostPort) + } + return hpi > hpj || pi > pj || (pi == pj && strings.EqualFold(s[i].portProto, "tcp")) } // SortPortMap sorts the list of ports and their respected mapping. The ports // will explicit HostPort will be placed first. -func SortPortMap(ports []Port, bindings PortMap) { +func SortPortMap(ports []Port, bindings map[Port][]PortBinding) { s := portMapSorter{} for _, p := range ports { + portInt, portProto := p.Int(), p.Proto() if binding, ok := bindings[p]; ok && len(binding) > 0 { for _, b := range binding { - s = append(s, portMapEntry{port: p, binding: b}) + b := b // capture loop variable for go < 1.22 + s = append(s, portMapEntry{ + port: p, binding: &b, + portInt: portInt, portProto: string(portProto), + }) } bindings[p] = []PortBinding{} } else { - s = append(s, portMapEntry{port: p}) + s = append(s, portMapEntry{ + port: p, + portInt: portInt, portProto: string(portProto), + }) } } @@ -81,16 +97,13 @@ func SortPortMap(ports []Port, bindings PortMap) { i++ } // reorder bindings for this port - if _, ok := bindings[entry.port]; ok { - bindings[entry.port] = append(bindings[entry.port], entry.binding) + if entry.binding != nil { + bindings[entry.port] = append(bindings[entry.port], *entry.binding) } } } -func toInt(s string) uint64 { - i, _, err := ParsePortRange(s) - if err != nil { - i = 0 - } +func toInt(s string) int { + i, _, _ := parsePortRange(s) return i } diff --git a/vendor/github.com/docker/go-connections/sockets/inmem_socket.go b/vendor/github.com/docker/go-connections/sockets/inmem_socket.go index 99846ffddb1a..06fcf747ac4b 100644 --- a/vendor/github.com/docker/go-connections/sockets/inmem_socket.go +++ b/vendor/github.com/docker/go-connections/sockets/inmem_socket.go @@ -1,48 +1,57 @@ package sockets import ( - "errors" "net" "sync" ) -var errClosed = errors.New("use of closed network connection") +// dummyAddr is used to satisfy net.Addr for the in-mem socket +// it is just stored as a string and returns the string for all calls +type dummyAddr string + +// Network returns the addr string, satisfies net.Addr +func (a dummyAddr) Network() string { + return string(a) +} -// InmemSocket implements net.Listener using in-memory only connections. +// String returns the string form +func (a dummyAddr) String() string { + return string(a) +} + +// InmemSocket implements [net.Listener] using in-memory only connections. type InmemSocket struct { chConn chan net.Conn chClose chan struct{} - addr string + addr dummyAddr mu sync.Mutex } -// dummyAddr is used to satisfy net.Addr for the in-mem socket -// it is just stored as a string and returns the string for all calls -type dummyAddr string - -// NewInmemSocket creates an in-memory only net.Listener -// The addr argument can be any string, but is used to satisfy the `Addr()` part -// of the net.Listener interface +// NewInmemSocket creates an in-memory only [net.Listener]. The addr argument +// can be any string, but is used to satisfy the [net.Listener.Addr] part +// of the [net.Listener] interface func NewInmemSocket(addr string, bufSize int) *InmemSocket { return &InmemSocket{ chConn: make(chan net.Conn, bufSize), chClose: make(chan struct{}), - addr: addr, + addr: dummyAddr(addr), } } // Addr returns the socket's addr string to satisfy net.Listener func (s *InmemSocket) Addr() net.Addr { - return dummyAddr(s.addr) + return s.addr } -// Accept implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn. +// Accept implements the Accept method in the Listener interface; it waits +// for the next call and returns a generic Conn. It returns a [net.ErrClosed] +// if the connection is already closed. func (s *InmemSocket) Accept() (net.Conn, error) { select { case conn := <-s.chConn: return conn, nil case <-s.chClose: - return nil, errClosed + return nil, net.ErrClosed } } @@ -58,24 +67,15 @@ func (s *InmemSocket) Close() error { return nil } -// Dial is used to establish a connection with the in-mem server +// Dial is used to establish a connection with the in-mem server. +// It returns a [net.ErrClosed] if the connection is already closed. func (s *InmemSocket) Dial(network, addr string) (net.Conn, error) { srvConn, clientConn := net.Pipe() select { case s.chConn <- srvConn: case <-s.chClose: - return nil, errClosed + return nil, net.ErrClosed } return clientConn, nil } - -// Network returns the addr string, satisfies net.Addr -func (a dummyAddr) Network() string { - return string(a) -} - -// String returns the string form -func (a dummyAddr) String() string { - return string(a) -} diff --git a/vendor/github.com/docker/go-connections/sockets/proxy.go b/vendor/github.com/docker/go-connections/sockets/proxy.go deleted file mode 100644 index f04980e40a5a..000000000000 --- a/vendor/github.com/docker/go-connections/sockets/proxy.go +++ /dev/null @@ -1,31 +0,0 @@ -package sockets - -import ( - "net" - "os" - "strings" -) - -// GetProxyEnv allows access to the uppercase and the lowercase forms of -// proxy-related variables. See the Go specification for details on these -// variables. https://golang.org/pkg/net/http/ -// -// Deprecated: this function was used as helper for [DialerFromEnvironment] and is no longer used. It will be removed in the next release. -func GetProxyEnv(key string) string { - proxyValue := os.Getenv(strings.ToUpper(key)) - if proxyValue == "" { - return os.Getenv(strings.ToLower(key)) - } - return proxyValue -} - -// DialerFromEnvironment was previously used to configure a net.Dialer to route -// connections through a SOCKS proxy. -// -// Deprecated: SOCKS proxies are now supported by configuring only -// http.Transport.Proxy, and no longer require changing http.Transport.Dial. -// Therefore, only [sockets.ConfigureTransport] needs to be called, and any -// [sockets.DialerFromEnvironment] calls can be dropped. -func DialerFromEnvironment(direct *net.Dialer) (*net.Dialer, error) { - return direct, nil -} diff --git a/vendor/github.com/docker/go-connections/sockets/sockets.go b/vendor/github.com/docker/go-connections/sockets/sockets.go index 6117297860db..0d7789bbdbda 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets.go @@ -27,11 +27,19 @@ var ErrProtocolNotAvailable = errors.New("protocol not available") // make sure you do it _after_ any subsequent calls to ConfigureTransport is made against the same // [http.Transport]. func ConfigureTransport(tr *http.Transport, proto, addr string) error { + if tr.MaxIdleConns == 0 { + // prevent long-lived processes from leaking connections + // due to idle connections not being released. + // + // TODO: see if we can also address this from the server side; see: https://github.com/moby/moby/issues/45539 + tr.MaxIdleConns = 6 + tr.IdleConnTimeout = 30 * time.Second + } switch proto { case "unix": - return configureUnixTransport(tr, proto, addr) + return configureUnixTransport(tr, addr) case "npipe": - return configureNpipeTransport(tr, proto, addr) + return configureNpipeTransport(tr, addr) default: tr.Proxy = http.ProxyFromEnvironment tr.DisableCompression = false @@ -42,15 +50,7 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error { return nil } -// DialPipe connects to a Windows named pipe. It is not supported on -// non-Windows platforms. -// -// Deprecated: use [github.com/Microsoft/go-winio.DialPipe] or [github.com/Microsoft/go-winio.DialPipeContext]. -func DialPipe(addr string, timeout time.Duration) (net.Conn, error) { - return dialPipe(addr, timeout) -} - -func configureUnixTransport(tr *http.Transport, proto, addr string) error { +func configureUnixTransport(tr *http.Transport, addr string) error { if len(addr) > maxUnixSocketPathSize { return fmt.Errorf("unix socket path %q is too long", addr) } @@ -60,7 +60,7 @@ func configureUnixTransport(tr *http.Transport, proto, addr string) error { Timeout: defaultTimeout, } tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { - return dialer.DialContext(ctx, proto, addr) + return dialer.DialContext(ctx, "unix", addr) } return nil } diff --git a/vendor/github.com/docker/go-connections/sockets/sockets_unix.go b/vendor/github.com/docker/go-connections/sockets/sockets_unix.go index 913d2f00dd2f..b37c39eab828 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets_unix.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets_unix.go @@ -2,17 +2,6 @@ package sockets -import ( - "net" - "net/http" - "syscall" - "time" -) - -func configureNpipeTransport(tr *http.Transport, proto, addr string) error { +func configureNpipeTransport(any, string) error { return ErrProtocolNotAvailable } - -func dialPipe(_ string, _ time.Duration) (net.Conn, error) { - return nil, syscall.EAFNOSUPPORT -} diff --git a/vendor/github.com/docker/go-connections/sockets/sockets_windows.go b/vendor/github.com/docker/go-connections/sockets/sockets_windows.go index 6d6beb3855c0..0863fc36a792 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets_windows.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets_windows.go @@ -4,12 +4,11 @@ import ( "context" "net" "net/http" - "time" "github.com/Microsoft/go-winio" ) -func configureNpipeTransport(tr *http.Transport, proto, addr string) error { +func configureNpipeTransport(tr *http.Transport, addr string) error { // No need for compression in local communications. tr.DisableCompression = true tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { @@ -17,7 +16,3 @@ func configureNpipeTransport(tr *http.Transport, proto, addr string) error { } return nil } - -func dialPipe(addr string, timeout time.Duration) (net.Conn, error) { - return winio.DialPipe(addr, &timeout) -} diff --git a/vendor/github.com/moby/moby/api/types/container/config.go b/vendor/github.com/moby/moby/api/types/container/config.go index 12fa6b310233..c5650f10c9c0 100644 --- a/vendor/github.com/moby/moby/api/types/container/config.go +++ b/vendor/github.com/moby/moby/api/types/container/config.go @@ -4,6 +4,7 @@ import ( "time" dockerspec "github.com/moby/docker-image-spec/specs-go/v1" + "github.com/moby/moby/api/types/network" ) // MinimumDuration puts a minimum on user configured duration. @@ -28,7 +29,7 @@ type Config struct { AttachStdin bool // Attach the standard input, makes possible user interaction AttachStdout bool // Attach the standard output AttachStderr bool // Attach the standard error - ExposedPorts PortSet `json:",omitempty"` // List of exposed ports + ExposedPorts network.PortSet `json:",omitempty"` // List of exposed ports Tty bool // Attach standard streams to a tty, including stdin if it is not closed. OpenStdin bool // Open stdin StdinOnce bool // If true, close stdin after the 1 attached client disconnects. diff --git a/vendor/github.com/moby/moby/api/types/container/hostconfig.go b/vendor/github.com/moby/moby/api/types/container/hostconfig.go index f167d805990a..0f889c65124c 100644 --- a/vendor/github.com/moby/moby/api/types/container/hostconfig.go +++ b/vendor/github.com/moby/moby/api/types/container/hostconfig.go @@ -3,6 +3,7 @@ package container import ( "errors" "fmt" + "net/netip" "strings" "github.com/docker/go-units" @@ -420,7 +421,7 @@ type HostConfig struct { ContainerIDFile string // File (path) where the containerId is written LogConfig LogConfig // Configuration of the logs for this container NetworkMode NetworkMode // Network mode to use for the container - PortBindings PortMap // Port mapping between the exposed port (container) and the host + PortBindings network.PortMap // Port mapping between the exposed port (container) and the host RestartPolicy RestartPolicy // Restart policy to be used for the container AutoRemove bool // Automatically remove container when it exits VolumeDriver string // Name of the volume driver used to mount volumes @@ -432,7 +433,7 @@ type HostConfig struct { CapAdd []string // List of kernel capabilities to add to the container CapDrop []string // List of kernel capabilities to remove from the container CgroupnsMode CgroupnsMode // Cgroup namespace mode to use for the container - DNS []string `json:"Dns"` // List of DNS server to lookup + DNS []netip.Addr `json:"Dns"` // List of DNS server to lookup DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for ExtraHosts []string // List of extra hosts diff --git a/vendor/github.com/moby/moby/api/types/container/nat_aliases.go b/vendor/github.com/moby/moby/api/types/container/nat_aliases.go deleted file mode 100644 index 470f15655cc3..000000000000 --- a/vendor/github.com/moby/moby/api/types/container/nat_aliases.go +++ /dev/null @@ -1,24 +0,0 @@ -package container - -import "github.com/docker/go-connections/nat" - -// PortRangeProto is a string containing port number and protocol in the format "80/tcp", -// or a port range and protocol in the format "80-83/tcp". -// -// It is currently an alias for [nat.Port] but may become a concrete type in a future release. -type PortRangeProto = nat.Port - -// PortSet is a collection of structs indexed by [HostPort]. -// -// It is currently an alias for [nat.PortSet] but may become a concrete type in a future release. -type PortSet = nat.PortSet - -// PortBinding represents a binding between a Host IP address and a [HostPort]. -// -// It is currently an alias for [nat.PortBinding] but may become a concrete type in a future release. -type PortBinding = nat.PortBinding - -// PortMap is a collection of [PortBinding] indexed by [HostPort]. -// -// It is currently an alias for [nat.PortMap] but may become a concrete type in a future release. -type PortMap = nat.PortMap diff --git a/vendor/github.com/moby/moby/api/types/container/network_settings.go b/vendor/github.com/moby/moby/api/types/container/network_settings.go index fb7f59df087e..c51c0839d237 100644 --- a/vendor/github.com/moby/moby/api/types/container/network_settings.go +++ b/vendor/github.com/moby/moby/api/types/container/network_settings.go @@ -6,10 +6,13 @@ import ( // NetworkSettings exposes the network settings in the api type NetworkSettings struct { - SandboxID string // SandboxID uniquely represents a container's network stack - SandboxKey string // SandboxKey identifies the sandbox - Ports PortMap // Ports is a collection of PortBinding indexed by Port - Networks map[string]*network.EndpointSettings + SandboxID string // SandboxID uniquely represents a container's network stack + SandboxKey string // SandboxKey identifies the sandbox + + // Ports is a collection of [network.PortBinding] indexed by [network.Port] + Ports network.PortMap + + Networks map[string]*network.EndpointSettings } // NetworkSettingsSummary provides a summary of container's networks diff --git a/vendor/github.com/moby/moby/api/types/container/port_summary.go b/vendor/github.com/moby/moby/api/types/container/port_summary.go index 3956224dcc6e..68148eece462 100644 --- a/vendor/github.com/moby/moby/api/types/container/port_summary.go +++ b/vendor/github.com/moby/moby/api/types/container/port_summary.go @@ -5,6 +5,10 @@ package container // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command +import ( + "net/netip" +) + // PortSummary Describes a port-mapping between the container and the host. // // Example: {"PrivatePort":8080,"PublicPort":80,"Type":"tcp"} @@ -13,7 +17,7 @@ package container type PortSummary struct { // Host IP address that the container's port is mapped to - IP string `json:"IP,omitempty"` + IP netip.Addr `json:"IP,omitempty"` // Port on the container // Required: true diff --git a/vendor/github.com/moby/moby/api/types/image/image_inspect.go b/vendor/github.com/moby/moby/api/types/image/image_inspect.go index 69a263756356..baded4945701 100644 --- a/vendor/github.com/moby/moby/api/types/image/image_inspect.go +++ b/vendor/github.com/moby/moby/api/types/image/image_inspect.go @@ -47,12 +47,14 @@ type InspectResponse struct { // // Depending on how the image was created, this field may be empty and // is only set for images that were built/created locally. This field - // is empty if the image was pulled from an image registry. - Parent string + // is omitted if the image was pulled from an image registry. + // + // Deprecated: this field is deprecated, and will be removed in the next release. + Parent string `json:",omitempty"` // Comment is an optional message that can be set when committing or - // importing the image. - Comment string + // importing the image. This field is omitted if not set. + Comment string `json:",omitempty"` // Created is the date and time at which the image was created, formatted in // RFC 3339 nano-seconds (time.RFC3339Nano). @@ -79,12 +81,15 @@ type InspectResponse struct { // DockerVersion is the version of Docker that was used to build the image. // - // Depending on how the image was created, this field may be empty. - DockerVersion string + // Depending on how the image was created, this field may be omitted. + // + // Deprecated: this field is deprecated, and will be removed in the next release. + DockerVersion string `json:",omitempty"` // Author is the name of the author that was specified when committing the // image, or as specified through MAINTAINER (deprecated) in the Dockerfile. - Author string + // This field is omitted if not set. + Author string `json:",omitempty"` Config *dockerspec.DockerOCIImageConfig // Architecture is the hardware CPU architecture that the image runs on. diff --git a/vendor/github.com/moby/moby/api/types/network/endpoint.go b/vendor/github.com/moby/moby/api/types/network/endpoint.go index 39b732e0e7e2..ee5223d052a7 100644 --- a/vendor/github.com/moby/moby/api/types/network/endpoint.go +++ b/vendor/github.com/moby/moby/api/types/network/endpoint.go @@ -1,10 +1,8 @@ package network import ( - "errors" - "fmt" "maps" - "net" + "net/netip" "slices" ) @@ -28,11 +26,11 @@ type EndpointSettings struct { // Operational data NetworkID string EndpointID string - Gateway string - IPAddress string + Gateway netip.Addr + IPAddress netip.Addr IPPrefixLen int - IPv6Gateway string - GlobalIPv6Address string + IPv6Gateway netip.Addr + GlobalIPv6Address netip.Addr GlobalIPv6PrefixLen int // DNSNames holds all the (non fully qualified) DNS names associated to this endpoint. First entry is used to // generate PTR records. @@ -57,9 +55,9 @@ func (es *EndpointSettings) Copy() *EndpointSettings { // EndpointIPAMConfig represents IPAM configurations for the endpoint type EndpointIPAMConfig struct { - IPv4Address string `json:",omitempty"` - IPv6Address string `json:",omitempty"` - LinkLocalIPs []string `json:",omitempty"` + IPv4Address netip.Addr `json:",omitempty"` + IPv6Address netip.Addr `json:",omitempty"` + LinkLocalIPs []netip.Addr `json:",omitempty"` } // Copy makes a copy of the endpoint ipam config @@ -71,76 +69,3 @@ func (cfg *EndpointIPAMConfig) Copy() *EndpointIPAMConfig { cfgCopy.LinkLocalIPs = slices.Clone(cfg.LinkLocalIPs) return &cfgCopy } - -// NetworkSubnet describes a user-defined subnet for a specific network. It's only used to validate if an -// EndpointIPAMConfig is valid for a specific network. -type NetworkSubnet interface { - // Contains checks whether the NetworkSubnet contains [addr]. - Contains(addr net.IP) bool - // IsStatic checks whether the subnet was statically allocated (ie. user-defined). - IsStatic() bool -} - -// IsInRange checks whether static IP addresses are valid in a specific network. -func (cfg *EndpointIPAMConfig) IsInRange(v4Subnets []NetworkSubnet, v6Subnets []NetworkSubnet) error { - var errs []error - - if err := validateEndpointIPAddress(cfg.IPv4Address, v4Subnets); err != nil { - errs = append(errs, err) - } - if err := validateEndpointIPAddress(cfg.IPv6Address, v6Subnets); err != nil { - errs = append(errs, err) - } - - return errJoin(errs...) -} - -func validateEndpointIPAddress(epAddr string, ipamSubnets []NetworkSubnet) error { - if epAddr == "" { - return nil - } - - var staticSubnet bool - parsedAddr := net.ParseIP(epAddr) - for _, subnet := range ipamSubnets { - if subnet.IsStatic() { - staticSubnet = true - if subnet.Contains(parsedAddr) { - return nil - } - } - } - - if staticSubnet { - return fmt.Errorf("no configured subnet or ip-range contain the IP address %s", epAddr) - } - - return errors.New("user specified IP address is supported only when connecting to networks with user configured subnets") -} - -// Validate checks whether cfg is valid. -func (cfg *EndpointIPAMConfig) Validate() error { - if cfg == nil { - return nil - } - - var errs []error - - if cfg.IPv4Address != "" { - if addr := net.ParseIP(cfg.IPv4Address); addr == nil || addr.To4() == nil || addr.IsUnspecified() { - errs = append(errs, fmt.Errorf("invalid IPv4 address: %s", cfg.IPv4Address)) - } - } - if cfg.IPv6Address != "" { - if addr := net.ParseIP(cfg.IPv6Address); addr == nil || addr.To4() != nil || addr.IsUnspecified() { - errs = append(errs, fmt.Errorf("invalid IPv6 address: %s", cfg.IPv6Address)) - } - } - for _, addr := range cfg.LinkLocalIPs { - if parsed := net.ParseIP(addr); parsed == nil || parsed.IsUnspecified() { - errs = append(errs, fmt.Errorf("invalid link-local IP address: %s", addr)) - } - } - - return errJoin(errs...) -} diff --git a/vendor/github.com/moby/moby/api/types/network/endpoint_resource.go b/vendor/github.com/moby/moby/api/types/network/endpoint_resource.go index 9780253baf6d..6ff25b1bb6c0 100644 --- a/vendor/github.com/moby/moby/api/types/network/endpoint_resource.go +++ b/vendor/github.com/moby/moby/api/types/network/endpoint_resource.go @@ -5,6 +5,10 @@ package network // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command +import ( + "net/netip" +) + // EndpointResource contains network resources allocated and used for a container in a network. // // swagger:model EndpointResource @@ -24,8 +28,8 @@ type EndpointResource struct { // IPv4 address // Example: 172.19.0.2/16 - IPv4Address string `json:"IPv4Address"` + IPv4Address netip.Prefix `json:"IPv4Address"` // IPv6 address - IPv6Address string `json:"IPv6Address"` + IPv6Address netip.Prefix `json:"IPv6Address"` } diff --git a/vendor/github.com/moby/moby/api/types/network/ipam.go b/vendor/github.com/moby/moby/api/types/network/ipam.go index 2421d79654dc..e57be481b72c 100644 --- a/vendor/github.com/moby/moby/api/types/network/ipam.go +++ b/vendor/github.com/moby/moby/api/types/network/ipam.go @@ -1,10 +1,7 @@ package network import ( - "errors" - "fmt" "net/netip" - "strings" ) // IPAM represents IP Address Management @@ -16,160 +13,10 @@ type IPAM struct { // IPAMConfig represents IPAM configurations type IPAMConfig struct { - Subnet string `json:",omitempty"` - IPRange string `json:",omitempty"` - Gateway string `json:",omitempty"` - AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` + Subnet netip.Prefix `json:",omitempty"` + IPRange netip.Prefix `json:",omitempty"` + Gateway netip.Addr `json:",omitempty"` + AuxAddress map[string]netip.Addr `json:"AuxiliaryAddresses,omitempty"` } type SubnetStatuses = map[netip.Prefix]SubnetStatus - -type ipFamily string - -const ( - ip4 ipFamily = "IPv4" - ip6 ipFamily = "IPv6" -) - -// ValidateIPAM checks whether the network's IPAM passed as argument is valid. It returns a joinError of the list of -// errors found. -func ValidateIPAM(ipam *IPAM, enableIPv6 bool) error { - if ipam == nil { - return nil - } - - var errs []error - for _, cfg := range ipam.Config { - subnet, err := netip.ParsePrefix(cfg.Subnet) - if err != nil { - errs = append(errs, fmt.Errorf("invalid subnet %s: invalid CIDR block notation", cfg.Subnet)) - continue - } - subnetFamily := ip4 - if subnet.Addr().Is6() { - subnetFamily = ip6 - } - - if !enableIPv6 && subnetFamily == ip6 { - continue - } - - if subnet != subnet.Masked() { - errs = append(errs, fmt.Errorf("invalid subnet %s: it should be %s", subnet, subnet.Masked())) - } - - if ipRangeErrs := validateIPRange(cfg.IPRange, subnet, subnetFamily); len(ipRangeErrs) > 0 { - errs = append(errs, ipRangeErrs...) - } - - if err := validateAddress(cfg.Gateway, subnet, subnetFamily); err != nil { - errs = append(errs, fmt.Errorf("invalid gateway %s: %w", cfg.Gateway, err)) - } - - for auxName, aux := range cfg.AuxAddress { - if err := validateAddress(aux, subnet, subnetFamily); err != nil { - errs = append(errs, fmt.Errorf("invalid auxiliary address %s: %w", auxName, err)) - } - } - } - - if err := errJoin(errs...); err != nil { - return fmt.Errorf("invalid network config:\n%w", err) - } - - return nil -} - -func validateIPRange(ipRange string, subnet netip.Prefix, subnetFamily ipFamily) []error { - if ipRange == "" { - return nil - } - prefix, err := netip.ParsePrefix(ipRange) - if err != nil { - return []error{fmt.Errorf("invalid ip-range %s: invalid CIDR block notation", ipRange)} - } - family := ip4 - if prefix.Addr().Is6() { - family = ip6 - } - - if family != subnetFamily { - return []error{fmt.Errorf("invalid ip-range %s: parent subnet is an %s block", ipRange, subnetFamily)} - } - - var errs []error - if prefix.Bits() < subnet.Bits() { - errs = append(errs, fmt.Errorf("invalid ip-range %s: CIDR block is bigger than its parent subnet %s", ipRange, subnet)) - } - if prefix != prefix.Masked() { - errs = append(errs, fmt.Errorf("invalid ip-range %s: it should be %s", prefix, prefix.Masked())) - } - if !subnet.Overlaps(prefix) { - errs = append(errs, fmt.Errorf("invalid ip-range %s: parent subnet %s doesn't contain ip-range", ipRange, subnet)) - } - - return errs -} - -func validateAddress(address string, subnet netip.Prefix, subnetFamily ipFamily) error { - if address == "" { - return nil - } - addr, err := netip.ParseAddr(address) - if err != nil { - return errors.New("invalid address") - } - family := ip4 - if addr.Is6() { - family = ip6 - } - - if family != subnetFamily { - return fmt.Errorf("parent subnet is an %s block", subnetFamily) - } - if !subnet.Contains(addr) { - return fmt.Errorf("parent subnet %s doesn't contain this address", subnet) - } - - return nil -} - -func errJoin(errs ...error) error { - n := 0 - for _, err := range errs { - if err != nil { - n++ - } - } - if n == 0 { - return nil - } - e := &joinError{ - errs: make([]error, 0, n), - } - for _, err := range errs { - if err != nil { - e.errs = append(e.errs, err) - } - } - return e -} - -type joinError struct { - errs []error -} - -func (e *joinError) Error() string { - if len(e.errs) == 1 { - return strings.TrimSpace(e.errs[0].Error()) - } - stringErrs := make([]string, 0, len(e.errs)) - for _, subErr := range e.errs { - stringErrs = append(stringErrs, strings.ReplaceAll(subErr.Error(), "\n", "\n\t")) - } - return "* " + strings.Join(stringErrs, "\n* ") -} - -func (e *joinError) Unwrap() []error { - return e.errs -} diff --git a/vendor/github.com/moby/moby/api/types/network/network_types.go b/vendor/github.com/moby/moby/api/types/network/network_types.go index 413e5fbed051..5401f55f826c 100644 --- a/vendor/github.com/moby/moby/api/types/network/network_types.go +++ b/vendor/github.com/moby/moby/api/types/network/network_types.go @@ -30,14 +30,6 @@ type CreateRequest struct { Labels map[string]string // Labels holds metadata specific to the network being created. } -// ServiceInfo represents service parameters with the list of service's tasks -type ServiceInfo struct { - VIP string - Ports []string - LocalLBIndex int - Tasks []Task -} - // NetworkingConfig represents the container's networking configuration for each of its interfaces // Carries the networking configs specified in the `docker run` and `docker network connect` commands type NetworkingConfig struct { diff --git a/vendor/github.com/moby/moby/api/types/network/peer_info.go b/vendor/github.com/moby/moby/api/types/network/peer_info.go index 0522a2392d54..dc88ec16fa9b 100644 --- a/vendor/github.com/moby/moby/api/types/network/peer_info.go +++ b/vendor/github.com/moby/moby/api/types/network/peer_info.go @@ -5,6 +5,10 @@ package network // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command +import ( + "net/netip" +) + // PeerInfo represents one peer of an overlay network. // // swagger:model PeerInfo @@ -16,5 +20,5 @@ type PeerInfo struct { // IP-address of the peer-node in the Swarm cluster. // Example: 10.133.77.91 - IP string `json:"IP"` + IP netip.Addr `json:"IP"` } diff --git a/vendor/github.com/moby/moby/api/types/network/port.go b/vendor/github.com/moby/moby/api/types/network/port.go new file mode 100644 index 000000000000..171d9f51d35c --- /dev/null +++ b/vendor/github.com/moby/moby/api/types/network/port.go @@ -0,0 +1,346 @@ +package network + +import ( + "errors" + "fmt" + "iter" + "net/netip" + "strconv" + "strings" + "unique" +) + +// IPProtocol represents a network protocol for a port. +type IPProtocol string + +const ( + TCP IPProtocol = "tcp" + UDP IPProtocol = "udp" + SCTP IPProtocol = "sctp" +) + +// Sentinel port proto value for zero Port and PortRange values. +var protoZero unique.Handle[IPProtocol] + +// Port is a type representing a single port number and protocol in the format "/[]". +// +// The zero port value, i.e. Port{}, is invalid; use [ParsePort] to create a valid Port value. +type Port struct { + num uint16 + proto unique.Handle[IPProtocol] +} + +// ParsePort parses s as a [Port]. +// +// It normalizes the provided protocol such that "80/tcp", "80/TCP", and "80/tCp" are equivalent. +// If a port number is provided, but no protocol, the default ("tcp") protocol is returned. +func ParsePort(s string) (Port, error) { + if s == "" { + return Port{}, errors.New("invalid port: value is empty") + } + + port, proto, _ := strings.Cut(s, "/") + + portNum, err := parsePortNumber(port) + if err != nil { + return Port{}, fmt.Errorf("invalid port '%s': %w", port, err) + } + + normalizedPortProto := normalizePortProto(proto) + return Port{num: portNum, proto: normalizedPortProto}, nil +} + +// MustParsePort calls [ParsePort](s) and panics on error. +// +// It is intended for use in tests with hard-coded strings. +func MustParsePort(s string) Port { + p, err := ParsePort(s) + if err != nil { + panic(err) + } + return p +} + +// PortFrom returns a [Port] with the given number and protocol. +// +// If no protocol is specified (i.e. proto == ""), then PortFrom returns Port{}, false. +func PortFrom(num uint16, proto IPProtocol) (p Port, ok bool) { + if proto == "" { + return Port{}, false + } + normalized := normalizePortProto(string(proto)) + return Port{num: num, proto: normalized}, true +} + +// Num returns p's port number. +func (p Port) Num() uint16 { + return p.num +} + +// Proto returns p's network protocol. +func (p Port) Proto() IPProtocol { + return p.proto.Value() +} + +// IsZero reports whether p is the zero value. +func (p Port) IsZero() bool { + return p.proto == protoZero +} + +// IsValid reports whether p is an initialized valid port (not the zero value). +func (p Port) IsValid() bool { + return p.proto != protoZero +} + +// String returns a string representation of the port in the format "/". +// If the port is the zero value, it returns "invalid port". +func (p Port) String() string { + switch p.proto { + case protoZero: + return "invalid port" + default: + return string(p.AppendTo(nil)) + } +} + +// AppendText implements [encoding.TextAppender] interface. +// It is the same as [Port.AppendTo] but returns an error to satisfy the interface. +func (p Port) AppendText(b []byte) ([]byte, error) { + return p.AppendTo(b), nil +} + +// AppendTo appends a text encoding of p to b and returns the extended buffer. +func (p Port) AppendTo(b []byte) []byte { + if p.IsZero() { + return b + } + return fmt.Appendf(b, "%d/%s", p.num, p.proto.Value()) +} + +// MarshalText implements [encoding.TextMarshaler] interface. +func (p Port) MarshalText() ([]byte, error) { + return p.AppendText(nil) +} + +// UnmarshalText implements [encoding.TextUnmarshaler] interface. +func (p *Port) UnmarshalText(text []byte) error { + if len(text) == 0 { + *p = Port{} + return nil + } + + port, err := ParsePort(string(text)) + if err != nil { + return err + } + + *p = port + return nil +} + +// Range returns a [PortRange] representing the single port. +func (p Port) Range() PortRange { + return PortRange{start: p.num, end: p.num, proto: p.proto} +} + +// PortSet is a collection of structs indexed by [Port]. +type PortSet = map[Port]struct{} + +// PortBinding represents a binding between a Host IP address and a Host Port. +type PortBinding struct { + // HostIP is the host IP Address + HostIP netip.Addr `json:"HostIp"` + // HostPort is the host port number + HostPort string `json:"HostPort"` +} + +// PortMap is a collection of [PortBinding] indexed by [Port]. +type PortMap = map[Port][]PortBinding + +// PortRange represents a range of port numbers and a protocol in the format "8000-9000/tcp". +// +// The zero port range value, i.e. PortRange{}, is invalid; use [ParsePortRange] to create a valid PortRange value. +type PortRange struct { + start uint16 + end uint16 + proto unique.Handle[IPProtocol] +} + +// ParsePortRange parses s as a [PortRange]. +// +// It normalizes the provided protocol such that "80-90/tcp", "80-90/TCP", and "80-90/tCp" are equivalent. +// If a port number range is provided, but no protocol, the default ("tcp") protocol is returned. +func ParsePortRange(s string) (PortRange, error) { + if s == "" { + return PortRange{}, errors.New("invalid port range: value is empty") + } + + portRange, proto, _ := strings.Cut(s, "/") + + start, end, ok := strings.Cut(portRange, "-") + startVal, err := parsePortNumber(start) + if err != nil { + return PortRange{}, fmt.Errorf("invalid start port '%s': %w", start, err) + } + + portProto := normalizePortProto(proto) + + if !ok || start == end { + return PortRange{start: startVal, end: startVal, proto: portProto}, nil + } + + endVal, err := parsePortNumber(end) + if err != nil { + return PortRange{}, fmt.Errorf("invalid end port '%s': %w", end, err) + } + if endVal < startVal { + return PortRange{}, errors.New("invalid port range: " + s) + } + return PortRange{start: startVal, end: endVal, proto: portProto}, nil +} + +// MustParsePortRange calls [ParsePortRange](s) and panics on error. +// It is intended for use in tests with hard-coded strings. +func MustParsePortRange(s string) PortRange { + pr, err := ParsePortRange(s) + if err != nil { + panic(err) + } + return pr +} + +// PortRangeFrom returns a [PortRange] with the given start and end port numbers and protocol. +// +// If end < start or no protocol is specified (i.e. proto == ""), then PortRangeFrom returns PortRange{}, false. +func PortRangeFrom(start, end uint16, proto IPProtocol) (pr PortRange, ok bool) { + if end < start || proto == "" { + return PortRange{}, false + } + normalized := normalizePortProto(string(proto)) + return PortRange{start: start, end: end, proto: normalized}, true +} + +// Start returns pr's start port number. +func (pr PortRange) Start() uint16 { + return pr.start +} + +// End returns pr's end port number. +func (pr PortRange) End() uint16 { + return pr.end +} + +// Proto returns pr's network protocol. +func (pr PortRange) Proto() IPProtocol { + return pr.proto.Value() +} + +// IsZero reports whether pr is the zero value. +func (pr PortRange) IsZero() bool { + return pr.proto == protoZero +} + +// IsValid reports whether pr is an initialized valid port range (not the zero value). +func (pr PortRange) IsValid() bool { + return pr.proto != protoZero +} + +// String returns a string representation of the port range in the format "-/" or "/" if start == end. +// If the port range is the zero value, it returns "invalid port range". +func (pr PortRange) String() string { + switch pr.proto { + case protoZero: + return "invalid port range" + default: + return string(pr.AppendTo(nil)) + } +} + +// AppendText implements [encoding.TextAppender] interface. +// It is the same as [PortRange.AppendTo] but returns an error to satisfy the interface. +func (pr PortRange) AppendText(b []byte) ([]byte, error) { + return pr.AppendTo(b), nil +} + +// AppendTo appends a text encoding of pr to b and returns the extended buffer. +func (pr PortRange) AppendTo(b []byte) []byte { + if pr.IsZero() { + return b + } + if pr.start == pr.end { + return fmt.Appendf(b, "%d/%s", pr.start, pr.proto.Value()) + } + return fmt.Appendf(b, "%d-%d/%s", pr.start, pr.end, pr.proto.Value()) +} + +// MarshalText implements [encoding.TextMarshaler] interface. +func (pr PortRange) MarshalText() ([]byte, error) { + return pr.AppendText(nil) +} + +// UnmarshalText implements [encoding.TextUnmarshaler] interface. +func (pr *PortRange) UnmarshalText(text []byte) error { + if len(text) == 0 { + *pr = PortRange{} + return nil + } + + portRange, err := ParsePortRange(string(text)) + if err != nil { + return err + } + *pr = portRange + return nil +} + +// Range returns pr. +func (pr PortRange) Range() PortRange { + return pr +} + +// All returns an iterator over all the individual ports in the range. +// +// For example: +// +// for port := range pr.All() { +// // ... +// } +func (pr PortRange) All() iter.Seq[Port] { + return func(yield func(Port) bool) { + for i := uint32(pr.Start()); i <= uint32(pr.End()); i++ { + if !yield(Port{num: uint16(i), proto: pr.proto}) { + return + } + } + } +} + +// parsePortNumber parses rawPort into an int, unwrapping strconv errors +// and returning a single "out of range" error for any value outside 0–65535. +func parsePortNumber(rawPort string) (uint16, error) { + if rawPort == "" { + return 0, errors.New("value is empty") + } + port, err := strconv.ParseUint(rawPort, 10, 16) + if err != nil { + var numErr *strconv.NumError + if errors.As(err, &numErr) { + err = numErr.Err + } + return 0, err + } + + return uint16(port), nil +} + +// normalizePortProto normalizes the protocol string such that "tcp", "TCP", and "tCp" are equivalent. +// If proto is not specified, it defaults to "tcp". +func normalizePortProto(proto string) unique.Handle[IPProtocol] { + if proto == "" { + return unique.Make(TCP) + } + + proto = strings.ToLower(proto) + + return unique.Make(IPProtocol(proto)) +} diff --git a/vendor/github.com/moby/moby/api/types/network/service_info.go b/vendor/github.com/moby/moby/api/types/network/service_info.go new file mode 100644 index 000000000000..fdd92f161151 --- /dev/null +++ b/vendor/github.com/moby/moby/api/types/network/service_info.go @@ -0,0 +1,28 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package network + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/netip" +) + +// ServiceInfo represents service parameters with the list of service's tasks +// +// swagger:model ServiceInfo +type ServiceInfo struct { + + // v IP + VIP netip.Addr `json:"VIP"` + + // ports + Ports []string `json:"Ports"` + + // local l b index + LocalLBIndex int `json:"LocalLBIndex"` + + // tasks + Tasks []Task `json:"Tasks"` +} diff --git a/vendor/github.com/moby/moby/api/types/network/task.go b/vendor/github.com/moby/moby/api/types/network/task.go index 9a55fa17373b..a547523a44ea 100644 --- a/vendor/github.com/moby/moby/api/types/network/task.go +++ b/vendor/github.com/moby/moby/api/types/network/task.go @@ -5,6 +5,10 @@ package network // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command +import ( + "net/netip" +) + // Task carries the information about one backend task // // swagger:model Task @@ -17,7 +21,7 @@ type Task struct { EndpointID string `json:"EndpointID"` // endpoint IP - EndpointIP string `json:"EndpointIP"` + EndpointIP netip.Addr `json:"EndpointIP"` // info Info map[string]string `json:"Info"` diff --git a/vendor/github.com/moby/moby/api/types/plugin/plugin.go b/vendor/github.com/moby/moby/api/types/plugin/plugin.go index b7c768d7fc66..e1adda629250 100644 --- a/vendor/github.com/moby/moby/api/types/plugin/plugin.go +++ b/vendor/github.com/moby/moby/api/types/plugin/plugin.go @@ -51,8 +51,11 @@ type Config struct { // Required: true Description string `json:"Description"` - // Docker Version used to create the plugin - // Example: 17.06.0-ce + // Docker Version used to create the plugin. + // + // Depending on how the plugin was created, this field may be empty or omitted. + // + // Deprecated: this field is no longer set, and will be removed in the next API version. DockerVersion string `json:"DockerVersion,omitempty"` // documentation diff --git a/vendor/github.com/moby/moby/api/types/registry/registry.go b/vendor/github.com/moby/moby/api/types/registry/registry.go index 97c396fe0c73..ef5a486ed6c7 100644 --- a/vendor/github.com/moby/moby/api/types/registry/registry.go +++ b/vendor/github.com/moby/moby/api/types/registry/registry.go @@ -1,15 +1,14 @@ package registry import ( - "encoding/json" - "net" + "net/netip" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ServiceConfig stores daemon registry services configuration. type ServiceConfig struct { - InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"` + InsecureRegistryCIDRs []netip.Prefix `json:"InsecureRegistryCIDRs"` IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"` Mirrors []string @@ -17,34 +16,6 @@ type ServiceConfig struct { ExtraFields map[string]any `json:"-"` } -// NetIPNet is the net.IPNet type, which can be marshalled and -// unmarshalled to JSON -type NetIPNet net.IPNet - -// String returns the CIDR notation of ipnet -func (ipnet *NetIPNet) String() string { - return (*net.IPNet)(ipnet).String() -} - -// MarshalJSON returns the JSON representation of the IPNet -func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) { - return json.Marshal((*net.IPNet)(ipnet).String()) -} - -// UnmarshalJSON sets the IPNet from a byte array of JSON -func (ipnet *NetIPNet) UnmarshalJSON(b []byte) error { - var ipnetStr string - if err := json.Unmarshal(b, &ipnetStr); err != nil { - return err - } - _, cidr, err := net.ParseCIDR(ipnetStr) - if err != nil { - return err - } - *ipnet = NetIPNet(*cidr) - return nil -} - // IndexInfo contains information about a registry // // RepositoryInfo Examples: diff --git a/vendor/github.com/moby/moby/api/types/swarm/container.go b/vendor/github.com/moby/moby/api/types/swarm/container.go index bc8690d8cbb5..268565ec8ae4 100644 --- a/vendor/github.com/moby/moby/api/types/swarm/container.go +++ b/vendor/github.com/moby/moby/api/types/swarm/container.go @@ -1,6 +1,7 @@ package swarm import ( + "net/netip" "time" "github.com/moby/moby/api/types/container" @@ -14,7 +15,7 @@ import ( // TODO: `domain` is not supported yet. type DNSConfig struct { // Nameservers specifies the IP addresses of the name servers - Nameservers []string `json:",omitempty"` + Nameservers []netip.Addr `json:",omitempty"` // Search specifies the search list for host-name lookup Search []string `json:",omitempty"` // Options allows certain internal resolver variables to be modified diff --git a/vendor/github.com/moby/moby/api/types/swarm/network.go b/vendor/github.com/moby/moby/api/types/swarm/network.go index 95a5fb385a51..5e2517174f48 100644 --- a/vendor/github.com/moby/moby/api/types/swarm/network.go +++ b/vendor/github.com/moby/moby/api/types/swarm/network.go @@ -1,6 +1,8 @@ package swarm import ( + "net/netip" + "github.com/moby/moby/api/types/network" ) @@ -30,7 +32,7 @@ const ( // PortConfig represents the config of a port. type PortConfig struct { Name string `json:",omitempty"` - Protocol PortConfigProtocol `json:",omitempty"` + Protocol network.IPProtocol `json:",omitempty"` // TargetPort is the port inside the container TargetPort uint32 `json:",omitempty"` // PublishedPort is the port on the swarm hosts @@ -52,24 +54,10 @@ const ( PortConfigPublishModeHost PortConfigPublishMode = "host" ) -// PortConfigProtocol represents the protocol of a port. -type PortConfigProtocol string - -const ( - // TODO(stevvooe): These should be used generally, not just for PortConfig. - - // PortConfigProtocolTCP TCP - PortConfigProtocolTCP PortConfigProtocol = "tcp" - // PortConfigProtocolUDP UDP - PortConfigProtocolUDP PortConfigProtocol = "udp" - // PortConfigProtocolSCTP SCTP - PortConfigProtocolSCTP PortConfigProtocol = "sctp" -) - // EndpointVirtualIP represents the virtual ip of a port. type EndpointVirtualIP struct { - NetworkID string `json:",omitempty"` - Addr string `json:",omitempty"` + NetworkID string `json:",omitempty"` + Addr netip.Addr `json:",omitempty"` } // Network represents a network. @@ -103,8 +91,8 @@ type NetworkAttachmentConfig struct { // NetworkAttachment represents a network attachment. type NetworkAttachment struct { - Network Network `json:",omitempty"` - Addresses []string `json:",omitempty"` + Network Network `json:",omitempty"` + Addresses []netip.Addr `json:",omitempty"` } // IPAMOptions represents ipam options. @@ -115,7 +103,7 @@ type IPAMOptions struct { // IPAMConfig represents ipam configuration. type IPAMConfig struct { - Subnet string `json:",omitempty"` - Range string `json:",omitempty"` - Gateway string `json:",omitempty"` + Subnet netip.Prefix `json:",omitempty"` + Range netip.Prefix `json:",omitempty"` + Gateway netip.Addr `json:",omitempty"` } diff --git a/vendor/github.com/moby/moby/api/types/swarm/swarm.go b/vendor/github.com/moby/moby/api/types/swarm/swarm.go index 4c37a982d170..7d683b30ae64 100644 --- a/vendor/github.com/moby/moby/api/types/swarm/swarm.go +++ b/vendor/github.com/moby/moby/api/types/swarm/swarm.go @@ -1,6 +1,7 @@ package swarm import ( + "net/netip" "time" ) @@ -12,7 +13,7 @@ type ClusterInfo struct { Spec Spec TLSInfo TLSInfo RootRotationInProgress bool - DefaultAddrPool []string + DefaultAddrPool []netip.Prefix SubnetSize uint32 DataPathPort uint32 } @@ -159,7 +160,7 @@ type InitRequest struct { Spec Spec AutoLockManagers bool Availability NodeAvailability - DefaultAddrPool []string + DefaultAddrPool []netip.Prefix SubnetSize uint32 } diff --git a/vendor/github.com/moby/moby/api/types/system/info.go b/vendor/github.com/moby/moby/api/types/system/info.go index 4bbc8c8aba6e..0147b42ee526 100644 --- a/vendor/github.com/moby/moby/api/types/system/info.go +++ b/vendor/github.com/moby/moby/api/types/system/info.go @@ -1,6 +1,8 @@ package system import ( + "net/netip" + "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/registry" "github.com/moby/moby/api/types/swarm" @@ -146,7 +148,7 @@ type Commit struct { // NetworkAddressPool is a temp struct used by [Info] struct. type NetworkAddressPool struct { - Base string + Base netip.Prefix Size int } diff --git a/vendor/github.com/moby/moby/api/types/types.go b/vendor/github.com/moby/moby/api/types/types.go index 3d00f354b6fe..32fbcc639fd7 100644 --- a/vendor/github.com/moby/moby/api/types/types.go +++ b/vendor/github.com/moby/moby/api/types/types.go @@ -11,6 +11,15 @@ const ( // MediaTypeMultiplexedStream is vendor specific MIME-Type set for stdin/stdout/stderr multiplexed streams MediaTypeMultiplexedStream = "application/vnd.docker.multiplexed-stream" + + // MediaTypeJSON is the MIME-Type for JSON objects + MediaTypeJSON = "application/json" + + // MediaTypeNDJson is the MIME-Type for Newline Delimited JSON objects streams + MediaTypeNDJSON = "application/x-ndjson" + + // MediaTypeJsonSequence is the MIME-Type for JSON Text Sequences (RFC7464) + MediaTypeJSONSequence = "application/json-seq" ) // Ping contains response of Engine API: diff --git a/vendor/github.com/moby/moby/client/build_cancel.go b/vendor/github.com/moby/moby/client/build_cancel.go index f39b8761611f..c9bea9d0068e 100644 --- a/vendor/github.com/moby/moby/client/build_cancel.go +++ b/vendor/github.com/moby/moby/client/build_cancel.go @@ -5,9 +5,11 @@ import ( "net/url" ) +type BuildCancelOptions struct{} + // BuildCancel requests the daemon to cancel the ongoing build request // with the given id. -func (cli *Client) BuildCancel(ctx context.Context, id string) error { +func (cli *Client) BuildCancel(ctx context.Context, id string, _ BuildCancelOptions) error { query := url.Values{} query.Set("id", id) diff --git a/vendor/github.com/moby/moby/client/build_prune.go b/vendor/github.com/moby/moby/client/build_prune.go index c7cbf076e865..9ea3bb6a77d5 100644 --- a/vendor/github.com/moby/moby/client/build_prune.go +++ b/vendor/github.com/moby/moby/client/build_prune.go @@ -21,8 +21,14 @@ type BuildCachePruneOptions struct { Filters filters.Args } +// BuildCachePruneResult holds the result from the BuildCachePrune method. +type BuildCachePruneResult struct { + Report build.CachePruneReport +} + // BuildCachePrune requests the daemon to delete unused cache data. -func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (*build.CachePruneReport, error) { +func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (BuildCachePruneResult, error) { + var out BuildCachePruneResult query := url.Values{} if opts.All { query.Set("all", "1") @@ -45,7 +51,7 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOpti } f, err := filters.ToJSON(opts.Filters) if err != nil { - return nil, fmt.Errorf("prune could not marshal filters option: %w", err) + return BuildCachePruneResult{}, fmt.Errorf("prune could not marshal filters option: %w", err) } query.Set("filters", f) @@ -53,13 +59,14 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOpti defer ensureReaderClosed(resp) if err != nil { - return nil, err + return BuildCachePruneResult{}, err } report := build.CachePruneReport{} if err := json.NewDecoder(resp.Body).Decode(&report); err != nil { - return nil, fmt.Errorf("error retrieving disk usage: %w", err) + return BuildCachePruneResult{}, fmt.Errorf("error retrieving disk usage: %w", err) } - return &report, nil + out.Report = report + return out, nil } diff --git a/vendor/github.com/moby/moby/client/client_interfaces.go b/vendor/github.com/moby/moby/client/client_interfaces.go index 2f2fd3ce3df0..70a7e2331ef3 100644 --- a/vendor/github.com/moby/moby/client/client_interfaces.go +++ b/vendor/github.com/moby/moby/client/client_interfaces.go @@ -6,7 +6,6 @@ import ( "net" "github.com/moby/moby/api/types" - "github.com/moby/moby/api/types/build" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/events" "github.com/moby/moby/api/types/filters" @@ -109,8 +108,8 @@ type DistributionAPIClient interface { // ImageAPIClient defines API client methods for the images type ImageAPIClient interface { ImageBuild(ctx context.Context, context io.Reader, options ImageBuildOptions) (ImageBuildResponse, error) - BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (*build.CachePruneReport, error) - BuildCancel(ctx context.Context, id string) error + BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (BuildCachePruneResult, error) + BuildCancel(ctx context.Context, id string, opts BuildCancelOptions) error ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (io.ReadCloser, error) ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (io.ReadCloser, error) diff --git a/vendor/github.com/moby/moby/client/container_create.go b/vendor/github.com/moby/moby/client/container_create.go index b15a5e65157c..89e4306e393c 100644 --- a/vendor/github.com/moby/moby/client/container_create.go +++ b/vendor/github.com/moby/moby/client/container_create.go @@ -48,10 +48,6 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config } if hostConfig != nil { - if versions.LessThan(cli.ClientVersion(), "1.25") { - // When using API 1.24 and under, the client is responsible for removing the container - hostConfig.AutoRemove = false - } if platform != nil && platform.OS == "linux" && versions.LessThan(cli.ClientVersion(), "1.42") { // When using API under 1.42, the Linux daemon doesn't respect the ConsoleSize hostConfig.ConsoleSize = [2]uint{0, 0} diff --git a/vendor/github.com/moby/moby/client/internal/json-stream.go b/vendor/github.com/moby/moby/client/internal/json-stream.go new file mode 100644 index 000000000000..552978f9a183 --- /dev/null +++ b/vendor/github.com/moby/moby/client/internal/json-stream.go @@ -0,0 +1,50 @@ +package internal + +import ( + "encoding/json" + "io" + "slices" + + "github.com/moby/moby/api/types" +) + +const rs = 0x1E + +type DecoderFn func(v any) error + +// NewJSONStreamDecoder builds adequate DecoderFn to read json records formatted with specified content-type +func NewJSONStreamDecoder(r io.Reader, contentType string) DecoderFn { + switch contentType { + case types.MediaTypeJSONSequence: + return json.NewDecoder(NewRSFilterReader(r)).Decode + case types.MediaTypeJSON, types.MediaTypeNDJSON: + fallthrough + default: + return json.NewDecoder(r).Decode + } +} + +// RSFilterReader wraps an io.Reader and filters out ASCII RS characters +type RSFilterReader struct { + reader io.Reader + buffer []byte +} + +// NewRSFilterReader creates a new RSFilterReader that filters out RS characters +func NewRSFilterReader(r io.Reader) *RSFilterReader { + return &RSFilterReader{ + reader: r, + buffer: make([]byte, 4096), // Internal buffer for reading chunks + } +} + +// Read implements the io.Reader interface, filtering out RS characters +func (r *RSFilterReader) Read(p []byte) (n int, err error) { + if len(p) == 0 { + return 0, nil + } + + n, err = r.reader.Read(p) + filtered := slices.DeleteFunc(p[:n], func(b byte) bool { return b == rs }) + return len(filtered), err +} diff --git a/vendor/github.com/moby/moby/client/system_events.go b/vendor/github.com/moby/moby/client/system_events.go index 93b12cdefd8b..bee7402b5938 100644 --- a/vendor/github.com/moby/moby/client/system_events.go +++ b/vendor/github.com/moby/moby/client/system_events.go @@ -2,12 +2,14 @@ package client import ( "context" - "encoding/json" + "net/http" "net/url" "time" + "github.com/moby/moby/api/types" "github.com/moby/moby/api/types/events" "github.com/moby/moby/api/types/filters" + "github.com/moby/moby/client/internal" "github.com/moby/moby/client/internal/timestamp" ) @@ -37,7 +39,10 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha return } - resp, err := cli.get(ctx, "/events", query, nil) + headers := http.Header{} + headers.Add("Accept", types.MediaTypeJSONSequence) + headers.Add("Accept", types.MediaTypeNDJSON) + resp, err := cli.get(ctx, "/events", query, headers) if err != nil { close(started) errs <- err @@ -45,7 +50,8 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha } defer resp.Body.Close() - decoder := json.NewDecoder(resp.Body) + contentType := resp.Header.Get("Content-Type") + decoder := internal.NewJSONStreamDecoder(resp.Body, contentType) close(started) for { @@ -55,7 +61,7 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha return default: var event events.Message - if err := decoder.Decode(&event); err != nil { + if err := decoder(&event); err != nil { errs <- err return } diff --git a/vendor/modules.txt b/vendor/modules.txt index db9b74444ca5..fc502761fec9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -72,8 +72,8 @@ github.com/docker/docker-credential-helpers/credentials # github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c ## explicit github.com/docker/go/canonical/json -# github.com/docker/go-connections v0.6.0 -## explicit; go 1.18 +# github.com/docker/go-connections v0.6.0 => github.com/austinvazquez/go-connections v0.0.0-20251007024048-9df23d402fa0 +## explicit; go 1.23.0 github.com/docker/go-connections/nat github.com/docker/go-connections/sockets github.com/docker/go-connections/tlsconfig @@ -168,7 +168,7 @@ github.com/moby/docker-image-spec/specs-go/v1 github.com/moby/go-archive github.com/moby/go-archive/compression github.com/moby/go-archive/tarheader -# github.com/moby/moby/api v1.52.0-beta.1.0.20250930082920-4ca8aedf929f +# github.com/moby/moby/api v1.52.0-beta.1.0.20251007000938-19e498ea6522 ## explicit; go 1.23.0 github.com/moby/moby/api/pkg/authconfig github.com/moby/moby/api/pkg/progress @@ -194,9 +194,10 @@ github.com/moby/moby/api/types/swarm github.com/moby/moby/api/types/system github.com/moby/moby/api/types/versions github.com/moby/moby/api/types/volume -# github.com/moby/moby/client v0.1.0-beta.0.0.20250930082920-4ca8aedf929f +# github.com/moby/moby/client v0.1.0-beta.0.0.20251006143509-694e30abff1c ## explicit; go 1.23.0 github.com/moby/moby/client +github.com/moby/moby/client/internal github.com/moby/moby/client/internal/timestamp github.com/moby/moby/client/pkg/jsonmessage github.com/moby/moby/client/pkg/security @@ -568,3 +569,4 @@ gotest.tools/v3/skip # tags.cncf.io/container-device-interface v1.0.1 ## explicit; go 1.20 tags.cncf.io/container-device-interface/pkg/parser +# github.com/docker/go-connections => github.com/austinvazquez/go-connections v0.0.0-20251007024048-9df23d402fa0