Skip to content

feat: implement replicaset primitive#22

Merged
sourcehawk merged 22 commits intomainfrom
feature/replicaset-primitive
Mar 25, 2026
Merged

feat: implement replicaset primitive#22
sourcehawk merged 22 commits intomainfrom
feature/replicaset-primitive

Conversation

@sourcehawk
Copy link
Owner

Implements the replicaset Kubernetes resource primitive following the pattern established by the existing ConfigMap and Deployment primitives.

Summary

  • Adds replicaset primitive package under pkg/primitives/replicaset/
  • Implements required lifecycle interfaces
  • Includes editors, mutator, flavors, and builder

Checklist

  • Compiles cleanly
  • Tests pass
  • Follows naming conventions in CONTEXT.md
  • Does not modify shared files

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new replicaset workload primitive to the operator-component-framework, mirroring the existing deployment/configmap primitive patterns so controllers can manage apps/v1.ReplicaSet resources with the same mutation/flavor/handler APIs.

Changes:

  • Introduces pkg/primitives/replicaset/ with builder, resource wrapper, mutator, default handlers, and field-application flavors.
  • Adds a new ReplicaSetSpecEditor under pkg/mutation/editors/ to support typed ReplicaSet spec mutations.
  • Adds a runnable example (examples/replicaset-primitive/) and primitive docs (docs/primitives/replicaset.md).

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/primitives/replicaset/resource.go ReplicaSet resource wrapper + default field applicator (selector immutability handling).
pkg/primitives/replicaset/builder.go Fluent builder wiring defaults (handlers, applicator, mutator factory, etc.).
pkg/primitives/replicaset/mutator.go Plan/apply mutator with feature-boundary ordering and container/pod/spec editors.
pkg/primitives/replicaset/handlers.go Default converging/grace/suspension handlers for ReplicaSet health & suspension.
pkg/primitives/replicaset/flavors.go Field-application flavors to preserve labels/annotations (object + pod template).
pkg/primitives/replicaset/*_test.go Unit tests for builder, mutator ordering, handlers, and flavors.
pkg/mutation/editors/replicasetspec.go New typed editor for ReplicaSetSpec mutations (replicas, minReadySeconds, raw).
pkg/mutation/editors/replicasetspec_test.go Unit tests for the new ReplicaSetSpecEditor.
examples/replicaset-primitive/** Example controller, features, and resource factory showing primitive usage.
examples/replicaset-primitive/README.md Documentation for running/understanding the example.
docs/primitives/replicaset.md New end-user documentation for the replicaset primitive.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.

@sourcehawk
Copy link
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • mutator.go:75 (FeatureMutator interface bug): Exported beginFeature()BeginFeature() across the FeatureMutator interface in internal/generic and all primitive mutators (replicaset, deployment, configmap). The unexported method made it impossible for mutators outside internal/generic to satisfy the interface, so ApplyMutations never called beginFeature() between mutations — all feature plans were merged into one, breaking per-mutation ordering guarantees.
  • mutator.go:67 (misleading EndFeature reference): Fixed the comment to remove the non-existent EndFeature reference and clarify that a new BeginFeature call starts a new scope.
  • docs/primitives/replicaset.md:62 (Feature field inconsistency): Clarified that a ResourceFeature with no constraints (nil gate) is always enabled, reconciling the text with the example code.
  • mutator_test.go:574 & resource_test.go:149 (tests for unreachable feature boundaries): These tests are now valid because BeginFeature() is exported and actually called by the pipeline. The TestMutator_CrossFeatureVisibility test validates mutator-level feature boundary logic, and TestResource_Mutate_FeatureOrdering exercises the full pipeline path.

Intentionally ignored:
None

<!-- claude-review-cycle -->

Copilot AI review requested due to automatic review settings March 22, 2026 19:51
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated 2 comments.

Copilot AI review requested due to automatic review settings March 22, 2026 23:00
@sourcehawk
Copy link
Owner Author

approved

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated 2 comments.

@sourcehawk
Copy link
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • Fixed range variable pointer bug in findEnv helper (mutator_test.go:55): changed for _, e := range env with return &e to for i := range env with return &env[i] to avoid returning a pointer to a loop-variable copy.

Intentionally ignored:

  • Comment on internal/generic/resource_workload.go:20 requesting PR description updates to call out broader API changes: This is a documentation/PR-description concern, not a code issue. The shared internal changes (BeginFeature, ApplyMutations) were already merged in prior commits and are not part of this diff. No code change needed.

<!-- claude-review-cycle -->

Copilot AI review requested due to automatic review settings March 23, 2026 16:08
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated 19 comments.

Copilot AI review requested due to automatic review settings March 23, 2026 16:58
@sourcehawk
Copy link
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • Replaced t.Fatalf with require.NoError(t, err) for Apply error handling in TestMutator_InitContainers, TestMutator_ContainerPresence, TestMutator_InitContainerPresence, TestMutator_SelectorSnapshotSemantics, TestMutator_Ordering_PresenceBeforeEdit, TestMutator_CrossFeatureOrdering, TestMutator_WithinFeatureCategoryOrdering, TestMutator_CrossFeatureVisibility, TestMutator_InitContainer_OrderingAndSnapshots
  • Replaced t.Fatalf length checks with require.Len in TestMutator_ContainerPresence, TestMutator_InitContainerPresence, TestMutator_Ordering_PresenceBeforeEdit
  • Replaced t.Errorf assertions with assert.Equal in TestMutator_InitContainers, TestMutator_ContainerPresence, TestMutator_InitContainerPresence, TestMutator_SelectorSnapshotSemantics, TestMutator_Ordering_PresenceBeforeEdit

Intentionally ignored:
None

<!-- claude-review-cycle -->

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated 2 comments.

sourcehawk and others added 8 commits March 23, 2026 20:17
… flavors

Implements the ReplicaSet workload primitive following the same pattern as the
Deployment primitive. Includes ReplicaSetSpecEditor, DefaultFieldApplicator that
preserves the immutable spec.selector, and all standard workload lifecycle handlers.

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 building a ReplicaSet resource with feature mutations,
flavors, data extraction, and suspension using the replicaset primitive.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address GitHub Copilot PR review comments:

- Fix DefaultFieldApplicator: capture ResourceVersion before DeepCopy
  overwrites it, so immutable spec.selector is correctly preserved on
  updates (was silently broken because ResourceVersion was always empty
  after the copy).
- Fix comment typo: "custom customFieldApplicator" → "custom field
  applicator".
- Fix docs capabilities table: remove "Failing" status and "Graceful
  rollouts" row that described unimplemented functionality.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add comprehensive test coverage for the ReplicaSet Resource, matching
the patterns established by the deployment primitive:

- Identity format verification
- Object deep copy semantics
- Mutate with feature mutations and env var injection
- Feature ordering across multiple mutations
- DefaultFieldApplicator: creation applies all fields, update preserves
  immutable selector and ResourceVersion, desired object not mutated
- Status handler delegation (ConvergingStatus, GraceStatus) with both
  custom mock handlers and default handler verification
- DeleteOnSuspend with custom and default handlers
- Suspend + Mutate integration (default scales to zero, custom handler)
- SuspensionStatus with custom and default handlers
- ExtractData captures container image from reconciled object
- CustomFieldApplicator selective field application and error propagation

Addresses Copilot review comment requesting resource-level test coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Iterate by index instead of copying the range variable to avoid
returning a pointer to a reused loop-variable copy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sourcehawk sourcehawk requested review from Copilot and removed request for Copilot March 25, 2026 16:07
@sourcehawk sourcehawk requested review from Copilot and removed request for Copilot March 25, 2026 16:08
@sourcehawk sourcehawk requested review from Copilot and removed request for Copilot March 25, 2026 16:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.

//
// This function is used as the default handler by the Resource if no custom handler is registered via
// Builder.WithCustomSuspendMutation. It can be reused within custom handlers.
func DefaultSuspendMutationHandler(mutator *Mutator) error {
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DefaultSuspendMutationHandler records mutations without calling BeginFeature(). Since EnsureReplicas() ultimately appends to m.active via EditReplicaSetSpec, this will panic if the caller hasn’t already started a feature plan. To make the handler robust and self-contained, either start a feature plan inside this handler (e.g., call mutator.BeginFeature()), or change the mutator to lazily create a plan when m.active is nil.

Suggested change
func DefaultSuspendMutationHandler(mutator *Mutator) error {
func DefaultSuspendMutationHandler(mutator *Mutator) error {
if err := mutator.BeginFeature(); err != nil {
return err
}

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +91
func (m *Mutator) EditReplicaSetSpec(edit func(*editors.ReplicaSetSpecEditor) error) {
if edit == nil {
return
}
m.active.replicaSetSpecEdits = append(m.active.replicaSetSpecEdits, edit)
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EditReplicaSetSpec (and the other Edit* / Ensure* methods) dereference m.active without a guard, so using the mutator without calling BeginFeature() will panic. Even if the framework “normally” calls BeginFeature(), this is easy to misuse (including from default handlers). Consider enforcing safety by either (a) lazily BeginFeature() when m.active == nil, or (b) returning an error from Apply() if no plan was started and documenting the contract clearly (prefer (a) if you want the API to be hard to misuse).

Copilot uses AI. Check for mistakes.
) *Builder {
if extractor != nil {
b.base.WithDataExtractor(func(rs *appsv1.ReplicaSet) error {
return extractor(*rs)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WithDataExtractor passes *rs by value, which is only a shallow copy of a ReplicaSet (maps/slices/pointers remain shared). In resource.go, the public contract says extractors receive a deep copy to prevent accidental mutation. To make that contract true at this boundary, pass a deep copy into the user function (e.g., extractor(*rs.DeepCopy())) or change the extractor signature to accept a pointer to a deep-copied object.

Suggested change
return extractor(*rs)
if rs == nil {
var empty appsv1.ReplicaSet
return extractor(empty)
}
return extractor(*rs.DeepCopy())

Copilot uses AI. Check for mistakes.
Namespace: "test-ns",
},
}
res, _ := NewBuilder(rs).Build()
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests are discarding the Build() error in multiple places. If builder validation changes, these tests could start passing a nil resource and then fail later with less actionable panics. Prefer require.NoError(t, err) (and require.NotNil(t, res) where needed) for clearer failures at the correct source.

Copilot uses AI. Check for mistakes.
run-examples: ## Run all examples to verify they execute without error.
go run ./examples/deployment-primitive/.
go run ./examples/configmap-primitive/.
go run ./examples/replicaset-primitive/.
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR checklist states “Does not modify shared files,” but this PR modifies shared repo files (e.g., Makefile, docs/primitives.md, and adds pkg/mutation/editors/replicasetspec.go). Update the PR description/checklist to reflect the actual scope, or adjust the change set if those shared modifications weren’t intended.

Copilot uses AI. Check for mistakes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sourcehawk
Copy link
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • resource_test.go: Tests now check Build() errors with require.NoError(t, err) instead of discarding them with _, preventing nil-resource panics if builder validation changes.

Intentionally ignored:

  • handlers.go:97 (BeginFeature in DefaultSuspendMutationHandler): The framework already calls BeginFeature() in mutate_helper.go before invoking any mutation handler. Adding it here would be redundant. The deployment primitive follows the identical pattern.
  • mutator.go:91 (nil guard on m.active): Same framework-level design as deployment. BeginFeature() is always called by the framework before mutations run. Adding lazy init only to replicaset would be inconsistent with the rest of the codebase.
  • builder.go:154 (WithDataExtractor deep copy): ExtractData() in resource_base.go already calls DeepCopyObject() before passing to extractors, so the extractor already receives a deep-copied object. The deployment primitive uses the identical pattern.
  • Makefile:125 (PR checklist accuracy): This is a PR description concern, not a code issue. The checklist in the PR description should be updated to reflect the actual scope of shared file modifications.

<!-- claude-review-cycle -->

@sourcehawk sourcehawk requested review from Copilot and removed request for Copilot March 25, 2026 16:16
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

@sourcehawk sourcehawk merged commit 88d0f65 into main Mar 25, 2026
2 checks passed
@sourcehawk sourcehawk deleted the feature/replicaset-primitive branch March 25, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants