From 33b912691e34221ddc5296a4b437a64ac60c0030 Mon Sep 17 00:00:00 2001 From: Fabricio Aguiar Date: Tue, 27 Oct 2026 17:16:05 +0000 Subject: [PATCH] docs(object-controller): document standalone COD workflows Add a reusable sample and a tutorial for installing the standalone object-controller and managing ClusterObjectDeployment revisions. Explain ownership, forward revisions, status, deadlines, recovery, referenced content, history retention, and cleanup using the merged API. Embed the sample in the tutorial so its rendered manifest stays aligned with the executable example, and link it from the COS concepts page. Refs: OPRUN-4796 Signed-off-by: Fabricio Aguiar rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- .../olm_v1_clusterobjectdeployment.yaml | 31 +++ docs/draft/concepts/clusterobjectsets.md | 3 + .../manage-clusterobjectdeployments.md | 258 ++++++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 config/samples/olm_v1_clusterobjectdeployment.yaml create mode 100644 docs/draft/tutorials/manage-clusterobjectdeployments.md diff --git a/config/samples/olm_v1_clusterobjectdeployment.yaml b/config/samples/olm_v1_clusterobjectdeployment.yaml new file mode 100644 index 0000000000..dd9fb960a7 --- /dev/null +++ b/config/samples/olm_v1_clusterobjectdeployment.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: cod-example +--- +apiVersion: olm.operatorframework.io/v1 +kind: ClusterObjectDeployment +metadata: + name: cod-example +spec: + revisionHistoryLimit: 2 + template: + metadata: + labels: + app.kubernetes.io/name: cod-example + annotations: + example.com/description: Configuration managed by a standalone COD + spec: + collisionProtection: Prevent + progressDeadlineMinutes: 10 + phases: + - name: configuration + objects: + - object: + apiVersion: v1 + kind: ConfigMap + metadata: + name: cod-example-settings + namespace: cod-example + data: + message: Hello from revision A diff --git a/docs/draft/concepts/clusterobjectsets.md b/docs/draft/concepts/clusterobjectsets.md index f7f328d671..2ed94c03b4 100644 --- a/docs/draft/concepts/clusterobjectsets.md +++ b/docs/draft/concepts/clusterobjectsets.md @@ -21,6 +21,9 @@ Each ClusterObjectSet has: manages ClusterObjectDeployments instead of ClusterObjectSets directly. --> ClusterObjectSets can be used by any controller or system that needs to manage the rollout of a set of Kubernetes resources in a controlled, phased manner. Within OLM, the operator-controller uses ClusterObjectSets as the mechanism to deploy and upgrade ClusterExtensions. +For a standalone walkthrough that manages mutable desired content through COS +revisions, see [Manage standalone ClusterObjectDeployments](../tutorials/manage-clusterobjectdeployments.md). + The `group` value 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. Set `metadata.name` explicitly to `-`, with the revision in decimal diff --git a/docs/draft/tutorials/manage-clusterobjectdeployments.md b/docs/draft/tutorials/manage-clusterobjectdeployments.md new file mode 100644 index 0000000000..7c932ef23b --- /dev/null +++ b/docs/draft/tutorials/manage-clusterobjectdeployments.md @@ -0,0 +1,258 @@ +# Manage standalone ClusterObjectDeployments + +A `ClusterObjectDeployment` (COD) manages mutable desired content by creating +immutable `ClusterObjectSet` (COS) revisions. This tutorial installs the standalone +experimental object-controller, deploys a ConfigMap, and follows an A → B → A +template change through three forward revisions. + +The COD owns its COS revisions through controller owner references. Each COS owns +the Kubernetes objects it manages: + +```mermaid +flowchart LR + COD[ClusterObjectDeployment] --> COS[ClusterObjectSet revision] + COS --> CM[ConfigMap] +``` + +## Prerequisites + +- A Kubernetes test cluster where you can install CRDs and cluster-scoped RBAC. +- A schedulable Linux node labeled `node-role.kubernetes.io/control-plane`, + matching the chart's default node selector. If your cluster lacks this label, + customize `deployments.templateSpec.nodeSelector` when rendering the chart. +- The `cod-example` namespace must not already exist. The sample creates it; + cleanup deletes it and all resources in it. +- `kubectl` and Helm, with `kubectl` configured for the test cluster. +- A checkout of this repository. Run the commands below from its root directory. +- An object-controller image built from the checkout and available to the cluster. + See [Controller deployment](../concepts/clusterobjectsets.md#controller-deployment) + for build and image configuration details. For KIND, load the image into the + cluster before installing the controller. + +The example uses the new COD name and COS group `cod-example`. Use a fresh group +for the walkthrough: group membership identifies the revision family. + +## Install the standalone controller + +Set the image reference and install only object-controller and its experimental +APIs: + +```sh +OBJECT_CONTROLLER_IMAGE='' +helm template object-controller helm/olmv1 \ + --set options.featureSet=experimental \ + --set options.objectController.enabled=true \ + --set options.operatorController.enabled=false \ + --set options.catalogd.enabled=false \ + --set-string options.objectController.deployment.image="$OBJECT_CONTROLLER_IMAGE" \ + > /tmp/standalone-object-controller.yaml +kubectl apply -f /tmp/standalone-object-controller.yaml +kubectl -n olmv1-system rollout status deployment/object-controller-controller-manager --timeout=180s +``` + +This installation uses the chart's existing cluster-admin service account binding +for managing arbitrary Kubernetes resources. ClusterExtension and catalog +controllers are unnecessary for standalone COD use. + +## Create the first revision + +The sample in `config/samples/olm_v1_clusterobjectdeployment.yaml` creates a +namespace and a COD whose template contains one inline ConfigMap: + +```yaml +--8<-- "config/samples/olm_v1_clusterobjectdeployment.yaml" +``` + +```sh +kubectl apply -f config/samples/olm_v1_clusterobjectdeployment.yaml +kubectl wait --for=condition=Available clusterobjectdeployment/cod-example --timeout=180s +kubectl get clusterobjectdeployment cod-example +kubectl get clusterobjectsets --field-selector spec.group=cod-example +kubectl -n cod-example get configmap cod-example-settings -o jsonpath='{.data.message}{"\n"}' +``` + +The message is `Hello from revision A`. The COD creates `cod-example-1` with +`spec.group: cod-example`, `spec.revision: 1`, and `spec.lifecycleState: Active`. +Its `Progressing` condition becomes `False` with reason +`NewClusterObjectSetProgressed` after rollout completes, and `Available` becomes +`True`. + +The namespace in the sample is created separately from the COD's managed content. +The ConfigMap is owned by the COS. Inspect that ownership and record the +ConfigMap's UID for comparison after the update: + +```sh +kubectl -n cod-example get configmap cod-example-settings \ + -o jsonpath='{.metadata.ownerReferences[?(@.controller==true)].name}{"\n"}{.metadata.uid}{"\n"}' +``` + +## Change the template + +Change the ConfigMap message to B: + +```sh +kubectl patch clusterobjectdeployment cod-example --type=json \ + -p='[{"op":"replace","path":"/spec/template/spec/phases/0/objects/0/object/data/message","value":"Hello from revision B"}]' +kubectl wait --for=create clusterobjectset/cod-example-2 --timeout=180s +kubectl wait --for=condition=Ready clusterobjectset/cod-example-2 --timeout=180s +kubectl -n cod-example get configmap cod-example-settings \ + -o jsonpath='{.data.message}{"\n"}{.metadata.ownerReferences[?(@.controller==true)].name}{"\n"}{.metadata.uid}{"\n"}' +``` + +The message becomes `Hello from revision B`, ownership moves to `cod-example-2`, +and the ConfigMap keeps its UID. During the handoff, more than one COS can be +Active. An older Ready revision can keep the COD `Available=True` while its +successor is still progressing. Waiting for the new COS to become Ready observes +that specific rollout. + +Reapply the original sample to restore A: + +```sh +kubectl apply -f config/samples/olm_v1_clusterobjectdeployment.yaml +kubectl wait --for=create clusterobjectset/cod-example-3 --timeout=180s +kubectl wait --for=condition=Ready clusterobjectset/cod-example-3 --timeout=180s +kubectl -n cod-example get configmap cod-example-settings -o jsonpath='{.data.message}{"\n"}' +kubectl get clusterobjectsets --field-selector spec.group=cod-example +``` + +The message returns to `Hello from revision A` through revision 3. Restoring an +earlier template creates a new, higher revision; an archived revision cannot +become Active again. Reapplying an unchanged template keeps the current revision. + +### Which edits create revisions? + +| Edit | Result | +| --- | --- | +| Objects, references, phase/object order, probes, or collision protection in `spec.template.spec` | A new revision | +| Labels or annotations in `spec.template.metadata` | A new revision | +| `spec.template.spec.progressDeadlineMinutes` | A new revision, because the deadline is part of the template | +| Root COD labels or annotations | The current revision is retained | +| `spec.revisionHistoryLimit` | Retention changes; the current revision is retained | + +Template labels and annotations are placed on each generated COS. Set metadata +for a managed object in that object's manifest. The controller assigns COS group, +revision, lifecycle state, ownership, and the reserved +`olm.operatorframework.io/template-hash` label. + +## Read status and diagnose failures + +The COD exposes `Available` and `Progressing`. A child COS exposes `Ready` with +the details of its resource reconciliation. + +| COD condition | Status | Reason | Meaning | +| --- | --- | --- | --- | +| `Available` | `True` | `Available` | At least one active, non-deleting COS is Ready | +| `Available` | `False` | `NoActiveRevision` | No active, non-deleting COS exists yet | +| `Available` | `False` | `Unavailable` | Active revisions exist, but none are Ready | +| `Progressing` | `True` | `NewClusterObjectSetProgressing` | Waiting to create the desired revision, or rolling out while the COS reports `Pending` or `ObjectsNotReady` | +| `Progressing` | `True` | `RevisionPreconditionsNotMet` | The desired revision's preconditions are unmet, such as a mutable referenced Secret; reconciliation keeps retrying | +| `Progressing` | `False` | `NewClusterObjectSetProgressed` | The desired revision completed rollout at least once | +| `Progressing` | `False` | `ProgressDeadlineExceeded` | An incomplete, still-retrying revision exceeded its rollout budget | +| `Progressing` | `False` | `InvalidSpecification` | Template labels or annotations are invalid; correct them to allow creation | +| `Progressing` | `False` | `RevisionObjectCollision` | The desired revision's objects are owned by another controller or already exist; resolve the conflicting ownership | +| `Progressing` | `False` | `RevisionInvalidSpecification` | The desired revision's objects are invalid; correct the COD template | + +For revision-specific reasons, the COD's condition message names the latest +active COS. Inspect that COS's `Ready` condition for the specific cause. +`RevisionInvalidSpecification` concerns the revision's objects, while +`InvalidSpecification` concerns the COD's `spec.template.metadata` and prevents +creation of a new revision. + +Inspect the parent, the current child, and the controller logs: + +```sh +kubectl describe clusterobjectdeployment cod-example +kubectl get clusterobjectset cod-example-3 -o yaml +kubectl -n olmv1-system logs deployment/object-controller-controller-manager +``` + +A COS can report `Ready=False` with `PreconditionsNotMet` for a mutable +referenced Secret; before the deadline, the COD reports +`Progressing=True/RevisionPreconditionsNotMet`. A COS reports `ObjectsNotReady` +for objects still waiting on probes. A missing referenced Secret causes a +retryable read error in controller logs and leaves the COS's existing `Ready` +condition unchanged. A newly created COS can remain `Ready=Unknown` with reason +`Pending` until the Secret is available. + +Supply the expected immutable Secret content or resolve the readiness failure +and let reconciliation retry. Change the COD template when the desired object +content needs to change. See [COS status conditions](../concepts/clusterobjectsets.md#status-conditions) +for the child reasons. + +### Deadlines and recovery + +`spec.template.spec.progressDeadlineMinutes` supplies one budget for the whole +revision. The sample uses 10 minutes, the minimum; the maximum is 720 minutes. +Omit the field to disable deadline enforcement. + +Timing starts at COS creation. Failed or delayed creation attempts consume no +budget. Completing phases, polling, retries, and controller restarts preserve the +same deadline. A five-phase revision with a ten-minute deadline has ten minutes +total. Each new revision receives a fresh budget. + +The COD schedules its own expiry check even when the COS emits no further events. +The deadline still applies while preconditions are unmet: after expiry, the COD +reports `Progressing=False/ProgressDeadlineExceeded` while the COS retains its +readiness failure. `RevisionObjectCollision` and `RevisionInvalidSpecification` +require user action and take precedence over `ProgressDeadlineExceeded`, even +after expiry. The legacy deadline field remains available on directly created +COSs; COD enforces its budget independently. + +Reconciliation continues after expiry. Completion of the entire revision records +`COS.status.completedAt` and recovers the COD to +`Progressing=False/NewClusterObjectSetProgressed`. Completing only some phases +does not clear an expired deadline. Once completion is recorded, later health regressions +can affect availability but never restart that revision's rollout timer. + +## Inspect history and referenced content + +The COS controller archives superseded revisions after handoff and cleans up +objects that were removed from the desired content. COD prunes excess archived +history after teardown completes. Active revisions are excluded from the limit. + +The sample retains two archived revisions with `spec.revisionHistoryLimit: 2`. +The default is 5, the supported range is 0–50, and 0 retains no archived revisions. +After A → B → A, revisions 1 and 2 can remain as archived history while revision 3 +is Active: + +```sh +kubectl get clusterobjectsets --field-selector spec.group=cod-example \ + -o custom-columns=NAME:.metadata.name,REVISION:.spec.revision,STATE:.spec.lifecycleState +``` + +For larger content, each template object can use the existing COS `ref` mechanism +to read a JSON manifest from an immutable Secret. Create the referenced Secret +before applying the COD; it must remain available for every retained revision +that references it. Changing the intended content requires a new Secret reference +in the template. See [Object storage](../concepts/clusterobjectsets.md#object-storage-inline-vs-references). +Standalone callers manage the lifetime of these content Secrets. + +ClusterExtension integration is separate work tracked by +[OPRUN-4744](https://redhat.atlassian.net/browse/OPRUN-4744). The current +ClusterExtension path still creates COSs directly; this standalone workflow uses +object-controller without bundle resolution or catalog components. + +For all fields, see the [COD API reference](../../api-reference/olmv1-api-reference.md#clusterobjectdeployment). + +## Clean up + +Deleting the COD cascades to its owned COSs and managed objects. Wait for the +ConfigMap to disappear before removing the separately created namespace. Only +remove `cod-example` if it was created for this walkthrough and contains no +unrelated resources: + +```sh +kubectl delete clusterobjectdeployment cod-example +kubectl -n cod-example wait --for=delete configmap/cod-example-settings --timeout=180s +kubectl delete namespace cod-example +``` + +After cleaning up the example resources, remove the standalone installation only +if every resource in the rendered manifest is dedicated to this walkthrough. The +manifest includes the `olmv1-system` namespace and the COD/COS CRDs; deleting them +also deletes the namespace's contents and all COD/COS instances. If any resources +are shared, leave the installation in place: + +```sh +kubectl delete -f /tmp/standalone-object-controller.yaml +```