Skip to content

Commit 1586800

Browse files
pedjakclaude
andauthored
Use dedicated Secret type for externalized object Secrets (#2613)
Set custom Secret type `olm.operatorframework.io/object-data` on Secrets that store externalized COS object content. This distinguishes OLM-managed Secrets from user-created ones and enables easy identification and filtering. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0a6b9de commit 1586800

6 files changed

Lines changed: 47 additions & 4 deletions

File tree

docs/concepts/large-bundle-support.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,19 @@ follow for consistency and safe lifecycle management.
139139

140140
Recommended conventions:
141141

142-
1. **Immutability**: Secrets should set `immutable: true`. Because COS phases
142+
1. **Secret type**: Secrets should use the dedicated type
143+
`olm.operatorframework.io/object-data` to distinguish them from user-created
144+
Secrets and enable easy identification. The system always sets this type on
145+
Secrets it creates. The reconciler does not enforce the type when resolving
146+
refs — Secrets with any type are accepted — but producers should set it for
147+
consistency.
148+
149+
2. **Immutability**: Secrets should set `immutable: true`. Because COS phases
143150
are immutable, the content backing a ref should not change after creation.
144151
Mutable referenced Secrets are not rejected, but modifying them after the
145152
COS is created leads to undefined behavior.
146153

147-
2. **Owner references**: Referenced Secrets should carry an ownerReference to
154+
3. **Owner references**: Referenced Secrets should carry an ownerReference to
148155
the COS so that Kubernetes garbage collection removes them when the COS is
149156
deleted:
150157
```yaml
@@ -159,7 +166,7 @@ Recommended conventions:
159166
Secret when the COS is deleted. The reconciler does not delete referenced
160167
Secrets itself.
161168
162-
3. **Revision label**: A label identifying the owning revision aids discovery,
169+
4. **Revision label**: A label identifying the owning revision aids discovery,
163170
debugging, and bulk cleanup:
164171
```
165172
olm.operatorframework.io/revision-name: <COS-name>
@@ -237,6 +244,7 @@ metadata:
237244
uid: <revision-uid>
238245
controller: true
239246
immutable: true
247+
type: olm.operatorframework.io/object-data
240248
data:
241249
service-account: <base64(JSON ServiceAccount manifest)>
242250
cluster-role: <base64(JSON ClusterRole manifest)>
@@ -255,6 +263,7 @@ metadata:
255263
uid: <revision-uid>
256264
controller: true
257265
immutable: true
266+
type: olm.operatorframework.io/object-data
258267
data:
259268
my-crd: <base64(JSON CRD manifest)>
260269
---
@@ -272,6 +281,7 @@ metadata:
272281
uid: <revision-uid>
273282
controller: true
274283
immutable: true
284+
type: olm.operatorframework.io/object-data
275285
data:
276286
deployment: <base64(JSON Deployment manifest)>
277287
```
@@ -653,7 +663,7 @@ rollout semantics are unchanged.
653663
| **Crash safety** | 3-step: Secrets → COS → patch ownerRefs; orphan cleanup via revision label | 2-step: COS → Secrets with ownerRefs; simpler but reconciler may see missing Secrets temporarily |
654664
| **Flexibility** | Mixed inline/ref per object within the same phase is possible | All-or-nothing — either all phases inline or all externalized |
655665
| **Storage efficiency** | Per-object compression misses cross-object redundancy; potentially more Secrets created in edge cases | Better compression from cross-phase redundancy; fewer Secrets |
656-
| **Resource type** | Secret only | Secret only (with dedicated type) |
666+
| **Resource type** | Secret with dedicated type `olm.operatorframework.io/object-data` | Secret with dedicated type `olm.operatorframework.io/revision-phase-data` |
657667
| **Phases structure** | Unchanged — phases array preserved as-is; only individual objects gain a new resolution path | Replaced at the top level — phases field swapped for phasesRef |
658668
| **Content addressability** | Content hash as Secret data key — key changes when content changes | Content hash embedded in Secret name — detects changes without fetching contents |
659669

internal/operator-controller/applier/secretpacker.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ func (p *SecretPacker) newSecret(data map[string][]byte) corev1.Secret {
143143
},
144144
},
145145
Immutable: ptr.To(true),
146+
Type: labels.SecretTypeObjectData,
146147
Data: data,
147148
}
148149
}

internal/operator-controller/applier/secretpacker_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func TestSecretPacker_Pack(t *testing.T) {
4646
assert.True(t, strings.HasPrefix(result.Secrets[0].Name, "my-ext-3-"), "Secret name should be content-addressable with revision prefix")
4747
assert.Equal(t, "olmv1-system", result.Secrets[0].Namespace)
4848
assert.True(t, *result.Secrets[0].Immutable)
49+
assert.Equal(t, labels.SecretTypeObjectData, result.Secrets[0].Type)
4950
assert.Equal(t, "my-ext-3", result.Secrets[0].Labels[labels.RevisionNameKey])
5051
assert.Equal(t, "my-ext", result.Secrets[0].Labels[labels.OwnerNameKey])
5152

internal/operator-controller/labels/labels.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package labels
22

3+
import corev1 "k8s.io/api/core/v1"
4+
35
const (
6+
// SecretTypeObjectData is the custom Secret type used for Secrets that store
7+
// externalized object content referenced by ClusterObjectSet ref entries.
8+
// It distinguishes OLM-managed ref Secrets from user-created Secrets.
9+
SecretTypeObjectData corev1.SecretType = "olm.operatorframework.io/object-data" //nolint:gosec // G101 false positive: this is a Kubernetes Secret type identifier, not a credential
10+
411
// OwnerKindKey is the label key used to record the kind of the owner
512
// resource responsible for creating or managing a ClusterObjectSet.
613
OwnerKindKey = "olm.operatorframework.io/owner-kind"

test/e2e/features/install.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ Feature: Install ClusterExtension
529529
And ClusterObjectSet "${NAME}-1" ref Secrets are immutable
530530
And ClusterObjectSet "${NAME}-1" ref Secrets are labeled with revision and owner
531531
And ClusterObjectSet "${NAME}-1" ref Secrets have ownerReference to the revision
532+
And ClusterObjectSet "${NAME}-1" ref Secrets have type "olm.operatorframework.io/object-data"
532533

533534
@DeploymentConfig
534535
Scenario: deploymentConfig nodeSelector is applied to the operator deployment

test/e2e/steps/steps.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ func RegisterSteps(sc *godog.ScenarioContext) {
101101
sc.Step(`^(?i)ClusterObjectSet "([^"]+)" ref Secrets are immutable$`, ClusterObjectSetRefSecretsAreImmutable)
102102
sc.Step(`^(?i)ClusterObjectSet "([^"]+)" ref Secrets are labeled with revision and owner$`, ClusterObjectSetRefSecretsLabeled)
103103
sc.Step(`^(?i)ClusterObjectSet "([^"]+)" ref Secrets have ownerReference to the revision$`, ClusterObjectSetRefSecretsHaveOwnerRef)
104+
sc.Step(`^(?i)ClusterObjectSet "([^"]+)" ref Secrets have type "([^"]+)"$`, ClusterObjectSetReferredSecretsHaveType)
104105

105106
sc.Step(`^(?i)resource "([^"]+)" is installed$`, ResourceAvailable)
106107
sc.Step(`^(?i)resource "([^"]+)" is available$`, ResourceAvailable)
@@ -875,6 +876,28 @@ func ClusterObjectSetRefSecretsHaveOwnerRef(ctx context.Context, revisionName st
875876
return nil
876877
}
877878

879+
// ClusterObjectSetReferredSecretsHaveType verifies that all ref Secrets for the named
880+
// ClusterObjectSet have the specified Secret type.
881+
func ClusterObjectSetReferredSecretsHaveType(ctx context.Context, revisionName, expectedType string) error {
882+
sc := scenarioCtx(ctx)
883+
revisionName = substituteScenarioVars(strings.TrimSpace(revisionName), sc)
884+
885+
secrets, err := listRefSecrets(ctx, revisionName)
886+
if err != nil {
887+
return err
888+
}
889+
if len(secrets) == 0 {
890+
return fmt.Errorf("no ref Secrets found for revision %q", revisionName)
891+
}
892+
893+
for _, s := range secrets {
894+
if string(s.Type) != expectedType {
895+
return fmt.Errorf("secret %s/%s has type %q, expected %q", s.Namespace, s.Name, s.Type, expectedType)
896+
}
897+
}
898+
return nil
899+
}
900+
878901
// collectRefSecretNames returns the unique set of Secret names referenced by the ClusterObjectSet's phase objects.
879902
func collectRefSecretNames(ctx context.Context, revisionName string) ([]string, error) {
880903
var names []string

0 commit comments

Comments
 (0)