Skip to content

feat: implement job primitive#26

Merged
sourcehawk merged 24 commits intomainfrom
feature/job-primitive
Mar 25, 2026
Merged

feat: implement job primitive#26
sourcehawk merged 24 commits intomainfrom
feature/job-primitive

Conversation

@sourcehawk
Copy link
Owner

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

Summary

  • Adds job primitive package under pkg/primitives/job/
  • 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

Implements a new Kubernetes Job primitive for the operator-component-framework, mirroring the existing primitive patterns (builder/resource/handlers/mutator/flavors), and adds an example + documentation to demonstrate usage.

Changes:

  • Added pkg/primitives/job/ with builder/resource/handlers/flavors and a Job-specific mutator.
  • Added a shared JobSpecEditor under pkg/mutation/editors/ with tests.
  • Added a runnable examples/job-primitive/ plus new documentation at docs/primitives/job.md.

Reviewed changes

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

Show a summary per file
File Description
pkg/primitives/job/resource.go Job primitive Resource wrapper over internal/generic.TaskResource.
pkg/primitives/job/builder.go Builder for configuring Job resources (mutations, flavors, handlers, extractors).
pkg/primitives/job/handlers.go Default completion + suspension handlers for Jobs.
pkg/primitives/job/mutator.go Plan/apply mutator for Job (metadata/spec/pod/container mutations).
pkg/primitives/job/flavors.go Field application flavors for preserving live labels/annotations.
pkg/primitives/job/*_test.go Unit tests for builder, handlers, mutator, and flavors.
pkg/mutation/editors/jobspec.go New shared typed editor for batchv1.JobSpec.
pkg/mutation/editors/jobspec_test.go Tests for the new JobSpecEditor.
examples/job-primitive/** Example controller + features demonstrating the job primitive.
docs/primitives/job.md User-facing documentation for the new Job primitive API and behavior.

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

@sourcehawk
Copy link
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • handlers.go:38 — Use cond.Reason as fallback when cond.Message is empty in Job failure status handler. Added corresponding test case for reason-only failures.
  • resource.go:20 — Fixed doc comments to reference component.Resource (and other component.* interfaces) instead of concepts.*, matching conventions used by configmap and deployment primitives.
  • resource.go:114 — Added comprehensive resource_test.go covering Identity, Object deep-copy, Mutate with mutations and feature ordering, DeleteOnSuspend (default + custom handler), Suspend wiring, SuspensionStatus delegation, ConvergingStatus delegation, ExtractData, and custom field applicator.

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

@sourcehawk
Copy link
Owner Author

Claude Review Cycle 1 Complete

Addressed:

  • resource_test.go:127 — Added TestResource_Mutate_CrossMutationSelectorSnapshot test that validates a container added by mutation A can be selected and configured by mutation B, exercising the cross-mutation feature-boundary semantics.

Intentionally ignored:

  • jobspec.go:52 — This is a PR description/checklist accuracy issue, not a code issue. The shared JobSpecEditor under pkg/mutation/editors is an intentional part of this PR (follows the same pattern as the existing PodSpecEditor and ContainerEditor). The PR description checklist should be updated to reflect this, but that is outside the scope of code changes.
  • docs/primitives/job.md:126 — The comment claims the feature-boundary mechanism is not invoked between registered mutations, but this is incorrect. The ApplyMutations helper in internal/generic/mutate_helper.go calls beginFeature() on the mutator before each mutation via the FeatureMutator interface. Each mutation gets its own featurePlan, and container selector snapshots are taken per-plan after presence operations. The documentation accurately describes the implemented behavior.

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

@sourcehawk
Copy link
Owner Author

Claude Review Cycle 1 Complete

Addressed:
None

Intentionally ignored:

  • resource.go:16 (Status preservation): The code already preserves status via generic.PreserveStatus(current, original) on line 18, and this is verified by TestDefaultFieldApplicator_PreservesStatus. The Copilot suggestion achieves the same result with a different approach but the current implementation is correct and consistent with the framework's generic helpers.
  • job.md:123 (Table formatting): The table already uses single | delimiters correctly. The claim about || at the start of rows does not match the actual content. The suggestion only adds whitespace padding, which is a style preference.

sourcehawk and others added 12 commits March 23, 2026 20:17
Implements the Job primitive using the Task lifecycle (Completable,
Suspendable) from internal/generic. Includes default handlers for
completion status, suspension via Job.Spec.Suspend, and deletion on
suspend. Follows the same pod-template mutation pattern as Deployment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Provides SetCompletions, SetParallelism, SetBackoffLimit,
SetActiveDeadlineSeconds, SetTTLSecondsAfterFinished, and
SetCompletionMode methods with full test coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the Mutator for Job resources following the same pattern as
the Deployment mutator. Supports editing job metadata, job spec, pod
template metadata, pod spec, and container presence/edits with snapshot
semantics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Demonstrates building a Job primitive with version-gated image updates,
tracing env vars, retry policy configuration, field preservation flavors,
custom completion status handler, and data extraction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers capabilities, building, mutations, internal ordering, editors,
convenience methods, suspension behavior, flavors, and guidance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use corev1.ConditionTrue instead of string literal "True" in status
  condition checks (handlers.go)
- Fix range variable address issue in findEnv test helper by iterating
  by index (mutator_test.go)
- Fix doc comments to reference concepts.X instead of component.X for
  interface types (resource.go)

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

- Use cond.Reason as fallback when cond.Message is empty in Job failure
  status handler, since Kubernetes often provides details in Reason only
- Fix doc comments to reference component.Resource instead of
  concepts.Resource, matching conventions used by other primitives
- Add resource_test.go with comprehensive coverage for Resource-level
  behaviors including Suspend/DeleteOnSuspend wiring, SuspensionStatus
  and ConvergingStatus delegation, ExtractData, and custom field applicator

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Validates that a container added by one mutation can be selected and
configured by a subsequent mutation, exercising the feature-boundary
semantics of the mutator's plan-and-apply pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Match the pattern used by deployment/configmap primitives: initialize
with an empty featurePlan directly instead of calling beginFeature(),
which would create a duplicate empty feature when the generic
mutate_helper also calls beginFeature().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align with upstream architectural fixes: export beginFeature() to
BeginFeature() to satisfy the FeatureMutator interface, and add
status preservation to DefaultFieldApplicator so spec-level
reconciliation does not clear server-written status data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 23, 2026 21:33
@sourcehawk sourcehawk force-pushed the feature/job-primitive branch from c8fbe86 to 8f166b9 Compare March 23, 2026 21:33
@sourcehawk sourcehawk requested review from Copilot and removed request for Copilot March 25, 2026 16:07
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 4 comments.

@@ -0,0 +1,52 @@
package editors
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 description checklist says "Does not modify shared files", but this PR adds a new shared editor under pkg/mutation/editors/ (and also updates shared docs). Please either update the checklist item (if shared changes are expected for this primitive) or move/encapsulate the shared editor changes so the checklist remains accurate.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +70
// The return value includes a descriptive status (Completed, TaskRunning, TaskPending,
// or TaskFailing) and a human-readable reason, which are used to update the component's
// conditions.
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 comment references statuses TaskRunning, TaskPending, and TaskFailing, but the code (and tests) use concepts.CompletionStatusRunning/Pending/Failing. Please align the terminology in this comment (and any similar text in docs) to the actual concepts.CompletionStatus* names to avoid confusing API consumers.

Suggested change
// The return value includes a descriptive status (Completed, TaskRunning, TaskPending,
// or TaskFailing) and a human-readable reason, which are used to update the component's
// conditions.
// The return value includes a descriptive status (concepts.CompletionStatusCompleted,
// concepts.CompletionStatusRunning, concepts.CompletionStatusPending, or
// concepts.CompletionStatusFailing) and a human-readable reason, which are used to
// update the component's conditions.

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +80
func (m *Mutator) EditObjectMetadata(edit func(*editors.ObjectMetaEditor) error) {
if edit == nil {
return
}
m.active.jobMetadataEdits = append(m.active.jobMetadataEdits, 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 a guard; calling any of the Edit*/Ensure* methods before BeginFeature() will cause a nil-pointer panic. Since this is a public helper type, consider adding a small internal guard (e.g., an ensureActive() that either initializes a plan or panics with a clear message) so failures are deterministic and actionable rather than a generic nil dereference.

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +72
builder.WithDataExtractor(func(j batchv1.Job) error {
fmt.Printf("Reconciling job: %s, active: %d, succeeded: %d, failed: %d\n",
j.Name, j.Status.Active, j.Status.Succeeded, j.Status.Failed)

// Print the complete job resource object as yaml
y, err := yaml.Marshal(j)
if err != nil {
return fmt.Errorf("failed to marshal job to yaml: %w", err)
}
fmt.Printf("Complete Job Resource:\n---\n%s\n---\n", string(y))

return 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.

This example dumps the full Job object (including pod template) to stdout. In real usage, that can inadvertently expose sensitive env vars or other configuration in logs. Consider either removing the full YAML dump, redacting sensitive fields, or gating it behind an explicit debug flag/logger configuration (while keeping the rest of the example intact).

Copilot uses AI. Check for mistakes.
…ant names

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

Claude Review Cycle 1 Complete

Addressed:

  • Fixed ConvergingStatus doc comment in pkg/primitives/job/resource.go to reference actual concepts.CompletionStatusCompleted, concepts.CompletionStatusRunning, concepts.CompletionStatusPending, and concepts.CompletionStatusFailing constant names instead of informal terminology.

Intentionally ignored:

  • jobspec.go shared editor location: The pkg/mutation/editors/ package already contains deploymentspec.go, configmapdata.go, and other typed editors. Adding jobspec.go here follows the established pattern. This is a PR description checklist item, not a code issue.
  • Nil guard on m.active in mutator.go: The deployment primitive mutator follows the identical pattern with no nil guards. The doc comment already states "BeginFeature must be called before registering any mutations." Adding a guard here would be inconsistent with the existing primitives.
  • Example YAML dump of full Job object: The deployment primitive example (examples/deployment-primitive/resources/deployment.go) uses the exact same pattern of marshaling the full object to YAML. This is the established example convention and is appropriate for illustrative code.

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

@sourcehawk sourcehawk requested review from Copilot and removed request for Copilot March 25, 2026 16:15
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 and others added 2 commits March 25, 2026 18:57
Resolve conflicts in docs/primitives.md (add job to expanded primitives
table), jobspec.go (take main's doc comments), and jobspec_test.go
(combine require.NotNil checks with editor variable naming convention).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 18:59
@sourcehawk sourcehawk merged commit 67521b4 into main Mar 25, 2026
4 checks passed
@sourcehawk sourcehawk deleted the feature/job-primitive branch March 25, 2026 19:00
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 18 out of 18 changed files in this pull request and generated 2 comments.

| `pkg/primitives/statefulset` | Workload | [statefulset.md](primitives/statefulset.md) |
| `pkg/primitives/replicaset` | Workload | [replicaset.md](primitives/replicaset.md) |
| `pkg/primitives/daemonset` | Workload | [daemonset.md](primitives/daemonset.md) |
| `pkg/primitives/job` | Task | [job.md](primitives/job.md) |
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.

PR description checklist says this PR “does not modify shared files”, but this PR updates shared docs (this file) and also changes shared editor tests (pkg/mutation/editors/jobspec_test.go). Please either update the checklist/description to reflect these shared changes, or revert them if they were unintended.

Copilot uses AI. Check for mistakes.
| Capability | Detail |
| ----------------------- | ----------------------------------------------------------------------------------------------- |
| **Completion tracking** | Monitors Job conditions and reports `Completed`, `TaskRunning`, `TaskPending`, or `TaskFailing` |
| **Suspension** | Sets `spec.suspend=true` or deletes the Job (default); reports `Suspending` / `Suspended` |
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 capabilities table says suspension “sets spec.suspend=true or deletes the Job (default)”, but in the framework suspension flow Suspend() is still called even when DeleteOnSuspend() is true, and deletion only happens after the resource reaches Suspended. Consider rewording this to avoid implying these are mutually exclusive (e.g., suspend/stop creation, then delete by default once suspended).

Suggested change
| **Suspension** | Sets `spec.suspend=true` or deletes the Job (default); reports `Suspending` / `Suspended` |
| **Suspension** | Suspends/stops the Job (for example via `spec.suspend=true`) and, by default, deletes it once `Suspended`; reports `Suspending` / `Suspended` |

Copilot uses AI. Check for mistakes.
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