Skip to content

Commit f4f0efa

Browse files
chris-rockclaude
andcommitted
Fix non-deterministic annotation ordering and add test coverage
Sort annotation map keys before building CLI args to prevent spurious Kubernetes Deployment updates caused by Go's randomized map iteration. Add tests for annotation propagation across all scan controllers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b49a376 commit f4f0efa

6 files changed

Lines changed: 161 additions & 6 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright Mondoo, Inc. 2026
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package container_image
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
12+
"go.mondoo.com/mondoo-operator/api/v1alpha2"
13+
)
14+
15+
const testClusterUID = "abcdefg"
16+
17+
func TestInventory_WithAnnotations(t *testing.T) {
18+
auditConfig := v1alpha2.MondooAuditConfig{
19+
ObjectMeta: metav1.ObjectMeta{Name: "mondoo-client"},
20+
Spec: v1alpha2.MondooAuditConfigSpec{
21+
Annotations: map[string]string{
22+
"env": "prod",
23+
"team": "platform",
24+
},
25+
},
26+
}
27+
28+
inv, err := Inventory("", testClusterUID, auditConfig, v1alpha2.MondooOperatorConfig{})
29+
assert.NoError(t, err, "unexpected error generating inventory")
30+
assert.Contains(t, inv, "env")
31+
assert.Contains(t, inv, "prod")
32+
assert.Contains(t, inv, "team")
33+
assert.Contains(t, inv, "platform")
34+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright Mondoo, Inc. 2026
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package k8s_scan
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
12+
"go.mondoo.com/mondoo-operator/api/v1alpha2"
13+
)
14+
15+
const testClusterUID = "abcdefg"
16+
17+
func TestInventory_WithAnnotations(t *testing.T) {
18+
auditConfig := v1alpha2.MondooAuditConfig{
19+
ObjectMeta: metav1.ObjectMeta{Name: "mondoo-client"},
20+
Spec: v1alpha2.MondooAuditConfigSpec{
21+
Annotations: map[string]string{
22+
"env": "prod",
23+
"team": "platform",
24+
},
25+
},
26+
}
27+
28+
inv, err := Inventory("", testClusterUID, auditConfig, v1alpha2.MondooOperatorConfig{})
29+
assert.NoError(t, err, "unexpected error generating inventory")
30+
assert.Contains(t, inv, "env")
31+
assert.Contains(t, inv, "prod")
32+
assert.Contains(t, inv, "team")
33+
assert.Contains(t, inv, "platform")
34+
}
35+
36+
func TestExternalClusterInventory_WithAnnotations(t *testing.T) {
37+
auditConfig := v1alpha2.MondooAuditConfig{
38+
ObjectMeta: metav1.ObjectMeta{Name: "mondoo-client"},
39+
Spec: v1alpha2.MondooAuditConfigSpec{
40+
Annotations: map[string]string{
41+
"env": "staging",
42+
"team": "security",
43+
},
44+
},
45+
}
46+
47+
cluster := v1alpha2.ExternalCluster{
48+
Name: "remote-cluster",
49+
}
50+
51+
inv, err := ExternalClusterInventory("", testClusterUID, cluster, auditConfig, v1alpha2.MondooOperatorConfig{})
52+
assert.NoError(t, err, "unexpected error generating inventory")
53+
assert.Contains(t, inv, "env")
54+
assert.Contains(t, inv, "staging")
55+
assert.Contains(t, inv, "team")
56+
assert.Contains(t, inv, "security")
57+
}

controllers/nodes/resources_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,25 @@ func TestInventory(t *testing.T) {
242242
assert.Contains(t, inventory, integrationMRN)
243243
}
244244

245+
func TestInventory_WithAnnotations(t *testing.T) {
246+
auditConfig := v1alpha2.MondooAuditConfig{
247+
ObjectMeta: metav1.ObjectMeta{Name: "mondoo-client"},
248+
Spec: v1alpha2.MondooAuditConfigSpec{
249+
Annotations: map[string]string{
250+
"env": "prod",
251+
"team": "platform",
252+
},
253+
},
254+
}
255+
256+
inv, err := Inventory("", testClusterUID, auditConfig)
257+
assert.NoError(t, err, "unexpected error generating inventory")
258+
assert.Contains(t, inv, "env")
259+
assert.Contains(t, inv, "prod")
260+
assert.Contains(t, inv, "team")
261+
assert.Contains(t, inv, "platform")
262+
}
263+
245264
func testMondooAuditConfig() *v1alpha2.MondooAuditConfig {
246265
return &v1alpha2.MondooAuditConfig{
247266
ObjectMeta: metav1.ObjectMeta{

controllers/resource_watcher/resources.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package resource_watcher
55

66
import (
77
"fmt"
8+
"sort"
89
"strings"
910
"time"
1011

@@ -85,9 +86,14 @@ func Deployment(image string, m *v1alpha2.MondooAuditConfig, cfg v1alpha2.Mondoo
8586
cmd = append(cmd, "--api-proxy", *cfg.Spec.HttpProxy)
8687
}
8788

88-
// Add annotations
89-
for key, value := range m.Spec.Annotations {
90-
cmd = append(cmd, "--annotation", fmt.Sprintf("%s=%s", key, value))
89+
// Add annotations (sorted for deterministic ordering)
90+
annotationKeys := make([]string, 0, len(m.Spec.Annotations))
91+
for k := range m.Spec.Annotations {
92+
annotationKeys = append(annotationKeys, k)
93+
}
94+
sort.Strings(annotationKeys)
95+
for _, key := range annotationKeys {
96+
cmd = append(cmd, "--annotation", fmt.Sprintf("%s=%s", key, m.Spec.Annotations[key]))
9197
}
9298

9399
envVars := feature_flags.AllFeatureFlagsAsEnv()

controllers/resource_watcher/resources_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,39 @@ func TestDeployment_WatchAllResources(t *testing.T) {
234234
assert.Contains(t, cmdStr, "--watch-all-resources")
235235
}
236236

237+
func TestDeployment_WithAnnotations(t *testing.T) {
238+
config := &v1alpha2.MondooAuditConfig{
239+
ObjectMeta: metav1.ObjectMeta{
240+
Name: "my-config",
241+
Namespace: "mondoo-operator",
242+
},
243+
Spec: v1alpha2.MondooAuditConfigSpec{
244+
KubernetesResources: v1alpha2.KubernetesResources{
245+
Enable: true,
246+
ResourceWatcher: v1alpha2.ResourceWatcherSpec{
247+
Enable: true,
248+
},
249+
},
250+
Annotations: map[string]string{
251+
"env": "prod",
252+
"team": "platform",
253+
},
254+
},
255+
}
256+
257+
operatorConfig := v1alpha2.MondooOperatorConfig{}
258+
259+
deployment := Deployment("ghcr.io/mondoohq/cnspec:latest", config, operatorConfig)
260+
261+
container := deployment.Spec.Template.Spec.Containers[0]
262+
cmdStr := ""
263+
for _, c := range container.Command {
264+
cmdStr += c + " "
265+
}
266+
assert.Contains(t, cmdStr, "--annotation env=prod")
267+
assert.Contains(t, cmdStr, "--annotation team=platform")
268+
}
269+
237270
func TestDeployment_HighPriorityByDefault(t *testing.T) {
238271
config := &v1alpha2.MondooAuditConfig{
239272
ObjectMeta: metav1.ObjectMeta{

controllers/resource_watcher/scanner.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"os"
1010
"os/exec"
11+
"sort"
1112
"time"
1213

1314
ctrl "sigs.k8s.io/controller-runtime"
@@ -80,9 +81,14 @@ func (s *Scanner) ScanManifests(ctx context.Context, manifests []byte) error {
8081
if s.config.APIProxy != "" {
8182
cnspecArgs = append(cnspecArgs, "--api-proxy", s.config.APIProxy)
8283
}
83-
// Add annotations as command-line arguments
84-
for key, value := range s.config.Annotations {
85-
cnspecArgs = append(cnspecArgs, "--annotation", fmt.Sprintf("%s=%s", key, value))
84+
// Add annotations as command-line arguments (sorted for deterministic ordering)
85+
annotationKeys := make([]string, 0, len(s.config.Annotations))
86+
for k := range s.config.Annotations {
87+
annotationKeys = append(annotationKeys, k)
88+
}
89+
sort.Strings(annotationKeys)
90+
for _, key := range annotationKeys {
91+
cnspecArgs = append(cnspecArgs, "--annotation", fmt.Sprintf("%s=%s", key, s.config.Annotations[key]))
8692
}
8793

8894
// Create context with timeout

0 commit comments

Comments
 (0)