NetworkPolicy Primitive¶
The networkpolicy primitive wraps a Kubernetes NetworkPolicy and integrates with the component lifecycle as a Static
resource, providing a structured mutation API for managing pod selectors, ingress rules, egress rules, and policy types.
Capabilities¶
| Capability | Detail |
|---|---|
| Static lifecycle | No health tracking, grace periods, or suspension. The resource is reconciled to desired state |
| Mutation pipeline | Typed editors for NetworkPolicy spec and object metadata, with a Raw() escape hatch |
| Append semantics | Ingress and egress rules have no unique key; AppendIngressRule/AppendEgressRule append unconditionally |
| DataExtractable | Reads values back from the reconciled NetworkPolicy after each sync cycle via WithDataExtractor |
See Lifecycle Interfaces for the full set of status values each interface reports.
Building a NetworkPolicy Primitive¶
import "github.com/sourcehawk/operator-component-framework/pkg/primitives/networkpolicy"
base := &networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "app-netpol",
Namespace: owner.Namespace,
},
Spec: networkingv1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{
MatchLabels: map[string]string{"app": owner.Name},
},
PolicyTypes: []networkingv1.PolicyType{
networkingv1.PolicyTypeIngress,
networkingv1.PolicyTypeEgress,
},
},
}
resource, err := networkpolicy.NewBuilder(base).
WithMutation(HTTPIngressMutation()).
Build()
Mutations¶
Register mutations with WithMutation. The mutation system, boolean-gated mutations, and version-gated mutations are
explained in The Mutation System,
Boolean-Gated Mutations, and
Version-Gated Mutations.
A kind-specific example appending an ingress rule unconditionally:
func HTTPIngressMutation() networkpolicy.Mutation {
return networkpolicy.Mutation{
Name: "http-ingress",
// Feature is nil: mutation is applied unconditionally.
Mutate: func(m *networkpolicy.Mutator) error {
m.EditNetworkPolicySpec(func(e *editors.NetworkPolicySpecEditor) error {
port := intstr.FromInt32(8080)
tcp := corev1.ProtocolTCP
e.AppendIngressRule(networkingv1.NetworkPolicyIngressRule{
Ports: []networkingv1.NetworkPolicyPort{
{Protocol: &tcp, Port: &port},
},
})
return nil
})
return nil
},
}
}
Internal Mutation Ordering¶
Within a single mutation, edits are applied in a fixed category order regardless of recording order:
| Step | Category | What it affects |
|---|---|---|
| 1 | Metadata edits | Labels and annotations on the NetworkPolicy |
| 2 | Spec edits | Pod selector, ingress rules, egress rules, policy types via Raw |
Within each category, edits run in registration order. Later features observe the NetworkPolicy as modified by all earlier ones.
Relevant Editors¶
See Mutation Editors for the general editor model.
NetworkPolicySpecEditor¶
The primary API for modifying the NetworkPolicy spec. Use m.EditNetworkPolicySpec for full control:
m.EditNetworkPolicySpec(func(e *editors.NetworkPolicySpecEditor) error {
e.SetPodSelector(metav1.LabelSelector{
MatchLabels: map[string]string{"app": "web"},
})
port := intstr.FromInt32(80)
tcp := corev1.ProtocolTCP
e.AppendIngressRule(networkingv1.NetworkPolicyIngressRule{
Ports: []networkingv1.NetworkPolicyPort{
{Protocol: &tcp, Port: &port},
},
})
return nil
})
SetPodSelector¶
Sets the pod selector that determines which pods the policy applies to within the namespace. An empty LabelSelector
matches all pods.
AppendIngressRule and AppendEgressRule¶
Append a rule unconditionally. Ingress and egress rules have no unique key, so these methods always append. To replace
the full set of rules atomically, call RemoveIngressRules or RemoveEgressRules first:
m.EditNetworkPolicySpec(func(e *editors.NetworkPolicySpecEditor) error {
// Replace all ingress rules atomically.
e.RemoveIngressRules()
e.AppendIngressRule(newRule1)
e.AppendIngressRule(newRule2)
return nil
})
RemoveIngressRules and RemoveEgressRules¶
Clear all ingress or egress rules respectively. Use before AppendIngressRule/AppendEgressRule to replace the full
set atomically.
SetPolicyTypes¶
Sets the policy types. Valid values are networkingv1.PolicyTypeIngress and networkingv1.PolicyTypeEgress. When
Egress is included, egress rules must be set explicitly to permit traffic; an empty list denies all egress.
Raw Escape Hatch¶
Raw() returns the underlying *networkingv1.NetworkPolicySpec for free-form editing:
m.EditNetworkPolicySpec(func(e *editors.NetworkPolicySpecEditor) error {
raw := e.Raw()
if raw.PodSelector.MatchLabels == nil {
raw.PodSelector.MatchLabels = make(map[string]string)
}
raw.PodSelector.MatchLabels["role"] = "db"
return nil
})
ObjectMetaEditor¶
Modifies labels and annotations via m.EditObjectMetadata.
Available methods: EnsureLabel, RemoveLabel, EnsureAnnotation, RemoveAnnotation, Raw.
m.EditObjectMetadata(func(e *editors.ObjectMetaEditor) error {
e.EnsureLabel("app.kubernetes.io/version", version)
e.EnsureAnnotation("policy/managed-by", "operator")
return nil
})
Data Extraction¶
Use WithDataExtractor to read values from the reconciled NetworkPolicy after each sync cycle. This is useful when
downstream resources need to observe the final applied policy (for example, its resource version or assigned labels):
var policyName string
resource, err := networkpolicy.NewBuilder(base).
WithDataExtractor(func(np networkingv1.NetworkPolicy) error {
policyName = np.Name
return nil
}).
Build()
Full Example¶
func HTTPIngressMutation() networkpolicy.Mutation {
return networkpolicy.Mutation{
Name: "http-ingress",
Mutate: func(m *networkpolicy.Mutator) error {
m.EditNetworkPolicySpec(func(e *editors.NetworkPolicySpecEditor) error {
port := intstr.FromInt32(8080)
tcp := corev1.ProtocolTCP
e.AppendIngressRule(networkingv1.NetworkPolicyIngressRule{
Ports: []networkingv1.NetworkPolicyPort{
{Protocol: &tcp, Port: &port},
},
})
return nil
})
return nil
},
}
}
func MetricsIngressMutation(version string, enabled bool) networkpolicy.Mutation {
return networkpolicy.Mutation{
Name: "metrics-ingress",
Feature: feature.NewVersionGate(version, nil).When(enabled),
Mutate: func(m *networkpolicy.Mutator) error {
m.EditNetworkPolicySpec(func(e *editors.NetworkPolicySpecEditor) error {
port := intstr.FromInt32(9090)
tcp := corev1.ProtocolTCP
e.AppendIngressRule(networkingv1.NetworkPolicyIngressRule{
Ports: []networkingv1.NetworkPolicyPort{
{Protocol: &tcp, Port: &port},
},
})
return nil
})
return nil
},
}
}
resource, err := networkpolicy.NewBuilder(base).
WithMutation(HTTPIngressMutation()).
WithMutation(MetricsIngressMutation(owner.Spec.Version, owner.Spec.EnableMetrics)).
Build()
When EnableMetrics is true, the final NetworkPolicy has both HTTP and metrics ingress rules. When false, only the HTTP
rule is present. Neither mutation needs to know about the other.
Guidance¶
Feature: nil applies unconditionally. Omit Feature for mutations that should always run. Use
feature.NewVersionGate(version, constraints) for version-based gating and chain .When(bool) for boolean conditions.
Use RemoveIngressRules/RemoveEgressRules for atomic replacement. Since rules have no unique key, there is no
upsert-by-key operation. To replace the full set of rules, call Remove*Rules first and then add the desired rules.
Alternatively, use Raw() for fine-grained manipulation.
Register mutations in dependency order. If mutation B relies on a rule added by mutation A, register A first. Since
AppendIngressRule/AppendEgressRule append unconditionally, the order of registration determines the order of rules
in the resulting spec.
NetworkPolicy is Static. It has no operational status, grace status, or suspension behavior. If the policy applies, the resource is considered ready.