Skip to content

feat: implement pod primitive#28

Merged
sourcehawk merged 28 commits into
mainfrom
feature/pod-primitive
Mar 25, 2026
Merged

feat: implement pod primitive#28
sourcehawk merged 28 commits into
mainfrom
feature/pod-primitive

Conversation

@sourcehawk
Copy link
Copy Markdown
Owner

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

Summary

  • Adds pod primitive package under pkg/primitives/pod/
  • 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
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 pod Kubernetes resource primitive to the operator-component-framework, following the existing primitive patterns (builder/resource wrappers, field applicator + flavors, mutation planning via a typed mutator, default status/suspension handlers), along with docs and an example demonstrating usage.

Changes:

  • Introduces pkg/primitives/pod/ with builder, resource wrapper, mutator, flavors, and default lifecycle handlers.
  • Adds unit tests for the pod mutator/handlers/flavors/builder.
  • Adds docs/primitives/pod.md and a runnable examples/pod-primitive/ showcasing feature mutations and flavors.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
pkg/primitives/pod/resource.go Pod workload resource wrapper + pod-specific default field applicator.
pkg/primitives/pod/builder.go Fluent builder wiring defaults, mutations, flavors, handlers, extractors.
pkg/primitives/pod/mutator.go Plan-and-apply mutator for pod metadata/spec/containers (incl. init containers).
pkg/primitives/pod/handlers.go Default converge/grace/suspend handlers for Pod lifecycle integration.
pkg/primitives/pod/flavors.go Pod-specific field application flavors (labels/annotations preservation).
pkg/primitives/pod/builder_test.go Builder validation and option wiring tests.
pkg/primitives/pod/mutator_test.go Mutator behavior tests (ordering, presence, selector snapshots, nil-safety).
pkg/primitives/pod/handlers_test.go Default handler behavior tests.
pkg/primitives/pod/flavors_test.go Flavor ordering and behavior tests.
docs/primitives/pod.md User-facing documentation for the new pod primitive.
examples/pod-primitive/** End-to-end example using the pod primitive with feature mutations and flavors.

Comment thread pkg/primitives/pod/handlers.go Outdated
Comment thread pkg/primitives/pod/handlers.go Outdated
Comment thread pkg/primitives/pod/resource.go Outdated
Comment thread pkg/primitives/pod/resource.go Outdated
Comment thread pkg/primitives/pod/mutator.go Outdated
Comment thread pkg/primitives/pod/mutator_test.go Outdated
Comment thread pkg/primitives/pod/resource.go
Comment thread examples/pod-primitive/features/mutations.go Outdated
Comment thread docs/primitives/pod.md
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 17 out of 17 changed files in this pull request and generated 9 comments.

Comment thread pkg/primitives/pod/handlers.go
Comment thread pkg/primitives/pod/handlers.go
Comment thread examples/pod-primitive/features/mutations.go Outdated
Comment thread docs/primitives/pod.md Outdated
Comment thread pkg/primitives/pod/flavors_test.go Outdated
Comment thread pkg/primitives/pod/mutator.go
Comment thread docs/primitives/pod.md Outdated
Comment thread pkg/primitives/pod/flavors_test.go Outdated
Comment thread pkg/primitives/pod/flavors_test.go Outdated
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • handlers.go:52 — DefaultConvergingStatusHandler now returns Creating (not Healthy) when ContainerStatuses is empty, consistent with DefaultGraceStatusHandler
  • handlers.go:75 — Changed reason from "Pod is being recreated" to "Pod is being updated" to reflect actual update-in-place behavior
  • examples/pod-primitive/features/mutations.go:51 — Rewrote example mutations to only mutate object metadata (labels), avoiding immutable Pod spec fields
  • docs/primitives/pod.md:113 — Corrected immutability note: env, args, resources, ports, and probes are immutable; only container images and feature-gated fields are mutable in-place
  • docs/primitives/pod.md:54 — Updated WithCustomFieldApplicator example to deep-copy maps instead of aliasing references
  • flavors_test.go:34,63,80 — All three Build() calls now assert require.NoError instead of discarding the error

Intentionally ignored:

  • mutator.go:314 (enforce immutability in Mutator.Apply) — This is a design-level change. The DefaultFieldApplicator already handles immutability by only propagating metadata to existing Pods. Adding enforcement in Apply() would be a significant architectural change that should be discussed separately, not addressed as a review fix.

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

Copilot AI review requested due to automatic review settings March 22, 2026 19:48
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 17 out of 17 changed files in this pull request and generated 6 comments.

Comment thread examples/pod-primitive/README.md Outdated
Comment thread examples/pod-primitive/README.md Outdated
Comment thread examples/pod-primitive/features/mutations.go Outdated
Comment thread examples/pod-primitive/features/mutations.go
Comment thread pkg/primitives/pod/handlers.go Outdated
Comment thread examples/pod-primitive/resources/pod.go Outdated
@sourcehawk
Copy link
Copy Markdown
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • README.md L8: Updated Feature Mutations bullet to say "metadata changes (labels)" instead of "sidecars, env vars"
  • README.md L16: Updated mutations.go description to say "label mutations applied to the Pod using the Mutator"
  • features/mutations.go L6: Reworded package comment to clarify Kubernetes allows some in-place spec updates (notably container images)
  • features/mutations.go L35: Corrected VersionFeature comment to note image mutation is allowed but this example intentionally keeps to metadata only
  • pkg/primitives/pod/handlers.go L13: Changed "being recreated" to "concepts.ConvergingOperationUpdated"
  • examples/pod-primitive/resources/pod.go L31: Replaced misleading "Will be overwritten by VersionFeature" with "Base image for the app container"

Intentionally ignored:
None

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

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

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

Claude Review Cycle 1 Complete

Addressed:

  • mutator_test.go L503: Converted t.Fatalf to require.NoError in TestMutator_InitContainer_OrderingAndSnapshots
  • mutator_test.go L249: Converted t.Fatalf/t.Errorf to require.NoError/require.Len/assert.Equal in TestMutator_ContainerPresence
  • mutator_test.go L318: Converted t.Fatalf/t.Errorf to require.NoError/assert.Equal in TestMutator_SelectorSnapshotSemantics
  • mutator_test.go L350: Converted t.Fatalf/t.Errorf to require.NoError/require.Len/assert.Equal in TestMutator_Ordering_PresenceBeforeEdit
  • mutator_test.go L396: Converted t.Fatalf to require.NoError in TestMutator_CrossFeatureOrdering
  • Also converted remaining t.Fatalf calls in TestMutator_InitContainers, TestMutator_InitContainerPresence, TestMutator_WithinFeatureCategoryOrdering, and TestMutator_CrossFeatureVisibility for full consistency

Intentionally ignored:

  • docs/primitives/pod.md L211 (double leading pipes): The table already uses correct single-pipe syntax — no || patterns found in the file. This appears to be a false positive from Copilot.

Copilot AI review requested due to automatic review settings March 23, 2026 16:08
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 17 out of 17 changed files in this pull request and generated 2 comments.

Comment thread pkg/primitives/pod/resource.go Outdated
Comment thread docs/primitives/pod.md
Comment on lines +7 to +16
## Capabilities

| Capability | Detail |
|-----------------------|-------------------------------------------------------------------------------------------------|
| **Health tracking** | Monitors pod phase and container statuses; reports `Healthy`, `Creating`, `Updating`, or `Failing` |
| **Graceful rollouts** | Detects degraded or down states via grace status handler |
| **Suspension** | Deletes the pod (pods cannot be paused); reports `Suspended` |
| **Mutation pipeline** | Typed editors for metadata, pod spec, and containers |
| **Flavors** | Preserves externally-managed fields (labels, annotations) |

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The capabilities table uses || at the start of each row, which renders as an extra empty column and breaks standard Markdown table formatting. Switch to single leading | for the header/separator/rows so it renders correctly on GitHub.

Copilot uses AI. Check for mistakes.
sourcehawk and others added 12 commits March 23, 2026 20:16
…and flavors

Implements the Pod primitive for managing Kubernetes Pod objects within the
operator-component-framework. The Pod follows the Workload lifecycle pattern
with pod-specific behavior: immutable spec preservation via DefaultFieldApplicator,
deletion-based suspension, and direct PodSpec/Container mutation (no pod template
indirection).

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

Demonstrates pod lifecycle management including version-gated mutations,
tracing sidecar injection, field preservation flavors, and deletion-based
suspension.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Handle PodFailed and PodSucceeded phases explicitly in
  DefaultConvergingStatusHandler instead of falling through to "pending"
- Check container readiness in DefaultGraceStatusHandler so healthy
  running pods are not incorrectly reported as degraded
- Clone label/annotation maps in DefaultFieldApplicator to prevent
  shared mutable references between desired and current pods
- Fix "custom customFieldApplicator" doc comment typo in Resource.Mutate
- Clarify Mutation type comment to document nil-feature semantics
- Fix findEnv test helper to return pointer to slice element, not copy
- Add Kubernetes immutability note for container presence operations in
  pod.md documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exercises the Resource wrapper integration including: identity format,
Object deep-copy semantics, Mutate pipeline with mutations and feature
ordering, disabled feature skipping, default and custom handler wiring
for converging status, grace status, suspension status, delete-on-suspend,
suspend mutation, data extraction, custom field applicator, and default
field applicator metadata-only propagation for existing pods.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix DefaultConvergingStatusHandler reporting Healthy when ContainerStatuses
  is empty; now returns Creating to match DefaultGraceStatusHandler behavior
- Fix misleading "Pod is being recreated" reason to "Pod is being updated"
- Rewrite example mutations to only mutate metadata, avoiding immutable
  Pod spec fields (containers, env, args)
- Fix Pod immutability docs: env/args/resources are immutable, not mutable
- Fix WithCustomFieldApplicator doc example to deep-copy maps
- Assert Build() errors in flavors_test.go instead of ignoring them

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix inaccurate comments and documentation:
- README: describe actual label mutations instead of sidecars/env vars
- mutations.go: clarify Kubernetes allows some in-place spec updates (e.g. images)
- handlers.go: fix GoDoc to say "updated" instead of "recreated"
- resources/pod.go: remove misleading "overwritten by VersionFeature" comment

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

Address Copilot review feedback: convert all raw testing.T assertion
calls to testify helpers for consistent test style and better failure
output across the pod primitive test suite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Initialize plans slice directly and set active pointer instead of
calling beginFeature(), preventing an empty feature plan when the
generic mutator helper also calls beginFeature().

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:08
@sourcehawk sourcehawk requested review from Copilot and removed request for Copilot March 25, 2026 16:11
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 16 out of 16 changed files in this pull request and generated 3 comments.

Comment on lines +81 to +86
func (m *Mutator) EditObjectMetadata(edit func(*editors.ObjectMetaEditor) error) {
if edit == nil {
return
}
m.active.podMetadataEdits = append(m.active.podMetadataEdits, 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.

m.active is dereferenced without ensuring BeginFeature() has been called, which will panic (nil pointer deref) if a caller uses NewMutator directly and forgets BeginFeature. Consider making registration methods robust by ensuring an active plan exists (e.g., create an implicit plan when m.active == nil, or fail fast with a clear panic/error via a dedicated internal guard). This avoids controller crashes from a simple usage mistake.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +24
type containerPresenceOp struct {
name string
container *corev1.Container // nil for remove
}
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.

containerPresenceOp stores a pointer to a corev1.Container, but presence ops ultimately copy the container value into the slice. Storing the container by value (plus an explicit remove bool) makes intent clearer, avoids pointer-escape/heap allocation for &container in EnsureContainer/EnsureInitContainer, and reduces the chance of subtle bugs if this struct evolves.

Copilot uses AI. Check for mistakes.
Comment thread pkg/primitives/pod/handlers.go Outdated
Comment on lines +118 to +120
return concepts.GraceStatusWithReason{
Status: concepts.GraceStatusDown,
Reason: "Pod is not running",
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.

For non-running phases, the reason is always "Pod is not running", which loses important debugging context (e.g., Pending vs Failed vs Unknown). Consider including the phase (and optionally the message/reason from pod conditions when available) in the returned Reason, while keeping GraceStatusDown unchanged.

Suggested change
return concepts.GraceStatusWithReason{
Status: concepts.GraceStatusDown,
Reason: "Pod is not running",
// Provide more detailed context for non-running phases while keeping the status as GraceStatusDown.
reason := "Pod phase is " + string(pod.Status.Phase)
// Optionally include the first non-true condition's reason and message, if available.
for _, cond := range pod.Status.Conditions {
if cond.Status == corev1.ConditionTrue {
continue
}
if cond.Reason != "" {
reason += " (" + cond.Reason
if cond.Message != "" {
reason += ": " + cond.Message
}
reason += ")"
} else if cond.Message != "" {
reason += " (" + cond.Message + ")"
}
// Only use the first relevant condition for brevity.
break
}
return concepts.GraceStatusWithReason{
Status: concepts.GraceStatusDown,
Reason: reason,

Copilot uses AI. Check for mistakes.
- Add requireActive() guard to mutator registration methods to fail fast
  with a clear message instead of nil pointer dereference
- Change containerPresenceOp to store container by value with explicit
  remove bool instead of using nil pointer as sentinel
- Include pod phase in grace status reason for better debugging context

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.go:86 — Added requireActive() guard to all registration methods. Panics with a clear message if BeginFeature() was not called, instead of a cryptic nil pointer dereference.
  • mutator.go:24 — Changed containerPresenceOp to store container by value with an explicit remove bool field instead of using a nil pointer as a sentinel. Clearer intent, avoids pointer escape to heap.
  • handlers.go:120 — Grace status reason now includes the actual pod phase (e.g., "Pod phase is Pending", "Pod phase is Failed") instead of the generic "Pod is not running".

Intentionally ignored:

  • None

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

@sourcehawk sourcehawk requested review from Copilot and removed request for Copilot March 25, 2026 16:19
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 16 out of 16 changed files in this pull request and generated 1 comment.

Comment thread pkg/primitives/pod/mutator.go Outdated
// It uses a "plan-and-apply" pattern: mutations are recorded first, and then
// applied to the Pod in a single controlled pass when Apply() is called.
//
// This approach ensures that mutations are applied consistently and minimises
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.

Spelling consistency: elsewhere in the repo (e.g. deployment mutator comment) this sentence uses American English (“minimizes”). Consider changing “minimises” to “minimizes” for consistency in GoDoc/comments.

Suggested change
// This approach ensures that mutations are applied consistently and minimises
// This approach ensures that mutations are applied consistently and minimizes

Copilot uses AI. Check for mistakes.
…ments

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:

  • pkg/primitives/pod/mutator.go:41 — Changed "minimises" to "minimizes" for American English consistency with the rest of the repo.

Intentionally ignored:
None

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

@sourcehawk sourcehawk requested review from Copilot and removed request for Copilot March 25, 2026 16:27
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 16 out of 16 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