From d3f54b3f04246b99de284394e210d7e5f9e3c516 Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 27 Aug 2026 09:47:25 -0400 Subject: [PATCH 1/3] feat(olm): add the bundle and catalog images to the artifacts repository The release workflow builds the production OLM bundle and catalog images. It attaches the CatalogSource manifest to the GitHub release, and then it writes the bundle to the artifacts repository. The operator deployment gets a RELATED_IMAGE_SIDECAR variable. operator-sdk changes this variable to a digest, and it writes the image into the relatedImages field of the ClusterServiceVersion (CSV). Four new patches correct the bundle. Two patches give the correct image to the CSV annotation and to the sample server, because both files had a fixed image that no registry holds. The third patch adds an olm.skipRange to the CSV, so a catalog that carries a single bundle can still offer an upgrade from any earlier release. The fourth patch removes the example secret, because OLM makes a secret for each secret in a bundle; the CSV description now lists the secrets a Server needs, since the bundle no longer ships them and the CRD requires the fields that name them. Digest resolution covers every image in the CSV, the operand included, so the olm job depends on the core job as well. That job is skipped on a pull request that only touches the operator, so the bundle is built against mutable tags there rather than making every such pull request pay for a core build. Assisted-by: Claude Signed-off-by: John Long --- .github/workflows/ci.yml | 16 ++- .github/workflows/release-publish.yml | 130 ++++++++++++++++++ .gitignore | 3 + Taskfile.yml | 108 ++++++++++++++- .../web/docs/developer/openshift_testing.md | 46 ++++++- .../klio-operator.clusterserviceversion.yaml | 18 +++ operator/config/manifests/kustomization.yaml | 25 ++++ .../manifests/remove_sample_secret.yaml | 10 ++ operator/internal/cnpgi/lifecycle.go | 18 ++- operator/internal/cnpgi/lifecycle_test.go | 40 ++++++ 10 files changed, 407 insertions(+), 7 deletions(-) create mode 100644 operator/config/manifests/remove_sample_secret.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9854b342..3da6cf3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -300,9 +300,23 @@ jobs: olm: name: OLM Bundle & Catalog + needs: + - change-triage + - core + - operator + # the bundle needs the core and operator images + + if: | + !cancelled() && + (needs.change-triage.outputs.operator-changed == 'true' || + needs.change-triage.outputs.run-openshift == 'true') && + needs.operator.result == 'success' && + (needs.core.result == 'success' || needs.core.result == 'skipped') env: - # The OpenShift Catalog is just for internal testing purpose until it won't. ENVIRONMENT: "testing" + SKIP_IMAGE_DIGESTS: >- + ${{ (needs.change-triage.outputs.core-changed != 'true' && + needs.change-triage.outputs.run-openshift != 'true') && 'true' || '' }} runs-on: ubuntu-24.04 permissions: contents: read diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index a92fbbf0..62000a25 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -31,3 +31,133 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | task all:publish + + check-release: + name: Evaluate the release tag + needs: + - release-publish + if: github.repository_owner == 'cloudnative-pg' + runs-on: ubuntu-24.04 + permissions: + contents: read + outputs: + is-latest: ${{ steps.check.outputs.is-latest }} + steps: + - name: Check whether this is the newest release + id: check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.release.tag_name }} + run: | + latest=$(gh api "repos/${GITHUB_REPOSITORY}/releases/latest" \ + --jq '.tag_name' 2>/dev/null || true) + is_latest="false" + if [ "${latest}" = "${TAG}" ]; then + is_latest="true" + fi + echo "is-latest=${is_latest}" >> "${GITHUB_OUTPUT}" + echo "Release ${TAG} is-latest=${is_latest} (latest is ${latest})" + + olm-bundle: + name: Build and push the OLM bundle and catalog + needs: + - release-publish + - check-release + if: needs.check-release.outputs.is-latest == 'true' + env: + ENVIRONMENT: "production" + runs-on: ubuntu-24.04 + permissions: + contents: write + packages: write + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + + - name: Cleanup disk + uses: ./.github/actions/cleanup-disk + + - name: Setup dagger + uses: ./.github/actions/setup-dagger + + - name: Login to ghcr.io using Podman + uses: redhat-actions/podman-login@50c2d9a331bb67c8fdab99b86455fad05e2e3252 # v2 + with: + registry: "ghcr.io" + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push the bundle and catalog + run: | + task olm:publish + + - name: Upload the bundle + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: bundle + path: operator/bundle + + - name: Attach the CatalogSource manifest to the release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.release.tag_name }} + run: | + gh release upload "${TAG}" operator/klio-operator-catalog-source.yaml \ + --repo "${GITHUB_REPOSITORY}" --clobber + + publish-bundle: + name: Publish the OLM bundle to the artifacts repository + needs: + - olm-bundle + env: + VERSION: ${{ github.event.release.tag_name }} + BUNDLE_DIR: klio/bundles + concurrency: + group: publish-bundle + cancel-in-progress: false + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: Checkout the artifacts repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + repository: cloudnative-pg/artifacts + token: ${{ secrets.REPO_GHA_PAT }} + ref: main + fetch-depth: 0 + + - name: Configure the git user + run: | + git config user.name "${GITHUB_ACTOR}" + git config user.email "${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com" + + - name: Download the bundle + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: bundle + path: downloaded-bundle + + - name: Copy the bundle + run: | + target="${BUNDLE_DIR}/${VERSION#v}" + rm -rf "${target}" + mkdir -p "${target}" + cp -R downloaded-bundle/* "${target}" + rm -rf downloaded-bundle + + - name: Commit and push the bundle + run: | + if [ -z "$(git status --porcelain)" ]; then + echo "Bundle for ${VERSION#v} is already up to date" + exit 0 + fi + git add "${BUNDLE_DIR}/${VERSION#v}" + git commit -sm "klio-operator (${VERSION#v})" + # The checkout above persisted REPO_GHA_PAT, so no separate push + # action is needed. Rebase first: the artifacts repository is shared + # with the other CloudNativePG projects and main may have moved. + git pull --rebase origin main + git push origin HEAD:main diff --git a/.gitignore b/.gitignore index 073b7fd0..2a9aa46f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ bundle/ operator/olm-manifests/ operator/config/manifest-build/ operator/config/olm-default/manager_sidecar_image_patch.yaml +operator/config/manifests/csv_container_image_patch.yaml +operator/config/manifests/csv_skip_range_patch.yaml +operator/config/manifests/sample_image_patch.yaml operator/catalog operator/catalog.Dockerfile operator/klio-operator-template.yaml diff --git a/Taskfile.yml b/Taskfile.yml index 941b2d9f..2e10f07f 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -31,6 +31,24 @@ vars: # image, since that is the variant submitted to Red Hat certification. UBI_TAG_SUFFIX: '-ubi9' ALLOWED_ENVS: [testing, production] + # OLM channel the bundle is published on. "stable" is the support tier and + # "v0" pins the major version, per the OLM channel-naming convention + # (https://olm.operatorframework.io/docs/best-practices/channel-naming/). + # Bump to stable-v1 at the 1.0 release, by adding the new channel instead + # of renaming this one. + OLM_CHANNELS: 'stable-v0' + OLM_DEFAULT_CHANNEL: 'stable-v0' + # Minimum OpenShift version the bundle declares support for + OPENSHIFT_VERSIONS: 'v4.14' + # First release that ships an OLM bundle. It is the lower bound of the CSV's + # olm.skipRange, which is what lets a catalog carrying a single bundle still + # offer an upgrade from any earlier release (see olm:manifest). Never lower + # it, and only raise it if a version range is ever declared unupgradable. + OLM_FIRST_BUNDLE_VERSION: '0.0.21' + # Set to a non-empty value to build the OLM bundle without pinning images to + # digests. Defaults to empty, so digest pinning is on wherever it can work; + # see olm:bundle for when CI turns it off. + SKIP_IMAGE_DIGESTS: '{{ .SKIP_IMAGE_DIGESTS | default "" }}' tasks: @@ -754,6 +772,10 @@ tasks: # registry and tag, differing only by the "klio" repository instead of # "klio-operator". The patch is consumed by # operator/config/olm-default/kustomization.yaml. + # + # RELATED_IMAGE_SIDECAR carries the same value under the name OLM + # tooling recognizes: operator-sdk's --use-image-digests only discovers + # container images and RELATED_IMAGE_* env vars, - | cat > operator/config/olm-default/manager_sidecar_image_patch.yaml < operator/config/manifests/csv_container_image_patch.yaml < operator/config/manifests/csv_skip_range_patch.yaml <={{ .OLM_FIRST_BUNDLE_VERSION }} <{{ .GIT_TAG_VERSION }}" + EOF + - | + cat > operator/config/manifests/sample_image_patch.yaml < GITHUB_REF= dagger -s call -m github.com/sagikazarmark/daggerverse/kustomize@${DAGGER_KUSTOMIZE_SHA} @@ -780,9 +824,31 @@ tasks: olm:bundle: desc: Generate the OLM bundle for the operator + summary: | + Note that --use-image-digests makes operator-sdk resolve every image in + the CSV against its registry, so the operator image for this ref must + already be pushed when this runs. Callers must therefore order themselves + after the operator build (in CI, the olm job needs the operator job). + + Digest pinning is enabled only when running in GitHub Actions, where the + images live on ghcr.io. + + Resolution covers *every* image in the CSV, the operand included, so a + caller that cannot guarantee the operand image was pushed for this ref + must set SKIP_IMAGE_DIGESTS. The CI olm job does that on pull requests + where the core job is skipped; releases always build both, and never set + it. + run: once + requires: + vars: + - name: ENVIRONMENT + enum: + ref: .ALLOWED_ENVS deps: - olm:manifest + env: + use_image_digests: '{{ if and .GITHUB_ACTIONS (not .SKIP_IMAGE_DIGESTS) }}--use-image-digests{{ end }}' cmds: - > docker @@ -794,11 +860,20 @@ tasks: generate bundle --input-dir olm-manifests/ --kustomize-dir config/manifests + --package klio-operator + --channels {{ .OLM_CHANNELS }} + --default-channel {{ .OLM_DEFAULT_CHANNEL }} + ${use_image_digests} --version {{ .GIT_TAG_VERSION }} + - | + printf '\n # OpenShift annotations.\n com.redhat.openshift.versions: "%s"\n' \ + '{{ .OPENSHIFT_VERSIONS }}' >> operator/bundle/metadata/annotations.yaml + printf '\n# OpenShift labels.\nLABEL com.redhat.openshift.versions="%s"\n' \ + '{{ .OPENSHIFT_VERSIONS }}' >> operator/bundle.Dockerfile sources: - operator/olm-manifests/** generates: - - bundle/ + - operator/bundle/** olm:build: desc: Build the require OLM bundle image after generating the bundle @@ -941,6 +1016,37 @@ tasks: image: ${registry}/klio-operator${suffix}:${tag}-catalog EOF + olm:publish: + desc: Publish the OLM bundle and catalog images for a release + summary: | + Release counterpart of olm:all. Builds and pushes the production bundle + and catalog images (no "-testing" suffix) for the tag being released, and + leaves operator/bundle/ on disk for the release workflow to upload and + forward to the artifacts repository. + + Scorecard is deliberately not re-run here: it already ran against the + same bundle in the CI olm job, and it needs a k3s-capable engine that the + release runner is not set up for. + requires: + # We expect this to run in a GitHub workflow, so we put a few + # GitHub-specific vars here to prevent running this task locally by + # accident. + vars: + - CI + - GITHUB_REPOSITORY + - GITHUB_REF + - GITHUB_REF_NAME + preconditions: + - sh: "[[ {{.GITHUB_REF}} =~ 'refs/tags/v.*' ]]" + msg: not a tag, failing + - sh: '[[ "{{ .GIT_TAG_VERSION }}" == "{{ trimPrefix "v" .GITHUB_REF_NAME }}" ]]' + msg: >- + bundle version {{ .GIT_TAG_VERSION }} does not match tag + {{ .GITHUB_REF_NAME }}; refusing to publish an inconsistent bundle + cmds: + - task: olm:catalog + - task: olm:catalog-source + olm:preflight-container: desc: Run Red Hat Preflight (check container) certification on the operator image dir: operator diff --git a/documentation/web/docs/developer/openshift_testing.md b/documentation/web/docs/developer/openshift_testing.md index 0b85ef27..ca3ec3fe 100644 --- a/documentation/web/docs/developer/openshift_testing.md +++ b/documentation/web/docs/developer/openshift_testing.md @@ -61,6 +61,12 @@ This procedure is for development and testing only. It requires a catalog image built from the branch under test. ::: +The bundle declares OpenShift 4.14 as the minimum supported version, +through the `com.redhat.openshift.versions` annotation. The value comes +from the `OPENSHIFT_VERSIONS` variable in `Taskfile.yml`, which +`olm:bundle` writes into both `bundle/metadata/annotations.yaml` and +the labels of `bundle.Dockerfile`. + ## Prerequisites - An OpenShift cluster with `oc` CLI configured @@ -137,10 +143,17 @@ the `openshift-operators` namespace. :::note The Klio sidecar (operand) image is baked into the operator -Deployment by the bundle as the `SIDECAR_IMAGE` environment -variable. It uses the same registry and tag as the operator -image by default, with the `klio` repository instead of -`klio-operator`. The Subscription can override it. +Deployment by the bundle, as both the `SIDECAR_IMAGE` and the +`RELATED_IMAGE_SIDECAR` environment variables; the operator prefers +the latter, and only the OLM bundle sets it. `RELATED_IMAGE_SIDECAR` +is the name operator-sdk expects, so that `--use-image-digests` +pins the operand to a digest and copies it into the CSV's +`relatedImages`, which is what disconnected installs mirror. Digest +pinning only happens in CI, where the images live on `ghcr.io`; a +local build leaves both variables on a tag. The images use the same +registry and tag as the operator image by default, with the `klio` +repository instead of `klio-operator`. The Subscription can override +either variable. ::: ## 3. Create TLS certificates @@ -277,3 +290,28 @@ in-engine. Raw artifacts are written to `operator/preflight-artifacts/`. The task never contacts Red Hat: the checks are always evaluated locally. + +## Publishing the bundle and catalog + +`olm:publish` is the release counterpart of `olm:all`: it builds and +pushes the production bundle and catalog images (no `-testing` suffix) +for the tag being released, and leaves `operator/bundle/` on disk. It +refuses to run outside a tagged CI checkout, and refuses to run if the +CSV version derived from `git describe` does not match the tag. + +The `olm-bundle` job in `.github/workflows/release-publish.yml` runs +it, attaches the resulting `CatalogSource` manifest to the GitHub +release, and hands `operator/bundle/` to the `publish-bundle` job, +which commits it to `klio/bundles/` in the +[artifacts repository](https://github.com/cloudnative-pg/artifacts). +Both jobs only run for the release GitHub marks as the latest one, +which excludes drafts and prereleases, so a release candidate never +reaches the `stable-v0` channel. + +Each release publishes its own catalog image, tagged with the release +version, and that catalog carries exactly one bundle. The upgrade edge +back to the installed version comes from the `olm.skipRange` annotation +`olm:manifest` writes into the CSV, which spans every release from +`OLM_FIRST_BUNDLE_VERSION` up to (but excluding) the one being built. +Raise that variable only to declare a version range unupgradable, and +never lower it. diff --git a/operator/config/manifests/bases/klio-operator.clusterserviceversion.yaml b/operator/config/manifests/bases/klio-operator.clusterserviceversion.yaml index cec60823..fdc95bd6 100644 --- a/operator/config/manifests/bases/klio-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/bases/klio-operator.clusterserviceversion.yaml @@ -395,6 +395,24 @@ spec: TLS certificates for the Klio server and its clients. * Persistent Volume Claims that support `ReadWriteOnce`. + ## Before creating a Server + + A `Server` names the TLS and encryption material it needs, and the + operator does not create it: the Secrets must already exist in the + `Server`'s namespace. The example `Server` below expects three that + are **not** part of this bundle: + + * `server-sample-tls`, the certificate the Klio server presents, and + `server-sample-ca`, the CA it validates clients against. Both are + normally issued by cert-manager `Certificate` resources. + * `server-sample-encryption`, holding an `encryption-key` entry (the + Age-encrypted encryption key) and a `secret-key` entry (the Age + identity that decrypts it). + + Those names are only the example's defaults: any names work, as long + as `tlsSecretName`, `caSecretName`, and the + `encryptionKeyFile`/`identityFile` references agree with them. + ## Getting started Full installation and configuration instructions are available in diff --git a/operator/config/manifests/kustomization.yaml b/operator/config/manifests/kustomization.yaml index adcc04c5..fc7c90b3 100644 --- a/operator/config/manifests/kustomization.yaml +++ b/operator/config/manifests/kustomization.yaml @@ -6,6 +6,31 @@ resources: - ../samples - ../scorecard +# Point the CSV's containerImage annotation at the operator image actually +# being built. The patch is generated (with the right image) by the +# `olm:manifest` task and is git ignored. +patches: +- path: csv_container_image_patch.yaml + target: + kind: ClusterServiceVersion +# Declare which already-released versions this bundle can be installed over, +# as olm.skipRange. Also generated by `olm:manifest` and git ignored; see the +# task for why a single-bundle catalog needs it. +- path: csv_skip_range_patch.yaml + target: + kind: ClusterServiceVersion +# Point the sample Server at the operand image actually being built, rather +# than the local dev registry the committed sample uses. Also generated by +# `olm:manifest` and git ignored. +- path: sample_image_patch.yaml + target: + group: klio.cnpg.io + version: v1alpha1 + kind: Server +# Keep the sample's example encryption Secret out of the bundle; see the patch +# for why. +- path: remove_sample_secret.yaml + # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. # These patches remove the unnecessary "cert" volume and its manager container volumeMount. diff --git a/operator/config/manifests/remove_sample_secret.yaml b/operator/config/manifests/remove_sample_secret.yaml new file mode 100644 index 00000000..12f414ce --- /dev/null +++ b/operator/config/manifests/remove_sample_secret.yaml @@ -0,0 +1,10 @@ +# The sample Server ships alongside an example encryption Secret, so that +# applying config/samples/klio_v1alpha1_server.yaml works in a single step +# locally. Secret is an OLM-supported bundle kind, so operator-sdk copies it +# into bundle/manifests/ and OLM would create it on every install of the +# operator: we do not need the example in the bundle +apiVersion: v1 +kind: Secret +metadata: + name: server-sample-encryption +$patch: delete diff --git a/operator/internal/cnpgi/lifecycle.go b/operator/internal/cnpgi/lifecycle.go index 2215895b..2b90b2a8 100644 --- a/operator/internal/cnpgi/lifecycle.go +++ b/operator/internal/cnpgi/lifecycle.go @@ -439,7 +439,7 @@ func reconcilePodSpec( // NOSONAR } sidecarTemplate := corev1.Container{ - Image: os.Getenv("SIDECAR_IMAGE"), + Image: sidecarImage(), RestartPolicy: new(corev1.ContainerRestartPolicyAlways), ImagePullPolicy: cluster.Spec.ImagePullPolicy, SecurityContext: sidecarSecurityContext(cfg.haveSCC), @@ -536,6 +536,22 @@ func reconcilePodSpec( // NOSONAR return nil } +// sidecarImage returns the Klio plugin sidecar (operand) image the operator +// injects into PostgreSQL pods. +// +// RELATED_IMAGE_SIDECAR takes precedence over SIDECAR_IMAGE because it is the +// name OLM tooling recognizes: operator-sdk pins RELATED_IMAGE_* env vars to +// image digests and copies them into the CSV's relatedImages, which is what +// disconnected installs mirror. Only the OLM bundle sets it; the Helm chart and +// every other deployment path keep using SIDECAR_IMAGE. +func sidecarImage() string { + if image := os.Getenv("RELATED_IMAGE_SIDECAR"); image != "" { + return image + } + + return os.Getenv("SIDECAR_IMAGE") +} + // sidecarSecurityContext returns the SecurityContext for the Klio plugin // sidecar container. On OpenShift it returns nil so that the restricted SCC // can assign a namespace-allocated UID. diff --git a/operator/internal/cnpgi/lifecycle_test.go b/operator/internal/cnpgi/lifecycle_test.go index 9b5edceb..b25a0092 100644 --- a/operator/internal/cnpgi/lifecycle_test.go +++ b/operator/internal/cnpgi/lifecycle_test.go @@ -184,6 +184,46 @@ func TestFindUserContainer(t *testing.T) { } } +func TestSidecarImage(t *testing.T) { + tests := []struct { + name string + relatedImage string + sidecarImage string + expectedImage string + }{ + { + name: "only SIDECAR_IMAGE set", + sidecarImage: "ghcr.io/cloudnative-pg/klio:v1", + expectedImage: "ghcr.io/cloudnative-pg/klio:v1", + }, + { + name: "RELATED_IMAGE_SIDECAR wins when both are set", + relatedImage: "ghcr.io/cloudnative-pg/klio@sha256:abc", + sidecarImage: "ghcr.io/cloudnative-pg/klio:v1", + expectedImage: "ghcr.io/cloudnative-pg/klio@sha256:abc", + }, + { + name: "empty RELATED_IMAGE_SIDECAR falls back", + relatedImage: "", + sidecarImage: "ghcr.io/cloudnative-pg/klio:v1", + expectedImage: "ghcr.io/cloudnative-pg/klio:v1", + }, + { + name: "neither set", + expectedImage: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("RELATED_IMAGE_SIDECAR", tt.relatedImage) + t.Setenv("SIDECAR_IMAGE", tt.sidecarImage) + + assert.Equal(t, tt.expectedImage, sidecarImage()) + }) + } +} + func TestEnsureEnvVar(t *testing.T) { tests := []struct { name string From d7b948c2892e2b6d4faa582c9e4ff44d0f8f6013 Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 27 Aug 2026 15:00:55 -0400 Subject: [PATCH 2/3] docs: add OLM terms to the spellcheck wordlist Signed-off-by: John Long --- documentation/.wordlist.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documentation/.wordlist.txt b/documentation/.wordlist.txt index 838d92a0..b4de90af 100644 --- a/documentation/.wordlist.txt +++ b/documentation/.wordlist.txt @@ -15,6 +15,7 @@ CRC CRC's CRDs CSV +CSV's CatalogSource CheckpointTime ClientCASecretName @@ -290,6 +291,7 @@ pre prefetch prefetching preflight +prereleases priorityClassName prometheus proto @@ -319,6 +321,7 @@ uint ulong unicode unreferenced +unupgradable uptime verifications wal From a650e22c30974aa60d43919f74c896bb82bca2e3 Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 27 Aug 2026 15:22:58 -0400 Subject: [PATCH 3/3] docs: backtick operator-sdk so spellcheck skips it Aspell splits the bare "operator-sdk" on the hyphen and rejects the lowercase "sdk". The spellcheck pipeline ignores code spans, and every other identifier in the paragraph is already backticked. Assisted-by: Claude Signed-off-by: John Long --- documentation/web/docs/developer/openshift_testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/web/docs/developer/openshift_testing.md b/documentation/web/docs/developer/openshift_testing.md index ca3ec3fe..6863c663 100644 --- a/documentation/web/docs/developer/openshift_testing.md +++ b/documentation/web/docs/developer/openshift_testing.md @@ -146,7 +146,7 @@ The Klio sidecar (operand) image is baked into the operator Deployment by the bundle, as both the `SIDECAR_IMAGE` and the `RELATED_IMAGE_SIDECAR` environment variables; the operator prefers the latter, and only the OLM bundle sets it. `RELATED_IMAGE_SIDECAR` -is the name operator-sdk expects, so that `--use-image-digests` +is the name `operator-sdk` expects, so that `--use-image-digests` pins the operand to a digest and copies it into the CSV's `relatedImages`, which is what disconnected installs mirror. Digest pinning only happens in CI, where the images live on `ghcr.io`; a