From 7059ef4c9cbdb0f68aaa42100648db4868cddbab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Fri, 24 Apr 2026 11:37:48 +0200 Subject: [PATCH] formatter: Sort labels for stable output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several Labels() methods iterated over maps without sorting, producing non-deterministic output. In early versions of Go, map iteration order happened to be stable in practice, so the original code appeared to work correctly. Since Go 1.12, the runtime intentionally randomizes map iteration order, making the output unpredictable between runs. The API response produces sorted labels and the container formatter already sorted its labels (changed in 5ee17eef). Apply the same fix to the volume, network, config, and secret formatters, and update tests to assert exact ordering instead of using order-independent comparison. Signed-off-by: Paweł Gronowski --- cli/command/config/formatter.go | 5 +++++ cli/command/formatter/volume.go | 5 +++++ cli/command/formatter/volume_test.go | 4 +--- cli/command/network/formatter.go | 5 +++++ cli/command/network/formatter_test.go | 4 +--- cli/command/secret/formatter.go | 5 +++++ 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cli/command/config/formatter.go b/cli/command/config/formatter.go index 09b264337ad0..8e0b296df710 100644 --- a/cli/command/config/formatter.go +++ b/cli/command/config/formatter.go @@ -1,7 +1,11 @@ +// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: +//go:build go1.25 + package config import ( "fmt" + "slices" "strings" "time" @@ -102,6 +106,7 @@ func (c *configContext) Labels() string { for k, v := range mapLabels { joinLabels = append(joinLabels, k+"="+v) } + slices.Sort(joinLabels) return strings.Join(joinLabels, ",") } diff --git a/cli/command/formatter/volume.go b/cli/command/formatter/volume.go index e3d4b1922dae..75b6ad007f94 100644 --- a/cli/command/formatter/volume.go +++ b/cli/command/formatter/volume.go @@ -1,7 +1,11 @@ +// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: +//go:build go1.25 + package formatter import ( "fmt" + "slices" "strconv" "strings" @@ -104,6 +108,7 @@ func (c *volumeContext) Labels() string { for k, v := range c.v.Labels { joinLabels = append(joinLabels, k+"="+v) } + slices.Sort(joinLabels) return strings.Join(joinLabels, ",") } diff --git a/cli/command/formatter/volume_test.go b/cli/command/formatter/volume_test.go index 1fa2770fa966..b85a75c387c8 100644 --- a/cli/command/formatter/volume_test.go +++ b/cli/command/formatter/volume_test.go @@ -48,9 +48,7 @@ func TestVolumeContext(t *testing.T) { for _, c := range cases { ctx = c.volumeCtx v := c.call() - if strings.Contains(v, ",") { - test.CompareMultipleValues(t, v, c.expValue) - } else if v != c.expValue { + if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } } diff --git a/cli/command/network/formatter.go b/cli/command/network/formatter.go index 2a62bdb925af..cbc72a7fca2d 100644 --- a/cli/command/network/formatter.go +++ b/cli/command/network/formatter.go @@ -1,6 +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.25 + package network import ( + "slices" "strconv" "strings" @@ -115,6 +119,7 @@ func (c *networkContext) Labels() string { for k, v := range c.n.Labels { joinLabels = append(joinLabels, k+"="+v) } + slices.Sort(joinLabels) return strings.Join(joinLabels, ",") } diff --git a/cli/command/network/formatter_test.go b/cli/command/network/formatter_test.go index 4642ee35b402..8a9635aefb05 100644 --- a/cli/command/network/formatter_test.go +++ b/cli/command/network/formatter_test.go @@ -68,9 +68,7 @@ func TestNetworkContext(t *testing.T) { for _, c := range cases { ctx = c.networkCtx v := c.call() - if strings.Contains(v, ",") { - test.CompareMultipleValues(t, v, c.expValue) - } else if v != c.expValue { + if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } } diff --git a/cli/command/secret/formatter.go b/cli/command/secret/formatter.go index 5762165261f7..bc7129960f06 100644 --- a/cli/command/secret/formatter.go +++ b/cli/command/secret/formatter.go @@ -1,7 +1,11 @@ +// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: +//go:build go1.25 + package secret import ( "fmt" + "slices" "strings" "time" @@ -109,6 +113,7 @@ func (c *secretContext) Labels() string { for k, v := range mapLabels { joinLabels = append(joinLabels, k+"="+v) } + slices.Sort(joinLabels) return strings.Join(joinLabels, ",") }