Skip to content

⚠ Introduce ClusterExtensionRevision API #2051

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

anik120
Copy link
Contributor

@anik120 anik120 commented Jun 26, 2025

Description

  • When a ClusterExtension is created with a pinned version
$ kubectl create -f - <<EOF
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
  name: strimzi-kafka-operator-ext
spec:
  # The namespace where the operator will be installed.
  namespace: kafka
  serviceAccount:
    name: strimzi-installer-sa
  source:
    sourceType: Catalog
    catalog:
      packageName: strimzi-kafka-operator
      # The version of the operator.
      version: "0.31.1"
EOF

the ClusterExtension is installed and no CluterExtensionRevision is created

$ kubectl get clusterextension 
NAME                         INSTALLED BUNDLE                   VERSION   INSTALLED   PROGRESSING   AGE
strimzi-kafka-operator-ext   strimzi-cluster-operator.v0.31.1   0.31.1    True        True          22s

$ kubectl get clusterextensionrevision 
no resources found 
  • When a ClusterExtension is created with a version range
$ kubectl create -f - <<EOF
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
  name: strimzi-kafka-operator-ext
spec:
  # The namespace where the operator will be installed.
  namespace: kafka
  serviceAccount:
    name: strimzi-installer-sa
  source:
    sourceType: Catalog
    catalog:
      packageName: strimzi-kafka-operator
      # The version range for the operator.
      version: ">=0.28.0 <=0.31.1"
EOF

the latest from that version range is installed (existing behavior, using the resolver)

$ kubectl get clusterextension 
NAME                         INSTALLED BUNDLE                   VERSION   INSTALLED   PROGRESSING   AGE
strimzi-kafka-operator-ext   strimzi-cluster-operator.v0.31.1   0.31.1    True        True          14s

When the ClusterExtension is edited to change the version range from >=0.28.0 <=0.31.1 to >=0.28.0 <=0.32.0

$ kubectl get clusterextension -o yaml 
apiVersion: v1
items:
- apiVersion: olm.operatorframework.io/v1
  kind: ClusterExtension
  metadata:
    creationTimestamp: "2025-06-27T15:17:04Z"
    finalizers:
    - olm.operatorframework.io/cleanup-unpack-cache
    - olm.operatorframework.io/cleanup-contentmanager-cache
    generation: 2
    name: strimzi-kafka-operator-ext
    resourceVersion: "12956"
    uid: e9b13014-29b1-47b6-b6e8-cc729c03b585
  spec:
    namespace: kafka
    serviceAccount:
      name: strimzi-installer-sa
    source:
      catalog:
        packageName: strimzi-kafka-operator
        upgradeConstraintPolicy: CatalogProvided
        version: '>=0.28.0 <=0.32.0'
      sourceType: Catalog
.
.
.

a ClusterExtensionRevision is created for the new available version (notifying that an upgrade is available)

$ kubectl get clusterextensionrevisions.olm.operatorframework.io 
NAME                                EXTENSION                    VERSION   APPROVED   APPROVED AT   AGE
strimzi-kafka-operator-ext-0.32.0   strimzi-kafka-operator-ext   0.32.0                             24s

Once the approval is granted (manually for now, until the Policy API is implemented and automatic upgrades for CRDs that pass the default allowed upgrade clauses on cluster is in place), the ClusterExtension is upgraded (ie ClusterExtension waits for approval while an upgrade is available):

 $ kubectl get clusterextensionrevisions.olm.operatorframework.io         
NAME                                EXTENSION                    VERSION   APPROVED   APPROVED AT   AGE
strimzi-kafka-operator-ext-0.32.0   strimzi-kafka-operator-ext   0.32.0    true       4s            73s

$ kubectl get clusterextension 
NAME                         INSTALLED BUNDLE                   VERSION   INSTALLED   PROGRESSING   AGE
strimzi-kafka-operator-ext   strimzi-cluster-operator.v0.32.0   0.32.0    True        True          2m49s

Once the upgrade is successful, the ClusterExtensionRevision is cleaned up:

$ kubectl get clusterextensionrevision 
No resources found
  • Architecture diagram of current, after (this PR), and "future" (with the Policy API)

Current:

graph TD
    A["User creates/edits ClusterExtension<br>spec.version = '>=0.28.0 <=0.31.1'"] --> B{OLM Resolves};
    B --> C["OLM Installs Latest Version in Range<br>e.g., 0.31.1"];
    C --> D((End State: Installed));
Loading

With this PR:

graph TD
    subgraph "Initial Install"
        A["User creates ClusterExtension<br>spec.version = '>=0.28.0 <=0.31.1'"] --> B["OLM Installs Latest in Range<br>e.g., v0.31.1"];
        B --> C((State: Installed));
    end

    C --> D["User edits ClusterExtension<br>spec.version = '>=0.28.0 <=0.32.0'"];

    subgraph "Upgrade Flow (New)"
        D --> E{OLM detects a newer version is available};
        E --> F["✨ Creates ClusterExtensionRevision for v0.32.0"];
        F --> G{"Upgrade Paused<br>Awaiting Approval"};
        G -- "Manual Approval Granted" --> H["OLM proceeds with upgrade"];
        H --> I["Cleans up the ClusterExtensionRevision"];
        I --> J((End State: Upgraded & Installed));
    end
Loading

With the Policy API

graph TD
    A["OLM detects a newer version is available"] --> B("✨ Policy API evaluates the proposed upgrade");

    subgraph "Policy Engine Evaluation"
        direction TB
        B --> C{"CRD breaking change?<br><strong>(Default: Block)</strong>"};
        C --> D{"RBAC permissions expanded?<br><em>(Opt-in policy)</em>"};
        D --> E{"New sensitive resources introduced?<br><em>(Opt-in policy)</em>"};
        E --> F{"Crosses major version?<br><em>(Configurable policy)</em>"};
    end

    F --> G{Any active policy violated?};

    G -- "No violations" --> H["✅ Upgrade is Automatically Approved"];

    G -- "Yes, policy violated" --> I["❌ Upgrade is Blocked"];

    subgraph "Manual Approval Workflow"
        direction TB
        I --> J["A <strong>ClusterExtensionRevision</strong> is created<br>containing the reason for the block"];
        J --> K["Notifier alerts administrators"];
        K --> L{"Administrator reviews the revision<br>and provides manual approval"};
    end

    H --> M["OLM proceeds with the upgrade"];
    L -- "On Approval" --> M;

    M --> N((End State: Upgraded & Installed));
Loading

Reviewer Checklist

  • API Go Documentation
  • Tests: Unit Tests (and E2E Tests, if appropriate)
  • Comprehensive Commit Messages
  • Links to related GitHub Issue(s)

@anik120 anik120 requested a review from a team as a code owner June 26, 2025 13:27
Copy link

openshift-ci bot commented Jun 26, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign thetechnick for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-merge-robot
Copy link

PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 26, 2025
Copy link

netlify bot commented Jun 26, 2025

Deploy Preview for olmv1 ready!

Name Link
🔨 Latest commit 2c5e338
🔍 Latest deploy log https://app.netlify.com/projects/olmv1/deploys/68600e8f35df3c000870178c
😎 Deploy Preview https://deploy-preview-2051--olmv1.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@anik120 anik120 marked this pull request as draft June 26, 2025 13:27
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 26, 2025
@anik120 anik120 force-pushed the clusterextensionrevision branch 3 times, most recently from a85e3c0 to bca3b28 Compare June 27, 2025 12:27
Copy link
Contributor

@everettraven everettraven left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saw this come through in my notification inbox and even though it is a draft thought I'd give some early feedback on the API

//
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule="self.matches(\"^([0-9]+)(\\\\.[0-9]+)?(\\\\.[0-9]+)?(-([-0-9A-Za-z]+(\\\\.[-0-9A-Za-z]+)*))?(\\\\+([-0-9A-Za-z]+(-\\\\.[-0-9A-Za-z]+)*))?\")",message="version must be well-formed semver"
Version string `json:"version"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a CEL validation means you need to add a maximum length or you risk easily exceeding your cost budget as it calculates it using the worst-case scenario (which in this case would be the longest possible string value).

Additionally, I suspect the empty string is not a valid value here - meaning you probably want a minimum length to prevent the value being able to be set to the empty string.

// as being available. This helps track how long an upgrade has been pending.
//
// +kubebuilder:validation:Required
AvailableSince metav1.Time `json:"availableSince"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kubernetes API conventions generally encourage time based naming to be of the format {thing}Time. In this case it would be something like AvailableTime as opposed to AvailableSince.

https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#naming-conventions

Comment on lines +58 to +62
// approvedAt indicates when this upgrade revision was approved for execution.
// This field is set automatically when the approved field changes from false to true.
//
// +optional
ApprovedAt *metav1.Time `json:"approvedAt,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better suited for a status field/condition?

I'd suggest going the direction of a status condition instead of a field because you would automatically get the transition time when you flip the Approved status condition to the True state.

// ClusterExtension to trigger the upgrade to this version.
//
// +optional
Approved bool `json:"approved,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use a boolean. It is strongly encouraged to avoid the use of booleans because they often evolve beyond the true/false value.

Use an enum instead that allows the empty string for the unset case.

Comment on lines +69 to +71
// +kubebuilder:validation:Required
// +kubebuilder:validation:MaxLength:=253
// +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="name must be a valid DNS1123 subdomain"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a heads up that users can't see these validations in the generated documentation so it is strongly encouraged to explicitly include them in plain english sentences in the GoDoc.


// ClusterExtensionRevisionStatus defines the observed state of ClusterExtensionRevision
type ClusterExtensionRevisionStatus struct {
// conditions represent the latest available observations of the ClusterExtensionRevision's current state.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you know the condition types you'll be using, it can be helpful for users to include them in the GoDoc so that they can use tools like kubectl explain ... and get that information included.

// status represents the current status of this ClusterExtensionRevision.
//
// +optional
Status ClusterExtensionRevisionStatus `json:"status,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a pointer

@anik120 anik120 force-pushed the clusterextensionrevision branch from bca3b28 to 9817f9a Compare June 27, 2025 14:34
@anik120 anik120 force-pushed the clusterextensionrevision branch from 9817f9a to 2c5e338 Compare June 28, 2025 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants