Skip to content

Commit f8a4c02

Browse files
pedjakclaude
andcommitted
Fix ClusterObjectSet ref resolution for Secrets outside system namespace
The controller cache only watches the system namespace, causing ref resolution to fail when Secrets are stored in other namespaces. Fix by introducing a client wrapper that falls back to direct API calls for Secret reads outside the system namespace, and grant cluster-wide Secret get permission when BoxcutterRuntime is enabled. Adds an e2e scenario covering this path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 735b41e commit f8a4c02

7 files changed

Lines changed: 164 additions & 6 deletions

File tree

cmd/operator-controller/main.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"github.com/spf13/cobra"
3232
"go.podman.io/image/v5/types"
33+
corev1 "k8s.io/api/core/v1"
3334
rbacv1 "k8s.io/api/rbac/v1"
3435
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3536
apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
@@ -697,8 +698,13 @@ func (c *boxcutterReconcilerConfigurator) Configure(ceReconciler *controllers.Cl
697698
return fmt.Errorf("unable to create revision engine factory: %w", err)
698699
}
699700

701+
cosClient := &secretFallbackClient{
702+
Client: c.mgr.GetClient(),
703+
apiReader: c.mgr.GetAPIReader(),
704+
systemNamespace: cfg.systemNamespace,
705+
}
700706
if err = (&controllers.ClusterObjectSetReconciler{
701-
Client: c.mgr.GetClient(),
707+
Client: cosClient,
702708
RevisionEngineFactory: revisionEngineFactory,
703709
TrackingCache: trackingCache,
704710
}).SetupWithManager(c.mgr); err != nil {
@@ -791,3 +797,18 @@ func main() {
791797
os.Exit(1)
792798
}
793799
}
800+
801+
// secretFallbackClient wraps a cached client.Client and falls back to direct
802+
// API reads for Secrets outside the system namespace, where the cache does not watch.
803+
type secretFallbackClient struct {
804+
client.Client
805+
apiReader client.Reader
806+
systemNamespace string
807+
}
808+
809+
func (c *secretFallbackClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
810+
if _, isSecret := obj.(*corev1.Secret); isSecret && key.Namespace != c.systemNamespace {
811+
return c.apiReader.Get(ctx, key, obj, opts...)
812+
}
813+
return c.Client.Get(ctx, key, obj, opts...)
814+
}

helm/olmv1/templates/rbac/clusterrole-operator-controller-manager-role.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ rules:
8686
verbs:
8787
- list
8888
- watch
89+
- apiGroups:
90+
- ""
91+
resources:
92+
- secrets
93+
verbs:
94+
- get
8995
- apiGroups:
9096
- olm.operatorframework.io
9197
resources:

manifests/experimental-e2e.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,6 +2167,12 @@ rules:
21672167
verbs:
21682168
- list
21692169
- watch
2170+
- apiGroups:
2171+
- ""
2172+
resources:
2173+
- secrets
2174+
verbs:
2175+
- get
21702176
- apiGroups:
21712177
- olm.operatorframework.io
21722178
resources:

manifests/experimental.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,6 +2128,12 @@ rules:
21282128
verbs:
21292129
- list
21302130
- watch
2131+
- apiGroups:
2132+
- ""
2133+
resources:
2134+
- secrets
2135+
verbs:
2136+
- get
21312137
- apiGroups:
21322138
- olm.operatorframework.io
21332139
resources:

test/e2e/features/revision.feature

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,4 +338,108 @@ Feature: Install ClusterObjectSet
338338
And resource "pod/test-pod" is installed
339339
And resource "configmap/test-configmap-3" is installed
340340
And ClusterObjectSet "${COS_NAME}" reports Progressing as True with Reason Succeeded
341-
And ClusterObjectSet "${COS_NAME}" reports Available as True with Reason ProbesSucceeded
341+
And ClusterObjectSet "${COS_NAME}" reports Available as True with Reason ProbesSucceeded
342+
343+
Scenario: User can install a ClusterObjectSet with objects stored in Secrets
344+
Given ServiceAccount "olm-sa" with needed permissions is available in test namespace
345+
When resource is applied
346+
"""
347+
apiVersion: v1
348+
kind: Secret
349+
metadata:
350+
name: ${COS_NAME}-ref-secret
351+
namespace: ${TEST_NAMESPACE}
352+
immutable: true
353+
type: olm.operatorframework.io/object-data
354+
stringData:
355+
configmap: |
356+
{
357+
"apiVersion": "v1",
358+
"kind": "ConfigMap",
359+
"metadata": {
360+
"name": "test-configmap-ref",
361+
"namespace": "${TEST_NAMESPACE}"
362+
},
363+
"data": {
364+
"key": "value"
365+
}
366+
}
367+
deployment: |
368+
{
369+
"apiVersion": "apps/v1",
370+
"kind": "Deployment",
371+
"metadata": {
372+
"name": "test-httpd",
373+
"namespace": "${TEST_NAMESPACE}",
374+
"labels": {
375+
"app": "test-httpd"
376+
}
377+
},
378+
"spec": {
379+
"replicas": 1,
380+
"selector": {
381+
"matchLabels": {
382+
"app": "test-httpd"
383+
}
384+
},
385+
"template": {
386+
"metadata": {
387+
"labels": {
388+
"app": "test-httpd"
389+
}
390+
},
391+
"spec": {
392+
"containers": [
393+
{
394+
"name": "httpd",
395+
"image": "busybox:1.36",
396+
"imagePullPolicy": "IfNotPresent",
397+
"command": ["httpd"],
398+
"args": ["-f", "-p", "8080"],
399+
"securityContext": {
400+
"runAsNonRoot": true,
401+
"runAsUser": 1000,
402+
"allowPrivilegeEscalation": false,
403+
"capabilities": {
404+
"drop": ["ALL"]
405+
},
406+
"seccompProfile": {
407+
"type": "RuntimeDefault"
408+
}
409+
}
410+
}
411+
]
412+
}
413+
}
414+
}
415+
}
416+
"""
417+
And ClusterObjectSet is applied
418+
"""
419+
apiVersion: olm.operatorframework.io/v1
420+
kind: ClusterObjectSet
421+
metadata:
422+
annotations:
423+
olm.operatorframework.io/service-account-name: olm-sa
424+
olm.operatorframework.io/service-account-namespace: ${TEST_NAMESPACE}
425+
name: ${COS_NAME}
426+
spec:
427+
lifecycleState: Active
428+
collisionProtection: Prevent
429+
phases:
430+
- name: resources
431+
objects:
432+
- ref:
433+
name: ${COS_NAME}-ref-secret
434+
namespace: ${TEST_NAMESPACE}
435+
key: configmap
436+
- ref:
437+
name: ${COS_NAME}-ref-secret
438+
namespace: ${TEST_NAMESPACE}
439+
key: deployment
440+
revision: 1
441+
"""
442+
Then ClusterObjectSet "${COS_NAME}" reports Progressing as True with Reason Succeeded
443+
And ClusterObjectSet "${COS_NAME}" reports Available as True with Reason ProbesSucceeded
444+
And resource "configmap/test-configmap-ref" is installed
445+
And resource "deployment/test-httpd" is installed

test/e2e/steps/hooks.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import (
2222
)
2323

2424
type resource struct {
25-
name string
26-
kind string
25+
name string
26+
kind string
27+
namespace string
2728
}
2829

2930
type scenarioContext struct {
@@ -195,8 +196,12 @@ func ScenarioCleanup(ctx context.Context, _ *godog.Scenario, err error) (context
195196
forDeletion = append(forDeletion, resource{name: sc.namespace, kind: "namespace"})
196197
for _, r := range forDeletion {
197198
go func(res resource) {
198-
if _, err := k8sClient("delete", res.kind, res.name, "--ignore-not-found=true"); err != nil {
199-
logger.Info("Error deleting resource", "name", res.name, "namespace", sc.namespace, "stderr", stderrOutput(err))
199+
args := []string{"delete", res.kind, res.name, "--ignore-not-found=true"}
200+
if res.namespace != "" {
201+
args = append(args, "-n", res.namespace)
202+
}
203+
if _, err := k8sClient(args...); err != nil {
204+
logger.Info("Error deleting resource", "name", res.name, "namespace", res.namespace, "stderr", stderrOutput(err))
200205
}
201206
}(r)
202207
}

test/e2e/steps/steps.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,16 @@ func ResourceIsApplied(ctx context.Context, yamlTemplate *godog.DocString) error
310310
sc.clusterExtensionName = res.GetName()
311311
} else if res.GetKind() == "ClusterObjectSet" {
312312
sc.clusterObjectSetName = res.GetName()
313+
} else {
314+
namespace := res.GetNamespace()
315+
if namespace == "" {
316+
namespace = sc.namespace
317+
}
318+
sc.addedResources = append(sc.addedResources, resource{
319+
name: res.GetName(),
320+
kind: strings.ToLower(res.GetKind()),
321+
namespace: namespace,
322+
})
313323
}
314324
return nil
315325
}

0 commit comments

Comments
 (0)