Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new serviceaccount primitive to the Operator Component Framework, mirroring the existing configmap/deployment patterns for managing Kubernetes ServiceAccount resources via a static desired-state resource, builder configuration, mutation planning, and field-application flavors.
Changes:
- Introduces
pkg/primitives/serviceaccount/withResource,Builder,Mutator, and field-application flavors forServiceAccount. - Adds unit tests for the new builder, mutator behavior (including ordering), and flavors.
- Adds a runnable example (
examples/serviceaccount-primitive/) plus dedicated primitive documentation (docs/primitives/serviceaccount.md).
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/primitives/serviceaccount/resource.go | Implements the static ServiceAccount primitive Resource wrapper over internal/generic.StaticResource. |
| pkg/primitives/serviceaccount/builder.go | Provides fluent builder API for configuring mutations, flavors, and data extractors for ServiceAccount. |
| pkg/primitives/serviceaccount/mutator.go | Adds plan-and-apply Mutator with ordered edit categories for metadata, image pull secrets, and automount token. |
| pkg/primitives/serviceaccount/flavors.go | Adds typed flavors to preserve current labels/annotations via shared pkg/flavors helpers. |
| pkg/primitives/serviceaccount/builder_test.go | Tests builder validation and builder configuration methods. |
| pkg/primitives/serviceaccount/mutator_test.go | Tests mutator operations, idempotency, ordering, and feature-boundary behavior. |
| pkg/primitives/serviceaccount/flavors_test.go | Tests label/annotation preservation and flavor ordering/error propagation through Mutate. |
| examples/serviceaccount-primitive/app/controller.go | Example controller wiring a component with the serviceaccount resource. |
| examples/serviceaccount-primitive/features/mutations.go | Example feature-gated mutations composing SA behavior. |
| examples/serviceaccount-primitive/resources/serviceaccount.go | Example resource factory building the SA primitive with mutations/flavors/extraction. |
| examples/serviceaccount-primitive/main.go | Runnable fake-client demo for multiple reconciliation cycles. |
| examples/serviceaccount-primitive/README.md | Documentation for running/understanding the new example. |
| docs/primitives/serviceaccount.md | New primitive documentation and usage guidance. |
|
approved |
2f416f3 to
7078efd
Compare
Claude Review Cycle 1 CompleteAddressed: Intentionally ignored:
<!-- claude-review-cycle --> |
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored: <!-- claude-review-cycle --> |
Implement the ServiceAccount static primitive with builder, resource, mutator, and flavors. The mutator exposes direct methods for imagePullSecrets and automountServiceAccountToken since ServiceAccount has no spec field. Apply order: metadata, image pull secrets, automount. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Demonstrates feature-gated image pull secret management, automount token control, version labelling, label preservation flavors, and data extraction across multiple reconciliation cycles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add clarifying comment in resources/serviceaccount.go explaining that EnableMetrics is reused to drive DisableAutomountMutation for demo purposes, and extract to a named variable for readability. - Fix misleading printf labels in main.go: PrivateRegistry/DisableAutomount renamed to EnableTracing/EnableMetrics to match actual spec field names. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ctor Align with the fix applied to deployment and configmap mutators — initialize the first feature plan inline instead of calling beginFeature(), which would create an empty extra plan when the mutator helper also calls beginFeature(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address PR review feedback: - Set Feature to nil for always-enabled mutations in examples instead of using feature.NewResourceFeature(version, nil), matching documented semantics and avoiding unnecessary version parsing. - Export BeginFeature() on the FeatureMutator interface and all primitive mutators (serviceaccount, configmap, deployment) so that external packages can satisfy the interface and participate in per-feature ordering semantics via the generic builder's type assertion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename unused version parameter to _ in ImagePullSecretMutation to fix revive lint error. Update docs to reflect server-managed metadata preservation in DefaultFieldApplicator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
443df47 to
9328bb2
Compare
…e to run-examples - Remove "Field Flavors" / PreserveCurrentLabels mention from README (the API was removed in cd27004 but the docs were not fully updated) - Add serviceaccount-primitive to the Makefile run-examples target Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
| func (m *Mutator) EditObjectMetadata(edit func(*editors.ObjectMetaEditor) error) { | ||
| if edit == nil { | ||
| return | ||
| } | ||
| m.active.metadataEdits = append(m.active.metadataEdits, edit) | ||
| } |
There was a problem hiding this comment.
These mutation registration methods dereference m.active without ensuring BeginFeature() has been called. If a caller invokes any of these before BeginFeature(), this will panic with a nil pointer dereference. Since this is a public mutator API, consider making it non-panicking by (mandatory) adding a guard that initializes an active plan when m.active == nil (e.g., lazily calling BeginFeature()), or alternatively returning a structured error (would require API changes) rather than crashing at runtime.
| func (m *Mutator) EnsureImagePullSecret(name string) { | ||
| m.active.imagePullSecretEdits = append(m.active.imagePullSecretEdits, func(sa *corev1.ServiceAccount) { | ||
| for _, ref := range sa.ImagePullSecrets { | ||
| if ref.Name == name { | ||
| return | ||
| } | ||
| } | ||
| sa.ImagePullSecrets = append(sa.ImagePullSecrets, corev1.LocalObjectReference{Name: name}) | ||
| }) | ||
| } |
There was a problem hiding this comment.
These mutation registration methods dereference m.active without ensuring BeginFeature() has been called. If a caller invokes any of these before BeginFeature(), this will panic with a nil pointer dereference. Since this is a public mutator API, consider making it non-panicking by (mandatory) adding a guard that initializes an active plan when m.active == nil (e.g., lazily calling BeginFeature()), or alternatively returning a structured error (would require API changes) rather than crashing at runtime.
| func (m *Mutator) RemoveImagePullSecret(name string) { | ||
| m.active.imagePullSecretEdits = append(m.active.imagePullSecretEdits, func(sa *corev1.ServiceAccount) { | ||
| filtered := sa.ImagePullSecrets[:0] | ||
| for _, ref := range sa.ImagePullSecrets { | ||
| if ref.Name != name { | ||
| filtered = append(filtered, ref) | ||
| } | ||
| } | ||
| sa.ImagePullSecrets = filtered | ||
| }) | ||
| } |
There was a problem hiding this comment.
These mutation registration methods dereference m.active without ensuring BeginFeature() has been called. If a caller invokes any of these before BeginFeature(), this will panic with a nil pointer dereference. Since this is a public mutator API, consider making it non-panicking by (mandatory) adding a guard that initializes an active plan when m.active == nil (e.g., lazily calling BeginFeature()), or alternatively returning a structured error (would require API changes) rather than crashing at runtime.
| func (m *Mutator) SetAutomountServiceAccountToken(v *bool) { | ||
| var snapshot *bool | ||
| if v != nil { | ||
| val := *v | ||
| snapshot = &val | ||
| } | ||
|
|
||
| m.active.automountEdits = append(m.active.automountEdits, func(sa *corev1.ServiceAccount) { | ||
| sa.AutomountServiceAccountToken = snapshot | ||
| }) | ||
| } |
There was a problem hiding this comment.
These mutation registration methods dereference m.active without ensuring BeginFeature() has been called. If a caller invokes any of these before BeginFeature(), this will panic with a nil pointer dereference. Since this is a public mutator API, consider making it non-panicking by (mandatory) adding a guard that initializes an active plan when m.active == nil (e.g., lazily calling BeginFeature()), or alternatively returning a structured error (would require API changes) rather than crashing at runtime.
Claude Review Cycle 1 CompleteAddressed: Intentionally ignored:
<!-- claude-review-cycle --> |
Prevent nil pointer panic when mutator methods are called without a prior BeginFeature() call. Each public mutation method now calls ensureActive() which panics with a clear message instead of a cryptic nil dereference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
…uard Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
Implements the
serviceaccountKubernetes resource primitive following the pattern established by the existingConfigMapandDeploymentprimitives.Summary
serviceaccountprimitive package underpkg/primitives/serviceaccount/docs/primitives.mdChecklist
docs/primitives.md)