feat: implement networkpolicy primitive#31
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new networkpolicy primitive to the operator-component-framework, providing a static resource wrapper + builder/mutator pipeline (with typed spec editor support), along with documentation and a runnable example demonstrating feature-gated composition of NetworkPolicy rules.
Changes:
- Introduces
pkg/primitives/networkpolicywithResource,Builder,Mutator, and field-application flavors. - Adds a shared
NetworkPolicySpecEditorunderpkg/mutation/editorswith unit tests. - Adds documentation (
docs/primitives/networkpolicy.md) and a complete example (examples/networkpolicy-primitive/).
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/primitives/networkpolicy/resource.go | Static resource wrapper + default field applicator for NetworkPolicy. |
| pkg/primitives/networkpolicy/builder.go | Fluent builder for configuring mutations, flavors, and extractors. |
| pkg/primitives/networkpolicy/mutator.go | Plan-and-apply mutator with feature boundaries + typed edit hooks. |
| pkg/primitives/networkpolicy/flavors.go | NetworkPolicy-specific wrappers around generic “preserve current” flavors. |
| pkg/primitives/networkpolicy/builder_test.go | Builder validation and option-wiring tests. |
| pkg/primitives/networkpolicy/mutator_test.go | Mutator behavior/order/error-propagation tests. |
| pkg/primitives/networkpolicy/flavors_test.go | Flavor behavior + integration tests via Resource.Mutate. |
| pkg/mutation/editors/networkpolicyspec.go | New shared typed editor for NetworkPolicySpec. |
| pkg/mutation/editors/networkpolicyspec_test.go | Unit tests for NetworkPolicySpecEditor. |
| docs/primitives/networkpolicy.md | New primitive documentation (builder usage, mutations, ordering, flavors). |
| examples/networkpolicy-primitive/app/controller.go | Example controller wiring the primitive into a component. |
| examples/networkpolicy-primitive/features/mutations.go | Example feature mutations for ingress/egress + version label. |
| examples/networkpolicy-primitive/resources/networkpolicy.go | Example resource factory assembling the primitive configuration. |
| examples/networkpolicy-primitive/main.go | Runnable demo using a fake client and multiple reconciliation passes. |
| examples/networkpolicy-primitive/README.md | Example overview and run instructions. |
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
|
|
approved |
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored: <!-- claude-review-cycle --> |
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
Implements the NetworkPolicy static primitive following the configmap reference pattern. Includes NetworkPolicySpecEditor tests, typed mutator with plan-and-apply pattern, and PreserveCurrentLabels/Annotations flavors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Demonstrates HTTP ingress, boolean-gated metrics ingress, DNS egress, version labels, and PreserveCurrentLabels flavor. The DefaultFieldApplicator preserves ResourceVersion across reconcile cycles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The NetworkPolicySpecEditor was referenced by tests and the networkpolicy primitive but was never committed, causing build failures in CI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| // 4. Preserve labels added by external controllers. | ||
| builder.WithFieldApplicationFlavor(networkpolicy.PreserveCurrentLabels) | ||
|
|
There was a problem hiding this comment.
Builder currently has no WithFieldApplicationFlavor method, and the networkpolicy package doesn't define PreserveCurrentLabels. As written, this example won't compile with go build ./examples/... (Makefile build-examples). Either remove this call or add the missing builder API and flavor implementation in pkg/primitives/networkpolicy (matching whatever mechanism other primitives use).
| - **Metadata Mutations**: Setting version labels on the NetworkPolicy via `EditObjectMetadata`. | ||
| - **Field Flavors**: Preserving labels managed by external controllers using `PreserveCurrentLabels`. |
There was a problem hiding this comment.
This README claims the example uses PreserveCurrentLabels, but there is no such symbol in pkg/primitives/networkpolicy, and the example code currently references a non-existent WithFieldApplicationFlavor API. Please update the README to match the actual, compilable example (or implement the missing flavor API/symbols).
| - **Metadata Mutations**: Setting version labels on the NetworkPolicy via `EditObjectMetadata`. | |
| - **Field Flavors**: Preserving labels managed by external controllers using `PreserveCurrentLabels`. | |
| - **Metadata Mutations**: Setting version labels on the NetworkPolicy via metadata editors. | |
| - **Label Coexistence**: Demonstrating how label updates from this component can coexist with labels managed by other controllers. |
The networkpolicy example still referenced WithFieldApplicationFlavor and PreserveCurrentLabels which were removed in the prior refactor. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The README still referenced PreserveCurrentLabels and field flavors which were removed in a prior refactor. Updated to match the current example code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
| func (m *Mutator) EditObjectMetadata(edit func(*editors.ObjectMetaEditor) error) { | ||
| if edit == nil { | ||
| return | ||
| } | ||
| m.active.metadataEdits = append(m.active.metadataEdits, edit) | ||
| } |
There was a problem hiding this comment.
EditObjectMetadata / EditNetworkPolicySpec dereference m.active without ensuring a feature plan has been started, which will panic if BeginFeature() hasn’t been called (e.g., if Mutator is ever used outside the generic framework wiring). Consider making these methods resilient by auto-starting a plan when m.active == nil (or returning a clear error—though that would require changing the API). Auto-starting keeps the current public method signatures and avoids a runtime nil-pointer dereference.
| func (m *Mutator) EditNetworkPolicySpec(edit func(*editors.NetworkPolicySpecEditor) error) { | ||
| if edit == nil { | ||
| return | ||
| } | ||
| m.active.specEdits = append(m.active.specEdits, edit) | ||
| } |
There was a problem hiding this comment.
EditObjectMetadata / EditNetworkPolicySpec dereference m.active without ensuring a feature plan has been started, which will panic if BeginFeature() hasn’t been called (e.g., if Mutator is ever used outside the generic framework wiring). Consider making these methods resilient by auto-starting a plan when m.active == nil (or returning a clear error—though that would require changing the API). Auto-starting keeps the current public method signatures and avoids a runtime nil-pointer dereference.
| | Editor | Scope | | ||
| | ------------------------- | ----------------------------------------------------------------------- | | ||
| | `ContainerEditor` | Environment variables, arguments, resource limits, ports | | ||
| | `PodSpecEditor` | Volumes, tolerations, node selectors, service account, security context | | ||
| | `DeploymentSpecEditor` | Replicas, update strategy, label selectors | | ||
| | `ConfigMapDataEditor` | `.data` entries — set, remove, deep-merge YAML patches, raw access | | ||
| | `NetworkPolicySpecEditor` | Pod selector, ingress/egress rules, policy types | | ||
| | `ObjectMetaEditor` | Labels and annotations on any Kubernetes object | |
There was a problem hiding this comment.
These tables use || at the start of each row, which GitHub-flavored Markdown renders as an extra empty leading column (because the first cell is blank). Use standard table syntax with a single leading | (or no leading pipe) for correct rendering.
Add ensureActive() helper that auto-starts a feature plan when EditObjectMetadata or EditNetworkPolicySpec is called without a prior BeginFeature() call, preventing a nil-pointer dereference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Review Cycle 2 CompleteAddressed:
Intentionally ignored:
|
| // NewMutator creates a new Mutator for the given NetworkPolicy. | ||
| // | ||
| // It is typically used within a Feature's Mutation logic to express desired | ||
| // changes to the NetworkPolicy. BeginFeature must be called before registering | ||
| // any mutations. | ||
| func NewMutator(np *networkingv1.NetworkPolicy) *Mutator { | ||
| return &Mutator{ | ||
| np: np, | ||
| } | ||
| } |
There was a problem hiding this comment.
The doc comment says BeginFeature must be called before registering any mutations, but the implementation auto-creates a plan when edits are registered (via ensureActive). Please either (a) update the comment to reflect the implicit feature plan behavior, or (b) remove the implicit behavior and enforce explicit BeginFeature (e.g., by making edit registration fail fast).
| func (b *Builder) WithDataExtractor(extractor func(networkingv1.NetworkPolicy) error) *Builder { | ||
| if extractor != nil { | ||
| b.base.WithDataExtractor(func(np *networkingv1.NetworkPolicy) error { | ||
| return extractor(*np) | ||
| }) | ||
| } | ||
| return b | ||
| } |
There was a problem hiding this comment.
Passing *np by value does not fully protect the underlying object from mutation because NetworkPolicy contains reference types (maps/slices) that remain shared across a shallow struct copy. To keep the 'extractor receives a copy' contract robust, pass a deep-copied value (e.g., copy := *np.DeepCopy()) to the extractor.
| res, err := NewBuilder(desired). | ||
| WithMutation(Mutation{ | ||
| Name: "test-mutation", | ||
| Feature: feature.NewResourceFeature("v1", nil).When(true), |
There was a problem hiding this comment.
Tests are using feature.NewResourceFeature(\"v1\", nil) even though the examples elsewhere pass semantic versions (e.g., 1.2.3). If NewResourceFeature expects semver, \"v1\" is likely invalid and could make the test brittle if feature evaluation becomes stricter. Prefer a semver-like value (e.g., \"1.0.0\") or omit the feature gate entirely for 'always on' behavior in this test.
| Feature: feature.NewResourceFeature("v1", nil).When(true), | |
| Feature: feature.NewResourceFeature("1.0.0", nil).When(true), |
The doc comment incorrectly stated that BeginFeature must be called before registering mutations, but ensureActive() auto-creates a plan. Updated to accurately describe the implicit behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
| The `networkpolicy` primitive is the framework's built-in static abstraction for managing Kubernetes `NetworkPolicy` | ||
| resources. It integrates with the component lifecycle and provides a structured mutation API for managing pod selectors, | ||
| ingress rules, egress rules, and policy types. | ||
|
|
There was a problem hiding this comment.
The PR description mentions that the networkpolicy primitive includes “flavors”, but this primitive package/docs don’t define any flavor helpers or builder options related to field-application flavors. Please either add the intended flavor support or update the PR description/docs to avoid advertising functionality that isn’t present.
| }) | ||
| require.NoError(t, m.Apply()) | ||
| require.Len(t, np.Spec.Ingress, 1) | ||
| assert.Equal(t, &port8080, np.Spec.Ingress[0].Ports[0].Port) |
There was a problem hiding this comment.
In this test, the assertion compares pointer identity of the Port field (&port8080). That makes the test brittle to harmless implementation changes (e.g., if the mutator/editor later deep-copies ports). Prefer asserting on the port value (e.g., IntVal/StrVal) instead of pointer equality.
| assert.Equal(t, &port8080, np.Spec.Ingress[0].Ports[0].Port) | |
| assert.Equal(t, port8080.IntVal, np.Spec.Ingress[0].Ports[0].Port.IntVal) |
… test Compare port IntVal instead of pointer identity to make the test resilient to implementation changes like deep-copying. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
Implements the
networkpolicyKubernetes resource primitive following the pattern established by the existingConfigMapandDeploymentprimitives.Summary
networkpolicyprimitive package underpkg/primitives/networkpolicy/NetworkPolicySpecEditorin sharedpkg/mutation/editors/packagedocs/primitives.mdwith networkpolicy entry and addsdocs/primitives/networkpolicy.mdChecklist