vendor: github.com/moby/moby/api, client 0769fe708773 (master)
full diff: 4ca8aedf92...0769fe7087
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@ -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
|
||||
}
|
||||
|
||||
@ -70,8 +70,7 @@ const (
|
||||
)
|
||||
|
||||
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||
pruneFilters := options.filter.Value()
|
||||
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
|
||||
|
||||
warning := normalWarning
|
||||
if options.all {
|
||||
@ -87,7 +86,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 +94,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")
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -6,9 +6,7 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
@ -33,7 +31,7 @@ type fakeClient struct {
|
||||
containerListFunc func(options client.ContainerListOptions) ([]container.Summary, error)
|
||||
imageListFunc func(options client.ImageListOptions) ([]image.Summary, error)
|
||||
networkListFunc func(ctx context.Context, options client.NetworkListOptions) ([]network.Summary, error)
|
||||
volumeListFunc func(filter filters.Args) (volume.ListResponse, error)
|
||||
volumeListFunc func(filter client.Filters) (volume.ListResponse, error)
|
||||
}
|
||||
|
||||
func (c *fakeClient) ContainerList(_ context.Context, options client.ContainerListOptions) ([]container.Summary, error) {
|
||||
@ -156,7 +154,7 @@ func TestCompleteContainerNames(t *testing.T) {
|
||||
}
|
||||
comp := ContainerNames(fakeCLI{&fakeClient{
|
||||
containerListFunc: func(opts client.ContainerListOptions) ([]container.Summary, error) {
|
||||
assert.Check(t, is.DeepEqual(opts, tc.expOpts, cmpopts.IgnoreUnexported(client.ContainerListOptions{}, filters.Args{})))
|
||||
assert.Check(t, is.DeepEqual(opts, tc.expOpts))
|
||||
if tc.expDirective == cobra.ShellCompDirectiveError {
|
||||
return nil, errors.New("some error occurred")
|
||||
}
|
||||
@ -339,7 +337,7 @@ func TestCompleteVolumeNames(t *testing.T) {
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
comp := VolumeNames(fakeCLI{&fakeClient{
|
||||
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
|
||||
volumeListFunc: func(filter client.Filters) (volume.ListResponse, error) {
|
||||
if tc.expDirective == cobra.ShellCompDirectiveError {
|
||||
return volume.ListResponse{}, errors.New("some error occurred")
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
@ -133,8 +132,8 @@ func TestConfigListWithFormat(t *testing.T) {
|
||||
func TestConfigListWithFilter(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
configListFunc: func(_ context.Context, options client.ConfigListOptions) ([]swarm.Config, error) {
|
||||
assert.Check(t, is.Equal("foo", options.Filters.Get("name")[0]))
|
||||
assert.Check(t, is.Equal("lbl1=Label-bar", options.Filters.Get("label")[0]))
|
||||
assert.Check(t, options.Filters["name"]["foo"])
|
||||
assert.Check(t, options.Filters["label"]["lbl1=Label-bar"])
|
||||
return []swarm.Config{
|
||||
*builders.Config(builders.ConfigID("ID-foo"),
|
||||
builders.ConfigName("foo"),
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/system"
|
||||
"github.com/moby/moby/client"
|
||||
@ -36,7 +35,7 @@ type fakeClient struct {
|
||||
containerRestartFunc func(ctx context.Context, containerID string, options client.ContainerStopOptions) error
|
||||
containerStopFunc func(ctx context.Context, containerID string, options client.ContainerStopOptions) error
|
||||
containerKillFunc func(ctx context.Context, containerID, signal string) error
|
||||
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
|
||||
containerPruneFunc func(ctx context.Context, pruneFilters client.Filters) (container.PruneReport, error)
|
||||
containerAttachFunc func(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.HijackedResponse, error)
|
||||
containerDiffFunc func(ctx context.Context, containerID string) ([]container.FilesystemChange, error)
|
||||
containerRenameFunc func(ctx context.Context, oldName, newName string) error
|
||||
@ -172,7 +171,7 @@ func (f *fakeClient) ContainerKill(ctx context.Context, containerID, signal stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
|
||||
func (f *fakeClient) ContainersPrune(ctx context.Context, pruneFilters client.Filters) (container.PruneReport, error) {
|
||||
if f.containerPruneFunc != nil {
|
||||
return f.containerPruneFunc(ctx, pruneFilters)
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
|
||||
expectedAll bool
|
||||
expectedSize bool
|
||||
expectedLimit int
|
||||
expectedFilters map[string]string
|
||||
expectedFilters client.Filters
|
||||
}{
|
||||
{
|
||||
psOpts: &psOptions{
|
||||
@ -35,13 +35,10 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
|
||||
last: 5,
|
||||
filter: filters,
|
||||
},
|
||||
expectedAll: true,
|
||||
expectedSize: true,
|
||||
expectedLimit: 5,
|
||||
expectedFilters: map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "foo",
|
||||
},
|
||||
expectedAll: true,
|
||||
expectedSize: true,
|
||||
expectedLimit: 5,
|
||||
expectedFilters: make(client.Filters).Add("foo", "bar").Add("baz", "foo"),
|
||||
},
|
||||
{
|
||||
psOpts: &psOptions{
|
||||
@ -50,10 +47,9 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
|
||||
last: -1,
|
||||
nLatest: true,
|
||||
},
|
||||
expectedAll: true,
|
||||
expectedSize: true,
|
||||
expectedLimit: 1,
|
||||
expectedFilters: make(map[string]string),
|
||||
expectedAll: true,
|
||||
expectedSize: true,
|
||||
expectedLimit: 1,
|
||||
},
|
||||
{
|
||||
psOpts: &psOptions{
|
||||
@ -64,13 +60,10 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
|
||||
// With .Size, size should be true
|
||||
format: "{{.Size}}",
|
||||
},
|
||||
expectedAll: true,
|
||||
expectedSize: true,
|
||||
expectedLimit: 5,
|
||||
expectedFilters: map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "foo",
|
||||
},
|
||||
expectedAll: true,
|
||||
expectedSize: true,
|
||||
expectedLimit: 5,
|
||||
expectedFilters: make(client.Filters).Add("foo", "bar").Add("baz", "foo"),
|
||||
},
|
||||
{
|
||||
psOpts: &psOptions{
|
||||
@ -81,13 +74,10 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
|
||||
// With .Size, size should be true
|
||||
format: "{{.Size}} {{.CreatedAt}} {{upper .Networks}}",
|
||||
},
|
||||
expectedAll: true,
|
||||
expectedSize: true,
|
||||
expectedLimit: 5,
|
||||
expectedFilters: map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "foo",
|
||||
},
|
||||
expectedAll: true,
|
||||
expectedSize: true,
|
||||
expectedLimit: 5,
|
||||
expectedFilters: make(client.Filters).Add("foo", "bar").Add("baz", "foo"),
|
||||
},
|
||||
{
|
||||
psOpts: &psOptions{
|
||||
@ -98,13 +88,10 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
|
||||
// Without .Size, size should be false
|
||||
format: "{{.CreatedAt}} {{.Networks}}",
|
||||
},
|
||||
expectedAll: true,
|
||||
expectedSize: false,
|
||||
expectedLimit: 5,
|
||||
expectedFilters: map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "foo",
|
||||
},
|
||||
expectedAll: true,
|
||||
expectedSize: false,
|
||||
expectedLimit: 5,
|
||||
expectedFilters: make(client.Filters).Add("foo", "bar").Add("baz", "foo"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -115,14 +102,7 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
|
||||
assert.Check(t, is.Equal(c.expectedAll, options.All))
|
||||
assert.Check(t, is.Equal(c.expectedSize, options.Size))
|
||||
assert.Check(t, is.Equal(c.expectedLimit, options.Limit))
|
||||
assert.Check(t, is.Equal(len(c.expectedFilters), options.Filters.Len()))
|
||||
|
||||
for k, v := range c.expectedFilters {
|
||||
f := options.Filters
|
||||
if !f.ExactMatch(k, v) {
|
||||
t.Fatalf("Expected filter with key %s to be %s but got %s", k, v, f.Get(k))
|
||||
}
|
||||
}
|
||||
assert.Check(t, is.DeepEqual(c.expectedFilters, options.Filters))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,11 +6,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -52,7 +52,7 @@ type containerOptions struct {
|
||||
deviceWriteBps opts.ThrottledeviceOpt
|
||||
links opts.ListOpts
|
||||
aliases opts.ListOpts
|
||||
linkLocalIPs opts.ListOpts
|
||||
linkLocalIPs opts.ListOpts // TODO(thaJeztah): we need a flag-type to handle []netip.Addr directly
|
||||
deviceReadIOps opts.ThrottledeviceOpt
|
||||
deviceWriteIOps opts.ThrottledeviceOpt
|
||||
env opts.ListOpts
|
||||
@ -64,7 +64,7 @@ type containerOptions struct {
|
||||
sysctls *opts.MapOpts
|
||||
publish opts.ListOpts
|
||||
expose opts.ListOpts
|
||||
dns opts.ListOpts
|
||||
dns opts.ListOpts // TODO(thaJeztah): we need a flag-type to handle []netip.Addr directly
|
||||
dnsSearch opts.ListOpts
|
||||
dnsOptions opts.ListOpts
|
||||
extraHosts opts.ListOpts
|
||||
@ -112,8 +112,8 @@ type containerOptions struct {
|
||||
swappiness int64
|
||||
netMode opts.NetworkOpt
|
||||
macAddress string
|
||||
ipv4Address string
|
||||
ipv6Address string
|
||||
ipv4Address 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
|
||||
ipcMode string
|
||||
pidsLimit int64
|
||||
restartPolicy string
|
||||
@ -239,8 +239,8 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
||||
flags.MarkHidden("dns-opt")
|
||||
flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains")
|
||||
flags.Var(&copts.expose, "expose", "Expose a port or a range of ports")
|
||||
flags.StringVar(&copts.ipv4Address, "ip", "", "IPv4 address (e.g., 172.30.100.104)")
|
||||
flags.StringVar(&copts.ipv6Address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)")
|
||||
flags.IPVar(&copts.ipv4Address, "ip", nil, "IPv4 address (e.g., 172.30.100.104)")
|
||||
flags.IPVar(&copts.ipv6Address, "ip6", nil, "IPv6 address (e.g., 2001:db8::33)")
|
||||
flags.Var(&copts.links, "link", "Add link to another container")
|
||||
flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses")
|
||||
flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g., 92:d0:c6:0a:29:33)")
|
||||
@ -426,38 +426,60 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
entrypoint = []string{""}
|
||||
}
|
||||
|
||||
// TODO(thaJeztah): remove uses of go-connections/nat here.
|
||||
convertedOpts, err := convertToStandardNotation(copts.publish.GetSlice())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ports, portBindings, err := nat.ParsePortSpecs(convertedOpts)
|
||||
ports, natPortBindings, err := nat.ParsePortSpecs(convertedOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
portBindings := network.PortMap{}
|
||||
for port, bindings := range natPortBindings {
|
||||
p, err := network.ParsePort(string(port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
portBindings[p] = []network.PortBinding{}
|
||||
for _, b := range bindings {
|
||||
var hostIP netip.Addr
|
||||
if b.HostIP != "" {
|
||||
hostIP, err = netip.ParseAddr(b.HostIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
portBindings[p] = append(portBindings[p], network.PortBinding{
|
||||
HostIP: hostIP,
|
||||
HostPort: b.HostPort,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add published ports as exposed ports.
|
||||
exposedPorts := network.PortSet{}
|
||||
for port := range ports {
|
||||
p, err := network.ParsePort(string(port))
|
||||
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, ":") {
|
||||
return nil, fmt.Errorf("invalid port format for --expose: %s", e)
|
||||
}
|
||||
// support two formats for expose, original format <portnum>/[<proto>]
|
||||
// or <startport-endport>/[<proto>]
|
||||
proto, port := nat.SplitProtoPort(e)
|
||||
pr, err := network.ParsePortRange(e)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid range format for --expose: %w", err)
|
||||
}
|
||||
// parse the start and end port and create a sequence of ports to expose
|
||||
// if expose a port, the start and end port are the same
|
||||
start, end, err := nat.ParsePortRange(port)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid range format for --expose: %s, error: %w", e, err)
|
||||
}
|
||||
for i := start; i <= end; i++ {
|
||||
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, exists := ports[p]; !exists {
|
||||
ports[p] = struct{}{}
|
||||
}
|
||||
for p := range pr.All() {
|
||||
exposedPorts[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
@ -626,7 +648,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 +684,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 +827,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 != nil {
|
||||
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 != nil {
|
||||
return invalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address"))
|
||||
}
|
||||
if n.MacAddress != "" && copts.macAddress != "" {
|
||||
@ -827,18 +849,21 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption
|
||||
n.Links = make([]string, copts.links.Len())
|
||||
copy(n.Links, copts.links.GetSlice())
|
||||
}
|
||||
if copts.ipv4Address != "" {
|
||||
n.IPv4Address = copts.ipv4Address
|
||||
if copts.ipv4Address != nil {
|
||||
if ipv4, ok := netip.AddrFromSlice(copts.ipv4Address.To4()); ok {
|
||||
n.IPv4Address = ipv4
|
||||
}
|
||||
}
|
||||
if copts.ipv6Address != "" {
|
||||
n.IPv6Address = copts.ipv6Address
|
||||
if copts.ipv6Address != nil {
|
||||
if ipv6, ok := netip.AddrFromSlice(copts.ipv6Address.To16()); ok {
|
||||
n.IPv6Address = ipv6
|
||||
}
|
||||
}
|
||||
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 +892,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 +1156,18 @@ func validateAttach(val string) (string, error) {
|
||||
}
|
||||
return val, errors.New("valid streams are STDIN, STDOUT and STDERR")
|
||||
}
|
||||
|
||||
func toNetipAddrSlice(ips []string) []netip.Addr {
|
||||
if len(ips) == 0 {
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@ -4,12 +4,14 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
networktypes "github.com/moby/moby/api/types/network"
|
||||
"github.com/spf13/pflag"
|
||||
@ -430,14 +432,14 @@ func TestParseHostnameDomainname(t *testing.T) {
|
||||
func TestParseWithExpose(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
":": "invalid port format for --expose: :",
|
||||
"8080:9090": "invalid port format for --expose: 8080:9090",
|
||||
"/tcp": "invalid range format for --expose: /tcp, error: empty string specified for ports",
|
||||
"/udp": "invalid range format for --expose: /udp, error: empty string specified for ports",
|
||||
"NaN/tcp": `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
|
||||
"NaN-NaN/tcp": `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
|
||||
"8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
|
||||
"1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
|
||||
":": `invalid range format for --expose: invalid start port ':': invalid syntax`,
|
||||
"8080:9090": `invalid range format for --expose: invalid start port '8080:9090': invalid syntax`,
|
||||
"/tcp": `invalid range format for --expose: invalid start port '': value is empty`,
|
||||
"/udp": `invalid range format for --expose: invalid start port '': value is empty`,
|
||||
"NaN/tcp": `invalid range format for --expose: invalid start port 'NaN': invalid syntax`,
|
||||
"NaN-NaN/tcp": `invalid range format for --expose: invalid start port 'NaN': invalid syntax`,
|
||||
"8080-NaN/tcp": `invalid range format for --expose: invalid end port 'NaN': invalid syntax`,
|
||||
"1234567890-8080/tcp": `invalid range format for --expose: invalid start port '1234567890': value out of range`,
|
||||
}
|
||||
for expose, expectedError := range tests {
|
||||
t.Run(expose, func(t *testing.T) {
|
||||
@ -447,12 +449,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][]networktypes.Port{
|
||||
"8080/tcp": {networktypes.MustParsePort("8080/tcp")},
|
||||
"8080/udp": {networktypes.MustParsePort("8080/udp")},
|
||||
"8080/ncp": {networktypes.MustParsePort("8080/ncp")},
|
||||
"8080-8080/udp": {networktypes.MustParsePort("8080/udp")},
|
||||
"8080-8082/tcp": {networktypes.MustParsePort("8080/tcp"), networktypes.MustParsePort("8081/tcp"), networktypes.MustParsePort("8082/tcp")},
|
||||
}
|
||||
for expose, exposedPorts := range tests {
|
||||
t.Run(expose, func(t *testing.T) {
|
||||
@ -460,7 +462,7 @@ func TestParseWithExpose(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
for _, port := range exposedPorts {
|
||||
_, ok := config.ExposedPorts[port]
|
||||
assert.Check(t, ok, "missing port %q in exposed ports", port)
|
||||
assert.Check(t, ok, "missing port %q in exposed ports: %#+v", port, config.ExposedPorts[port])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -471,10 +473,10 @@ 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 := []networktypes.Port{networktypes.MustParsePort("80/tcp"), networktypes.MustParsePort("81/tcp")}
|
||||
for _, port := range ports {
|
||||
_, ok := config.ExposedPorts[port]
|
||||
assert.Check(t, ok, "missing port %q in exposed ports", port)
|
||||
assert.Check(t, ok, "missing port %q in exposed ports: %#+v", port, config.ExposedPorts[port])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -606,9 +608,9 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
expected: map[string]*networktypes.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"},
|
||||
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"},
|
||||
@ -636,9 +638,9 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
"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"},
|
||||
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"},
|
||||
@ -647,15 +649,15 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
"net3": {
|
||||
DriverOpts: map[string]string{"field3": "value3"},
|
||||
IPAMConfig: &networktypes.EndpointIPAMConfig{
|
||||
IPv4Address: "172.20.88.22",
|
||||
IPv6Address: "2001:db8::8822",
|
||||
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"},
|
||||
LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.169.254")},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -671,8 +673,8 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
"field2": "value2",
|
||||
},
|
||||
IPAMConfig: &networktypes.EndpointIPAMConfig{
|
||||
IPv4Address: "172.20.88.22",
|
||||
IPv6Address: "2001:db8::8822",
|
||||
IPv4Address: netip.MustParseAddr("172.20.88.22"),
|
||||
IPv6Address: netip.MustParseAddr("2001:db8::8822"),
|
||||
},
|
||||
Aliases: []string{"web1", "web2"},
|
||||
MacAddress: "02:32:1c:23:00:04",
|
||||
@ -753,7 +755,7 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, config.MacAddress, tc.expectedCfg.MacAddress) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
assert.DeepEqual(t, hConfig.NetworkMode, tc.expectedHostCfg.NetworkMode)
|
||||
assert.DeepEqual(t, nwConfig.EndpointsConfig, tc.expected)
|
||||
assert.DeepEqual(t, nwConfig.EndpointsConfig, tc.expected, cmpopts.EquateComparable(netip.Addr{}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
)
|
||||
@ -13,32 +15,32 @@ import (
|
||||
func TestNewPortCommandOutput(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
ips []string
|
||||
ips []netip.Addr
|
||||
port string
|
||||
}{
|
||||
{
|
||||
name: "container-port-ipv4",
|
||||
ips: []string{"0.0.0.0"},
|
||||
ips: []netip.Addr{netip.MustParseAddr("0.0.0.0")},
|
||||
port: "80",
|
||||
},
|
||||
{
|
||||
name: "container-port-ipv6",
|
||||
ips: []string{"::"},
|
||||
ips: []netip.Addr{netip.MustParseAddr("::")},
|
||||
port: "80",
|
||||
},
|
||||
{
|
||||
name: "container-port-ipv6-and-ipv4",
|
||||
ips: []string{"::", "0.0.0.0"},
|
||||
ips: []netip.Addr{netip.MustParseAddr("::"), netip.MustParseAddr("0.0.0.0")},
|
||||
port: "80",
|
||||
},
|
||||
{
|
||||
name: "container-port-ipv6-and-ipv4-443-udp",
|
||||
ips: []string{"::", "0.0.0.0"},
|
||||
ips: []netip.Addr{netip.MustParseAddr("::"), netip.MustParseAddr("0.0.0.0")},
|
||||
port: "443/udp",
|
||||
},
|
||||
{
|
||||
name: "container-port-all-ports",
|
||||
ips: []string{"::", "0.0.0.0"},
|
||||
ips: []netip.Addr{netip.MustParseAddr("::"), netip.MustParseAddr("0.0.0.0")},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
@ -46,19 +48,19 @@ 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{
|
||||
ci.NetworkSettings.Ports[network.MustParsePort("80/tcp")][i] = network.PortBinding{
|
||||
HostIP: ip, HostPort: "3456",
|
||||
}
|
||||
ci.NetworkSettings.Ports["443/tcp"][i] = container.PortBinding{
|
||||
ci.NetworkSettings.Ports[network.MustParsePort("443/tcp")][i] = network.PortBinding{
|
||||
HostIP: ip, HostPort: "4567",
|
||||
}
|
||||
ci.NetworkSettings.Ports["443/udp"][i] = container.PortBinding{
|
||||
ci.NetworkSettings.Ports[network.MustParsePort("443/udp")][i] = network.PortBinding{
|
||||
HostIP: ip, HostPort: "5678",
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/client"
|
||||
)
|
||||
|
||||
func TestContainerPrunePromptTermination(t *testing.T) {
|
||||
@ -16,7 +16,7 @@ func TestContainerPrunePromptTermination(t *testing.T) {
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
|
||||
containerPruneFunc: func(ctx context.Context, pruneFilters client.Filters) (container.PruneReport, error) {
|
||||
return container.PruneReport{}, errors.New("fakeClient containerPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
|
||||
@ -10,13 +10,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -60,7 +60,7 @@ type StatsOptions struct {
|
||||
// filter options may be added in future (within the constraints described
|
||||
// above), but may require daemon-side validation as the list of accepted
|
||||
// filters can differ between daemon- and API versions.
|
||||
Filters *filters.Args
|
||||
Filters client.Filters
|
||||
}
|
||||
|
||||
// newStatsCommand creates a new [cobra.Command] for "docker container stats".
|
||||
@ -101,6 +101,24 @@ var acceptedStatsFilters = map[string]bool{
|
||||
"label": true,
|
||||
}
|
||||
|
||||
// cloneFilters returns a deep copy of f.
|
||||
//
|
||||
// TODO(thaJeztah): add this as a "Clone" method on client.Filters.
|
||||
func cloneFilters(f client.Filters) client.Filters {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
out := make(client.Filters, len(f))
|
||||
for term, values := range f {
|
||||
inner := make(map[string]bool, len(values))
|
||||
for v, ok := range values {
|
||||
inner[v] = ok
|
||||
}
|
||||
out[term] = inner
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// RunStats displays a live stream of resource usage statistics for one or more containers.
|
||||
// This shows real-time information on CPU usage, memory usage, and network I/O.
|
||||
//
|
||||
@ -123,12 +141,14 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
started := make(chan struct{})
|
||||
|
||||
if options.Filters == nil {
|
||||
f := filters.NewArgs()
|
||||
options.Filters = &f
|
||||
options.Filters = make(client.Filters)
|
||||
}
|
||||
|
||||
if err := options.Filters.Validate(acceptedStatsFilters); err != nil {
|
||||
return err
|
||||
// FIXME(thaJeztah): any way we can (and should?) validate allowed filters?
|
||||
for filter := range options.Filters {
|
||||
if _, ok := acceptedStatsFilters[filter]; !ok {
|
||||
return errdefs.ErrInvalidArgument.WithMessage("invalid filter '" + filter + "'")
|
||||
}
|
||||
}
|
||||
|
||||
eh := newEventHandler()
|
||||
@ -163,7 +183,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
// the original set of filters. Custom filters are used both
|
||||
// to list containers and to filter events, but the "type" filter
|
||||
// is not valid for filtering containers.
|
||||
f := options.Filters.Clone()
|
||||
f := cloneFilters(options.Filters)
|
||||
f.Add("type", string(events.ContainerEventType))
|
||||
eventChan, errChan := apiClient.Events(ctx, client.EventsListOptions{
|
||||
Filters: f,
|
||||
@ -199,7 +219,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
// to refresh the list of containers.
|
||||
cs, err := apiClient.ContainerList(ctx, client.ContainerListOptions{
|
||||
All: options.All,
|
||||
Filters: *options.Filters,
|
||||
Filters: options.Filters,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -219,7 +239,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
// only a single code-path is needed, and custom filters can be combined
|
||||
// with a list of container names/IDs.
|
||||
|
||||
if options.Filters != nil && options.Filters.Len() > 0 {
|
||||
if len(options.Filters) > 0 {
|
||||
return errors.New("filtering is not supported when specifying a list of containers")
|
||||
}
|
||||
|
||||
|
||||
@ -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.IsValid() {
|
||||
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 {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/api/types/system"
|
||||
"github.com/moby/moby/client"
|
||||
@ -19,8 +18,8 @@ type fakeClient struct {
|
||||
imageRemoveFunc func(image string, options client.ImageRemoveOptions) ([]image.DeleteResponse, error)
|
||||
imagePushFunc func(ref string, options client.ImagePushOptions) (io.ReadCloser, error)
|
||||
infoFunc func() (system.Info, error)
|
||||
imagePullFunc func(ref string, options client.ImagePullOptions) (io.ReadCloser, error)
|
||||
imagesPruneFunc func(pruneFilter filters.Args) (image.PruneReport, error)
|
||||
imagePullFunc func(ref string, options client.ImagePullOptions) (client.ImagePullResponse, error)
|
||||
imagesPruneFunc func(pruneFilter client.Filters) (image.PruneReport, error)
|
||||
imageLoadFunc func(input io.Reader, options ...client.ImageLoadOption) (client.LoadResponse, error)
|
||||
imageListFunc func(options client.ImageListOptions) ([]image.Summary, error)
|
||||
imageInspectFunc func(img string) (image.InspectResponse, error)
|
||||
@ -66,14 +65,14 @@ func (cli *fakeClient) Info(_ context.Context) (system.Info, error) {
|
||||
return system.Info{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ImagePull(_ context.Context, ref string, options client.ImagePullOptions) (io.ReadCloser, error) {
|
||||
func (cli *fakeClient) ImagePull(_ context.Context, ref string, options client.ImagePullOptions) (client.ImagePullResponse, error) {
|
||||
if cli.imagePullFunc != nil {
|
||||
return cli.imagePullFunc(ref, options)
|
||||
}
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
return client.ImagePullResponse{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ImagesPrune(_ context.Context, pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
func (cli *fakeClient) ImagesPrune(_ context.Context, pruneFilter client.Filters) (image.PruneReport, error) {
|
||||
if cli.imagesPruneFunc != nil {
|
||||
return cli.imagesPruneFunc(pruneFilter)
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
@ -69,7 +68,7 @@ func TestNewImagesCommandSuccess(t *testing.T) {
|
||||
name: "match-name",
|
||||
args: []string{"image"},
|
||||
imageListFunc: func(options client.ImageListOptions) ([]image.Summary, error) {
|
||||
assert.Check(t, is.Equal("image", options.Filters.Get("reference")[0]))
|
||||
assert.Check(t, options.Filters["reference"]["image"])
|
||||
return []image.Summary{}, nil
|
||||
},
|
||||
},
|
||||
@ -77,7 +76,7 @@ func TestNewImagesCommandSuccess(t *testing.T) {
|
||||
name: "filters",
|
||||
args: []string{"--filter", "name=value"},
|
||||
imageListFunc: func(options client.ImageListOptions) ([]image.Summary, error) {
|
||||
assert.Check(t, is.Equal("value", options.Filters.Get("name")[0]))
|
||||
assert.Check(t, options.Filters["name"]["value"])
|
||||
return []image.Summary{}, nil
|
||||
},
|
||||
},
|
||||
|
||||
@ -69,9 +69,8 @@ Are you sure you want to continue?`
|
||||
)
|
||||
|
||||
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||
pruneFilters := options.filter.Value().Clone()
|
||||
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
|
||||
pruneFilters.Add("dangling", strconv.FormatBool(!options.all))
|
||||
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||
|
||||
warning := danglingWarning
|
||||
if options.all {
|
||||
|
||||
@ -10,10 +10,9 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
@ -22,7 +21,7 @@ func TestNewPruneCommandErrors(t *testing.T) {
|
||||
name string
|
||||
args []string
|
||||
expectedError string
|
||||
imagesPruneFunc func(pruneFilter filters.Args) (image.PruneReport, error)
|
||||
imagesPruneFunc func(pruneFilter client.Filters) (image.PruneReport, error)
|
||||
}{
|
||||
{
|
||||
name: "wrong-args",
|
||||
@ -33,7 +32,7 @@ func TestNewPruneCommandErrors(t *testing.T) {
|
||||
name: "prune-error",
|
||||
args: []string{"--force"},
|
||||
expectedError: "something went wrong",
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
imagesPruneFunc: func(pruneFilter client.Filters) (image.PruneReport, error) {
|
||||
return image.PruneReport{}, errors.New("something went wrong")
|
||||
},
|
||||
},
|
||||
@ -55,21 +54,21 @@ func TestNewPruneCommandSuccess(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
imagesPruneFunc func(pruneFilter filters.Args) (image.PruneReport, error)
|
||||
imagesPruneFunc func(pruneFilter client.Filters) (image.PruneReport, error)
|
||||
}{
|
||||
{
|
||||
name: "all",
|
||||
args: []string{"--all"},
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
assert.Check(t, is.Equal("false", pruneFilter.Get("dangling")[0]))
|
||||
imagesPruneFunc: func(pruneFilter client.Filters) (image.PruneReport, error) {
|
||||
assert.Check(t, pruneFilter["dangling"]["false"])
|
||||
return image.PruneReport{}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "force-deleted",
|
||||
args: []string{"--force"},
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
assert.Check(t, is.Equal("true", pruneFilter.Get("dangling")[0]))
|
||||
imagesPruneFunc: func(pruneFilter client.Filters) (image.PruneReport, error) {
|
||||
assert.Check(t, pruneFilter["dangling"]["true"])
|
||||
return image.PruneReport{
|
||||
ImagesDeleted: []image.DeleteResponse{{Deleted: "image1"}},
|
||||
SpaceReclaimed: 1,
|
||||
@ -79,16 +78,16 @@ func TestNewPruneCommandSuccess(t *testing.T) {
|
||||
{
|
||||
name: "label-filter",
|
||||
args: []string{"--force", "--filter", "label=foobar"},
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
assert.Check(t, is.Equal("foobar", pruneFilter.Get("label")[0]))
|
||||
imagesPruneFunc: func(pruneFilter client.Filters) (image.PruneReport, error) {
|
||||
assert.Check(t, pruneFilter["label"]["foobar"])
|
||||
return image.PruneReport{}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "force-untagged",
|
||||
args: []string{"--force"},
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
assert.Check(t, is.Equal("true", pruneFilter.Get("dangling")[0]))
|
||||
imagesPruneFunc: func(pruneFilter client.Filters) (image.PruneReport, error) {
|
||||
assert.Check(t, pruneFilter["dangling"]["true"])
|
||||
return image.PruneReport{
|
||||
ImagesDeleted: []image.DeleteResponse{{Untagged: "image1"}},
|
||||
SpaceReclaimed: 2,
|
||||
@ -117,7 +116,7 @@ func TestPrunePromptTermination(t *testing.T) {
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
imagesPruneFunc: func(pruneFilter client.Filters) (image.PruneReport, error) {
|
||||
return image.PruneReport{}, errors.New("fakeClient imagesPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
@ -74,9 +73,9 @@ func TestNewPullCommandSuccess(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePullFunc: func(ref string, options client.ImagePullOptions) (io.ReadCloser, error) {
|
||||
imagePullFunc: func(ref string, options client.ImagePullOptions) (client.ImagePullResponse, error) {
|
||||
assert.Check(t, is.Equal(tc.expectedTag, ref), tc.name)
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
return client.ImagePullResponse{}, nil
|
||||
},
|
||||
})
|
||||
cmd := newPullCommand(cli)
|
||||
@ -120,8 +119,8 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePullFunc: func(ref string, options client.ImagePullOptions) (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader("")), errors.New("shouldn't try to pull image")
|
||||
imagePullFunc: func(ref string, options client.ImagePullOptions) (client.ImagePullResponse, error) {
|
||||
return client.ImagePullResponse{}, errors.New("shouldn't try to pull image")
|
||||
},
|
||||
})
|
||||
cli.SetNotaryClient(tc.notaryFunc)
|
||||
|
||||
@ -3,10 +3,6 @@
|
||||
"Id": "",
|
||||
"RepoTags": null,
|
||||
"RepoDigests": null,
|
||||
"Parent": "",
|
||||
"Comment": "",
|
||||
"DockerVersion": "",
|
||||
"Author": "",
|
||||
"Config": null,
|
||||
"Architecture": "",
|
||||
"Os": "",
|
||||
@ -20,10 +16,6 @@
|
||||
"Id": "",
|
||||
"RepoTags": null,
|
||||
"RepoDigests": null,
|
||||
"Parent": "",
|
||||
"Comment": "",
|
||||
"DockerVersion": "",
|
||||
"Author": "",
|
||||
"Config": null,
|
||||
"Architecture": "",
|
||||
"Os": "",
|
||||
|
||||
@ -3,10 +3,6 @@
|
||||
"Id": "",
|
||||
"RepoTags": null,
|
||||
"RepoDigests": null,
|
||||
"Parent": "",
|
||||
"Comment": "",
|
||||
"DockerVersion": "",
|
||||
"Author": "",
|
||||
"Config": null,
|
||||
"Architecture": "",
|
||||
"Os": "",
|
||||
|
||||
@ -17,7 +17,6 @@ import (
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/tui"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
imagetypes "github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/morikuni/aec"
|
||||
@ -26,7 +25,7 @@ import (
|
||||
|
||||
type treeOptions struct {
|
||||
all bool
|
||||
filters filters.Args
|
||||
filters client.Filters
|
||||
}
|
||||
|
||||
type treeView struct {
|
||||
|
||||
@ -3,7 +3,6 @@ package network
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/client"
|
||||
)
|
||||
@ -15,7 +14,7 @@ type fakeClient struct {
|
||||
networkDisconnectFunc func(ctx context.Context, networkID, container string, force bool) error
|
||||
networkRemoveFunc func(ctx context.Context, networkID string) error
|
||||
networkListFunc func(ctx context.Context, options client.NetworkListOptions) ([]network.Summary, error)
|
||||
networkPruneFunc func(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error)
|
||||
networkPruneFunc func(ctx context.Context, pruneFilters client.Filters) (network.PruneReport, error)
|
||||
networkInspectFunc func(ctx context.Context, networkID string, options client.NetworkInspectOptions) (network.Inspect, []byte, error)
|
||||
}
|
||||
|
||||
@ -61,7 +60,7 @@ func (c *fakeClient) NetworkInspectWithRaw(ctx context.Context, networkID string
|
||||
return network.Inspect{}, nil, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error) {
|
||||
func (c *fakeClient) NetworksPrune(ctx context.Context, pruneFilter client.Filters) (network.PruneReport, error) {
|
||||
if c.networkPruneFunc != nil {
|
||||
return c.networkPruneFunc(ctx, pruneFilter)
|
||||
}
|
||||
|
||||
@ -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,41 @@ func convertDriverOpt(options []string) (map[string]string, error) {
|
||||
}
|
||||
return driverOpt, nil
|
||||
}
|
||||
|
||||
func toNetipAddrSlice(ips []net.IP) []netip.Addr {
|
||||
if len(ips) == 0 {
|
||||
return nil
|
||||
}
|
||||
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 {
|
||||
a, _ := netip.AddrFromSlice(ip)
|
||||
return a.Unmap()
|
||||
}
|
||||
|
||||
// toPrefix converts n into a netip.Prefix. If n is not a valid IPv4 or IPV6
|
||||
// address, ToPrefix returns netip.Prefix{}, false.
|
||||
//
|
||||
// TODO(thaJeztah): create internal package similar to https://github.com/moby/moby/blob/0769fe708773892d6ac399ee137e71a777b35de7/daemon/internal/netiputil/netiputil.go#L21-L42
|
||||
func toPrefix(n net.IPNet) (netip.Prefix, bool) {
|
||||
if ll := len(n.Mask); ll != net.IPv4len && ll != net.IPv6len {
|
||||
return netip.Prefix{}, false
|
||||
}
|
||||
|
||||
addr, ok := netip.AddrFromSlice(n.IP)
|
||||
if !ok {
|
||||
return netip.Prefix{}, false
|
||||
}
|
||||
|
||||
ones, bits := n.Mask.Size()
|
||||
if ones == 0 && bits == 0 {
|
||||
return netip.Prefix{}, false
|
||||
}
|
||||
|
||||
return netip.PrefixFrom(addr.Unmap(), ones), true
|
||||
}
|
||||
|
||||
@ -4,9 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -46,9 +48,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"},
|
||||
@ -60,7 +62,7 @@ func TestNetworkConnectWithFlags(t *testing.T) {
|
||||
}
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
networkConnectFunc: func(ctx context.Context, networkID, container string, config *network.EndpointSettings) error {
|
||||
assert.Check(t, is.DeepEqual(expectedConfig, config))
|
||||
assert.Check(t, is.DeepEqual(expectedConfig, config, cmpopts.EquateComparable(netip.Addr{})))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
@ -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")
|
||||
@ -162,32 +163,36 @@ 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 := netip.ParsePrefix(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 {
|
||||
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 != "" {
|
||||
return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
|
||||
|
||||
// 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.String(), iData[s].IPRange.String(), s)
|
||||
}
|
||||
if ipRange, ok := toPrefix(r); ok {
|
||||
iData[s].IPRange = ipRange
|
||||
match = true
|
||||
}
|
||||
d := iData[s]
|
||||
d.IPRange = r
|
||||
match = true
|
||||
}
|
||||
if !match {
|
||||
return nil, fmt.Errorf("no matching subnet for range %s", r)
|
||||
return nil, fmt.Errorf("no matching subnet for range %s", r.String())
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,18 +200,18 @@ func createIPAMConfig(options ipamOptions) (*network.IPAM, error) {
|
||||
for _, g := range options.gateways {
|
||||
match := false
|
||||
for _, s := range options.subnets {
|
||||
ok, err := subnetMatches(s, g)
|
||||
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 {
|
||||
@ -215,17 +220,24 @@ func createIPAMConfig(options ipamOptions) (*network.IPAM, error) {
|
||||
}
|
||||
|
||||
// Validate and add aux-addresses
|
||||
for key, aa := range options.auxAddresses.GetAll() {
|
||||
for name, aa := range options.auxAddresses.GetAll() {
|
||||
if aa == "" {
|
||||
continue
|
||||
}
|
||||
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[name] = auxAddr
|
||||
match = true
|
||||
}
|
||||
if !match {
|
||||
|
||||
@ -4,10 +4,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
@ -35,24 +37,15 @@ func TestNetworkCreateErrors(t *testing.T) {
|
||||
args: []string{"toto"},
|
||||
flags: map[string]string{
|
||||
"ip-range": "255.255.0.0/24",
|
||||
"gateway": "255.0.255.0/24",
|
||||
"gateway": "255.0.255.0", // FIXME(thaJeztah): this used to accept a CIDR ("255.0.255.0/24")
|
||||
"subnet": "10.1.2.0.30.50",
|
||||
},
|
||||
expectedError: "invalid CIDR address: 10.1.2.0.30.50",
|
||||
expectedError: `netip.ParsePrefix("10.1.2.0.30.50"): no '/'`,
|
||||
},
|
||||
{
|
||||
args: []string{"toto"},
|
||||
flags: map[string]string{
|
||||
"ip-range": "255.255.0.0.30/24",
|
||||
"gateway": "255.0.255.0/24",
|
||||
"subnet": "255.0.0.0/24",
|
||||
},
|
||||
expectedError: "invalid CIDR address: 255.255.0.0.30/24",
|
||||
},
|
||||
{
|
||||
args: []string{"toto"},
|
||||
flags: map[string]string{
|
||||
"gateway": "255.0.0.0/24",
|
||||
"gateway": "255.0.0.0", // FIXME(thaJeztah): this used to accept a CIDR ("255.0.0.0/24")
|
||||
},
|
||||
expectedError: "every ip-range or gateway must have a corresponding subnet",
|
||||
},
|
||||
@ -67,7 +60,7 @@ func TestNetworkCreateErrors(t *testing.T) {
|
||||
args: []string{"toto"},
|
||||
flags: map[string]string{
|
||||
"ip-range": "255.0.0.0/24",
|
||||
"gateway": "255.0.0.0/24",
|
||||
"gateway": "255.0.0.0", // FIXME(thaJeztah): this used to accept a CIDR ("255.0.0.0/24")
|
||||
},
|
||||
expectedError: "every ip-range or gateway must have a corresponding subnet",
|
||||
},
|
||||
@ -75,7 +68,7 @@ func TestNetworkCreateErrors(t *testing.T) {
|
||||
args: []string{"toto"},
|
||||
flags: map[string]string{
|
||||
"ip-range": "255.255.0.0/24",
|
||||
"gateway": "255.0.255.0/24",
|
||||
"gateway": "255.0.255.0", // FIXME(thaJeztah): this used to accept a CIDR ("255.0.0.0/24")
|
||||
"subnet": "10.1.2.0/23,10.1.3.248/30",
|
||||
},
|
||||
expectedError: "multiple overlapping subnet configuration is not supported",
|
||||
@ -83,17 +76,17 @@ func TestNetworkCreateErrors(t *testing.T) {
|
||||
{
|
||||
args: []string{"toto"},
|
||||
flags: map[string]string{
|
||||
"ip-range": "192.168.1.0/24,192.168.1.200/24",
|
||||
"ip-range": "192.168.1.0/25,192.168.1.128/25",
|
||||
"gateway": "192.168.1.1,192.168.1.4",
|
||||
"subnet": "192.168.2.0/24,192.168.1.250/24",
|
||||
},
|
||||
expectedError: "cannot configure multiple ranges (192.168.1.200/24, 192.168.1.0/24) on the same subnet (192.168.1.250/24)",
|
||||
expectedError: "cannot configure multiple ranges (192.168.1.128/25, 192.168.1.0/25) on the same subnet (192.168.1.250/24)",
|
||||
},
|
||||
{
|
||||
args: []string{"toto"},
|
||||
flags: map[string]string{
|
||||
"ip-range": "255.255.200.0/24,255.255.120.0/24",
|
||||
"gateway": "255.0.255.0/24",
|
||||
"gateway": "255.0.255.0", // FIXME(thaJeztah): this used to accept a CIDR ("255.0.0.0/24")
|
||||
"subnet": "255.255.255.0/24,255.255.0.255/24",
|
||||
},
|
||||
expectedError: "no matching subnet for range 255.255.200.0/24",
|
||||
@ -119,21 +112,12 @@ func TestNetworkCreateErrors(t *testing.T) {
|
||||
{
|
||||
args: []string{"toto"},
|
||||
flags: map[string]string{
|
||||
"gateway": "255.255.0.0/24",
|
||||
"gateway": "255.255.0.0", // FIXME(thaJeztah): this used to accept a CIDR ("255.255.0.0/24")
|
||||
"subnet": "255.255.0.0/24",
|
||||
"aux-address": "255.255.0.30/24",
|
||||
"aux-address": "router=255.255.1.30", // outside 255.255.0.0/24 // FIXME(thaJeztah): this used to accept a CIDR ("255.255.0.30/24")
|
||||
},
|
||||
expectedError: "no matching subnet for aux-address",
|
||||
},
|
||||
{
|
||||
args: []string{"toto"},
|
||||
flags: map[string]string{
|
||||
"ip-range": "192.168.83.1-192.168.83.254",
|
||||
"gateway": "192.168.80.1",
|
||||
"subnet": "192.168.80.0/20",
|
||||
},
|
||||
expectedError: "invalid CIDR address: 192.168.83.1-192.168.83.254",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -175,16 +159,16 @@ 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"), // FIXME(thaJeztah): this used to accept a CIDR ("192.168.4.1/24")
|
||||
AuxAddress: map[string]netip.Addr{},
|
||||
},
|
||||
}
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
networkCreateFunc: func(ctx context.Context, name string, options client.NetworkCreateOptions) (network.CreateResponse, error) {
|
||||
assert.Check(t, is.Equal(expectedDriver, options.Driver), "not expected driver error")
|
||||
assert.Check(t, is.DeepEqual(expectedOpts, options.IPAM.Config), "not expected driver error")
|
||||
assert.Check(t, is.DeepEqual(expectedOpts, options.IPAM.Config, cmpopts.EquateComparable(netip.Addr{}, netip.Prefix{})), "not expected driver error")
|
||||
return network.CreateResponse{
|
||||
ID: name,
|
||||
}, nil
|
||||
@ -196,7 +180,7 @@ func TestNetworkCreateWithFlags(t *testing.T) {
|
||||
cmd.SetArgs(args)
|
||||
assert.Check(t, cmd.Flags().Set("driver", "foo"))
|
||||
assert.Check(t, cmd.Flags().Set("ip-range", "192.168.4.0/24"))
|
||||
assert.Check(t, cmd.Flags().Set("gateway", "192.168.4.1/24"))
|
||||
assert.Check(t, cmd.Flags().Set("gateway", "192.168.4.1")) // FIXME(thaJeztah): this used to accept a CIDR ("192.168.4.1/24")
|
||||
assert.Check(t, cmd.Flags().Set("subnet", "192.168.4.0/24"))
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Check(t, is.Equal("banana", strings.TrimSpace(cli.OutBuffer().String())))
|
||||
|
||||
@ -8,8 +8,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
@ -57,9 +55,9 @@ func TestNetworkList(t *testing.T) {
|
||||
golden: "network-list.golden",
|
||||
networkListFunc: func(ctx context.Context, options client.NetworkListOptions) ([]network.Summary, error) {
|
||||
expectedOpts := client.NetworkListOptions{
|
||||
Filters: filters.NewArgs(filters.Arg("image.name", "ubuntu")),
|
||||
Filters: make(client.Filters).Add("image.name", "ubuntu"),
|
||||
}
|
||||
assert.Check(t, is.DeepEqual(expectedOpts, options, cmp.AllowUnexported(filters.Args{})))
|
||||
assert.Check(t, is.DeepEqual(expectedOpts, options))
|
||||
|
||||
return []network.Summary{*builders.NetworkResource(builders.NetworkResourceID("123454321"),
|
||||
builders.NetworkResourceName("network_1"),
|
||||
|
||||
@ -7,8 +7,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/client"
|
||||
)
|
||||
|
||||
func TestNetworkPrunePromptTermination(t *testing.T) {
|
||||
@ -16,7 +16,7 @@ func TestNetworkPrunePromptTermination(t *testing.T) {
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
networkPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) {
|
||||
networkPruneFunc: func(ctx context.Context, pruneFilters client.Filters) (network.PruneReport, error) {
|
||||
return network.PruneReport{}, errors.New("fakeClient networkPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/plugin"
|
||||
"github.com/moby/moby/api/types/system"
|
||||
"github.com/moby/moby/client"
|
||||
@ -17,7 +16,7 @@ type fakeClient struct {
|
||||
pluginEnableFunc func(name string, options client.PluginEnableOptions) error
|
||||
pluginRemoveFunc func(name string, options client.PluginRemoveOptions) error
|
||||
pluginInstallFunc func(name string, options client.PluginInstallOptions) (io.ReadCloser, error)
|
||||
pluginListFunc func(filter filters.Args) (plugin.ListResponse, error)
|
||||
pluginListFunc func(filter client.Filters) (plugin.ListResponse, error)
|
||||
pluginInspectFunc func(name string) (*plugin.Plugin, []byte, error)
|
||||
pluginUpgradeFunc func(name string, options client.PluginInstallOptions) (io.ReadCloser, error)
|
||||
}
|
||||
@ -57,7 +56,7 @@ func (c *fakeClient) PluginInstall(_ context.Context, name string, installOption
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) PluginList(_ context.Context, filter filters.Args) (plugin.ListResponse, error) {
|
||||
func (c *fakeClient) PluginList(_ context.Context, filter client.Filters) (plugin.ListResponse, error) {
|
||||
if c.pluginListFunc != nil {
|
||||
return c.pluginListFunc(filter)
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ package plugin
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -22,7 +22,7 @@ const (
|
||||
// - "disabled": all disabled plugins
|
||||
func completeNames(dockerCLI completion.APIClientProvider, state pluginState) cobra.CompletionFunc {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
f := filters.NewArgs()
|
||||
f := make(client.Filters)
|
||||
switch state {
|
||||
case stateEnabled:
|
||||
f.Add("enabled", "true")
|
||||
|
||||
@ -6,11 +6,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/plugin"
|
||||
"github.com/moby/moby/client"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
@ -20,7 +19,7 @@ func TestListErrors(t *testing.T) {
|
||||
args []string
|
||||
flags map[string]string
|
||||
expectedError string
|
||||
listFunc func(filter filters.Args) (plugin.ListResponse, error)
|
||||
listFunc func(filter client.Filters) (plugin.ListResponse, error)
|
||||
}{
|
||||
{
|
||||
description: "too many arguments",
|
||||
@ -31,7 +30,7 @@ func TestListErrors(t *testing.T) {
|
||||
description: "error listing plugins",
|
||||
args: []string{},
|
||||
expectedError: "error listing plugins",
|
||||
listFunc: func(filter filters.Args) (plugin.ListResponse, error) {
|
||||
listFunc: func(filter client.Filters) (plugin.ListResponse, error) {
|
||||
return plugin.ListResponse{}, errors.New("error listing plugins")
|
||||
},
|
||||
},
|
||||
@ -61,7 +60,7 @@ func TestListErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
singlePluginListFunc := func(_ filters.Args) (plugin.ListResponse, error) {
|
||||
singlePluginListFunc := func(_ client.Filters) (plugin.ListResponse, error) {
|
||||
return plugin.ListResponse{
|
||||
{
|
||||
ID: "id-foo",
|
||||
@ -79,7 +78,7 @@ func TestList(t *testing.T) {
|
||||
args []string
|
||||
flags map[string]string
|
||||
golden string
|
||||
listFunc func(filter filters.Args) (plugin.ListResponse, error)
|
||||
listFunc func(filter client.Filters) (plugin.ListResponse, error)
|
||||
}{
|
||||
{
|
||||
description: "list with no additional flags",
|
||||
@ -94,8 +93,8 @@ func TestList(t *testing.T) {
|
||||
"filter": "foo=bar",
|
||||
},
|
||||
golden: "plugin-list-without-format.golden",
|
||||
listFunc: func(filter filters.Args) (plugin.ListResponse, error) {
|
||||
assert.Check(t, is.Equal("bar", filter.Get("foo")[0]))
|
||||
listFunc: func(filter client.Filters) (plugin.ListResponse, error) {
|
||||
assert.Check(t, filter["foo"]["bar"])
|
||||
return singlePluginListFunc(filter)
|
||||
},
|
||||
},
|
||||
@ -116,7 +115,7 @@ func TestList(t *testing.T) {
|
||||
"format": "{{ .ID }}",
|
||||
},
|
||||
golden: "plugin-list-with-no-trunc-option.golden",
|
||||
listFunc: func(_ filters.Args) (plugin.ListResponse, error) {
|
||||
listFunc: func(_ client.Filters) (plugin.ListResponse, error) {
|
||||
return plugin.ListResponse{
|
||||
{
|
||||
ID: "xyg4z2hiSLO5yTnBJfg4OYia9gKA6Qjd",
|
||||
@ -145,7 +144,7 @@ func TestList(t *testing.T) {
|
||||
"format": "{{ .Name }}",
|
||||
},
|
||||
golden: "plugin-list-sort.golden",
|
||||
listFunc: func(_ filters.Args) (plugin.ListResponse, error) {
|
||||
listFunc: func(_ client.Filters) (plugin.ListResponse, error) {
|
||||
return plugin.ListResponse{
|
||||
{
|
||||
ID: "id-1",
|
||||
|
||||
@ -57,7 +57,7 @@ func newSearchCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
func runSearch(ctx context.Context, dockerCli command.Cli, options searchOptions) error {
|
||||
if options.filter.Value().Contains("is-automated") {
|
||||
if _, ok := options.filter.Value()["is-automated"]; ok {
|
||||
_, _ = fmt.Fprintln(dockerCli.Err(), `WARNING: the "is-automated" filter is deprecated, and searching for "is-automated=true" will not yield any results in future.`)
|
||||
}
|
||||
encodedAuth, err := getAuth(dockerCli, options.term)
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
@ -135,8 +134,8 @@ func TestSecretListWithFormat(t *testing.T) {
|
||||
func TestSecretListWithFilter(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
secretListFunc: func(_ context.Context, options client.SecretListOptions) ([]swarm.Secret, error) {
|
||||
assert.Check(t, is.Equal("foo", options.Filters.Get("name")[0]), "foo")
|
||||
assert.Check(t, is.Equal("lbl1=Label-bar", options.Filters.Get("label")[0]))
|
||||
assert.Check(t, options.Filters["name"]["foo"])
|
||||
assert.Check(t, options.Filters["label"]["lbl1=Label-bar"])
|
||||
return []swarm.Secret{
|
||||
*builders.Secret(builders.SecretID("ID-foo"),
|
||||
builders.SecretName("foo"),
|
||||
|
||||
@ -68,13 +68,9 @@ func TestSetConfigsWithCredSpecAndConfigs(t *testing.T) {
|
||||
|
||||
// set up a function to use as the list function
|
||||
var fakeClient fakeConfigAPIClientList = func(_ context.Context, opts client.ConfigListOptions) ([]swarm.Config, error) {
|
||||
f := opts.Filters
|
||||
|
||||
// we're expecting the filter to have names "foo" and "bar"
|
||||
names := f.Get("name")
|
||||
assert.Equal(t, len(names), 2)
|
||||
assert.Assert(t, is.Contains(names, "foo"))
|
||||
assert.Assert(t, is.Contains(names, "bar"))
|
||||
expected := make(client.Filters).Add("name", "foo", "bar")
|
||||
assert.Assert(t, is.DeepEqual(opts.Filters, expected))
|
||||
|
||||
return []swarm.Config{
|
||||
{
|
||||
@ -148,11 +144,8 @@ func TestSetConfigsOnlyCredSpec(t *testing.T) {
|
||||
|
||||
// set up a function to use as the list function
|
||||
var fakeClient fakeConfigAPIClientList = func(_ context.Context, opts client.ConfigListOptions) ([]swarm.Config, error) {
|
||||
f := opts.Filters
|
||||
|
||||
names := f.Get("name")
|
||||
assert.Equal(t, len(names), 1)
|
||||
assert.Assert(t, is.Contains(names, "foo"))
|
||||
expected := make(client.Filters).Add("name", "foo")
|
||||
assert.Assert(t, is.DeepEqual(opts.Filters, expected))
|
||||
|
||||
return []swarm.Config{
|
||||
{
|
||||
@ -199,11 +192,8 @@ func TestSetConfigsOnlyConfigs(t *testing.T) {
|
||||
}
|
||||
|
||||
var fakeClient fakeConfigAPIClientList = func(_ context.Context, opts client.ConfigListOptions) ([]swarm.Config, error) {
|
||||
f := opts.Filters
|
||||
|
||||
names := f.Get("name")
|
||||
assert.Equal(t, len(names), 1)
|
||||
assert.Assert(t, is.Contains(names, "bar"))
|
||||
expected := make(client.Filters).Add("name", "bar")
|
||||
assert.Assert(t, is.DeepEqual(opts.Filters, expected))
|
||||
|
||||
return []swarm.Config{
|
||||
{
|
||||
|
||||
@ -735,7 +735,7 @@ type portRange struct {
|
||||
pEnd uint32
|
||||
tStart uint32
|
||||
tEnd uint32
|
||||
protocol swarm.PortConfigProtocol
|
||||
protocol network.IPProtocol
|
||||
}
|
||||
|
||||
func (pr portRange) String() string {
|
||||
|
||||
@ -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.MustParsePrefix("10.255.0.4/16"), // FIXME(thaJeztah): this was testing with "10.255.0.4/16"
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/spf13/cobra"
|
||||
@ -109,7 +108,7 @@ func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) er
|
||||
// that don't have ServiceStatus set, and perform a lookup for those.
|
||||
func AppendServiceStatus(ctx context.Context, c client.APIClient, services []swarm.Service) ([]swarm.Service, error) {
|
||||
status := map[string]*swarm.ServiceStatus{}
|
||||
taskFilter := filters.NewArgs()
|
||||
taskFilter := make(client.Filters)
|
||||
for i, s := range services {
|
||||
// there is no need in this switch to check for job modes. jobs are not
|
||||
// supported until after ServiceStatus was introduced.
|
||||
|
||||
@ -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,18 @@ const (
|
||||
flagUlimitRemove = "ulimit-rm"
|
||||
flagOomScoreAdj = "oom-score-adj"
|
||||
)
|
||||
|
||||
func toNetipAddrSlice(ips []string) []netip.Addr {
|
||||
if len(ips) == 0 {
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
)
|
||||
@ -27,7 +26,7 @@ func ParseSecrets(ctx context.Context, apiClient client.SecretAPIClient, request
|
||||
secretRefs[secret.File.Name] = secretRef
|
||||
}
|
||||
|
||||
args := filters.NewArgs()
|
||||
args := make(client.Filters)
|
||||
for _, s := range secretRefs {
|
||||
args.Add("name", s.SecretName)
|
||||
}
|
||||
@ -104,7 +103,7 @@ func ParseConfigs(ctx context.Context, apiClient client.ConfigAPIClient, request
|
||||
configRefs[config.File.Name] = configRef
|
||||
}
|
||||
|
||||
args := filters.NewArgs()
|
||||
args := make(client.Filters)
|
||||
for _, s := range configRefs {
|
||||
args.Add("name", s.ConfigName)
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/moby/moby/api/pkg/progress"
|
||||
"github.com/moby/moby/api/pkg/streamformatter"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
)
|
||||
@ -142,10 +141,9 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
||||
return nil
|
||||
}
|
||||
|
||||
tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{Filters: filters.NewArgs(
|
||||
filters.KeyValuePair{Key: "service", Value: service.ID},
|
||||
filters.KeyValuePair{Key: "_up-to-date", Value: "true"},
|
||||
)})
|
||||
tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
|
||||
Filters: make(client.Filters).Add("service", service.ID).Add("_up-to-date", "true"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/node"
|
||||
"github.com/docker/cli/cli/command/task"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -81,11 +80,11 @@ func runPS(ctx context.Context, dockerCli command.Cli, options psOptions) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func createFilter(ctx context.Context, apiClient client.APIClient, options psOptions) (filters.Args, []string, error) {
|
||||
func createFilter(ctx context.Context, apiClient client.APIClient, options psOptions) (client.Filters, []string, error) {
|
||||
filter := options.filter.Value()
|
||||
|
||||
serviceIDFilter := filters.NewArgs()
|
||||
serviceNameFilter := filters.NewArgs()
|
||||
serviceIDFilter := make(client.Filters)
|
||||
serviceNameFilter := make(client.Filters)
|
||||
for _, service := range options.services {
|
||||
serviceIDFilter.Add("id", service)
|
||||
serviceNameFilter.Add("name", service)
|
||||
@ -139,15 +138,15 @@ loop:
|
||||
return filter, notfound, err
|
||||
}
|
||||
|
||||
func updateNodeFilter(ctx context.Context, apiClient client.APIClient, filter filters.Args) error {
|
||||
if filter.Contains("node") {
|
||||
nodeFilters := filter.Get("node")
|
||||
for _, nodeFilter := range nodeFilters {
|
||||
func updateNodeFilter(ctx context.Context, apiClient client.APIClient, filter client.Filters) error {
|
||||
if nodeFilters, ok := filter["node"]; ok {
|
||||
for nodeFilter := range nodeFilters {
|
||||
nodeReference, err := node.Reference(ctx, apiClient, nodeFilter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter.Del("node", nodeFilter)
|
||||
// TODO(thaJeztah): add utility to remove?
|
||||
delete(filter["node"], nodeFilter)
|
||||
filter.Add("node", nodeReference)
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/api/types/system"
|
||||
"github.com/moby/moby/client"
|
||||
@ -38,13 +36,8 @@ func TestCreateFilter(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(notfound, []string{"no such service: notfound"}))
|
||||
|
||||
expected := filters.NewArgs(
|
||||
filters.Arg("service", "idmatch"),
|
||||
filters.Arg("service", "idprefixmatch"),
|
||||
filters.Arg("service", "cccccccc"),
|
||||
filters.Arg("node", "somenode"),
|
||||
)
|
||||
assert.DeepEqual(t, expected, actual, cmpFilters)
|
||||
expected := make(client.Filters).Add("service", "idmatch").Add("service", "idprefixmatch").Add("service", "cccccccc").Add("node", "somenode")
|
||||
assert.DeepEqual(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestCreateFilterWithAmbiguousIDPrefixError(t *testing.T) {
|
||||
@ -114,11 +107,7 @@ func TestRunPSQuiet(t *testing.T) {
|
||||
|
||||
func TestUpdateNodeFilter(t *testing.T) {
|
||||
selfNodeID := "foofoo"
|
||||
filter := filters.NewArgs(
|
||||
filters.Arg("node", "one"),
|
||||
filters.Arg("node", "two"),
|
||||
filters.Arg("node", "self"),
|
||||
)
|
||||
filter := make(client.Filters).Add("node", "one", "two", "self")
|
||||
|
||||
apiClient := &fakeClient{
|
||||
infoFunc: func(_ context.Context) (system.Info, error) {
|
||||
@ -129,12 +118,6 @@ func TestUpdateNodeFilter(t *testing.T) {
|
||||
err := updateNodeFilter(context.Background(), apiClient, filter)
|
||||
assert.NilError(t, err)
|
||||
|
||||
expected := filters.NewArgs(
|
||||
filters.Arg("node", "one"),
|
||||
filters.Arg("node", "two"),
|
||||
filters.Arg("node", selfNodeID),
|
||||
)
|
||||
assert.DeepEqual(t, expected, filter, cmpFilters)
|
||||
expected := make(client.Filters).Add("node", "one", "two", selfNodeID)
|
||||
assert.DeepEqual(t, expected, filter)
|
||||
}
|
||||
|
||||
var cmpFilters = cmp.AllowUnexported(filters.Args{})
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.23
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@ -15,6 +20,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 +1005,9 @@ func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeDuplicates(entries []string) []string {
|
||||
hit := map[string]bool{}
|
||||
newEntries := []string{}
|
||||
func removeDuplicates[T comparable](entries []T) []T {
|
||||
hit := map[T]bool{}
|
||||
newEntries := []T{}
|
||||
for _, v := range entries {
|
||||
if !hit[v] {
|
||||
newEntries = append(newEntries, v)
|
||||
@ -1017,24 +1023,34 @@ 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)
|
||||
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,7 +1065,7 @@ 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 {
|
||||
if _, exists := toRemove[option]; !exists {
|
||||
@ -1120,16 +1136,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 {
|
||||
|
||||
@ -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"},
|
||||
}
|
||||
@ -221,9 +222,9 @@ func TestUpdateDNSConfig(t *testing.T) {
|
||||
updateDNSConfig(flags, &config)
|
||||
|
||||
assert.Assert(t, is.Len(config.Nameservers, 3))
|
||||
assert.Check(t, is.Equal("1.1.1.1", config.Nameservers[0]))
|
||||
assert.Check(t, is.Equal("2001:db8:abc8::1", config.Nameservers[1]))
|
||||
assert.Check(t, is.Equal("5.5.5.5", config.Nameservers[2]))
|
||||
assert.Check(t, is.Equal(netip.MustParseAddr("1.1.1.1"), config.Nameservers[0]))
|
||||
assert.Check(t, is.Equal(netip.MustParseAddr("5.5.5.5"), config.Nameservers[1]))
|
||||
assert.Check(t, is.Equal(netip.MustParseAddr("2001:db8:abc8::1"), config.Nameservers[2]))
|
||||
|
||||
assert.Assert(t, is.Len(config.Search, 2))
|
||||
assert.Check(t, is.Equal("example.com", config.Search[0]))
|
||||
@ -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,
|
||||
},
|
||||
}
|
||||
@ -1219,12 +1220,12 @@ func TestUpdateGetUpdatedConfigs(t *testing.T) {
|
||||
// fakeConfigAPIClientList is actually defined in create_test.go,
|
||||
// but we'll use it here as well
|
||||
var fakeClient fakeConfigAPIClientList = func(_ context.Context, opts client.ConfigListOptions) ([]swarm.Config, error) {
|
||||
names := opts.Filters.Get("name")
|
||||
names := opts.Filters["name"]
|
||||
assert.Equal(t, len(names), len(tc.lookupConfigs))
|
||||
|
||||
configs := []swarm.Config{}
|
||||
for _, lookup := range tc.lookupConfigs {
|
||||
assert.Assert(t, is.Contains(names, lookup))
|
||||
assert.Assert(t, names[lookup])
|
||||
cfg, ok := cannedConfigs[lookup]
|
||||
assert.Assert(t, ok)
|
||||
configs = append(configs, *cfg)
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
"github.com/moby/moby/api/types"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
@ -226,8 +225,13 @@ func configFromName(name string) swarm.Config {
|
||||
}
|
||||
}
|
||||
|
||||
func namespaceFromFilters(fltrs filters.Args) string {
|
||||
label := fltrs.Get("label")[0]
|
||||
func namespaceFromFilters(fltrs client.Filters) string {
|
||||
// FIXME(thaJeztah): more elegant way for this? Should we have a utility for this?
|
||||
var label string
|
||||
for fltr := range fltrs["label"] {
|
||||
label = fltr
|
||||
break
|
||||
}
|
||||
return strings.TrimPrefix(label, convert.LabelNamespace+"=")
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
@ -38,22 +37,18 @@ func quotesOrWhitespace(r rune) bool {
|
||||
return unicode.IsSpace(r) || r == '"' || r == '\''
|
||||
}
|
||||
|
||||
func getStackFilter(namespace string) filters.Args {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("label", convert.LabelNamespace+"="+namespace)
|
||||
return filter
|
||||
func getStackFilter(namespace string) client.Filters {
|
||||
return make(client.Filters).Add("label", convert.LabelNamespace+"="+namespace)
|
||||
}
|
||||
|
||||
func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) filters.Args {
|
||||
func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) client.Filters {
|
||||
filter := opt.Value()
|
||||
filter.Add("label", convert.LabelNamespace+"="+namespace)
|
||||
return filter
|
||||
}
|
||||
|
||||
func getAllStacksFilter() filters.Args {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("label", convert.LabelNamespace)
|
||||
return filter
|
||||
func getAllStacksFilter() client.Filters {
|
||||
return make(client.Filters).Add("label", convert.LabelNamespace)
|
||||
}
|
||||
|
||||
func getStackServices(ctx context.Context, apiclient client.APIClient, namespace string) ([]swarm.Service, error) {
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
@ -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): change opts.defaultAddrPools a []netip.Prefix; see https://github.com/docker/cli/pull/6545#discussion_r2420361609
|
||||
defaultAddrPool := make([]netip.Prefix, 0, len(opts.defaultAddrPools))
|
||||
for _, p := range opts.defaultAddrPools {
|
||||
defaultAddrPool = append(defaultAddrPool, p.String())
|
||||
if 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(),
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"github.com/moby/moby/api/types"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
@ -20,12 +19,12 @@ type fakeClient struct {
|
||||
|
||||
version string
|
||||
containerListFunc func(context.Context, client.ContainerListOptions) ([]container.Summary, error)
|
||||
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
|
||||
containerPruneFunc func(ctx context.Context, pruneFilters client.Filters) (container.PruneReport, error)
|
||||
eventsFn func(context.Context, client.EventsListOptions) (<-chan events.Message, <-chan error)
|
||||
imageListFunc func(ctx context.Context, options client.ImageListOptions) ([]image.Summary, error)
|
||||
infoFunc func(ctx context.Context) (system.Info, error)
|
||||
networkListFunc func(ctx context.Context, options client.NetworkListOptions) ([]network.Summary, error)
|
||||
networkPruneFunc func(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error)
|
||||
networkPruneFunc func(ctx context.Context, pruneFilter client.Filters) (network.PruneReport, error)
|
||||
nodeListFunc func(ctx context.Context, options client.NodeListOptions) ([]swarm.Node, error)
|
||||
serverVersion func(ctx context.Context) (types.Version, error)
|
||||
volumeListFunc func(ctx context.Context, options client.VolumeListOptions) (volume.ListResponse, error)
|
||||
@ -42,7 +41,7 @@ func (cli *fakeClient) ContainerList(ctx context.Context, options client.Contain
|
||||
return []container.Summary{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
|
||||
func (cli *fakeClient) ContainersPrune(ctx context.Context, pruneFilters client.Filters) (container.PruneReport, error) {
|
||||
if cli.containerPruneFunc != nil {
|
||||
return cli.containerPruneFunc(ctx, pruneFilters)
|
||||
}
|
||||
@ -74,7 +73,7 @@ func (cli *fakeClient) NetworkList(ctx context.Context, options client.NetworkLi
|
||||
return []network.Summary{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error) {
|
||||
func (cli *fakeClient) NetworksPrune(ctx context.Context, pruneFilter client.Filters) (network.PruneReport, error) {
|
||||
if cli.networkPruneFunc != nil {
|
||||
return cli.networkPruneFunc(ctx, pruneFilter)
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/idresolver"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/spf13/cobra"
|
||||
@ -240,7 +239,7 @@ func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []str
|
||||
// pluginNames contacts the API to get a list of plugin names.
|
||||
// In case of an error, an empty list is returned.
|
||||
func pluginNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
|
||||
list, err := dockerCLI.Client().PluginList(cmd.Context(), filters.Args{})
|
||||
list, err := dockerCLI.Client().PluginList(cmd.Context(), nil)
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.MustParsePrefix("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,
|
||||
},
|
||||
},
|
||||
|
||||
@ -161,11 +161,11 @@ func dryRun(ctx context.Context, dockerCli command.Cli, options pruneOptions) (s
|
||||
|
||||
var filters []string
|
||||
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
|
||||
if pruneFilters.Len() > 0 {
|
||||
if len(pruneFilters) > 0 {
|
||||
// TODO remove fixed list of filters, and print all filters instead,
|
||||
// because the list of filters that is supported by the engine may evolve over time.
|
||||
for _, name := range []string{"label", "label!", "until"} {
|
||||
for _, v := range pruneFilters.Get(name) {
|
||||
for v := range pruneFilters[name] {
|
||||
filters = append(filters, name+"="+v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,8 +9,8 @@ import (
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
|
||||
@ -72,10 +72,10 @@ func TestSystemPrunePromptTermination(t *testing.T) {
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
|
||||
containerPruneFunc: func(ctx context.Context, pruneFilters client.Filters) (container.PruneReport, error) {
|
||||
return container.PruneReport{}, errors.New("fakeClient containerPruneFunc should not be called")
|
||||
},
|
||||
networkPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) {
|
||||
networkPruneFunc: func(ctx context.Context, pruneFilters client.Filters) (network.PruneReport, error) {
|
||||
return network.PruneReport{}, errors.New("fakeClient networkPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
|
||||
@ -11,17 +11,20 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/client"
|
||||
)
|
||||
|
||||
// PruneFilters merges prune filters specified in config.json with those specified
|
||||
// as command-line flags.
|
||||
// as command-line flags. It returns a deep copy of filters to prevent mutating
|
||||
// the original.
|
||||
//
|
||||
// CLI label filters have precedence over those specified in config.json. If a
|
||||
// label filter specified as flag conflicts with a label defined in config.json
|
||||
// (i.e., "label=some-value" conflicts with "label!=some-value", and vice versa),
|
||||
// then the filter defined in config.json is omitted.
|
||||
func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters.Args {
|
||||
func PruneFilters(dockerCLI config.Provider, filters client.Filters) client.Filters {
|
||||
pruneFilters := cloneFilters(filters)
|
||||
|
||||
cfg := dockerCLI.ConfigFile()
|
||||
if cfg == nil {
|
||||
return pruneFilters
|
||||
@ -37,13 +40,13 @@ func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters.
|
||||
switch k {
|
||||
case "label":
|
||||
// "label != some-value" conflicts with "label = some-value"
|
||||
if pruneFilters.ExactMatch("label!", v) {
|
||||
if pruneFilters["label!"][v] {
|
||||
continue
|
||||
}
|
||||
pruneFilters.Add(k, v)
|
||||
case "label!":
|
||||
// "label != some-value" conflicts with "label = some-value"
|
||||
if pruneFilters.ExactMatch("label", v) {
|
||||
if pruneFilters["label"][v] {
|
||||
continue
|
||||
}
|
||||
pruneFilters.Add(k, v)
|
||||
@ -55,6 +58,21 @@ func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters.
|
||||
return pruneFilters
|
||||
}
|
||||
|
||||
// cloneFilters returns a deep copy of f, creating a new filter if f is nil.
|
||||
//
|
||||
// TODO(thaJeztah): add this as a "Clone" method on client.Filters.
|
||||
func cloneFilters(f client.Filters) client.Filters {
|
||||
out := make(client.Filters, len(f))
|
||||
for term, values := range f {
|
||||
inner := make(map[string]bool, len(values))
|
||||
for v, ok := range values {
|
||||
inner[v] = ok
|
||||
}
|
||||
out[term] = inner
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ValidateOutputPath validates the output paths of the "docker cp" command.
|
||||
func ValidateOutputPath(path string) error {
|
||||
dir := filepath.Dir(filepath.Clean(path))
|
||||
|
||||
@ -3,7 +3,6 @@ package volume
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
"github.com/moby/moby/client"
|
||||
)
|
||||
@ -12,9 +11,9 @@ type fakeClient struct {
|
||||
client.Client
|
||||
volumeCreateFunc func(volume.CreateOptions) (volume.Volume, error)
|
||||
volumeInspectFunc func(volumeID string) (volume.Volume, error)
|
||||
volumeListFunc func(filter filters.Args) (volume.ListResponse, error)
|
||||
volumeListFunc func(filter client.Filters) (volume.ListResponse, error)
|
||||
volumeRemoveFunc func(volumeID string, force bool) error
|
||||
volumePruneFunc func(filter filters.Args) (volume.PruneReport, error)
|
||||
volumePruneFunc func(filter client.Filters) (volume.PruneReport, error)
|
||||
}
|
||||
|
||||
func (c *fakeClient) VolumeCreate(_ context.Context, options volume.CreateOptions) (volume.Volume, error) {
|
||||
@ -38,7 +37,7 @@ func (c *fakeClient) VolumeList(_ context.Context, options client.VolumeListOpti
|
||||
return volume.ListResponse{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) VolumesPrune(_ context.Context, filter filters.Args) (volume.PruneReport, error) {
|
||||
func (c *fakeClient) VolumesPrune(_ context.Context, filter client.Filters) (volume.PruneReport, error) {
|
||||
if c.volumePruneFunc != nil {
|
||||
return c.volumePruneFunc(filter)
|
||||
}
|
||||
|
||||
@ -8,8 +8,8 @@ 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/filters"
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
@ -18,7 +18,7 @@ func TestVolumeListErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
volumeListFunc func(filter filters.Args) (volume.ListResponse, error)
|
||||
volumeListFunc func(filter client.Filters) (volume.ListResponse, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -26,7 +26,7 @@ func TestVolumeListErrors(t *testing.T) {
|
||||
expectedError: "accepts no argument",
|
||||
},
|
||||
{
|
||||
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
|
||||
volumeListFunc: func(filter client.Filters) (volume.ListResponse, error) {
|
||||
return volume.ListResponse{}, errors.New("error listing volumes")
|
||||
},
|
||||
expectedError: "error listing volumes",
|
||||
@ -50,7 +50,7 @@ func TestVolumeListErrors(t *testing.T) {
|
||||
|
||||
func TestVolumeListWithoutFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
|
||||
volumeListFunc: func(filter client.Filters) (volume.ListResponse, error) {
|
||||
return volume.ListResponse{
|
||||
Volumes: []*volume.Volume{
|
||||
builders.Volume(),
|
||||
@ -69,7 +69,7 @@ func TestVolumeListWithoutFormat(t *testing.T) {
|
||||
|
||||
func TestVolumeListWithConfigFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
|
||||
volumeListFunc: func(filter client.Filters) (volume.ListResponse, error) {
|
||||
return volume.ListResponse{
|
||||
Volumes: []*volume.Volume{
|
||||
builders.Volume(),
|
||||
@ -91,7 +91,7 @@ func TestVolumeListWithConfigFormat(t *testing.T) {
|
||||
|
||||
func TestVolumeListWithFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
|
||||
volumeListFunc: func(filter client.Filters) (volume.ListResponse, error) {
|
||||
return volume.ListResponse{
|
||||
Volumes: []*volume.Volume{
|
||||
builders.Volume(),
|
||||
@ -111,7 +111,7 @@ func TestVolumeListWithFormat(t *testing.T) {
|
||||
|
||||
func TestVolumeListSortOrder(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
|
||||
volumeListFunc: func(filter client.Filters) (volume.ListResponse, error) {
|
||||
return volume.ListResponse{
|
||||
Volumes: []*volume.Volume{
|
||||
builders.Volume(builders.VolumeName("volume-2-foo")),
|
||||
@ -129,7 +129,7 @@ func TestVolumeListSortOrder(t *testing.T) {
|
||||
|
||||
func TestClusterVolumeList(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
|
||||
volumeListFunc: func(filter client.Filters) (volume.ListResponse, error) {
|
||||
return volume.ListResponse{
|
||||
Volumes: []*volume.Volume{
|
||||
{
|
||||
|
||||
@ -74,7 +74,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
|
||||
warning := unusedVolumesWarning
|
||||
if versions.GreaterThanOrEqualTo(dockerCli.CurrentVersion(), "1.42") {
|
||||
if options.all {
|
||||
if pruneFilters.Contains("all") {
|
||||
if _, ok := pruneFilters["all"]; ok {
|
||||
return 0, "", invalidParamErr{errors.New("conflicting options: cannot specify both --all and --filter all=1")}
|
||||
}
|
||||
pruneFilters.Add("all", "true")
|
||||
@ -124,7 +124,7 @@ func pruneFn(ctx context.Context, dockerCli command.Cli, options pruner.PruneOpt
|
||||
// TODO version this once "until" filter is supported for volumes
|
||||
// Ideally, this check wasn't done on the CLI because the list of
|
||||
// filters that is supported by the daemon may evolve over time.
|
||||
if options.Filter.Value().Contains("until") {
|
||||
if _, ok := options.Filter.Value()["until"]; ok {
|
||||
return 0, "", errors.New(`ERROR: The "until" filter is not supported with "--volumes"`)
|
||||
}
|
||||
if !options.Confirmed {
|
||||
|
||||
@ -11,8 +11,8 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
@ -24,7 +24,7 @@ func TestVolumePruneErrors(t *testing.T) {
|
||||
name string
|
||||
args []string
|
||||
flags map[string]string
|
||||
volumePruneFunc func(args filters.Args) (volume.PruneReport, error)
|
||||
volumePruneFunc func(args client.Filters) (volume.PruneReport, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -37,7 +37,7 @@ func TestVolumePruneErrors(t *testing.T) {
|
||||
flags: map[string]string{
|
||||
"force": "true",
|
||||
},
|
||||
volumePruneFunc: func(args filters.Args) (volume.PruneReport, error) {
|
||||
volumePruneFunc: func(args client.Filters) (volume.PruneReport, error) {
|
||||
return volume.PruneReport{}, errors.New("error pruning volumes")
|
||||
},
|
||||
expectedError: "error pruning volumes",
|
||||
@ -74,21 +74,21 @@ func TestVolumePruneSuccess(t *testing.T) {
|
||||
name string
|
||||
args []string
|
||||
input string
|
||||
volumePruneFunc func(args filters.Args) (volume.PruneReport, error)
|
||||
volumePruneFunc func(args client.Filters) (volume.PruneReport, error)
|
||||
}{
|
||||
{
|
||||
name: "all",
|
||||
args: []string{"--all"},
|
||||
input: "y",
|
||||
volumePruneFunc: func(pruneFilter filters.Args) (volume.PruneReport, error) {
|
||||
assert.Check(t, is.DeepEqual([]string{"true"}, pruneFilter.Get("all")))
|
||||
volumePruneFunc: func(pruneFilter client.Filters) (volume.PruneReport, error) {
|
||||
assert.Check(t, is.DeepEqual(pruneFilter["all"], map[string]bool{"true": true}))
|
||||
return volume.PruneReport{}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all-forced",
|
||||
args: []string{"--all", "--force"},
|
||||
volumePruneFunc: func(pruneFilter filters.Args) (volume.PruneReport, error) {
|
||||
volumePruneFunc: func(pruneFilter client.Filters) (volume.PruneReport, error) {
|
||||
return volume.PruneReport{}, nil
|
||||
},
|
||||
},
|
||||
@ -96,8 +96,8 @@ func TestVolumePruneSuccess(t *testing.T) {
|
||||
name: "label-filter",
|
||||
args: []string{"--filter", "label=foobar"},
|
||||
input: "y",
|
||||
volumePruneFunc: func(pruneFilter filters.Args) (volume.PruneReport, error) {
|
||||
assert.Check(t, is.DeepEqual([]string{"foobar"}, pruneFilter.Get("label")))
|
||||
volumePruneFunc: func(pruneFilter client.Filters) (volume.PruneReport, error) {
|
||||
assert.Check(t, is.DeepEqual(pruneFilter["label"], map[string]bool{"foobar": true}))
|
||||
return volume.PruneReport{}, nil
|
||||
},
|
||||
},
|
||||
@ -121,7 +121,7 @@ func TestVolumePruneSuccess(t *testing.T) {
|
||||
func TestVolumePruneForce(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
volumePruneFunc func(args filters.Args) (volume.PruneReport, error)
|
||||
volumePruneFunc func(args client.Filters) (volume.PruneReport, error)
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
@ -180,7 +180,7 @@ func TestVolumePrunePromptNo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func simplePruneFunc(filters.Args) (volume.PruneReport, error) {
|
||||
func simplePruneFunc(client.Filters) (volume.PruneReport, error) {
|
||||
return volume.PruneReport{
|
||||
VolumesDeleted: []string{
|
||||
"foo", "bar", "baz",
|
||||
@ -194,7 +194,7 @@ func TestVolumePrunePromptTerminate(t *testing.T) {
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumePruneFunc: func(filter filters.Args) (volume.PruneReport, error) {
|
||||
volumePruneFunc: func(filter client.Filters) (volume.PruneReport, error) {
|
||||
return volume.PruneReport{}, errors.New("fakeClient volumePruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@ -84,8 +85,9 @@ func Networks(namespace Namespace, networks networkMap, servicesNetworks map[str
|
||||
Driver: nw.Ipam.Driver,
|
||||
}
|
||||
for _, ipamConfig := range nw.Ipam.Config {
|
||||
sn, _ := netip.ParsePrefix(ipamConfig.Subnet)
|
||||
createOpts.IPAM.Config = append(createOpts.IPAM.Config, network.IPAMConfig{
|
||||
Subnet: ipamConfig.Subnet,
|
||||
Subnet: sn,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
@ -57,7 +59,7 @@ func TestNetworks(t *testing.T) {
|
||||
Driver: "driver",
|
||||
Config: []*composetypes.IPAMPool{
|
||||
{
|
||||
Subnet: "10.0.0.0",
|
||||
Subnet: "10.0.0.0/32",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -89,7 +91,7 @@ func TestNetworks(t *testing.T) {
|
||||
Driver: "driver",
|
||||
Config: []network.IPAMConfig{
|
||||
{
|
||||
Subnet: "10.0.0.0",
|
||||
Subnet: netip.MustParsePrefix("10.0.0.0/32"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -114,7 +116,7 @@ func TestNetworks(t *testing.T) {
|
||||
}
|
||||
|
||||
networks, externals := Networks(namespace, source, serviceNetworks)
|
||||
assert.DeepEqual(t, expected, networks)
|
||||
assert.DeepEqual(t, expected, networks, cmpopts.EquateComparable(netip.Addr{}, netip.Prefix{}))
|
||||
assert.DeepEqual(t, []string{"special"}, externals)
|
||||
}
|
||||
|
||||
|
||||
@ -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,30 @@ 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 {
|
||||
if len(ips) == 0 {
|
||||
return nil
|
||||
}
|
||||
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
|
||||
|
||||
|
||||
@ -3,12 +3,14 @@ package convert
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
@ -307,17 +309,17 @@ var (
|
||||
func TestConvertDNSConfigAll(t *testing.T) {
|
||||
dnsConfig := convertDNSConfig(nameservers, search)
|
||||
assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
|
||||
Nameservers: nameservers,
|
||||
Nameservers: toNetipAddrSlice(nameservers),
|
||||
Search: search,
|
||||
}, dnsConfig))
|
||||
}, dnsConfig, cmpopts.EquateComparable(netip.Addr{})))
|
||||
}
|
||||
|
||||
func TestConvertDNSConfigNameservers(t *testing.T) {
|
||||
dnsConfig := convertDNSConfig(nameservers, nil)
|
||||
assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
|
||||
Nameservers: nameservers,
|
||||
Nameservers: toNetipAddrSlice(nameservers),
|
||||
Search: nil,
|
||||
}, dnsConfig))
|
||||
}, dnsConfig, cmpopts.EquateComparable(netip.Addr{})))
|
||||
}
|
||||
|
||||
func TestConvertDNSConfigSearch(t *testing.T) {
|
||||
@ -325,7 +327,7 @@ func TestConvertDNSConfigSearch(t *testing.T) {
|
||||
assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
|
||||
Nameservers: nil,
|
||||
Search: search,
|
||||
}, dnsConfig))
|
||||
}, dnsConfig, cmpopts.EquateComparable(netip.Addr{})))
|
||||
}
|
||||
|
||||
func TestConvertCredentialSpec(t *testing.T) {
|
||||
@ -514,8 +516,8 @@ func TestConvertServiceSecrets(t *testing.T) {
|
||||
}
|
||||
apiClient := &fakeClient{
|
||||
secretListFunc: func(opts client.SecretListOptions) ([]swarm.Secret, error) {
|
||||
assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_secret"))
|
||||
assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_secret"))
|
||||
assert.Check(t, opts.Filters["name"]["foo_secret"])
|
||||
assert.Check(t, opts.Filters["name"]["bar_secret"])
|
||||
return []swarm.Secret{
|
||||
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo_secret"}}},
|
||||
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "bar_secret"}}},
|
||||
@ -572,9 +574,9 @@ func TestConvertServiceConfigs(t *testing.T) {
|
||||
}
|
||||
apiClient := &fakeClient{
|
||||
configListFunc: func(opts client.ConfigListOptions) ([]swarm.Config, error) {
|
||||
assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_config"))
|
||||
assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_config"))
|
||||
assert.Check(t, is.Contains(opts.Filters.Get("name"), "baz_config"))
|
||||
assert.Check(t, opts.Filters["name"]["foo_config"])
|
||||
assert.Check(t, opts.Filters["name"]["bar_config"])
|
||||
assert.Check(t, opts.Filters["name"]["baz_config"])
|
||||
return []swarm.Config{
|
||||
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "foo_config"}}},
|
||||
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "bar_config"}}},
|
||||
|
||||
@ -25,7 +25,7 @@ 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/network"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
@ -925,7 +925,7 @@ 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))
|
||||
for port := range ports {
|
||||
keys = append(keys, string(port))
|
||||
}
|
||||
@ -933,7 +933,11 @@ func toServicePortConfigs(value string) ([]any, error) {
|
||||
|
||||
for _, key := range keys {
|
||||
// Reuse ConvertPortToPortConfig so that it is consistent
|
||||
portConfig, err := swarmopts.ConvertPortToPortConfig(container.PortRangeProto(key), portBindings)
|
||||
port, err := network.ParsePort(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
portConfig, err := swarmopts.ConvertPortToPortConfig(port, portBindings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user