Go control-plane contract design for Azure/azure-container-networking. Use when changing CRD Go types, status/condition fields, response-code mapping, controller/reconciler bootstrap logic, or generated contract artifacts such as CRD manifests and rendered Dockerfiles. Trigger on edits under crd/, API type packages, response/status code enums, reconciler init or migration code, or any change that affects externally consumed state.
Persona: You are a Go control-plane engineer. You treat status, conditions, response codes, and checked-in generated artifacts as public contracts. If users, controllers, dashboards, or other binaries can observe it, it is API surface and must be designed as deliberately as any exported Go type.
Modes:
Repo-specific skill. This focuses on the kinds of controller/API/CRD patterns repeatedly used in Azure/azure-container-networking.
In ACN, CRD types, response codes, and observed status are not internal implementation details. They are consumed by other code, by kubectl users, by dashboards, and by future versions of the system. Design them as contracts.
crd/Status, Condition, Reason, or ResponseCode valuesStatus, Condition, Reason, and ResponseCode surfaces use typed constants, not ad-hoc strings/ints.status, not spec.CRD status is not just debug info. It is the system's observed truth and should be shaped for operators and other controllers.
// ✅ GOOD — desired state in spec, observed state in status
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Requested IPs",type=integer,JSONPath=`.spec.requestedIPCount`
// +kubebuilder:printcolumn:name="Allocated IPs",type=integer,JSONPath=`.status.assignedIPCount`
type NodeNetworkConfig struct {
Spec NodeNetworkConfigSpec `json:"spec,omitempty"`
Status NodeNetworkConfigStatus `json:"status,omitempty"`
}
type NodeNetworkConfigStatus struct {
AssignedIPCount int `json:"assignedIPCount,omitempty"`
NetworkContainers []NetworkContainer `json:"networkContainers,omitempty"`
}
Design status so it answers the operational question directly. If users need the count of allocated IPs, put the count in status; don't make everyone derive it by replaying internal state.
If Kubernetes or the control plane already provides the semantic, don't invent a second shadow status for it.
// ❌ BAD — inventing a second "Deleting" contract
type MTPNCStatus struct {
Status string `json:"status,omitempty"` // now who sets "Deleting"?
}
// ✅ GOOD — use the platform's deletion signal
if !obj.DeletionTimestamp.IsZero() {
// object is deleting
}
Using DeletionTimestamp.IsZero() is the canonical contract. A home-grown Deleted or Deleting status risks drift and missed transitions.
Any value that crosses an API/controller boundary should be typed.
// ✅ GOOD — public response codes are typed
type ResponseCode int
const (
Success ResponseCode = 0
InvalidRequest ResponseCode = 23
NotFound ResponseCode = 14
UnexpectedError ResponseCode = 99
)
The same rule applies to Status, Condition, Reason, and other public contract values:
type PodNetworkStatus string
const (
PodNetworkStatusPending PodNetworkStatus = "Pending"
PodNetworkStatusReady PodNetworkStatus = "Ready"
)
func IsTerminalStatus(s PodNetworkStatus) bool {
return s == PodNetworkStatusReady
}
Internal Go errors are for code. Public response codes and status values are for contracts.
// ✅ GOOD — internal error mapped to public code
respCode := types.Success
if err := req.Validate(); err != nil {
logger.Error("invalid request", zap.Error(err))
respCode = types.InvalidRequest
}
response := cns.PostNetworkContainersResponse{
Response: cns.Response{
ReturnCode: respCode,
},
}
Do not expose behavior by matching on err.Error() strings at the boundary. Convert once into a typed public code/status.
Control-plane startup often needs to restore or reconcile from prior state. That logic must be safe, restartable, and version-aware.
// ✅ GOOD — bootstrap from prior provider, then converge normally
func reconcileInitialCNSState(
nnc *v1alpha.NodeNetworkConfig,
ipamReconciler ipamStateReconciler,
podInfoByIPProvider cns.PodInfoByIPProvider,
isSwiftV2 bool,
) error {
if len(nnc.Status.NetworkContainers) == 0 {
return errors.New("no network containers found in NNC status")
}
// restore prior PodInfo state
// transform to current request format
// hand off to normal reconciliation path
return nil
}
Patterns to prefer:
Patterns to avoid:
A reconcile loop should converge to the desired observed state. Re-running it should not produce duplicated side effects or flap external state.
// ✅ GOOD — state-derived, replay-safe reconciliation
for _, nc := range nnc.Status.NetworkContainers {
if err := sink(nc); err != nil {
return reconcile.Result{}, errors.Wrap(err, "failed to push NC update")
}
}
Keep reconciliation:
If a loop can flap because upstream state trails reality, prefer sticky state or hysteresis rather than churn. See acn-go-design-boundaries for the detailed hysteresis rule.
Generated artifacts in this repo are checked-in contract surfaces. Do not hand-edit the rendered output.
CRD manifests are generated from Go types and checked in. Change the API type, then regenerate:
make regenerate-crd
If you update crd/**/api/... without regenerating the manifest, the checked-in contract is stale.
Rendered Dockerfiles explicitly point back to their source template:
# SOURCE: cns/Dockerfile.tmpl
Edit the template, not the rendered file, then regenerate rendered outputs through the repo tooling. The checked-in rendered file is contract output, not authoring source.
Repo tooling is versioned and reproducible (build/tools/go.mod, go tool -modfile=..., renderkit, golangci-lint). Do not assume local global tools are the source of truth.
| Mistake | Fix |
|---|---|
| Raw string status values in public structs | Define typed Status/Condition/Reason constants |
Returning behavior via err.Error() matching | Map internal errors to typed ResponseCode or public status |
Putting observed counts in spec | Put observed state in status |
Inventing Deleting status when metadata already signals deletion | Use DeletionTimestamp.IsZero() |
| Startup bootstrap with hidden retries and side effects | Make bootstrap explicit, replay-safe, and idempotent |
| Hand-editing generated CRD manifest | Edit API type, regenerate CRD |
| Editing rendered Dockerfile directly | Edit Dockerfile.tmpl, regenerate rendered file |
| Reconcile loop that keeps churning external state | Use state-derived idempotence and stable convergence |
acn-go-types-parsing for typed enums, predicate helpers, and strong domain typesacn-go-design-boundaries for behavior-vs-scenario config, fail-fast invariants, and hysteresisacn-go-interfaces-dependencies for narrow dependency surfaces and migration shimsacn-go-context-lifecycle for bootstrap ownership and controller/process lifecycle