mirror of
https://github.com/docker/cli.git
synced 2026-09-27 01:40:31 -04:00
Merge pull request #7268 from thaJeztah/slice_and_dice
modernize code with slices and maps packages
This commit is contained in:
@@ -3,6 +3,7 @@ package manager
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@@ -45,7 +46,7 @@ func TestPluginStubCompletionRestoresOSArgs(t *testing.T) {
|
||||
t.Cleanup(func() { os.Args = savedArgs })
|
||||
|
||||
originalArgs := []string{"docker", "image", "ls"}
|
||||
os.Args = append([]string(nil), originalArgs...)
|
||||
os.Args = slices.Clone(originalArgs)
|
||||
|
||||
_, directive := cmd.ValidArgsFunction(cmd, []string{"--all"}, "alp")
|
||||
assert.Equal(t, directive, cobra.ShellCompDirectiveError)
|
||||
|
||||
@@ -3,9 +3,9 @@ package completion
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
@@ -177,9 +177,10 @@ func TestCompleteEnvVarNames(t *testing.T) {
|
||||
values, directives := EnvVarNames()(nil, nil, "")
|
||||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
||||
|
||||
sort.Strings(values)
|
||||
expected := []string{"ENV_A", "ENV_B"}
|
||||
assert.Check(t, is.DeepEqual(values, expected))
|
||||
assert.Check(t, is.DeepEqual(values, expected, cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
})))
|
||||
}
|
||||
|
||||
func TestCompleteFileNames(t *testing.T) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/system"
|
||||
"github.com/moby/moby/client"
|
||||
@@ -284,12 +284,12 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
|
||||
"ALL_PROXY=allProxy",
|
||||
"all_proxy=allProxy",
|
||||
}
|
||||
sort.Strings(expected)
|
||||
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
|
||||
sort.Strings(options.Config.Env)
|
||||
assert.DeepEqual(t, options.Config.Env, expected)
|
||||
assert.DeepEqual(t, options.Config.Env, expected, cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}))
|
||||
return client.ContainerCreateResult{}, nil
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@@ -87,8 +87,9 @@ func TestRestart(t *testing.T) {
|
||||
} else {
|
||||
assert.Check(t, is.Nil(err))
|
||||
}
|
||||
sort.Strings(restarted)
|
||||
assert.Check(t, is.DeepEqual(restarted, tc.restarted))
|
||||
assert.Check(t, is.DeepEqual(restarted, tc.restarted, cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
})))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
@@ -53,8 +53,10 @@ func TestRemoveForce(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
assert.Equal(t, cli.ErrBuffer().String(), "")
|
||||
sort.Strings(removed)
|
||||
assert.DeepEqual(t, removed, []string{"mycontainer", "nosuchcontainer"})
|
||||
expected := []string{"mycontainer", "nosuchcontainer"}
|
||||
assert.DeepEqual(t, removed, expected, cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@@ -88,8 +88,9 @@ func TestStop(t *testing.T) {
|
||||
} else {
|
||||
assert.Check(t, is.Nil(err))
|
||||
}
|
||||
sort.Strings(stopped)
|
||||
assert.Check(t, is.DeepEqual(stopped, tc.stopped))
|
||||
assert.Check(t, is.DeepEqual(stopped, tc.stopped, cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
})))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.26
|
||||
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -52,20 +55,20 @@ shared: {{.Shared}}
|
||||
}
|
||||
|
||||
func buildCacheSort(buildCache []build.CacheRecord) {
|
||||
sort.Slice(buildCache, func(i, j int) bool {
|
||||
lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt
|
||||
slices.SortFunc(buildCache, func(a, b build.CacheRecord) int {
|
||||
switch {
|
||||
case lui == nil && luj == nil:
|
||||
return strings.Compare(buildCache[i].ID, buildCache[j].ID) < 0
|
||||
case lui == nil:
|
||||
return true
|
||||
case luj == nil:
|
||||
return false
|
||||
case lui.Equal(*luj):
|
||||
return strings.Compare(buildCache[i].ID, buildCache[j].ID) < 0
|
||||
default:
|
||||
return lui.Before(*luj)
|
||||
case a.LastUsedAt == nil && b.LastUsedAt == nil:
|
||||
return strings.Compare(a.ID, b.ID)
|
||||
case a.LastUsedAt == nil:
|
||||
return -1
|
||||
case b.LastUsedAt == nil:
|
||||
return 1
|
||||
}
|
||||
|
||||
if c := a.LastUsedAt.Compare(*b.LastUsedAt); c != 0 {
|
||||
return c
|
||||
}
|
||||
return strings.Compare(a.ID, b.ID)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -295,7 +296,7 @@ func (c *ContainerContext) Labels() string {
|
||||
for k, v := range c.c.Labels {
|
||||
joinLabels = append(joinLabels, k+"="+v)
|
||||
}
|
||||
sort.Strings(joinLabels)
|
||||
slices.Sort(joinLabels)
|
||||
return strings.Join(joinLabels, ",")
|
||||
}
|
||||
|
||||
@@ -395,9 +396,7 @@ func DisplayablePorts(ports []container.PortSummary) string {
|
||||
var result []string
|
||||
var hostMappings []string
|
||||
var groupMapKeys []string
|
||||
sort.Slice(ports, func(i, j int) bool {
|
||||
return comparePorts(ports[i], ports[j])
|
||||
})
|
||||
slices.SortFunc(ports, comparePorts)
|
||||
|
||||
for _, port := range ports {
|
||||
current := port.PrivatePort
|
||||
@@ -452,18 +451,13 @@ func formGroup(key string, start, last uint16) string {
|
||||
return group + "/" + groupType
|
||||
}
|
||||
|
||||
func comparePorts(i, j container.PortSummary) bool {
|
||||
if i.PrivatePort != j.PrivatePort {
|
||||
return i.PrivatePort < j.PrivatePort
|
||||
}
|
||||
|
||||
if i.IP != j.IP {
|
||||
return i.IP.Less(j.IP)
|
||||
}
|
||||
|
||||
if i.PublicPort != j.PublicPort {
|
||||
return i.PublicPort < j.PublicPort
|
||||
}
|
||||
|
||||
return i.Type < j.Type
|
||||
// comparePorts compares ports by private port, IP address, public port,
|
||||
// and protocol, in that order.
|
||||
func comparePorts(a, b container.PortSummary) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.PrivatePort, b.PrivatePort),
|
||||
a.IP.Compare(b.IP),
|
||||
cmp.Compare(a.PublicPort, b.PublicPort),
|
||||
cmp.Compare(a.Type, b.Type),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.26
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
@@ -7,6 +10,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/sys/symlink"
|
||||
@@ -202,7 +206,7 @@ func (repo gitRepo) checkout(root string) (string, error) {
|
||||
}
|
||||
|
||||
func (repo gitRepo) gitWithinDir(dir string, args ...string) ([]byte, error) {
|
||||
args = append([]string{"-c", "protocol.file.allow=never"}, args...) // Block sneaky repositories from using repos from the filesystem as submodules.
|
||||
args = slices.Concat([]string{"-c", "protocol.file.allow=never"}, args) // Block sneaky repositories from using repos from the filesystem as submodules.
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = dir
|
||||
// Disable unsafe remote protocols.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
@@ -211,6 +211,6 @@ func (f *fakeBuild) filenames(t *testing.T) []string {
|
||||
for _, header := range h {
|
||||
names = append(names, header.Name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
slices.Sort(names)
|
||||
return names
|
||||
}
|
||||
|
||||
@@ -102,9 +102,11 @@ func TestRunPushRespectsNoColorForAuxNotes(t *testing.T) {
|
||||
SelectedManifest: ocispec.Descriptor{Digest: "sha256:2222222222222222222222222222222222222222222222222222222222222222"},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
line := append([]byte(`{"aux":`), aux...)
|
||||
line = append(line, '}', '\n')
|
||||
return fakeStreamResult{ReadCloser: io.NopCloser(bytes.NewReader(line))}, nil
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`{"aux":`)
|
||||
buf.Write(aux)
|
||||
buf.WriteString("}\n")
|
||||
return fakeStreamResult{ReadCloser: io.NopCloser(&buf)}, nil
|
||||
},
|
||||
})
|
||||
cli.Out().SetIsTerminal(true)
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.26
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -775,17 +780,16 @@ func (c *serviceContext) Ports() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
pr := portRange{}
|
||||
ports := []string{}
|
||||
|
||||
servicePorts := c.service.Endpoint.Ports
|
||||
sort.Slice(servicePorts, func(i, j int) bool {
|
||||
if servicePorts[i].Protocol == servicePorts[j].Protocol {
|
||||
return servicePorts[i].PublishedPort < servicePorts[j].PublishedPort
|
||||
}
|
||||
return servicePorts[i].Protocol < servicePorts[j].Protocol
|
||||
// Sort by protocol first, then by published port.
|
||||
slices.SortFunc(c.service.Endpoint.Ports, func(a, b swarm.PortConfig) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.Protocol, b.Protocol),
|
||||
cmp.Compare(a.PublishedPort, b.PublishedPort),
|
||||
)
|
||||
})
|
||||
|
||||
var pr portRange
|
||||
var ports []string
|
||||
for _, p := range c.service.Endpoint.Ports {
|
||||
if p.PublishMode == swarm.PortConfigPublishModeIngress {
|
||||
prIsRange := pr.tEnd != pr.tStart
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.26
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
@@ -6,7 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -314,7 +317,7 @@ func (lw *logWriter) Write(buf []byte) (int, error) {
|
||||
d = append(d, k+"="+details[k])
|
||||
}
|
||||
// then sort em
|
||||
sort.Strings(d)
|
||||
slices.Sort(d)
|
||||
// then join and append
|
||||
output = append(output, []byte(strings.Join(d, ","))...)
|
||||
output = append(output, ' ')
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -737,15 +737,15 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
|
||||
}
|
||||
|
||||
networks := convertNetworks(options.networks)
|
||||
for i, net := range networks {
|
||||
nwID, err := resolveNetworkID(ctx, apiClient, net.Target)
|
||||
for i := range networks {
|
||||
nwID, err := resolveNetworkID(ctx, apiClient, networks[i].Target)
|
||||
if err != nil {
|
||||
return service, err
|
||||
}
|
||||
networks[i].Target = nwID
|
||||
}
|
||||
sort.Slice(networks, func(i, j int) bool {
|
||||
return networks[i].Target < networks[j].Target
|
||||
slices.SortFunc(networks, func(a, b swarm.NetworkAttachmentConfig) int {
|
||||
return cmp.Compare(a.Target, b.Target)
|
||||
})
|
||||
|
||||
resources, err := options.resources.ToResourceRequirements(flags)
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -637,7 +637,7 @@ func updatePlacementConstraints(flags *pflag.FlagSet, placement *swarm.Placement
|
||||
}
|
||||
}
|
||||
// Sort so that result is predictable.
|
||||
sort.Strings(newConstraints)
|
||||
slices.Sort(newConstraints)
|
||||
|
||||
placement.Constraints = newConstraints
|
||||
}
|
||||
@@ -722,7 +722,7 @@ func updateSysCtls(flags *pflag.FlagSet, field *map[string]string) {
|
||||
}
|
||||
|
||||
func updateUlimits(flags *pflag.FlagSet, ulimits []*container.Ulimit) []*container.Ulimit {
|
||||
newUlimits := make(map[string]*container.Ulimit)
|
||||
newUlimits := make(map[string]*container.Ulimit, len(ulimits))
|
||||
|
||||
for _, ulimit := range ulimits {
|
||||
newUlimits[ulimit.Name] = ulimit
|
||||
@@ -739,17 +739,9 @@ func updateUlimits(flags *pflag.FlagSet, ulimits []*container.Ulimit) []*contain
|
||||
newUlimits[ulimit.Name] = ulimit
|
||||
}
|
||||
}
|
||||
if len(newUlimits) == 0 {
|
||||
return nil
|
||||
}
|
||||
limits := make([]*container.Ulimit, 0, len(newUlimits))
|
||||
for _, ulimit := range newUlimits {
|
||||
limits = append(limits, ulimit)
|
||||
}
|
||||
sort.SliceStable(limits, func(i, j int) bool {
|
||||
return limits[i].Name < limits[j].Name
|
||||
return slices.SortedFunc(maps.Values(newUlimits), func(a, b *container.Ulimit) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
return limits
|
||||
}
|
||||
|
||||
func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
|
||||
@@ -955,14 +947,12 @@ func updateMounts(flags *pflag.FlagSet, mounts *[]mount.Mount) error {
|
||||
newMounts = append(newMounts, mnt)
|
||||
}
|
||||
}
|
||||
sort.Slice(newMounts, func(i, j int) bool {
|
||||
a, b := newMounts[i], newMounts[j]
|
||||
|
||||
if a.Source == b.Source {
|
||||
return a.Target < b.Target
|
||||
}
|
||||
|
||||
return a.Source < b.Source
|
||||
// Sort mounts by source, then by target.
|
||||
slices.SortFunc(newMounts, func(a, b mount.Mount) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.Source, b.Source),
|
||||
cmp.Compare(a.Target, b.Target),
|
||||
)
|
||||
})
|
||||
*mounts = newMounts
|
||||
return nil
|
||||
@@ -982,7 +972,7 @@ func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
|
||||
}
|
||||
}
|
||||
// Sort so that result is predictable.
|
||||
sort.Strings(newGroups)
|
||||
slices.Sort(newGroups)
|
||||
|
||||
*groups = newGroups
|
||||
return nil
|
||||
@@ -1041,7 +1031,7 @@ func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error {
|
||||
}
|
||||
}
|
||||
// Sort so that result is predictable.
|
||||
sort.Strings(newConfig.Search)
|
||||
slices.Sort(newConfig.Search)
|
||||
|
||||
options := (*config).Options
|
||||
if flags.Changed(flagDNSOptionAdd) {
|
||||
@@ -1056,7 +1046,7 @@ func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error {
|
||||
}
|
||||
}
|
||||
// Sort so that result is predictable.
|
||||
sort.Strings(newConfig.Options)
|
||||
slices.Sort(newConfig.Options)
|
||||
|
||||
*config = newConfig
|
||||
return nil
|
||||
@@ -1109,12 +1099,8 @@ portLoop:
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the PortConfig to avoid unnecessary updates
|
||||
sort.Slice(newPorts, func(i, j int) bool {
|
||||
// We convert PortConfig into `port/protocol`, e.g., `80/tcp`
|
||||
// In updatePorts we already filter out with map so there is duplicate entries
|
||||
return portConfigToString(&newPorts[i]) < portConfigToString(&newPorts[j])
|
||||
})
|
||||
// Sort the PortConfig to avoid unnecessary updates.
|
||||
slices.SortFunc(newPorts, swarm.PortConfig.Compare)
|
||||
*portConfig = newPorts
|
||||
return nil
|
||||
}
|
||||
@@ -1350,8 +1336,8 @@ func updateNetworks(ctx context.Context, apiClient client.NetworkAPIClient, flag
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(newNetworks, func(i, j int) bool {
|
||||
return newNetworks[i].Target < newNetworks[j].Target
|
||||
slices.SortFunc(newNetworks, func(a, b swarm.NetworkAttachmentConfig) int {
|
||||
return cmp.Compare(a.Target, b.Target)
|
||||
})
|
||||
|
||||
spec.TaskTemplate.Networks = newNetworks
|
||||
@@ -1525,10 +1511,6 @@ func capsList(caps map[string]bool) []string {
|
||||
if caps[opts.AllCapabilities] {
|
||||
return []string{opts.AllCapabilities}
|
||||
}
|
||||
out := make([]string, 0, len(caps))
|
||||
for c := range caps {
|
||||
out = append(out, c)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
|
||||
return slices.Sorted(maps.Keys(caps))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -141,7 +141,7 @@ func TestUpdateEnvironment(t *testing.T) {
|
||||
updateEnvironment(flags, &envs)
|
||||
assert.Assert(t, is.Len(envs, 2))
|
||||
// Order has been removed in updateEnvironment (map)
|
||||
sort.Strings(envs)
|
||||
slices.Sort(envs)
|
||||
assert.Check(t, is.Equal("toadd=newenv", envs[0]))
|
||||
assert.Check(t, is.Equal("tokeep=value", envs[1]))
|
||||
}
|
||||
@@ -308,7 +308,7 @@ func TestUpdatePorts(t *testing.T) {
|
||||
assert.Assert(t, is.Len(portConfigs, 2))
|
||||
// Do a sort to have the order (might have changed by map)
|
||||
targetPorts := []int{int(portConfigs[0].TargetPort), int(portConfigs[1].TargetPort)}
|
||||
sort.Ints(targetPorts)
|
||||
slices.Sort(targetPorts)
|
||||
assert.Check(t, is.Equal(555, targetPorts[0]))
|
||||
assert.Check(t, is.Equal(1000, targetPorts[1]))
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
@@ -75,9 +75,9 @@ func getDictsFrom(configFiles []composetypes.ConfigFile) []map[string]any {
|
||||
func propertyWarnings(properties map[string]string) string {
|
||||
msgs := make([]string, 0, len(properties))
|
||||
for name, description := range properties {
|
||||
msgs = append(msgs, fmt.Sprintf("%s: %s", name, description))
|
||||
msgs = append(msgs, name+": "+description)
|
||||
}
|
||||
sort.Strings(msgs)
|
||||
slices.Sort(msgs)
|
||||
return strings.Join(msgs, "\n\n")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +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.26
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"slices"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
@@ -96,15 +100,11 @@ func runRemove(ctx context.Context, dockerCli command.Cli, opts removeOptions) e
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func sortServiceByName(services []swarm.Service) func(i, j int) bool {
|
||||
return func(i, j int) bool {
|
||||
return services[i].Spec.Name < services[j].Spec.Name
|
||||
}
|
||||
}
|
||||
|
||||
func removeServices(ctx context.Context, dockerCLI command.Cli, services []swarm.Service) bool {
|
||||
slices.SortFunc(services, func(a, b swarm.Service) int {
|
||||
return cmp.Compare(a.Spec.Name, b.Spec.Name)
|
||||
})
|
||||
var hasError bool
|
||||
sort.Slice(services, sortServiceByName(services))
|
||||
for _, service := range services {
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), "Removing service", service.Spec.Name)
|
||||
if _, err := dockerCLI.Client().ServiceRemove(ctx, service.ID, client.ServiceRemoveOptions{}); err != nil {
|
||||
|
||||
@@ -1,10 +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.26
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -133,18 +137,13 @@ func prettyPrintEvent(out io.Writer, event events.Message) error {
|
||||
_, _ = fmt.Fprintf(out, "%s %s %s", event.Type, event.Action, event.Actor.ID)
|
||||
|
||||
if len(event.Actor.Attributes) > 0 {
|
||||
keys := make([]string, 0, len(event.Actor.Attributes))
|
||||
for k := range event.Actor.Attributes {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
keys := slices.Sorted(maps.Keys(event.Actor.Attributes))
|
||||
attrs := make([]string, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
v := event.Actor.Attributes[k]
|
||||
attrs = append(attrs, k+"="+v)
|
||||
attrs = append(attrs, k+"="+event.Actor.Attributes[k])
|
||||
}
|
||||
_, _ = fmt.Fprintf(out, " (%s)", strings.Join(attrs, ", "))
|
||||
}
|
||||
_, _ = fmt.Fprint(out, "\n")
|
||||
_, _ = fmt.Fprintln(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
@@ -468,11 +468,11 @@ func printSwarmInfo(output io.Writer, info system.Info) {
|
||||
}
|
||||
fprintln(output, " Node Address:", info.Swarm.NodeAddr)
|
||||
if len(info.Swarm.RemoteManagers) > 0 {
|
||||
managers := []string{}
|
||||
managers := make([]string, 0, len(info.Swarm.RemoteManagers))
|
||||
for _, entry := range info.Swarm.RemoteManagers {
|
||||
managers = append(managers, entry.Addr)
|
||||
}
|
||||
sort.Strings(managers)
|
||||
slices.Sort(managers)
|
||||
fprintln(output, " Manager Addresses:")
|
||||
for _, entry := range managers {
|
||||
fprintf(output, " %s\n", entry)
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.26
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"runtime"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -252,10 +256,5 @@ func newVersionTemplate(templateFormat string) (*template.Template, error) {
|
||||
}
|
||||
|
||||
func getDetailsOrder(v system.ComponentVersion) []string {
|
||||
out := make([]string, 0, len(v.Details))
|
||||
for k := range v.Details {
|
||||
out = append(out, k)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
return slices.Sorted(maps.Keys(v.Details))
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
package volume
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
@@ -117,89 +118,13 @@ func hasClusterVolumeOptionSet(flags *pflag.FlagSet) bool {
|
||||
}
|
||||
|
||||
func runCreate(ctx context.Context, dockerCli command.Cli, options createOptions) error {
|
||||
volOpts := client.VolumeCreateOptions{
|
||||
Driver: options.driver,
|
||||
DriverOpts: options.driverOpts.GetAll(),
|
||||
Name: options.name,
|
||||
Labels: opts.ConvertKVStringsToMap(options.labels.GetSlice()),
|
||||
}
|
||||
if options.cluster {
|
||||
volOpts.ClusterVolumeSpec = &volume.ClusterVolumeSpec{
|
||||
Group: options.group,
|
||||
AccessMode: &volume.AccessMode{
|
||||
Scope: volume.Scope(options.scope),
|
||||
Sharing: volume.SharingMode(options.sharing),
|
||||
},
|
||||
Availability: volume.Availability(options.availability),
|
||||
}
|
||||
|
||||
switch options.accessType {
|
||||
case "mount":
|
||||
volOpts.ClusterVolumeSpec.AccessMode.MountVolume = &volume.TypeMount{}
|
||||
case "block":
|
||||
volOpts.ClusterVolumeSpec.AccessMode.BlockVolume = &volume.TypeBlock{}
|
||||
}
|
||||
|
||||
vcr := &volume.CapacityRange{}
|
||||
if r := options.requiredBytes.Value(); r >= 0 {
|
||||
vcr.RequiredBytes = r
|
||||
}
|
||||
|
||||
if l := options.limitBytes.Value(); l >= 0 {
|
||||
vcr.LimitBytes = l
|
||||
}
|
||||
volOpts.ClusterVolumeSpec.CapacityRange = vcr
|
||||
|
||||
for key, secret := range options.secrets.GetAll() {
|
||||
volOpts.ClusterVolumeSpec.Secrets = append(
|
||||
volOpts.ClusterVolumeSpec.Secrets,
|
||||
volume.Secret{
|
||||
Key: key,
|
||||
Secret: secret,
|
||||
},
|
||||
)
|
||||
}
|
||||
sort.SliceStable(volOpts.ClusterVolumeSpec.Secrets, func(i, j int) bool {
|
||||
return volOpts.ClusterVolumeSpec.Secrets[i].Key < volOpts.ClusterVolumeSpec.Secrets[j].Key
|
||||
})
|
||||
|
||||
// TODO(dperny): ignore if no topology specified
|
||||
topology := &volume.TopologyRequirement{}
|
||||
for _, top := range options.requisiteTopology.GetSlice() {
|
||||
// each topology takes the form segment=value,segment=value
|
||||
// comma-separated list of equal separated maps
|
||||
segments := map[string]string{}
|
||||
for segment := range strings.SplitSeq(top, ",") {
|
||||
// TODO(dperny): validate topology syntax
|
||||
k, v, _ := strings.Cut(segment, "=")
|
||||
segments[k] = v
|
||||
}
|
||||
topology.Requisite = append(
|
||||
topology.Requisite,
|
||||
volume.Topology{Segments: segments},
|
||||
)
|
||||
}
|
||||
|
||||
for _, top := range options.preferredTopology.GetSlice() {
|
||||
// each topology takes the form segment=value,segment=value
|
||||
// comma-separated list of equal separated maps
|
||||
segments := map[string]string{}
|
||||
for segment := range strings.SplitSeq(top, ",") {
|
||||
// TODO(dperny): validate topology syntax
|
||||
k, v, _ := strings.Cut(segment, "=")
|
||||
segments[k] = v
|
||||
}
|
||||
|
||||
topology.Preferred = append(
|
||||
topology.Preferred,
|
||||
volume.Topology{Segments: segments},
|
||||
)
|
||||
}
|
||||
|
||||
volOpts.ClusterVolumeSpec.AccessibilityRequirements = topology
|
||||
}
|
||||
|
||||
res, err := dockerCli.Client().VolumeCreate(ctx, volOpts)
|
||||
res, err := dockerCli.Client().VolumeCreate(ctx, client.VolumeCreateOptions{
|
||||
Driver: options.driver,
|
||||
DriverOpts: options.driverOpts.GetAll(),
|
||||
Name: options.name,
|
||||
Labels: opts.ConvertKVStringsToMap(options.labels.GetSlice()),
|
||||
ClusterVolumeSpec: clusterVolumeSpec(options),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -207,3 +132,54 @@ func runCreate(ctx context.Context, dockerCli command.Cli, options createOptions
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), res.Volume.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func clusterVolumeSpec(options createOptions) *volume.ClusterVolumeSpec {
|
||||
if !options.cluster {
|
||||
return nil
|
||||
}
|
||||
|
||||
var secrets []volume.Secret
|
||||
for key, secret := range options.secrets.GetAll() {
|
||||
secrets = append(secrets, volume.Secret{Key: key, Secret: secret})
|
||||
}
|
||||
slices.SortFunc(secrets, func(a, b volume.Secret) int {
|
||||
return cmp.Compare(a.Key, b.Key)
|
||||
})
|
||||
|
||||
accessMode := &volume.AccessMode{
|
||||
Scope: volume.Scope(options.scope),
|
||||
Sharing: volume.SharingMode(options.sharing),
|
||||
}
|
||||
switch options.accessType {
|
||||
case "mount":
|
||||
accessMode.MountVolume = &volume.TypeMount{}
|
||||
case "block":
|
||||
accessMode.BlockVolume = &volume.TypeBlock{}
|
||||
}
|
||||
|
||||
return &volume.ClusterVolumeSpec{
|
||||
Group: options.group,
|
||||
AccessMode: accessMode,
|
||||
AccessibilityRequirements: &volume.TopologyRequirement{
|
||||
Requisite: parseTopologies(options.requisiteTopology.GetSlice()),
|
||||
Preferred: parseTopologies(options.preferredTopology.GetSlice()),
|
||||
},
|
||||
CapacityRange: &volume.CapacityRange{
|
||||
RequiredBytes: max(options.requiredBytes.Value(), 0),
|
||||
LimitBytes: max(options.limitBytes.Value(), 0),
|
||||
},
|
||||
Secrets: secrets,
|
||||
Availability: volume.Availability(options.availability),
|
||||
}
|
||||
}
|
||||
|
||||
func parseTopologies(values []string) []volume.Topology {
|
||||
topologies := make([]volume.Topology, 0, len(values))
|
||||
for _, top := range values {
|
||||
// TODO(dperny): validate topology syntax
|
||||
topologies = append(topologies, volume.Topology{
|
||||
Segments: opts.ConvertKVStringsToMap(strings.Split(top, ",")),
|
||||
})
|
||||
}
|
||||
return topologies
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -230,10 +230,9 @@ func TestVolumeCreateClusterOpts(t *testing.T) {
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeCreateFunc: func(options client.VolumeCreateOptions) (client.VolumeCreateResult, error) {
|
||||
sort.SliceStable(options.ClusterVolumeSpec.Secrets, func(i, j int) bool {
|
||||
return options.ClusterVolumeSpec.Secrets[i].Key < options.ClusterVolumeSpec.Secrets[j].Key
|
||||
})
|
||||
assert.DeepEqual(t, options, expectedOptions)
|
||||
assert.Check(t, is.DeepEqual(options, expectedOptions, cmpopts.SortSlices(func(a, b volume.Secret) bool {
|
||||
return a.Key < b.Key
|
||||
})))
|
||||
return client.VolumeCreateResult{}, nil
|
||||
},
|
||||
})
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -235,8 +234,8 @@ func convertServiceNetworks(
|
||||
nets = append(nets, netAttachConfig)
|
||||
}
|
||||
|
||||
sort.Slice(nets, func(i, j int) bool {
|
||||
return nets[i].Target < nets[j].Target
|
||||
slices.SortFunc(nets, func(a, b swarm.NetworkAttachmentConfig) int {
|
||||
return cmp.Compare(a.Target, b.Target)
|
||||
})
|
||||
return nets, nil
|
||||
}
|
||||
@@ -276,8 +275,10 @@ func convertServiceSecrets(
|
||||
return nil, err
|
||||
}
|
||||
// sort to ensure idempotence (don't restart services just because the entries are in different order)
|
||||
sort.SliceStable(secrs, func(i, j int) bool { return secrs[i].SecretName < secrs[j].SecretName })
|
||||
return secrs, err
|
||||
slices.SortStableFunc(secrs, func(a, b *swarm.SecretReference) int {
|
||||
return cmp.Compare(a.SecretName, b.SecretName)
|
||||
})
|
||||
return secrs, nil
|
||||
}
|
||||
|
||||
// convertServiceConfigObjs takes an API client, a namespace, a ServiceConfig,
|
||||
@@ -352,8 +353,10 @@ func convertServiceConfigObjs(
|
||||
return nil, err
|
||||
}
|
||||
// sort to ensure idempotence (don't restart services just because the entries are in different order)
|
||||
sort.SliceStable(confs, func(i, j int) bool { return confs[i].ConfigName < confs[j].ConfigName })
|
||||
return confs, err
|
||||
slices.SortStableFunc(confs, func(a, b *swarm.ConfigReference) int {
|
||||
return cmp.Compare(a.ConfigName, b.ConfigName)
|
||||
})
|
||||
return confs, nil
|
||||
}
|
||||
|
||||
type swarmReferenceTarget struct {
|
||||
@@ -609,8 +612,7 @@ func convertEndpointSpec(endpointMode string, source []composetypes.ServicePortC
|
||||
// convertEnvironment converts key/value mappings to a slice, and sorts
|
||||
// the results.
|
||||
func convertEnvironment(source map[string]*string) []string {
|
||||
var output []string
|
||||
|
||||
output := make([]string, 0, len(source))
|
||||
for name, value := range source {
|
||||
switch value {
|
||||
case nil:
|
||||
@@ -619,7 +621,7 @@ func convertEnvironment(source map[string]*string) []string {
|
||||
output = append(output, name+"="+*value)
|
||||
}
|
||||
}
|
||||
sort.Strings(output)
|
||||
slices.Sort(output)
|
||||
return output
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -35,20 +36,16 @@ var interpolateTypeCastMapping = map[interp.Path]interp.Cast{
|
||||
servicePath("tty"): toBoolean,
|
||||
servicePath("volumes", interp.PathMatchList, "read_only"): toBoolean,
|
||||
servicePath("volumes", interp.PathMatchList, "volume", "nocopy"): toBoolean,
|
||||
iPath("networks", interp.PathMatchAll, "external"): toBoolean,
|
||||
iPath("networks", interp.PathMatchAll, "internal"): toBoolean,
|
||||
iPath("networks", interp.PathMatchAll, "attachable"): toBoolean,
|
||||
iPath("volumes", interp.PathMatchAll, "external"): toBoolean,
|
||||
iPath("secrets", interp.PathMatchAll, "external"): toBoolean,
|
||||
iPath("configs", interp.PathMatchAll, "external"): toBoolean,
|
||||
}
|
||||
|
||||
func iPath(parts ...string) interp.Path {
|
||||
return interp.NewPath(parts...)
|
||||
interp.NewPath("networks", interp.PathMatchAll, "external"): toBoolean,
|
||||
interp.NewPath("networks", interp.PathMatchAll, "internal"): toBoolean,
|
||||
interp.NewPath("networks", interp.PathMatchAll, "attachable"): toBoolean,
|
||||
interp.NewPath("volumes", interp.PathMatchAll, "external"): toBoolean,
|
||||
interp.NewPath("secrets", interp.PathMatchAll, "external"): toBoolean,
|
||||
interp.NewPath("configs", interp.PathMatchAll, "external"): toBoolean,
|
||||
}
|
||||
|
||||
func servicePath(parts ...string) interp.Path {
|
||||
return iPath(append([]string{"services", interp.PathMatchAll}, parts...)...)
|
||||
return interp.NewPath(slices.Concat([]string{"services", interp.PathMatchAll}, parts)...)
|
||||
}
|
||||
|
||||
func toInt(value string) (any, error) {
|
||||
@@ -70,7 +67,3 @@ func toBoolean(value string) (any, error) {
|
||||
return nil, fmt.Errorf("invalid boolean: %s", value)
|
||||
}
|
||||
}
|
||||
|
||||
func interpolateConfig(configDict map[string]any, opts interp.Options) (map[string]any, error) {
|
||||
return interp.Interpolate(configDict, opts)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -111,7 +111,7 @@ func Load(configDetails types.ConfigDetails, opt ...func(*Options)) (*types.Conf
|
||||
}
|
||||
|
||||
if !options.SkipInterpolation {
|
||||
configDict, err = interpolateConfig(configDict, *options.Interpolate)
|
||||
configDict, err = interp.Interpolate(configDict, *options.Interpolate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -231,16 +231,7 @@ func GetUnsupportedProperties(configDicts ...map[string]any) []string {
|
||||
}
|
||||
}
|
||||
|
||||
return sortedKeys(unsupported)
|
||||
}
|
||||
|
||||
func sortedKeys(set map[string]bool) []string {
|
||||
keys := make([]string, 0, len(set))
|
||||
for key := range set {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
return slices.Sorted(maps.Keys(unsupported))
|
||||
}
|
||||
|
||||
// GetDeprecatedProperties returns the list of any deprecated properties that
|
||||
@@ -827,8 +818,7 @@ func transformListOrMapping(listOrMapping any, sep string, allowNil bool, allowS
|
||||
result := make([]string, 0, len(value))
|
||||
for _, entry := range value {
|
||||
for i, allowSep := range allowSeps {
|
||||
entry := fmt.Sprint(entry)
|
||||
k, v, ok := strings.Cut(entry, allowSep)
|
||||
k, v, ok := strings.Cut(fmt.Sprint(entry), allowSep)
|
||||
if ok {
|
||||
// Entry uses this allowed separator. Add it to the result, using
|
||||
// sep as a separator.
|
||||
@@ -885,7 +875,7 @@ var transformShellCommand TransformerFunc = func(value any) (any, error) {
|
||||
var transformHealthCheckTest TransformerFunc = func(data any) (any, error) {
|
||||
switch value := data.(type) {
|
||||
case string:
|
||||
return append([]string{"CMD-SHELL"}, value), nil
|
||||
return []string{"CMD-SHELL", value}, nil
|
||||
case []any:
|
||||
return value, nil
|
||||
default:
|
||||
@@ -925,16 +915,10 @@ 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 := make([]string, 0, len(ports))
|
||||
for port := range ports {
|
||||
keys = append(keys, string(port))
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var portConfigs []any
|
||||
for _, key := range keys {
|
||||
for _, key := range slices.Sorted(maps.Keys(ports)) {
|
||||
// Reuse ConvertPortToPortConfig so that it is consistent
|
||||
port, err := network.ParsePort(key)
|
||||
port, err := network.ParsePort(string(key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -975,13 +959,13 @@ func toString(value any, allowNil bool) any {
|
||||
}
|
||||
|
||||
func toStringList(value map[string]any, separator string, allowNil bool) []string {
|
||||
output := []string{}
|
||||
output := make([]string, 0, len(value))
|
||||
for key, value := range value {
|
||||
if value == nil && !allowNil {
|
||||
continue
|
||||
}
|
||||
output = append(output, fmt.Sprintf("%s%s%s", key, separator, value))
|
||||
output = append(output, fmt.Sprintf("%s%s%v", key, separator, value))
|
||||
}
|
||||
sort.Strings(output)
|
||||
slices.Sort(output)
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"bytes"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/compose/types"
|
||||
@@ -236,7 +235,9 @@ func TestLoad(t *testing.T) {
|
||||
actual, err := Load(buildConfigDetails(sampleDict, nil))
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(sampleConfig.Version, actual.Version))
|
||||
assert.Check(t, is.DeepEqual(serviceSort(sampleConfig.Services), serviceSort(actual.Services)))
|
||||
assert.Check(t, is.DeepEqual(sampleConfig.Services, actual.Services, cmpopts.SortSlices(func(a, b types.ServiceConfig) bool {
|
||||
return a.Name < b.Name
|
||||
})))
|
||||
assert.Check(t, is.DeepEqual(sampleConfig.Networks, actual.Networks))
|
||||
assert.Check(t, is.DeepEqual(sampleConfig.Volumes, actual.Volumes))
|
||||
}
|
||||
@@ -310,7 +311,9 @@ services:
|
||||
func TestParseAndLoad(t *testing.T) {
|
||||
actual, err := loadYAML(sampleYAML)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(serviceSort(sampleConfig.Services), serviceSort(actual.Services)))
|
||||
assert.Check(t, is.DeepEqual(sampleConfig.Services, actual.Services, cmpopts.SortSlices(func(a, b types.ServiceConfig) bool {
|
||||
return a.Name < b.Name
|
||||
})))
|
||||
assert.Check(t, is.DeepEqual(sampleConfig.Networks, actual.Networks))
|
||||
assert.Check(t, is.DeepEqual(sampleConfig.Volumes, actual.Volumes))
|
||||
}
|
||||
@@ -1191,13 +1194,6 @@ services:
|
||||
assert.ErrorContains(t, err, "services.tmpfs.volumes.0.tmpfs.size: must be an integer")
|
||||
}
|
||||
|
||||
func serviceSort(services []types.ServiceConfig) []types.ServiceConfig {
|
||||
sort.Slice(services, func(i, j int) bool {
|
||||
return services[i].Name < services[j].Name
|
||||
})
|
||||
return services
|
||||
}
|
||||
|
||||
func TestLoadAttachableNetwork(t *testing.T) {
|
||||
config, err := loadYAML(`
|
||||
version: "3.2"
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"github.com/docker/cli/cli/compose/types"
|
||||
@@ -152,7 +151,9 @@ func toServiceSecretConfigsSlice(dst reflect.Value, m map[any]any) error {
|
||||
for _, v := range m {
|
||||
s = append(s, v.(types.ServiceSecretConfig))
|
||||
}
|
||||
sort.Slice(s, func(i, j int) bool { return s[i].Source < s[j].Source })
|
||||
slices.SortFunc(s, func(a, b types.ServiceSecretConfig) int {
|
||||
return cmp.Compare(a.Source, b.Source)
|
||||
})
|
||||
dst.Set(reflect.ValueOf(s))
|
||||
return nil
|
||||
}
|
||||
@@ -162,7 +163,9 @@ func toSServiceConfigObjConfigsSlice(dst reflect.Value, m map[any]any) error {
|
||||
for _, v := range m {
|
||||
s = append(s, v.(types.ServiceConfigObjConfig))
|
||||
}
|
||||
sort.Slice(s, func(i, j int) bool { return s[i].Source < s[j].Source })
|
||||
slices.SortFunc(s, func(a, b types.ServiceConfigObjConfig) int {
|
||||
return cmp.Compare(a.Source, b.Source)
|
||||
})
|
||||
dst.Set(reflect.ValueOf(s))
|
||||
return nil
|
||||
}
|
||||
@@ -172,7 +175,9 @@ func toServicePortConfigsSlice(dst reflect.Value, m map[any]any) error {
|
||||
for _, v := range m {
|
||||
s = append(s, v.(types.ServicePortConfig))
|
||||
}
|
||||
sort.Slice(s, func(i, j int) bool { return s[i].Published < s[j].Published })
|
||||
slices.SortFunc(s, func(a, b types.ServicePortConfig) int {
|
||||
return cmp.Compare(a.Published, b.Published)
|
||||
})
|
||||
dst.Set(reflect.ValueOf(s))
|
||||
return nil
|
||||
}
|
||||
@@ -182,7 +187,9 @@ func toServiceVolumeConfigsSlice(dst reflect.Value, m map[any]any) error {
|
||||
for _, v := range m {
|
||||
s = append(s, v.(types.ServiceVolumeConfig))
|
||||
}
|
||||
sort.Slice(s, func(i, j int) bool { return s[i].Target < s[j].Target })
|
||||
slices.SortFunc(s, func(a, b types.ServiceVolumeConfig) int {
|
||||
return cmp.Compare(a.Target, b.Target)
|
||||
})
|
||||
dst.Set(reflect.ValueOf(s))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
@@ -98,9 +97,11 @@ func SetDir(dir string) {
|
||||
|
||||
// Path returns the path to a file relative to the config dir
|
||||
func Path(p ...string) (string, error) {
|
||||
path := filepath.Join(append([]string{Dir()}, p...)...)
|
||||
if !strings.HasPrefix(path, Dir()+string(filepath.Separator)) {
|
||||
return "", fmt.Errorf("path %q is outside of root config directory %q", path, Dir())
|
||||
root := Dir()
|
||||
path := filepath.Join(append([]string{root}, p...)...)
|
||||
|
||||
if rel, err := filepath.Rel(root, path); err != nil || !filepath.IsLocal(rel) {
|
||||
return "", fmt.Errorf("path %q is outside of root config directory %q", path, root)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"slices"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
@@ -113,8 +113,7 @@ func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *Repo
|
||||
var rootKeyID string
|
||||
// always select the first root key
|
||||
if len(keys) > 0 {
|
||||
sort.Strings(keys)
|
||||
rootKeyID = keys[0]
|
||||
rootKeyID = slices.Min(keys)
|
||||
} else {
|
||||
rootPublicKey, err := repo.GetCryptoService().Create(data.CanonicalRootRole, "", data.ECDSAKey)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -110,9 +111,6 @@ func lookupTrustInfo(ctx context.Context, cli command.Cli, remote string) ([]tru
|
||||
}
|
||||
|
||||
func formatAdminRole(roleWithSigs client.RoleWithSignatures) string {
|
||||
adminKeyList := roleWithSigs.KeyIDs
|
||||
sort.Strings(adminKeyList)
|
||||
|
||||
var role string
|
||||
switch roleWithSigs.Name {
|
||||
case data.CanonicalTargetsRole:
|
||||
@@ -122,6 +120,7 @@ func formatAdminRole(roleWithSigs client.RoleWithSignatures) string {
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
adminKeyList := slices.Sorted(slices.Values(roleWithSigs.KeyIDs))
|
||||
return fmt.Sprintf("%s:\t%s\n", role, strings.Join(adminKeyList, ", "))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
@@ -73,8 +73,7 @@ func (c *trustTagContext) Digest() string {
|
||||
|
||||
// Signers returns the sorted list of entities who signed this tag
|
||||
func (c *trustTagContext) Signers() string {
|
||||
sort.Strings(c.s.Signers)
|
||||
return strings.Join(c.s.Signers, ", ")
|
||||
return strings.Join(slices.Sorted(slices.Values(c.s.Signers)), ", ")
|
||||
}
|
||||
|
||||
// signerInfoWrite writes the context.
|
||||
@@ -108,15 +107,13 @@ type signerInfoContext struct {
|
||||
|
||||
// Keys returns the sorted list of keys associated with the signer
|
||||
func (c *signerInfoContext) Keys() string {
|
||||
sort.Strings(c.s.Keys)
|
||||
truncatedKeys := []string{}
|
||||
keys := slices.Sorted(slices.Values(c.s.Keys))
|
||||
if c.trunc {
|
||||
for _, keyID := range c.s.Keys {
|
||||
truncatedKeys = append(truncatedKeys, formatter.TruncateID(keyID))
|
||||
for i, keyID := range keys {
|
||||
keys[i] = formatter.TruncateID(keyID)
|
||||
}
|
||||
return strings.Join(truncatedKeys, ", ")
|
||||
}
|
||||
return strings.Join(c.s.Keys, ", ")
|
||||
return strings.Join(keys, ", ")
|
||||
}
|
||||
|
||||
// Signer returns the name of the signer
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"slices"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
@@ -80,36 +81,38 @@ func getRepoTrustInfo(ctx context.Context, dockerCLI command.Cli, remote string)
|
||||
}
|
||||
}
|
||||
|
||||
signerList, adminList := []trustSigner{}, []trustSigner{}
|
||||
|
||||
signerRoleToKeyIDs := getDelegationRoleToKeyMap(delegationRoles)
|
||||
|
||||
for signerName, signerKeys := range signerRoleToKeyIDs {
|
||||
signerKeyList := []trustKey{}
|
||||
var signerList []trustSigner
|
||||
for signerName, signerKeys := range getDelegationRoleToKeyMap(delegationRoles) {
|
||||
signerKeyList := make([]trustKey, 0, len(signerKeys))
|
||||
for _, keyID := range signerKeys {
|
||||
signerKeyList = append(signerKeyList, trustKey{ID: keyID})
|
||||
}
|
||||
signerList = append(signerList, trustSigner{signerName, signerKeyList})
|
||||
}
|
||||
sort.Slice(signerList, func(i, j int) bool { return signerList[i].Name > signerList[j].Name })
|
||||
// Sort by name in descending order.
|
||||
slices.SortFunc(signerList, func(a, b trustSigner) int { return cmp.Compare(b.Name, a.Name) })
|
||||
|
||||
var adminList []trustSigner
|
||||
for _, adminRole := range adminRolesWithSigs {
|
||||
var name string
|
||||
switch adminRole.Name {
|
||||
case data.CanonicalRootRole:
|
||||
rootKeys := []trustKey{}
|
||||
for _, keyID := range adminRole.KeyIDs {
|
||||
rootKeys = append(rootKeys, trustKey{ID: keyID})
|
||||
}
|
||||
adminList = append(adminList, trustSigner{"Root", rootKeys})
|
||||
name = "Root"
|
||||
case data.CanonicalTargetsRole:
|
||||
targetKeys := []trustKey{}
|
||||
for _, keyID := range adminRole.KeyIDs {
|
||||
targetKeys = append(targetKeys, trustKey{ID: keyID})
|
||||
}
|
||||
adminList = append(adminList, trustSigner{"Repository", targetKeys})
|
||||
name = "Repository"
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
keys := make([]trustKey, 0, len(adminRole.KeyIDs))
|
||||
for _, keyID := range adminRole.KeyIDs {
|
||||
keys = append(keys, trustKey{ID: keyID})
|
||||
}
|
||||
adminList = append(adminList, trustSigner{name, keys})
|
||||
}
|
||||
sort.Slice(adminList, func(i, j int) bool { return adminList[i].Name > adminList[j].Name })
|
||||
|
||||
// Sort by name in descending order.
|
||||
slices.SortFunc(adminList, func(a, b trustSigner) int { return cmp.Compare(b.Name, a.Name) })
|
||||
|
||||
return json.Marshal(trustRepo{
|
||||
Name: remote,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
@@ -44,7 +46,10 @@ func prettyPrintTrustInfo(ctx context.Context, dockerCLI command.Cli, remote str
|
||||
}
|
||||
|
||||
func printSortedAdminKeys(out io.Writer, adminRoles []client.RoleWithSignatures) {
|
||||
sort.Slice(adminRoles, func(i, j int) bool { return adminRoles[i].Name > adminRoles[j].Name })
|
||||
// Sort by name in descending order.
|
||||
slices.SortFunc(adminRoles, func(a, b client.RoleWithSignatures) int {
|
||||
return cmp.Compare(b.Name, a.Name)
|
||||
})
|
||||
for _, adminRole := range adminRoles {
|
||||
if formattedAdminRole := formatAdminRole(adminRole); formattedAdminRole != "" {
|
||||
_, _ = fmt.Fprintf(out, " %s", formattedAdminRole)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
@@ -189,8 +189,8 @@ func getExistingSignatureInfoForReleasedTag(notaryRepo notaryclient.Repository,
|
||||
}
|
||||
|
||||
func prettyPrintExistingSignatureInfo(out io.Writer, existingSigInfo trustTagRow) {
|
||||
sort.Strings(existingSigInfo.Signers)
|
||||
joinedSigners := strings.Join(existingSigInfo.Signers, ", ")
|
||||
signers := slices.Sorted(slices.Values(existingSigInfo.Signers))
|
||||
joinedSigners := strings.Join(signers, ", ")
|
||||
_, _ = fmt.Fprintf(out, "Existing signatures for tag %s digest %s from:\n%s\n", existingSigInfo.SignedTag, existingSigInfo.Digest, joinedSigners)
|
||||
}
|
||||
|
||||
@@ -228,8 +228,7 @@ func getOrGenerateNotaryKey(notaryRepo notaryclient.Repository, role data.RoleNa
|
||||
var key data.PublicKey
|
||||
// always select the first key by ID
|
||||
if len(keys) > 0 {
|
||||
sort.Strings(keys)
|
||||
keyID := keys[0]
|
||||
keyID := slices.Min(keys)
|
||||
privKey, _, err := notaryRepo.GetCryptoService().GetPrivateKey(keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
"gotest.tools/v3/icmd"
|
||||
@@ -21,7 +21,7 @@ func TestDeployWithNamedResources(t *testing.T) {
|
||||
result.Assert(t, icmd.Success)
|
||||
stdout := strings.Split(result.Stdout(), "\n")
|
||||
expected := strings.Split(string(golden.Get(t, "stack-deploy-with-names.golden")), "\n")
|
||||
sort.Strings(stdout)
|
||||
sort.Strings(expected)
|
||||
assert.DeepEqual(t, stdout, expected)
|
||||
assert.DeepEqual(t, stdout, expected, cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.26
|
||||
|
||||
package opts
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -82,8 +85,8 @@ func EffectiveCapAddCapDrop(add, drop []string) (capAdd, capDrop []string) {
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(capAdd)
|
||||
sort.Strings(capDrop)
|
||||
slices.Sort(capAdd)
|
||||
slices.Sort(capDrop)
|
||||
|
||||
return capAdd, capDrop
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,8 +1,13 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.26
|
||||
|
||||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
@@ -41,20 +46,15 @@ func (o *UlimitOpt) String() string {
|
||||
for _, v := range *o.values {
|
||||
out = append(out, v.String())
|
||||
}
|
||||
sort.Strings(out)
|
||||
return fmt.Sprintf("%v", out)
|
||||
slices.Sort(out)
|
||||
return fmt.Sprint(out)
|
||||
}
|
||||
|
||||
// GetList returns a slice of pointers to Ulimits. Values are sorted by name.
|
||||
func (o *UlimitOpt) GetList() []*container.Ulimit {
|
||||
ulimits := make([]*container.Ulimit, 0, len(*o.values))
|
||||
for _, v := range *o.values {
|
||||
ulimits = append(ulimits, v)
|
||||
}
|
||||
sort.SliceStable(ulimits, func(i, j int) bool {
|
||||
return ulimits[i].Name < ulimits[j].Name
|
||||
return slices.SortedFunc(maps.Values(*o.values), func(a, b *container.Ulimit) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
return ulimits
|
||||
}
|
||||
|
||||
// Type returns the option type
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
@@ -125,12 +125,12 @@ func joinElements(elems any, sep string) (string, error) {
|
||||
return b.String(), nil
|
||||
|
||||
case reflect.Map:
|
||||
var out []string
|
||||
out := make([]string, 0, rv.Len())
|
||||
for _, k := range rv.MapKeys() {
|
||||
out = append(out, fmt.Sprint(rv.MapIndex(k).Interface()))
|
||||
}
|
||||
// Not ideal, but trying to keep a consistent order
|
||||
sort.Strings(out)
|
||||
slices.Sort(out)
|
||||
return strings.Join(out, sep), nil
|
||||
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user