Skip to content

Commit e80f892

Browse files
authored
pkg/configmap: sort configmap data by key when loading bundle (#1630)
This functionality is used by OLMv0 to construct an `api.Bundle` object that is needed to create install plan steps, which are eventually put into the installplan.status.plan field. It is important to have deterministic ordering of slice fields in Kubernetes APIs to avoid constant re-reconciliation and/or false positive change detection. Specifically in the case of InstallPlan steps, the order of the steps does have _some_ semantic requirements (CSV, then CRDs, then everything else). The problem is that if "everything else" is in a random order, the order of the resulting steps will still have non-determinism. If we have deterministic order of steps, OLM may be able to resolve a class of bugs related to unnecessary creation of multiple install plans that would otherwise be identical. Signed-off-by: Joe Lanford <[email protected]>
1 parent fe65d05 commit e80f892

File tree

2 files changed

+11
-3
lines changed

2 files changed

+11
-3
lines changed

pkg/configmap/configmap.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package configmap
33
import (
44
"errors"
55
"fmt"
6+
"maps"
7+
"slices"
68
"strings"
79

810
"github.com/sirupsen/logrus"
@@ -68,7 +70,9 @@ func loadBundle(entry *logrus.Entry, cm *corev1.ConfigMap) (*api.Bundle, map[str
6870
}
6971

7072
// Add kube resources to the bundle.
71-
for name, content := range data {
73+
// Sort keys by name to guarantee consistent ordering of bundle objects.
74+
for _, name := range slices.Sorted(maps.Keys(data)) {
75+
content := data[name]
7276
reader := strings.NewReader(content)
7377
logger := entry.WithFields(logrus.Fields{
7478
"key": name,

pkg/configmap/configmap_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ func TestLoad(t *testing.T) {
6262

6363
unst, err := unstructuredlib.FromString(csvGot)
6464
require.NoError(t, err)
65-
assert.True(t, unst.GetName() == "first" || unst.GetName() == "second")
65+
66+
// The last CSV (by lexicographical sort of configmap data keys) always wins.
67+
assert.Equal(t, "second", unst.GetName())
6668
},
6769
},
6870
{
@@ -167,7 +169,9 @@ func TestLoadWriteRead(t *testing.T) {
167169
bundleObjects, err := unstructuredlib.FromBundle(bundle)
168170
require.NoError(t, err)
169171

170-
assert.ElementsMatch(t, expectedObjects, bundleObjects)
172+
// Assert that the order of manifests from the original manifests
173+
// directory is preserved (by lexicographical sorting by filename)
174+
assert.Equal(t, expectedObjects, bundleObjects)
171175
})
172176
}
173177
}

0 commit comments

Comments
 (0)