diff --git a/cli/compose/convert/service.go b/cli/compose/convert/service.go index 762bf144ff..d30597451a 100644 --- a/cli/compose/convert/service.go +++ b/cli/compose/convert/service.go @@ -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 } diff --git a/cli/compose/loader/interpolate.go b/cli/compose/loader/interpolate.go index 11c44113d4..ef84ea10a4 100644 --- a/cli/compose/loader/interpolate.go +++ b/cli/compose/loader/interpolate.go @@ -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) -} diff --git a/cli/compose/loader/loader.go b/cli/compose/loader/loader.go index 91d85cfd0a..0a9cb03ae0 100644 --- a/cli/compose/loader/loader.go +++ b/cli/compose/loader/loader.go @@ -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 } diff --git a/cli/compose/loader/loader_test.go b/cli/compose/loader/loader_test.go index 1b97ffaf2d..41f70c7cb2 100644 --- a/cli/compose/loader/loader_test.go +++ b/cli/compose/loader/loader_test.go @@ -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" diff --git a/cli/compose/loader/merge.go b/cli/compose/loader/merge.go index 4b397a2bd6..cf666ab0b2 100644 --- a/cli/compose/loader/merge.go +++ b/cli/compose/loader/merge.go @@ -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 }