From eef928c1063897d631ff0452e1750c0440ffb3e3 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:22:41 +0200 Subject: [PATCH] fix(olm): support the 26.7.0 chart and registry layout SDP 26.7.0 changed how the operator Helm charts reference registries, which broke OLM manifest generation in three ways: * Operator images moved under the "sdp" path on quay.io, so the digest lookup no longer resolved. Introduce QUAY_REPO for the repository prefix. Also add the trailing slash to the tag API URL, which quay now 308-redirects to. * The charts no longer define image.repository in values.yaml; it comes from a per-registry overlay instead. Without one the chart fails to render, and the new IMAGE_REPOSITORY env var (which tells the operator where to pull product images from) has no value. Pass values/quay.io.yaml so bundles reference quay.io throughout. * The product cluster role is now empty unless the OpenShift API is advertised, because its only remaining rule is the securitycontextconstraints grant. Pass --api-versions security.openshift.io/v1 and drop the Python patching of that rule. The charts gate it themselves, and some operators gate more than one cluster role this way (spark has three, opa has two) which the single-role patch silently missed. Also correct the module docstring, which documented a nonexistent --output-dir, and the secret/listener rejection message, which pointed at a deleted script. Co-Authored-By: Claude Opus 5 (1M context) --- olm/build-manifests.py | 58 +++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/olm/build-manifests.py b/olm/build-manifests.py index f178ad5..2d7a607 100755 --- a/olm/build-manifests.py +++ b/olm/build-manifests.py @@ -8,15 +8,22 @@ """ (Re)Generate Stackable operator manifests for the Operator Lifecycle Manager (OLM). -The script renders the Helm chart, looks up image digests on -quay.io, and writes a complete OLM bundle under deploy/olm//. +The script renders the Helm chart, looks up image digests on quay.io, and writes a +complete OLM bundle to /operators/stackable-//. + +Ensure the operator repository is checked out at the release tag before running this. Usage: - uv run --script olm/generate-olm.py --version 26.3.0 --repo-operator ~/repo/stackable/airflow-operator --output-dir deploy/olm + uv run --script olm/build-manifests.py \ + --release 26.7.0 \ + --openshift-versions v4.18-v4.22 \ + --repo-operator ~/repo/stackable/airflow-operator \ + --repo-certified-operators ~/repo/stackable/openshift-certified-operators # Or directly with python3 (PyYAML must be installed): - python3 olm/generate-olm.py --version 26.3.0 --repo-operator ~/repo/stackable/airflow-operator --openshift-versions v4.18-v4.21 + python3 olm/build-manifests.py --release 26.7.0 --openshift-versions v4.18-v4.22 \ + --repo-operator ~/repo/stackable/airflow-operator Requirements: - uv (https://docs.astral.sh/uv/) — installs PyYAML automatically @@ -39,6 +46,11 @@ __version__ = "0.0.1" +# Quay.io repository that hosts the operator images. Since SDP 26.7.0 the images live +# under the "sdp" path, e.g. quay.io/stackable/sdp/hbase-operator. +QUAY_REPO = "stackable/sdp" + + class ManifestException(Exception): pass @@ -130,7 +142,8 @@ def parse_args(argv: list[str]) -> argparse.Namespace: if args.op_name in {"secret-operator", "listener-operator"}: raise ManifestException( - f"Operator '{args.op_name}' is not supported by this script. Use the 'build-manifests.sh' for it." + f"Operator '{args.op_name}' is not supported by this script. " + f"Use 'scripts/generate-olm.py' in the {args.op_name} repository instead." ) args.op_name = args.repo_operator.name @@ -413,9 +426,27 @@ def generate_helm_templates(args: argparse.Namespace) -> list[dict]: helm_values_path = template_path / "values.yaml" # Path to the custom values for OLM. olm_values_path = pathlib.Path(__file__).parent / "resources" / "values" / args.repo_operator.name / "values.yaml" + # Since SDP 26.7.0 the charts no longer define image.repository in values.yaml. It is + # supplied by a per-registry overlay instead. OLM bundles must reference quay.io, and + # the overlay also drives the IMAGE_REPOSITORY env var that tells the operator where to + # pull product images from. Without it the chart fails to render. + registry_values_path = template_path / "values" / "quay.io.yaml" + if not registry_values_path.exists(): + raise ManifestException( + f"Registry values overlay not found: {registry_values_path}" + ) helm_template_cmd = ["helm", "template", args.op_name, + # Advertise the OpenShift API so that the charts render the + # securitycontextconstraints rules guarded by + # '.Capabilities.APIVersions.Has "security.openshift.io/v1"'. + # Some operators gate more than one cluster role this way + # (spark has three, opa has two), so relying on the chart is both + # more complete and more deterministic than patching a single role + # here. + "--api-versions", "security.openshift.io/v1", "--values", helm_values_path, "--values", olm_values_path, + "--values", registry_values_path, template_path] try: logging.debug("start generate_helm_templates") @@ -440,19 +471,6 @@ def generate_helm_templates(args: argparse.Namespace) -> list[dict]: except KeyError: pass - ### Patch the product cluster role with the SCC rule - if ( - man["kind"] == "ClusterRole" - and man["metadata"]["name"] == f"{args.product}-clusterrole" - ): - man["rules"].append( - { - "apiGroups": ["security.openshift.io"], - "resources": ["securitycontextconstraints"], - "resourceNames": ["nonroot-v2"], - "verbs": ["use"], - } - ) ### Patch the version label try: if ( @@ -490,7 +508,7 @@ def quay_image(images: list[tuple[str, str]]) -> list[dict[str, str]]: for image, release in images: release_tag = urllib.parse.urlencode({"specificTag": release}) tag_url = ( - f"https://quay.io/api/v1/repository/stackable/{image}/tag?{release_tag}" + f"https://quay.io/api/v1/repository/{QUAY_REPO}/{image}/tag/?{release_tag}" ) logging.debug(f"Fetching image manifest from {tag_url}") with urllib.request.urlopen(tag_url) as response: @@ -517,7 +535,7 @@ def quay_image(images: list[tuple[str, str]]) -> list[dict[str, str]]: result.append( { "name": image, - "image": f"quay.io/stackable/{image}@{manifest_digest[0]}", + "image": f"quay.io/{QUAY_REPO}/{image}@{manifest_digest[0]}", } ) logging.debug("finish op_image")