diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf7aadea..9854b342 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -227,13 +227,13 @@ jobs: run: | task operator:ci - # TODO(fcanovai): this has been disabled in #85 because the image build there is now Debian based and - # the Red Hat preflight certification fails. It should be re-enabled once we have an image built on Red Hat UBI. - #- name: Run Red Hat preflight certification - # run: | - # # Certify the operator image just built and pushed by operator:ci - # IMAGE=$(jq -r '.default."image.name" | split(",")[0]' operator/metadata.json) - # task olm:preflight-container IMAGE="${IMAGE}" + - name: Run Red Hat preflight certification + run: | + # Certify the UBI variant of the operator image just built and pushed + # by operator:ci. Preflight only accepts a Red Hat UBI base, so the + # distroless variant is not a candidate. + IMAGE=$(jq -r '.ubi."image.name" | split(",")[0]' operator/metadata.json) + task olm:preflight-container IMAGE="${IMAGE}" integration-tests: name: Integration Tests diff --git a/Taskfile.yml b/Taskfile.yml index 6deaffda..2b2db130 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -26,6 +26,10 @@ vars: # the two must use the same version. # renovate: datasource=docker depName=quay.io/opdev/preflight versioning=docker PREFLIGHT_VERSION: 1.20.0@sha256:b8fa820097c088957a00f83b729595d26a9e0a97da568188bdc3361f9346aa26 + # Tag suffix of the UBI variant of the operator image, as produced by the + # "ubi" target of operator/docker-bake.hcl. The OLM bundle ships the UBI + # image, since that is the variant submitted to Red Hat certification. + UBI_TAG_SUFFIX: '-ubi9' ALLOWED_ENVS: [testing, production] tasks: @@ -740,6 +744,7 @@ tasks: insecure: '{{ if .GITHUB_ACTIONS }}false{{ else }}true{{ end }}' suffix: '{{ if ( ne .ENVIRONMENT "production" ) }}-testing{{end}}' tag: '{{ .TAG | default .IMAGE_TAG }}' + ubi_suffix: '{{ .UBI_TAG_SUFFIX }}' # renovate: datasource=git-refs depName=kustomize lookupName=https://github.com/sagikazarmark/daggerverse currentValue=main DAGGER_KUSTOMIZE_SHA: ff27cd50f6b4eed2e3753c520632cd6099e1ce52 cmds: @@ -761,7 +766,7 @@ tasks: GITHUB_REF= dagger -s call -m github.com/sagikazarmark/daggerverse/kustomize@${DAGGER_KUSTOMIZE_SHA} edit --source . --dir operator/config/manifests - set image --image controller=${registry}/klio-operator${suffix}:${tag} + set image --image controller=${registry}/klio-operator${suffix}:${tag}${ubi_suffix} directory directory --path operator/config/manifests export --path operator/config/manifest-build - > GITHUB_REF= dagger -s call -m github.com/sagikazarmark/daggerverse/kustomize@${DAGGER_KUSTOMIZE_SHA} diff --git a/documentation/.wordlist.txt b/documentation/.wordlist.txt index 8d9adc9f..838d92a0 100644 --- a/documentation/.wordlist.txt +++ b/documentation/.wordlist.txt @@ -229,6 +229,7 @@ deduplicated deduplication deployable dev +distroless docusaurus durations env diff --git a/documentation/web/docs/developer/openshift_testing.md b/documentation/web/docs/developer/openshift_testing.md index 99428031..0b85ef27 100644 --- a/documentation/web/docs/developer/openshift_testing.md +++ b/documentation/web/docs/developer/openshift_testing.md @@ -235,7 +235,9 @@ certification policies. Two checks cover the two artifacts: - **`check container`** — static policy checks on the operator image (labels, layers, license, base image). It needs no cluster and runs - in the Dagger engine via `task olm:preflight-container`. + in the Dagger engine on every PR via `task olm:preflight-container`. + It is run against the UBI variant of the operator image, since the + base-image policy only accepts a Red Hat UBI base. - **`check operator`** — installs the bundle through OLM into a live OpenShift cluster and verifies it is deployable. Because it needs a real OpenShift cluster (OLM and Security Context Constraints), it runs @@ -247,14 +249,12 @@ certification policies. Two checks cover the two artifacts: :::note -Both checks are currently **disabled in CI**. The operator and operand -images are built on Debian instead of Red Hat UBI, which the -`check container` base-image policy rejects, and `check operator` is -parked alongside it. The steps are commented out in -`.github/workflows/ci.yml` and `.github/workflows/openshift-e2e.yml`, -ready to be restored once a UBI-based image variant is built again — see -[issue #85](https://github.com/cloudnative-pg/klio/issues/85). Both -tasks still work when run manually, as described below. +The operator image is built in two variants: a distroless Debian one, +which is the default and carries the plain tag, and a Red Hat UBI one, +tagged with a `-ubi9` suffix. Certification concerns the UBI variant +only: `check container` runs against it, and the OLM bundle references +it, so `check operator` exercises it too. The operand (`klio`) image is +Debian based and is not part of the operator certification. ::: diff --git a/operator/config/manifests/bases/klio-operator.clusterserviceversion.yaml b/operator/config/manifests/bases/klio-operator.clusterserviceversion.yaml index 6d925218..cec60823 100644 --- a/operator/config/manifests/bases/klio-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/bases/klio-operator.clusterserviceversion.yaml @@ -5,7 +5,7 @@ metadata: alm-examples: '[]' capabilities: Basic Install categories: Database - containerImage: ghcr.io/cloudnative-pg/klio-operator:latest + containerImage: ghcr.io/cloudnative-pg/klio-operator:latest-ubi9 createdAt: description: Enterprise-grade backup and recovery manager for PostgreSQL clusters managed by CloudNativePG on Kubernetes. diff --git a/operator/docker-bake.hcl b/operator/docker-bake.hcl index b059d941..bd8b21aa 100644 --- a/operator/docker-bake.hcl +++ b/operator/docker-bake.hcl @@ -34,6 +34,26 @@ variable "base_image" { default = "gcr.io/distroless/static-debian13:nonroot@sha256:f7f8f729987ad0fdf6b05eeeae94b26e6a0f613bdf46feea7fc40f7bd72953e6" } +variable "ubi_base_image" { + // renovate image: datasource=docker depName=registry.access.redhat.com/ubi9/ubi-micro versioning=docker + default = "registry.access.redhat.com/ubi9/ubi-micro:9.8-1786321990@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95" +} + +// The image variants we build. Each one is a separate target of the "default" +// group, built from the same Dockerfile with a different base image. The +// distroless variant is the primary one and keeps the plain tag; the UBI +// variant is the one submitted to Red Hat certification. +distros = { + distroless = { + baseImage = base_image + tagSuffix = "" + } + ubi = { + baseImage = ubi_base_image + tagSuffix = "-ubi9" + } +} + function "getRegistry" { params = [] result = lower(registry) @@ -49,6 +69,11 @@ variable "version" { } target "default" { + matrix = { + distro = ["distroless", "ubi"] + } + name = distro + dockerfile = "Dockerfile" context = "." platforms = [ @@ -57,12 +82,12 @@ target "default" { ] tags = [ - latest("${getImageName()}", "${latest}"), - "${getImageName()}:${version}", + latest("${getImageName()}", "${latest}", "${distros[distro].tagSuffix}"), + "${getImageName()}:${version}${distros[distro].tagSuffix}", ] args = { - "BASE_IMAGE" = "${base_image}", + "BASE_IMAGE" = "${distros[distro].baseImage}", } output = [ @@ -89,8 +114,8 @@ target "default" { "index,manifest:org.opencontainers.image.documentation=${documentation}", "index,manifest:org.opencontainers.image.authors=${authors}", "index,manifest:org.opencontainers.image.licenses=${license}", - "index,manifest:org.opencontainers.image.base.name=${baseName(base_image)}", - "index,manifest:org.opencontainers.image.base.digest=${digest(base_image)}", + "index,manifest:org.opencontainers.image.base.name=${baseName(distros[distro].baseImage)}", + "index,manifest:org.opencontainers.image.base.digest=${digest(distros[distro].baseImage)}", ] labels = { "org.opencontainers.image.created" = "${now}", @@ -104,8 +129,8 @@ target "default" { "org.opencontainers.image.documentation" = "${documentation}", "org.opencontainers.image.authors" = "${authors}", "org.opencontainers.image.licenses" = "${license}", - "org.opencontainers.image.base.name" = "${baseName(base_image)}", - "org.opencontainers.image.base.digest" = "${digest(base_image)}", + "org.opencontainers.image.base.name" = "${baseName(distros[distro].baseImage)}", + "org.opencontainers.image.base.digest" = "${digest(distros[distro].baseImage)}", "name" = "${title}", "maintainer" = "${authors}", "vendor" = "${authors}", @@ -123,13 +148,15 @@ function digest { } // We get the image reference without the sha256, so that the base.name label -// is always derived from base_image and cannot drift away from it. +// is always derived from the variant's base image and cannot drift away from it. function baseName { params = [ imageNameWithSha ] result = index(split("@", imageNameWithSha), 0) } +// The moving tag of each variant: ":latest" for the primary one and +// ":latest" for the others, so the variants never collide on it. function latest { - params = [ image, latest ] - result = (latest == "true") ? "${image}:latest" : "" + params = [ image, latest, tagSuffix ] + result = (latest == "true") ? "${image}:latest${tagSuffix}" : "" }