The pdb primitive is the framework's built-in static abstraction for managing Kubernetes PodDisruptionBudget
resources. It integrates with the component lifecycle and provides a structured mutation API for managing disruption
policies and object metadata.
| Capability | Detail |
|---|---|
| Static lifecycle | No health tracking, grace periods, or suspension. The resource is reconciled to desired state |
| Mutation pipeline | Typed editors for PDB spec and object metadata, with a raw escape hatch for free-form access |
| Data extraction | Reads generated or updated values back from the reconciled PDB after each sync cycle |
import "github.com/sourcehawk/operator-component-framework/pkg/primitives/pdb"
minAvailable := intstr.FromString("50%")
base := &policyv1.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: "web-server-pdb",
Namespace: owner.Namespace,
},
Spec: policyv1.PodDisruptionBudgetSpec{
MinAvailable: &minAvailable,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": "web-server"},
},
},
}
resource, err := pdb.NewBuilder(base).
WithMutation(MyFeatureMutation(owner.Spec.Version)).
Build()Mutations are the primary mechanism for modifying a PodDisruptionBudget beyond its baseline. Each mutation is a named
function that receives a *Mutator and records edit intent through typed editors.
The Feature field controls when a mutation applies. Leaving it nil applies the mutation unconditionally. A feature
with no version constraints and no When() conditions is also always enabled:
func MyFeatureMutation(version string) pdb.Mutation {
return pdb.Mutation{
Name: "my-feature",
Feature: feature.NewVersionGate(version, nil), // always enabled
Mutate: func(m *pdb.Mutator) error {
// record edits here
return nil
},
}
}Mutations are applied in the order they are registered with the builder. If one mutation depends on a change made by another, register the dependency first.
func StrictAvailabilityMutation(version string, enabled bool) pdb.Mutation {
return pdb.Mutation{
Name: "strict-availability",
Feature: feature.NewVersionGate(version, nil).When(enabled),
Mutate: func(m *pdb.Mutator) error {
m.EditSpec(func(e *editors.PodDisruptionBudgetSpecEditor) error {
e.ClearMinAvailable()
e.SetMaxUnavailable(intstr.FromInt32(1))
return nil
})
return nil
},
}
}var legacyConstraint = mustSemverConstraint("< 2.0.0")
func LegacyPDBMutation(version string) pdb.Mutation {
return pdb.Mutation{
Name: "legacy-pdb",
Feature: feature.NewVersionGate(
version,
[]feature.VersionConstraint{legacyConstraint},
),
Mutate: func(m *pdb.Mutator) error {
m.EditSpec(func(e *editors.PodDisruptionBudgetSpecEditor) error {
e.SetMinAvailable(intstr.FromInt32(1))
return nil
})
return nil
},
}
}All version constraints and When() conditions must be satisfied for a mutation to apply.
Within a single mutation, edit operations are applied in a fixed category order regardless of the order they are recorded:
| Step | Category | What it affects |
|---|---|---|
| 1 | Metadata edits | Labels and annotations on the PodDisruptionBudget |
| 2 | Spec edits | MinAvailable, MaxUnavailable, selector, eviction policy |
Within each category, edits are applied in their registration order. Later features observe the PodDisruptionBudget as modified by all previous features.
The primary API for modifying the PDB spec. Use m.EditSpec for full control:
m.EditSpec(func(e *editors.PodDisruptionBudgetSpecEditor) error {
e.SetMinAvailable(intstr.FromString("50%"))
e.SetSelector(&metav1.LabelSelector{
MatchLabels: map[string]string{"app": "web"},
})
return nil
})SetMinAvailable sets the minimum number of pods that must remain available during a disruption. SetMaxUnavailable
sets the maximum number of pods that can be unavailable. Both accept intstr.IntOrString, either an integer count or a
percentage string (e.g. "50%").
These fields are mutually exclusive in the Kubernetes API. Use ClearMinAvailable or ClearMaxUnavailable to remove
the opposing constraint when switching between them:
m.EditSpec(func(e *editors.PodDisruptionBudgetSpecEditor) error {
e.ClearMinAvailable()
e.SetMaxUnavailable(intstr.FromInt32(1))
return nil
})SetSelector replaces the pod selector used by the PDB:
m.EditSpec(func(e *editors.PodDisruptionBudgetSpecEditor) error {
e.SetSelector(&metav1.LabelSelector{
MatchLabels: map[string]string{"app": "web", "tier": "frontend"},
})
return nil
})SetUnhealthyPodEvictionPolicy controls how unhealthy pods are handled during eviction. Valid values are
policyv1.IfHealthyBudget and policyv1.AlwaysAllow. Use ClearUnhealthyPodEvictionPolicy to revert to the cluster
default:
m.EditSpec(func(e *editors.PodDisruptionBudgetSpecEditor) error {
e.SetUnhealthyPodEvictionPolicy(policyv1.AlwaysAllow)
return nil
})Raw() returns the underlying *policyv1.PodDisruptionBudgetSpec for direct access when the typed API is insufficient:
m.EditSpec(func(e *editors.PodDisruptionBudgetSpecEditor) error {
e.Raw().MinAvailable = &customValue
return nil
})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("pdb.example.io/policy", "strict")
return nil
})func BasePDBMutation(version string) pdb.Mutation {
return pdb.Mutation{
Name: "base-pdb",
Feature: feature.NewVersionGate(version, nil),
Mutate: func(m *pdb.Mutator) error {
m.EditObjectMetadata(func(e *editors.ObjectMetaEditor) error {
e.EnsureLabel("app.kubernetes.io/version", version)
return nil
})
return nil
},
}
}
func StrictAvailabilityMutation(version string, enabled bool) pdb.Mutation {
return pdb.Mutation{
Name: "strict-availability",
Feature: feature.NewVersionGate(version, nil).When(enabled),
Mutate: func(m *pdb.Mutator) error {
m.EditSpec(func(e *editors.PodDisruptionBudgetSpecEditor) error {
e.ClearMinAvailable()
e.SetMaxUnavailable(intstr.FromInt32(1))
return nil
})
return nil
},
}
}
resource, err := pdb.NewBuilder(base).
WithMutation(BasePDBMutation(owner.Spec.Version)).
WithMutation(StrictAvailabilityMutation(owner.Spec.Version, owner.Spec.StrictMode)).
Build()When StrictMode is true, the PDB switches from percentage-based MinAvailable to an absolute MaxUnavailable of 1.
When false, only the base mutation runs and the original MinAvailable from the baseline is preserved. Neither mutation
needs to know about the other.
Feature: nil applies unconditionally. Omit Feature (leave it nil) for mutations that should always run. Use
feature.NewVersionGate(version, constraints) when version-based gating is needed, and chain .When(bool) for boolean
conditions.
MinAvailable and MaxUnavailable are mutually exclusive. When switching between them, always clear the opposing
field first. The typed API makes this explicit with ClearMinAvailable and ClearMaxUnavailable.
Register mutations in dependency order. If mutation B relies on state set by mutation A, register A first.