# Operator — Architecture

The operator manages the lifecycle of a Calico or Calico Enterprise installation: it owns the `operator.tigera.io` CRDs, renders the Kubernetes resources for each component, and reports their state through the TigeraStatus API. This document is the architecture and the invariants; operational guidance (build, test, debug recipes) lives in [`operator/CLAUDE.md`](./CLAUDE.md).

## Cross-cutting

- Repo-wide architecture, the component inventory, and Go module structure: see the root [`DESIGN.md`](../DESIGN.md).

## API Design

API design principles and the Go/kubebuilder coding conventions for `api/v1` CRD types have their own guide: see [`docs/api_design.md`](docs/api_design.md).

## Respect User Input

- **Never overwrite user-specified fields.** If a user sets a value on a resource, the operator must not silently replace it with a default or computed value.
- **Never delete user-created resources.** Secrets, ConfigMaps, and other resources created by the user are theirs. The operator should not remove them.
- **`operator.tigera.io` resources are user input too.** The operator must not delete its own CRs, not even the ones its controllers act on. If a resource can't be supported (a Goldmane CR on an Enterprise installation, say), set degraded with a message naming the resource and let the user delete it.
- **Error on inconsistent input, don't guess.** If a user provides configuration that is contradictory or ambiguous, surface a clear error rather than assuming intent. Guessing leads to subtle, hard-to-debug behavior.
- **Track field ownership on shared resources.** Where possible, write fields to APIs like FelixConfiguration, BGPConfiguration, etc. Use an annotation to track which fields the operator originally set. Never update or remove a field that wasn't set by the operator — that would overwrite user intent. If fields on these objects conflict with `operator.tigera.io` API configuration, surface an error.
- **Copy user-provided resources downstream, never modify them.** Users provide input (custom certs, ConfigMaps, pull secrets, etc.) in the `tigera-operator` namespace. The operator copies and reconciles those objects into the downstream namespaces that need them, but must never edit, update, or delete the originals.

## Component Isolation

- **New components default to `calico-system`.** Historically, each component got its own namespace for RBAC, NetworkPolicy, and resource cleanup isolation. In practice this increases cluster footprint significantly. Modern practice is to place new components in `calico-system` and only use a separate namespace when circumstances specifically call for it (e.g., strict multi-tenant isolation requirements).
- **One CRD per component.** Each component has its own CRD, controller, and status manager. Controllers interact through the Kubernetes API, not by calling each other directly.

## Controller Design

- **Watch, reconcile, render, apply.** Controllers follow a consistent pattern:
  1. Watch the primary CRD and dependent resources.
  2. Read current state from the Kubernetes API.
  3. Call into `pkg/render` to generate desired resources.
  4. Apply via `CreateOrUpdateOrDelete`.
  5. Report status via the TigeraStatus API.
- **Render packages are pure.** The `pkg/render` package generates Kubernetes manifests from inputs. It should not make API calls or have side effects — that's the controller's job.
- **Status messages are for users, not developers.** TigeraStatus conditions should be actionable and user-facing. Don't surface internal error strings or stack traces.

## Variants

- **Core code is variant-blind.** Controllers and render packages outside `pkg/enterprise` must not name a variant, in code or in comments. Behavior a single variant needs registers through `pkg/extensions`.
- **A controller only one variant runs lives in `pkg/enterprise/controller`, and its render code in `pkg/enterprise/render`.** Both mirror the core tree they came from. The controller is contributed through the controller list on `ControllerOptions` rather than named by `AddToManager`, so it carries no variant check of its own.
- **User overrides apply after a variant's modifier, not before.** A component whose workloads take overrides implements `render.Overridable`: `ObjectsBeforeOverrides` renders without them, and `OverrideTargets` names each overridden object (kind, namespace, name) and its override struct. `extensions.Decorate` runs the modifier on `ObjectsBeforeOverrides`, applies the overrides once, then runs any `WithDerive` stage, which builds objects copied from overridden ones. A wrapper around an overridable component must embed `render.Overridable` rather than `render.Component`, or `Decorate` falls back to `Objects()` and anything the modifier adds misses the user's overrides.

## Component Versions and Publishing

- **The versions the operator deploys are build inputs, not source.** `CALICO_VERSION`, `CALICO_REGISTRY` and `CALICO_IMAGE_PATH`, and `OPERATOR_IMAGE_REGISTRY` and `OPERATOR_IMAGE_PATH` for the operator's own image, are stamped into `pkg/components` at link time. Nothing in the tree records the tag a build resolves, so a component list must never grow a version literal alongside its image names.
- **The operator ships as a Calico component image.** It publishes as `calico/operator` to the same registries as the rest, on the Calico version stream, and a Calico release builds it rather than retagging one built for a different set of component versions.
- **A release names the version it deploys.** The operator's own tag and the versions it resolves are separate inputs. An operator-only release carries its own tag while still deploying the components of the release it patches, so a release that leaves `CALICO_VERSION` unset is refused rather than defaulted.
- **A build that names no version resolves the branch tag.** Development and release-branch builds fall back to the tag CI publishes for the branch, so an operator built from a checkout deploys images that exist.

## Security

- **Component-to-component communication must be authenticated and encrypted.** Use mTLS or TLS + token-based authentication for all internal communication between operator-managed components.

## Resource Ownership

- **Operator-created resources use OwnerReferences.** Objects created by the operator should have OwnerReferences so they get cleaned up automatically. Exceptions exist for resources where cascading deletion would be catastrophic (e.g., CRDs).
- **User-provided resources must NOT have OwnerReferences.** This is how the operator distinguishes user-provided from operator-managed resources. They may be copied to other namespaces (with OwnerReferences on the copies), but the originals must not be claimed.