Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new hpa primitive to the operator-component-framework, extending the existing primitives system with first-class support for Kubernetes autoscaling/v2 HorizontalPodAutoscaler resources (builder, mutator/editors, flavors, lifecycle handlers), plus accompanying documentation, tests, and an end-to-end example.
Changes:
- Introduces
pkg/primitives/hpa(builder/resource, mutator, handlers, and field-application flavors). - Adds a new shared mutation editor
editors.HPASpecEditorfor structured HPA spec mutation (metrics/behavior/target ref). - Adds documentation and a runnable example demonstrating feature-gated mutations, flavors, and data extraction.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/primitives/hpa/resource.go | Defines the HPA primitive resource wrapper over generic.IntegrationResource. |
| pkg/primitives/hpa/builder.go | Fluent builder for configuring HPA primitive (mutations, flavors, handlers, extractors). |
| pkg/primitives/hpa/builder_test.go | Unit tests for builder validation and configuration wiring. |
| pkg/primitives/hpa/mutator.go | Plan-and-apply mutator supporting metadata + HPA spec edit categories and feature boundaries. |
| pkg/primitives/hpa/mutator_test.go | Unit tests for mutator edits, ordering, and multi-feature behavior. |
| pkg/primitives/hpa/handlers.go | Default operational + suspension handler implementations for HPA. |
| pkg/primitives/hpa/handlers_test.go | Unit tests for default handler behavior and precedence rules. |
| pkg/primitives/hpa/flavors.go | Field-application flavors for preserving current labels/annotations. |
| pkg/primitives/hpa/flavors_test.go | Tests for flavor behavior and registration ordering. |
| pkg/mutation/editors/hpaspec.go | Adds HPASpecEditor for typed HPA spec mutations (metrics, behavior, etc.). |
| pkg/mutation/editors/hpaspec_test.go | Tests for HPASpecEditor methods (metric upsert/removal, behavior, raw access). |
| examples/hpa-primitive/main.go | Runnable fake-client example exercising reconciliation steps and suspension behavior. |
| examples/hpa-primitive/resources/hpa.go | Example resource factory assembling base HPA + mutations/flavors/extractor. |
| examples/hpa-primitive/features/mutations.go | Example feature-gated mutations and flavor helpers for the HPA primitive. |
| examples/hpa-primitive/app/controller.go | Example controller wiring the HPA resource into a Component reconciliation. |
| examples/hpa-primitive/app/owner.go | Re-exports shared example CRD types for the example package. |
| examples/hpa-primitive/README.md | Documents how to run and what the example demonstrates. |
| docs/primitives/hpa.md | New primitive documentation describing API, ordering, handlers, flavors, and examples. |
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored: <!-- claude-review-cycle --> |
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored: <!-- claude-review-cycle --> |
9ba72fe to
a8555be
Compare
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored: <!-- claude-review-cycle --> |
Provides typed methods for setting scale target ref, min/max replicas, metrics (with upsert-by-type/name semantics), and scaling behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nd handlers Integration lifecycle primitive for autoscaling/v2 HorizontalPodAutoscaler. DefaultDeleteOnSuspend = true (HPA has no native suspend field). Operational status derived from ScalingActive and AbleToScale conditions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers capabilities, building, mutations (always/boolean/version-gated), internal ordering, editors, operational status, suspension, flavors, and a full autoscaling example. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Demonstrates base HPA construction, CPU/memory metric mutations, boolean-gated features, conservative scale-down behavior, flavors, operational status reporting, delete-on-suspend, and data extraction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix custom field applicator example: preserve Status before DeepCopyInto overwrites it, matching the comment's intent of preserving live fields - Fix data extraction capability description: clarify it is opt-in via WithDataExtractor, not automatic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…suspend docs - Add explicit corev1.ConditionUnknown case in DefaultOperationalStatusHandler so the condition's Message is surfaced instead of a generic fallback - Add doc note on DefaultSuspensionStatusHandler that it assumes delete-on-suspend - Add guidance in hpa.md that keeping HPA on suspend requires custom handlers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The DefaultSuspensionStatusHandler reason string previously asserted that the HPA was deleted, which is misleading when consumers override deletion behaviour via WithCustomSuspendDeletionDecision. Update the reason to be deletion-agnostic and document that WithCustomSuspendStatus should be used when a custom reason is needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
| // metricIdentifiersMatch reports whether two MetricIdentifier values match. | ||
| // It compares both Name and Selector. | ||
| func metricIdentifiersMatch(a, b autoscalingv2.MetricIdentifier) bool { | ||
| return a.Name == b.Name && reflect.DeepEqual(a.Selector, b.Selector) | ||
| } |
There was a problem hiding this comment.
metricIdentifiersMatch uses reflect.DeepEqual on *metav1.LabelSelector. This treats semantically equivalent selectors as different when MatchExpressions are the same set but in a different order, which can cause EnsureMetric to append duplicates instead of upserting. Consider comparing selectors semantically (e.g., normalize/sort expressions+values before compare, or convert both selectors to a canonical labels.Selector representation and compare that).
| func (e *HPASpecEditor) RemoveMetric(metricType autoscalingv2.MetricSourceType, name string) { | ||
| filtered := e.spec.Metrics[:0] | ||
| for _, m := range e.spec.Metrics { | ||
| if m.Type == metricType && metricName(m) == name { | ||
| continue |
There was a problem hiding this comment.
RemoveMetric(metricType, name) matches only by type+name and ignores selector (and for Object metrics, ignores DescribedObject). Because EnsureMetric supports multiple metrics with the same name but different selector/described object, the typed API can’t remove a single specific metric identity. Consider extending RemoveMetric to remove by full identity (e.g., accept a MetricSpec/MetricIdentifier + described object), or clearly documenting that it removes all metrics for that type/name and directing users to Raw() for fine-grained removal.
| The default suspension status handler reports `Suspended` immediately with the reason | ||
| `"HorizontalPodAutoscaler suspended to prevent scaling interference"`. | ||
| Override this handler with `WithCustomSuspendStatus` if you need a reason that reflects custom deletion behaviour. | ||
|
|
||
| Override with `WithCustomSuspendDeletionDecision` if you want to retain the HPA during suspension (e.g. when the scale | ||
| target is managed externally and will not be present during suspension): | ||
|
|
||
| ```go | ||
| hpa.NewBuilder(base). | ||
| WithCustomSuspendDeletionDecision(func(_ *autoscalingv2.HorizontalPodAutoscaler) bool { | ||
| return false // keep HPA during suspension | ||
| }) |
There was a problem hiding this comment.
The Suspension section shows overriding WithCustomSuspendDeletionDecision to retain the HPA, but the default suspend status reason ("HorizontalPodAutoscaler deleted…") will still be reported unless WithCustomSuspendStatus is also overridden. Please document this coupling so users don’t end up with misleading suspension status/reason when they choose to retain the HPA.
Replace reflect.DeepEqual with order-insensitive comparison for LabelSelectors in metricIdentifiersMatch. This prevents EnsureMetric from appending duplicates when MatchExpressions or Values appear in a different order but are semantically equivalent. Also clarify RemoveMetric docs: it removes all metrics matching type+name regardless of selector, and directs users to Raw() for fine-grained removal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Review Cycle 2 CompleteAddressed:
Intentionally ignored: <!-- claude-review-cycle --> |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the
hpaKubernetes resource primitive following the pattern established by the existingConfigMapandDeploymentprimitives.Summary
hpaprimitive package underpkg/primitives/hpa/Checklist