ClusterObjectDeployments
A ClusterObjectDeployment (COD) is an experimental, cluster-scoped API that
manages mutable desired content through immutable
ClusterObjectSet (COS) revisions. Update the deployment's
template to change what should be deployed; the controller creates a new revision
rather than modifying an existing revision's content.
For a hands-on walkthrough, see Manage standalone ClusterObjectDeployments. For field definitions, see the COD API reference.
Responsibilities
COD and COS separate rollout orchestration from resource reconciliation:
| ClusterObjectDeployment | ClusterObjectSet |
|---|---|
| Stores the mutable desired template | Records the content of one revision |
| Creates and owns revisions | Applies objects in ordered phases |
| Enforces rollout deadlines | Evaluates readiness and records first completion |
| Reports deployment availability and progress | Reports resource reconciliation through Ready |
| Prunes excess archived history | Handles object ownership handoff and teardown |
Both APIs are reconciled by the experimental object-controller. They can be used without ClusterExtension or ClusterCatalog resources. See Controller deployment for standalone installation details.
Desired content and rollout policy
A COD has three main spec fields:
spec.templatecontains the desired revision content and revision metadata.spec.progressDeadlineMinutesconfigures the rollout budget independently of revision content.spec.revisionHistoryLimitcontrols how much archived history to retain.
For example, this deployment allows ten minutes for a revision to roll out and retains two archived revisions:
apiVersion: olm.operatorframework.io/v1
kind: ClusterObjectDeployment
metadata:
name: my-app
spec:
progressDeadlineMinutes: 10
revisionHistoryLimit: 2
template:
metadata:
labels:
app.kubernetes.io/name: my-app
spec:
collisionProtection: Prevent
phases:
- name: configuration
objects:
- type: InlineManifest
inlineManifest:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-settings
namespace: default
data:
message: Hello from revision A
The template's spec contains phases, optional custom progression probes, and
collision protection, with the same semantics as the corresponding COS fields.
Objects can be embedded as inlineManifest or stored in immutable Secrets and
referenced with secretRef. The controller assigns each revision's group,
revision number, lifecycle state, and controller owner reference; callers cannot
set these through the template.
spec.template.metadata contains labels and annotations propagated to each COS,
not to its managed objects. Set a managed object's metadata in its own manifest.
The reserved olm.operatorframework.io/template-hash label is always set by the
controller to identify the template that produced a revision.
Revision lifecycle
The COD's name identifies its COS group. Names must be 1–52 characters long, start with a lowercase letter, contain only lowercase letters, digits or hyphens, and end with a letter or digit.
When a new revision is needed, the controller:
- Finds the highest revision number in the group, including archived and foreign-owned revisions.
- Allocates the next number (
1for an empty group) and creates an owned, Active COS named<deployment-name>-<revision>. - Observes that revision's rollout while older revisions can remain Active.
- Reports completion once the new COS records
status.completedAt.
The COS controller handles ownership handoff between revisions and archives superseded revisions once handoff completes. The COD controller prunes excess archived history after teardown finishes.
Revision numbers range from 1 to 9999999999. Once that range is exhausted, the controller reports an error rather than allocating another revision.
Which edits create revisions?
The controller compares the desired template with the latest owned, active, non-deleting revision's template identity. A different template, or the absence of an active revision, causes a new revision to be created, subject to the ownership rules.
| Edit | Result |
|---|---|
Objects, Secret references, phases, probes, or collision protection in spec.template.spec |
A new revision |
| Phase, object, or probe order | A new revision |
Labels or annotations in spec.template.metadata |
A new revision |
spec.progressDeadlineMinutes |
The existing revision is retained; its rollout budget changes |
spec.revisionHistoryLimit |
Retention changes; the existing revision is retained |
| Root COD labels or annotations | The existing revision is retained |
Reapplying an unchanged template retains the current active revision. Restoring an earlier template after a different one has been deployed creates a new, higher-numbered revision; it does not reactivate archived history. For example, A → B → A produces revisions 1, 2, and 3.
Revision ownership
Group membership determines revision ordering; controller owner references determine which revisions a COD manages. Sharing a group does not by itself make a COS owned by the deployment.
The controller can adopt an ownerless, non-deleting group member only when it
carries the apps.kubernetes.io/managed-by=object-controller label. It does not
replace another controller's owner reference. A stale reference to an older UID
of the same deployment is left for Kubernetes garbage collection rather than
rewritten.
If the highest active, non-deleting group member is controlled by another
controller, the COD reports Progressing=False/OwnerConflict and creates no new
revision. If that group member is ownerless but lacks the managed-by label, the
COD reports Progressing=False/UnmanagedRevision. Resolve the foreign ownership,
remove the blocking revision, or restore a missing managed-by label only when the
revision was actually created by object-controller. A stale owner reference to an
older UID also blocks new revisions until garbage collection removes it, without
a distinct condition reason.
OwnerConflict concerns ownership of the COS itself; RevisionObjectCollision
concerns ownership of Kubernetes objects within a revision. Ownership checks take
precedence over rollout completion and deadline checks. Availability still
reflects only owned active revisions, so an older ready revision can keep the COD
available during a conflict.
Status conditions
COD exposes Available and Progressing. Its child COSs expose Ready and
record first rollout completion in status.completedAt.
Available
Availability describes current health across owned, active, non-deleting revisions, not just the latest revision:
| Status | Reason | Meaning |
|---|---|---|
| True | Available |
At least one owned active revision is Ready |
| False | NoActiveRevision |
No owned active revision exists |
| False | Unavailable |
Owned active revisions exist, but none are Ready |
An older ready revision can keep Available=True while its successor rolls out,
is stalled, or has exceeded its rollout deadline.
Progressing
Progress describes the desired rollout, including failures that prevent it from starting or completing:
| Status | Reason | Meaning |
|---|---|---|
| True | NewClusterObjectSetProgressing |
Waiting to create or adopt revisions, or the desired revision is rolling out |
| True | RevisionPreconditionsNotMet |
The desired revision's preconditions are unmet; reconciliation keeps retrying |
| False | NewClusterObjectSetProgressed |
The desired revision completed rollout at least once |
| False | ProgressDeadlineExceeded |
An incomplete, still-retrying revision exceeded its rollout budget |
| False | InvalidSpecification |
Template labels or annotations are invalid; no revision is created until corrected |
| False | OwnerConflict |
The highest active group member is controlled by another controller |
| False | UnmanagedRevision |
The highest active group member is ownerless and lacks the object-controller managed-by label |
| False | RevisionObjectCollision |
The desired revision cannot take ownership of one or more managed objects |
| False | RevisionInvalidSpecification |
The desired revision's rendered objects are invalid |
For revision-specific failures, the COD's condition message names the COS to
inspect. Its Ready condition provides the specific cause.
RevisionInvalidSpecification concerns the revision's rendered objects, while
InvalidSpecification concerns the COD's spec.template.metadata.
RevisionObjectCollision and RevisionInvalidSpecification are hard stalls:
they require user action and take precedence over ProgressDeadlineExceeded.
RevisionPreconditionsNotMet is a retrying state and remains subject to the
rollout deadline.
Completion is historical. Once the desired COS records status.completedAt,
later health regressions can affect Available but do not restart that
revision's rollout or its deadline. A new template starts a new rollout.
Progress deadlines and recovery
spec.progressDeadlineMinutes supplies one budget for the whole revision. The
supported range is 1–720 minutes. Omit the field to disable deadline enforcement.
The deadline belongs to the COD; COSs do not carry a deadline field.
Timing starts at the COS's creation timestamp. Failed or delayed creation attempts consume no budget. Completing phases, polling, retries, and controller restarts do not reset the timer. A five-phase revision with a ten-minute deadline has ten minutes total, not ten minutes per phase. Each new revision receives a fresh budget.
Changing the deadline does not create a revision or reset its start time. The new budget applies to the latest active revision and is still measured from that COS's original creation timestamp:
- Extending the budget beyond elapsed rollout time can clear an existing
ProgressDeadlineExceededcondition without new rollout progress. - Shortening the budget below elapsed rollout time can cause immediate expiry.
- Removing the field disables enforcement, including after expiry.
The COD schedules its own expiry check even if the COS emits no further events.
When an incomplete, retrying revision exceeds the budget, the COD reports
Progressing=False/ProgressDeadlineExceeded. The COS's Ready condition retains
its resource reconciliation status; for example, it can still report
PreconditionsNotMet. Hard stalls and revision ownership conflicts retain their
more actionable condition reasons rather than being replaced by a timeout.
Reconciliation continues after expiry. A revision that later completes records
status.completedAt, and the COD reports
Progressing=False/NewClusterObjectSetProgressed. Completing only some phases
does not clear an expired deadline. Once completion is recorded, the deadline no
longer applies to that revision, even if it later becomes unhealthy.
Revision history and deletion
spec.revisionHistoryLimit controls how many archived revisions the COD retains.
The default is 5, the supported range is 0–50, and 0 retains no archived history.
Active revisions never count against the limit and are not pruned by it.
Only owned archived revisions that have finished teardown are eligible for pruning. The controller deletes the oldest eligible revisions beyond the limit; revisions still tearing down are left until cleanup completes. Foreign-owned group members do not count toward the limit and are not pruned by the COD.
Deleting a COD normally cascades to its owned COSs and their managed objects. Deleting with orphan propagation leaves the revisions in their existing group; it does not change revision membership or ownership handoff behavior.
Relationship with ClusterExtension
When BoxcutterRuntime is enabled, operator-controller resolves and unpacks a
ClusterExtension's bundle and applies a COD with the same name. The
ClusterExtension owns the COD, the COD owns its COS revisions, and each COS owns
the Kubernetes objects it manages.
The COD's name also becomes the COS group. Revision lookup uses that group, not labels or controller owner references. Operator-controller observes COD rollout status to report the extension's installation and progress.
For a ClusterExtension, COD OwnerConflict maps to Progressing=True with reason
Retrying, preserving the deployment's message about the conflicting revision
and its controller. A first install reports Installed=False with reason
Failed. During an upgrade, the previously installed bundle and its
Installed=True condition are preserved until the new rollout completes, with
observedGeneration updated to acknowledge the current spec. Reconciliation
resumes automatically when the ownership conflict clears.