Skip to content

feat: implement daemonset primitive#23

Merged
sourcehawk merged 29 commits into
mainfrom
feature/daemonset-primitive
Mar 25, 2026
Merged

feat: implement daemonset primitive#23
sourcehawk merged 29 commits into
mainfrom
feature/daemonset-primitive

Conversation

@sourcehawk
Copy link
Copy Markdown
Owner

@sourcehawk sourcehawk commented Mar 22, 2026

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

Summary

  • Adds daemonset primitive package under pkg/primitives/daemonset/
  • Implements required lifecycle interfaces
  • Includes editors, mutator, flavors, and builder
  • Adds DaemonSetSpecEditor to shared pkg/mutation/editors/ package
  • Updates docs/primitives.md with daemonset primitive documentation

Checklist

  • Compiles cleanly
  • Tests pass
  • Follows naming conventions in CONTEXT.md
  • Shared files intentionally modified: docs/primitives.md, pkg/mutation/editors/daemonsetspec.go

Copy link
Copy Markdown

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

Adds a new Kubernetes DaemonSet primitive to the operator-component-framework, following the established patterns of existing workload primitives (e.g., deployment) and providing an end-to-end example plus documentation.

Changes:

  • Introduces pkg/primitives/daemonset/ with builder, resource wrapper, status handlers, mutator (with selectors/editors), flavors, and tests.
  • Adds a new shared editor pkg/mutation/editors/DaemonSetSpecEditor with unit tests.
  • Adds an examples/daemonset-primitive/ runnable example and docs/primitives/daemonset.md documentation.

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/daemonset/resource.go Workload resource wrapper delegating lifecycle hooks to generic workload resource.
pkg/primitives/daemonset/builder.go Fluent builder wiring defaults (applicator, handlers) and mutation/flavor/extractor registration.
pkg/primitives/daemonset/handlers.go Default converge/grace/suspend handlers for DaemonSet lifecycle/health.
pkg/primitives/daemonset/flavors.go Field-application “flavors” to preserve selected live fields (labels/annotations/template metadata).
pkg/primitives/daemonset/mutator.go Planned mutation engine for DaemonSet (metadata/spec/podspec/containers + presence ops + snapshots).
pkg/primitives/daemonset/builder_test.go Builder API/validation/handler wiring tests.
pkg/primitives/daemonset/handlers_test.go Unit tests for default status/suspension handlers.
pkg/primitives/daemonset/flavors_test.go Unit tests for flavors and flavor ordering/error propagation.
pkg/primitives/daemonset/mutator_test.go Extensive mutator behavior tests (ordering, presence ops, snapshots, nil-safety).
pkg/mutation/editors/daemonsetspec.go Adds typed editor for appsv1.DaemonSetSpec.
pkg/mutation/editors/daemonsetspec_test.go Unit tests for the new DaemonSetSpec editor.
examples/daemonset-primitive/app/owner.go Re-exports shared example CRD types for the new example.
examples/daemonset-primitive/app/controller.go Example controller wiring a component with the DaemonSet primitive.
examples/daemonset-primitive/features/mutations.go Example feature-gated mutations using the DaemonSet mutator APIs.
examples/daemonset-primitive/features/flavors.go Example flavors wiring for preserving labels/annotations.
examples/daemonset-primitive/resources/daemonset.go Example DaemonSet resource factory composing builder + features + flavors + extraction.
examples/daemonset-primitive/main.go Runnable fake-client reconciliation sequence demonstrating the primitive.
examples/daemonset-primitive/README.md Example documentation and run instructions.
docs/primitives/daemonset.md Public documentation for the new primitive (API, ordering, editors, suspension, status).

Comment thread pkg/primitives/daemonset/handlers.go
Comment thread pkg/primitives/daemonset/mutator.go Outdated
Comment thread pkg/mutation/editors/daemonsetspec.go Outdated
Comment thread docs/primitives/daemonset.md Outdated
Copy link
Copy Markdown

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 8 comments.

Comment thread pkg/primitives/daemonset/resource.go Outdated
Comment thread pkg/primitives/daemonset/resource.go Outdated
Comment thread pkg/primitives/daemonset/resource.go Outdated
Comment thread pkg/primitives/daemonset/builder.go Outdated
Comment thread pkg/primitives/daemonset/handlers.go Outdated
Comment thread pkg/primitives/daemonset/mutator.go
Comment thread docs/primitives/daemonset.md
Comment thread pkg/mutation/editors/daemonsetspec.go
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • resource.go:57 — Fixed "custom customFieldApplicator" typo (duplicate word)
  • resource.go:73 — Updated ConvergingStatus comment to accurately describe DefaultConvergingStatusHandler semantics (>= check, zero-desired + ObservedGeneration handling)
  • resource.go:85 — Fixed GraceStatusUp → GraceStatusHealthy to match actual constant name
  • builder.go:110 — Updated WithCustomConvergeStatus doc comment to match actual handler behavior
  • handlers.go:51 — Emit dedicated reason "Waiting for controller to observe latest generation" when ObservedGeneration is stale, instead of misleading "Waiting for pods: 0/0 ready"

Intentionally ignored:

  • mutator.go:75 (FeatureMutator unexported method) — This is a framework-level architectural concern that equally affects all external primitives (including deployment). The internal/generic.FeatureMutator interface uses an unexported method by design. Fixing this would require modifying shared infrastructure (internal/generic), which is out of scope for this PR.
  • docs/primitives/daemonset.md:98 (ordering guarantees) — Same root cause as above. The documented ordering is accurate for the within-mutation category ordering. Cross-mutation feature boundaries depend on the framework-level FeatureMutator issue.
  • pkg/mutation/editors/daemonsetspec.go:10 (PR description says no shared files modified) — This is a PR description accuracy concern, not a code issue. The DaemonSetSpecEditor is a new file added by this PR as part of the daemonset feature, not a modification to an existing shared file.

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

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

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.

Comment thread pkg/primitives/daemonset/handlers.go
Comment thread pkg/primitives/daemonset/handlers.go
Comment thread pkg/primitives/daemonset/resource.go
Comment thread docs/primitives/daemonset.md Outdated
Comment thread docs/primitives/daemonset.md Outdated
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • handlers.go:29 — Gated the healthy path in DefaultConvergingStatusHandler on ObservedGeneration >= Generation to prevent reporting convergence for stale status after an update. Added test case for stale ObservedGeneration with desired > 0, and updated existing healthy-path tests to set Generation/ObservedGeneration.
  • handlers.go:69 — Fixed comment referencing nonexistent GraceStatusUp; corrected to GraceStatusHealthy to match the actual constant.
  • resource.go:80 — The doc comment now accurately describes the handler behavior after the ObservedGeneration fix above (the handler does gate on ObservedGeneration for both code paths). No change needed.

Intentionally ignored:

  • docs/primitives/daemonset.md:14 and :96 — The tables already use standard |-delimited markdown syntax, not ||. The rendered output is correct. These appear to be false positives from the review.

Copy link
Copy Markdown

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 3 comments.

Comment thread pkg/primitives/daemonset/handlers.go
Comment thread pkg/primitives/daemonset/resource.go
Comment thread pkg/mutation/editors/daemonsetspec.go
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 2 Complete

Addressed:

  • Added comprehensive Resource-level tests (resource_test.go) matching deployment primitive coverage: Identity, Object deep-copy behavior, Mutate with mutations and feature ordering, custom/default handler wiring for ConvergingStatus, GraceStatus, DeleteOnSuspend, Suspend, SuspensionStatus, ExtractData, and CustomFieldApplicator.

Intentionally ignored:

  • handlers.go:127 (create→delete churn on suspend): Valid concern, but the fix requires modifying pkg/component/suspend.go (shared infrastructure) to short-circuit createOrUpdateResources when DeleteOnSuspend is true and the object doesn't exist. The DaemonSet handlers themselves cannot prevent this — they don't control whether createOrUpdateResources is called. Note that suspend.go:124-127 already handles IsNotFound on the delete path, so the churn is non-fatal but wasteful. This should be addressed as a separate framework-level change.
  • daemonsetspec.go:15 (shared editor location): The DaemonSetSpecEditor follows the same pattern as DeploymentSpecEditor in pkg/mutation/editors/ — this is the correct and idiomatic location. The PR description/checklist should be updated to reflect this shared addition, but the code placement itself is correct.

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

Copilot AI review requested due to automatic review settings March 23, 2026 00:12
@sourcehawk sourcehawk force-pushed the feature/daemonset-primitive branch from c0099c7 to 2638488 Compare March 23, 2026 00:12
@sourcehawk
Copy link
Copy Markdown
Owner Author

approved

Copy link
Copy Markdown

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.

Comment thread docs/primitives/daemonset.md Outdated
Comment thread docs/primitives/daemonset.md Outdated
Comment thread pkg/primitives/daemonset/builder.go
Comment thread pkg/primitives/daemonset/handlers.go Outdated
Comment thread pkg/primitives/daemonset/handlers_test.go
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • docs/primitives/daemonset.md line 9: Removed Failing from capabilities table since DefaultConvergingStatusHandler never returns it
  • pkg/primitives/daemonset/handlers.go line 77: Gated DefaultGraceStatusHandler zero-desired healthy path on ObservedGeneration >= Generation, returning Degraded when the controller hasn't observed the latest generation
  • pkg/primitives/daemonset/handlers_test.go line 151: Updated test to set Generation/ObservedGeneration explicitly, added new test case for stale observed generation returning Degraded
  • docs/primitives/daemonset.md GraceStatus table: Updated to reflect the new ObservedGeneration check and Degraded status for stale generation

Intentionally ignored:

  • docs/primitives/daemonset.md line 60 (mutation ordering): The framework does enforce per-mutation boundaries via beginFeature() in mutate_helper.go, so mutations are applied sequentially in registration order. The existing documentation is accurate.
  • pkg/primitives/daemonset/builder.go line 63 (sequential comment): Same as above — the comment correctly describes the behavior. Each mutation gets its own feature boundary before ApplyIntent() is called.

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

Copy link
Copy Markdown

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 2 comments.

Comment thread docs/primitives/daemonset.md Outdated
Comment thread pkg/primitives/daemonset/mutator_test.go Outdated
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 2 Complete

Addressed:

  • Fixed findEnv in mutator_test.go returning pointer to range variable copy — now iterates by index and returns &env[i] instead of &e

Intentionally ignored:

  • Double leading pipes (||) in docs/primitives/daemonset.md tables — the tables already use single pipes correctly; no || pattern exists in the file

Copilot AI review requested due to automatic review settings March 23, 2026 03:00
Copy link
Copy Markdown

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 3 comments.

Comment thread docs/primitives/daemonset.md Outdated
Comment thread pkg/primitives/daemonset/mutator_test.go
Comment thread examples/daemonset-primitive/resources/daemonset.go Outdated
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • pkg/primitives/daemonset/mutator_test.go: Replaced all t.Fatalf/t.Errorf usage with require.NoError/require.Len/assert.Equal to match the repo's testify convention
  • examples/daemonset-primitive/resources/daemonset.go: Fixed misleading data extractor that printed zero-value status fields (NumberReady/DesiredNumberScheduled) — now prints spec/metadata (Namespace/Name) since ExtractData runs on the desired object

Intentionally ignored:

  • docs/primitives/daemonset.md table syntax comment: The tables already use standard single-pipe (|) markdown syntax throughout. No double-pipe (||) separators exist in the file.

Copilot AI review requested due to automatic review settings March 23, 2026 16:08
@sourcehawk sourcehawk requested review from Copilot and removed request for Copilot March 24, 2026 02:25
Copy link
Copy Markdown

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 21 out of 21 changed files in this pull request and generated 1 comment.

Comment thread pkg/primitives/daemonset/resource.go Outdated
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 4 Complete

Addressed:
None — all comments were already resolved in prior cycles.

Intentionally ignored:

  • resource.go:18 (DefaultFieldApplicator docstring): Already fixed in commit 0cc4ea2. The doc comment at lines 10-13 already mentions Status subresource preservation. This appears to be a stale review comment that was not dismissed after the fix was applied.

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

Copy link
Copy Markdown

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 21 out of 21 changed files in this pull request and generated 1 comment.

Comment thread docs/primitives.md Outdated
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 5 Complete

Addressed:

  • Updated PR description checklist to accurately reflect that shared files (docs/primitives.md, pkg/mutation/editors/daemonsetspec.go) are intentionally modified as part of adding the DaemonSet primitive (Copilot comment on docs/primitives.md:160)

Intentionally ignored:
None

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

Copy link
Copy Markdown

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 21 out of 21 changed files in this pull request and generated no new comments.

sourcehawk and others added 4 commits March 24, 2026 17:51
Align daemonset NewMutator with deployment/configmap: do not create a
feature plan at construction time. BeginFeature must be called before
registering mutations. Update all tests to call BeginFeature and add
dedicated tests for constructor state and plan isolation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align with the framework's switch to Server-Side Apply (SSA):
- Remove DefaultFieldApplicator, WithCustomFieldApplicator, and
  WithFieldApplicationFlavor from builder
- Delete flavors.go and flavors_test.go
- Remove DefaultFieldApplicator parameter from NewWorkloadBuilder call
- Update tests to use Object() output instead of empty structs in Mutate
- Remove flavor/field applicator references from docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

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 3 comments.

Comment on lines +60 to +62
// 4. Configure flavors (e.g., preserve labels/annotations if they were modified externally).
builder.WithFieldApplicationFlavor(features.PreserveLabelsFlavor())
builder.WithFieldApplicationFlavor(features.PreserveAnnotationsFlavor())
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.

daemonset.Builder does not define WithFieldApplicationFlavor, and the underlying generic builders don’t appear to expose a field-application flavor concept either. This example won’t compile until either the builder API is added or these calls are removed/rewritten.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +12
- **Feature Mutations**: Applying version-gated or conditional changes (sidecars, env vars, annotations) using the
`Mutator`.
- **Field Flavors**: Preserving labels and annotations that might be managed by external tools (e.g., ArgoCD, manual
edits).
- **Suspension**: Demonstrating the delete-on-suspend behavior unique to DaemonSets.
- **Data Extraction**: Harvesting information from the reconciled resource.
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.

This README describes “Field Flavors” and implies a FieldApplicationFlavor API exists, but the daemonset primitive/example code currently references non-existent identifiers (e.g. WithFieldApplicationFlavor). Update the README to reflect the actual supported API, or implement the flavor feature it describes so the example and docs stay in sync.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +16

import (
"github.com/sourcehawk/operator-component-framework/pkg/primitives/daemonset"
)

// PreserveLabelsFlavor demonstrates using a flavor to keep external labels.
func PreserveLabelsFlavor() daemonset.FieldApplicationFlavor {
return daemonset.PreserveCurrentLabels
}

// PreserveAnnotationsFlavor demonstrates using a flavor to keep external annotations.
func PreserveAnnotationsFlavor() daemonset.FieldApplicationFlavor {
return daemonset.PreserveCurrentAnnotations
}
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.

daemonset.FieldApplicationFlavor, daemonset.PreserveCurrentLabels, and daemonset.PreserveCurrentAnnotations don’t exist in pkg/primitives/daemonset (or elsewhere in pkg/). This file won’t compile as written. Either add the missing flavor API to the daemonset primitive (and wire it into reconciliation), or remove/replace this example code to match the currently supported API surface.

Suggested change
import (
"github.com/sourcehawk/operator-component-framework/pkg/primitives/daemonset"
)
// PreserveLabelsFlavor demonstrates using a flavor to keep external labels.
func PreserveLabelsFlavor() daemonset.FieldApplicationFlavor {
return daemonset.PreserveCurrentLabels
}
// PreserveAnnotationsFlavor demonstrates using a flavor to keep external annotations.
func PreserveAnnotationsFlavor() daemonset.FieldApplicationFlavor {
return daemonset.PreserveCurrentAnnotations
}

Copilot uses AI. Check for mistakes.
sourcehawk and others added 2 commits March 25, 2026 15:52
The file references removed symbols (FieldApplicationFlavor,
PreserveCurrentLabels, PreserveCurrentAnnotations) causing typecheck
failures in the linter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 1 Complete

Addressed:
All three comments reference flavor-related code (WithFieldApplicationFlavor, FieldApplicationFlavor, PreserveCurrentLabels, PreserveCurrentAnnotations, and the features/flavors.go file) that was already removed in commit 91b4e8b ("Remove field applicators and flavors from daemonset primitive"). The example code (daemonset.go), README, and features/flavors.go were all cleaned up in that prior commit. Verified that go build ./... and go test ./... pass cleanly.

Intentionally ignored:
None — all comments were valid but had already been resolved.

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

Copy link
Copy Markdown

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 18 out of 18 changed files in this pull request and generated 1 comment.

Comment on lines +21 to +27
## Running the Example

You can run this example directly using `go run`:

```bash
go run examples/daemonset-primitive/main.go
```
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.

This repository’s make run-examples target is documented as running all examples, but the newly added daemonset-primitive example is not included in that target (Makefile currently runs only deployment/configmap/custom-resource examples). Please update run-examples to run this example as well so it’s exercised alongside the others.

Copilot uses AI. Check for mistakes.
The run-examples target was missing the daemonset-primitive example,
so it would not be exercised alongside the other examples.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 2 Complete

Addressed:

  • Added daemonset-primitive example to the run-examples Makefile target so it is exercised alongside deployment, configmap, and custom-resource examples.

Intentionally ignored:
None

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

Copy link
Copy Markdown

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 3 comments.

case concepts.ConvergingOperationCreated:
status = concepts.AliveConvergingStatusCreating
case concepts.ConvergingOperationUpdated:
status = concepts.AliveConvergingStatusUpdating
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.

ConvergingOperationNone currently falls into the default branch and is reported as Scaling. This is inconsistent with stale-generation handling (which treats None as Updating) and can misclassify normal converge progress as scaling. Add an explicit case concepts.ConvergingOperationNone: (and/or a dedicated mapping) to return AliveConvergingStatusUpdating for None.

Suggested change
status = concepts.AliveConvergingStatusUpdating
status = concepts.AliveConvergingStatusUpdating
case concepts.ConvergingOperationNone:
status = concepts.AliveConvergingStatusUpdating

Copilot uses AI. Check for mistakes.
Comment thread pkg/primitives/daemonset/builder.go Outdated
) *Builder {
if extractor != nil {
b.base.WithDataExtractor(func(d *appsv1.DaemonSet) error {
return extractor(*d)
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.

This passes a shallow-copied appsv1.DaemonSet value to the extractor. Because DaemonSet contains maps/slices (e.g., labels/annotations), extractor code that mutates those fields would mutate the underlying object held by the framework. To prevent accidental side-effects, pass a deep copy to the extractor (e.g., deep-copy d before dereferencing) so extractors are isolated from the reconciled in-memory object.

Suggested change
return extractor(*d)
if d == nil {
return extractor(appsv1.DaemonSet{})
}
return extractor(*d.DeepCopy())

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +98
m.active.containerEdits = append(m.active.containerEdits, containerEdit{
selector: selector,
edit: 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.

Most mutator registration methods assume m.active is non-nil, so calling them before BeginFeature() will panic with a nil-pointer dereference. Even if the framework typically calls BeginFeature() internally, Mutator is a public type and this is easy to misuse. Consider making the API safer by lazily creating the first plan when a registration method is called and m.active == nil, or returning an error (or at least a deterministic panic with a clear message) to improve debuggability.

Copilot uses AI. Check for mistakes.
Pass a deep copy of the DaemonSet to data extractors instead of a
shallow copy, preventing extractors from accidentally mutating shared
maps/slices on the in-memory object held by the framework.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • builder.go:154 — Data extractor now receives a DeepCopy() of the DaemonSet instead of a shallow dereference, preventing extractors from accidentally mutating shared maps/slices on the in-memory object. Also added nil guard for the pointer.

Intentionally ignored:

  • handlers.go:53 — ConvergingOperationNone mapping to AliveConvergingStatusScaling via default is intentional and consistent with the deployment primitive (pkg/primitives/deployment/handlers.go:47-48), which uses the exact same pattern. After the stale-generation check passes (generation is current), None with pods not ready means the workload is scaling (e.g., new nodes added). This is also documented in concepts/alive.go:29.
  • mutator.go:98 — Nil m.active if BeginFeature() not called before registration methods. BeginFeature() is called internally by the framework before any mutations are registered. The deployment mutator has the same pattern. Adding lazy init or guard logic would be inconsistent with the codebase and adds unnecessary complexity for an internal API.

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

Copy link
Copy Markdown

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 4 comments.

Comment on lines +127 to +132
func (m *Mutator) EnsureContainer(container corev1.Container) {
m.active.containerPresence = append(m.active.containerPresence, containerPresenceOp{
name: container.Name,
container: &container,
})
}
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.

Mutatormethods assumem.activeis non-nil, butNewMutatorinitializesactiveas nil and several public methods (e.g.EnsureContainer, RemoveContainer, EditObjectMetadata, etc.) dereference m.activeunconditionally. If a caller forgets to callBeginFeature()(or if framework wiring ever changes), this will panic. Consider making the mutator resilient by lazily creating a default feature plan whenactive == nil(non-breaking), or adding a single internal guard (e.g.,ensureActivePlan()`) used by all mutation registration methods; if you prefer to enforce the contract strictly, fail with a deterministic error mechanism (panic with a clear message is better than a nil-pointer panic) and add a unit test for that behavior.

Copilot uses AI. Check for mistakes.
Namespace: "test-ns",
},
}
res, _ := NewBuilder(ds).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.

Several tests ignore the Build() error (e.g. res, _ := ...Build()), which can mask failures and cause follow-on panics or misleading assertions if validation changes. Prefer require.NoError(t, err) (and require.NotNil(t, res) where appropriate) in all test setup paths that depend on successful builds.

Copilot uses AI. Check for mistakes.
Comment thread pkg/primitives/daemonset/handlers.go Outdated
op concepts.ConvergingOperation, ds *appsv1.DaemonSet,
) (concepts.AliveStatusWithReason, error) {
if status := concepts.StaleGenerationStatus(
op, ds.Status.ObservedGeneration, ds.Generation, "daemonset",
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 user-facing reason strings are inconsistent in capitalization ("daemonset controller" coming from StaleGenerationStatus(..., "daemonset") vs "DaemonSet controller" in DefaultGraceStatusHandler). This makes conditions harder to grep/alert on consistently. Standardize the resource kind casing used to construct these messages (e.g., pass "DaemonSet" into StaleGenerationStatus, or align the grace handler reason to the same convention used elsewhere in the framework).

Suggested change
op, ds.Status.ObservedGeneration, ds.Generation, "daemonset",
op, ds.Status.ObservedGeneration, ds.Generation, "DaemonSet",

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +89
return concepts.GraceStatusWithReason{
Status: concepts.GraceStatusDegraded,
Reason: "Waiting for DaemonSet controller to observe latest generation",
}, nil
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 user-facing reason strings are inconsistent in capitalization ("daemonset controller" coming from StaleGenerationStatus(..., "daemonset") vs "DaemonSet controller" in DefaultGraceStatusHandler). This makes conditions harder to grep/alert on consistently. Standardize the resource kind casing used to construct these messages (e.g., pass "DaemonSet" into StaleGenerationStatus, or align the grace handler reason to the same convention used elsewhere in the framework).

Copilot uses AI. Check for mistakes.
…tandardize casing

- Add requireActivePlan() guard to all Mutator methods that dereference
  m.active, producing a clear panic message instead of nil-pointer crash
  if BeginFeature() was not called
- Add unit test for the panic-on-missing-BeginFeature behavior
- Replace all `res, _ := ...Build()` in resource_test.go with proper
  `require.NoError(t, err)` checks
- Standardize "daemonset" to "DaemonSet" in StaleGenerationStatus call
  to match casing used elsewhere in handler reason strings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • Mutator nil-pointer guard (mutator.go:132): Added requireActivePlan() helper called by all mutation registration methods. Panics with a clear message if BeginFeature() was not called, instead of an opaque nil-pointer crash. Added unit test for this behavior.
  • Test error handling (resource_test.go:25): Replaced all res, _ := ...Build() patterns with res, err := ...Build() + require.NoError(t, err) across all test setup paths.
  • Inconsistent casing in handler reasons (handlers.go:27, handlers.go:89): Changed StaleGenerationStatus(..., "daemonset") to StaleGenerationStatus(..., "DaemonSet") so converging-status and grace-status reason strings use consistent "DaemonSet controller" casing. Updated corresponding test assertions.

Intentionally ignored:
None

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

Copy link
Copy Markdown

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.

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