From 46d2a0eb5aae155f783da1f410f7b5db92368dfc Mon Sep 17 00:00:00 2001 From: valmiranogueira Date: Tue, 21 Jul 2026 21:56:58 -0300 Subject: [PATCH 1/5] Update bundle automation and icon --- .gitignore | 8 + Makefile | 8 + installers/olm/Makefile | 435 +++++++++---- installers/olm/README.md | 195 +++++- installers/olm/build-image.sh | 42 ++ installers/olm/bundle.annotations.yaml | 2 +- installers/olm/bundle.csv.yaml | 95 +-- installers/olm/bundle.relatedImages.yaml | 16 - installers/olm/distributions/redhat.sh | 223 +++++++ installers/olm/generate.sh | 761 ++++++++++++++++------- installers/olm/validate-directory.sh | 10 + installers/olm/validate-image.sh | 36 ++ kubernetes.svg | 33 +- 13 files changed, 1391 insertions(+), 473 deletions(-) create mode 100755 installers/olm/build-image.sh delete mode 100644 installers/olm/bundle.relatedImages.yaml create mode 100755 installers/olm/distributions/redhat.sh create mode 100755 installers/olm/validate-directory.sh create mode 100755 installers/olm/validate-image.sh diff --git a/.gitignore b/.gitignore index 2c8d6a1d78..93057f9194 100644 --- a/.gitignore +++ b/.gitignore @@ -189,3 +189,11 @@ bin/ projects/ installers/olm/operator_*.yaml installers/olm/bundles +installers/olm/tools +installers/olm/catalogs + +# Generated namespace-scoped manifests +config/manager/namespace/operator.yaml +config/rbac/namespace/role.yaml +config/rbac/namespace/role_binding.yaml +config/rbac/namespace/service_account.yaml diff --git a/Makefile b/Makefile index 0b5e41f2ca..d11f17eac6 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,14 @@ undeploy: ## Undeploy operator test: envtest generate ## Run tests. DISABLE_TELEMETRY=true KUBEBUILDER_ASSETS="$(shell $(ENVTEST) --arch=amd64 use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out +.PHONY: validate +validate: ## Validate OLM bundles + $(MAKE) -C installers/olm validate VERSION=$(VERSION) + +.PHONY: validate/community validate/certified +validate/community validate/certified: validate/%: ## Validate one OLM bundle + $(MAKE) -C installers/olm validate/$* VERSION=$(VERSION) + # go-get-tool will 'go get' any package $2 and install it to $1. PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) define go-get-tool diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 8ec3d8f80f..311613d435 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -2,97 +2,359 @@ # Percona Server MongoDB Operator - OLM Bundle Generation # ============================================================================== -# Default target .DEFAULT_GOAL := help .SUFFIXES: SHELL := /bin/bash # ============================================================================== -# Configuration Variables +# Project Configuration # ============================================================================== -# Project configuration NAME ?= percona-server-mongodb-operator -IMAGE_TAG_OWNER ?= perconalab -IMAGE_TAG_BASE ?= $(IMAGE_TAG_OWNER)/$(NAME) -MODE ?= namespace +BUNDLE_TYPES := community certified -# Version detection SED := $(shell which gsed || which sed) VERSION ?= $(shell git rev-parse --abbrev-ref HEAD | $(SED) -e 's^/^-^g; s^[.]^-^g;' | tr '[:upper:]' '[:lower:]') -IMAGE := $(IMAGE_TAG_BASE):$(VERSION) +CSV_VERSION ?= $(shell printf '%s\n' '$(VERSION)' | $(SED) -nE 's/.*([0-9]+)[.-]([0-9]+)[.-]([0-9]+).*/\1.\2.\3/p') +RELEASE_VERSIONS ?= ../../e2e-tests/release_versions -# Bundle configuration -OPENSHIFT_VERSIONS ?= v4.16-v4.19 PACKAGE_CHANNEL ?= stable MIN_KUBE_VERSION ?= "" +OPENSHIFT_VERSIONS ?= $(shell awk -F= '$$1 == "OPENSHIFT_MIN" { split($$2, v, "."); min = "v" v[1] "." v[2] } $$1 == "OPENSHIFT_MAX" { split($$2, v, "."); max = "v" v[1] "." v[2] } END { if (min && max) print min "-" max }' '$(RELEASE_VERSIONS)') + +# ============================================================================== +# Image Configuration +# ============================================================================== + +COMMUNITY_REGISTRY ?= docker.io +IMAGE_TAG_OWNER ?= percona +IMAGE_TAG_BASE ?= $(IMAGE_TAG_OWNER)/$(NAME) +IMAGE ?= $(COMMUNITY_REGISTRY)/$(IMAGE_TAG_BASE):$(VERSION) + +REDHAT_OPERATOR_IMAGE ?= registry.connect.redhat.com/percona/percona-server-mongodb-operator:$(VERSION) + +CONTAINER ?= docker DOCKER_DEFAULT_PLATFORM ?= linux/amd64 -# Paths +BUNDLE_TYPE ?= community +BUNDLE_REPO ?= $(IMAGE_TAG_BASE) +DEV_BUNDLE_REPO ?= docker.io/perconalab/$(NAME) +DEV_PACKAGE_SUFFIX ?= -dev +DEV_DISPLAY_NAME_SUFFIX ?= (Dev) +DEPLOY_BUNDLE_REPO ?= $(DEV_BUNDLE_REPO) +BUNDLE_IMAGE_VERSION ?= $(CSV_VERSION) +BUNDLE_PLATFORMS ?= linux/amd64,linux/arm64 +BUNDLE_TAG_TYPE = $(if $(filter redhat certified,$(BUNDLE_TYPE)),certified,$(BUNDLE_TYPE)) +BUNDLE_IMG ?= $(BUNDLE_REPO):$(BUNDLE_IMAGE_VERSION)-$(BUNDLE_TAG_TYPE)-bundle +CATALOG_REPO ?= $(DEV_BUNDLE_REPO) +CATALOG_NAMESPACE ?= openshift-marketplace +CATALOG_PLATFORMS ?= linux/amd64,linux/arm64 +CATALOG_SOURCE_SUFFIX ?= + +CONFIRM_PUSH ?= 1 +SKIP_DIGEST_FAILURE ?= 0 +RUN_BUNDLE_TIMEOUT ?= 10m + +# ============================================================================== +# Tool Configuration +# ============================================================================== + REPO_ROOT := $(shell git rev-parse --show-toplevel) KUSTOMIZE := $(REPO_ROOT)/bin/kustomize -# Tool versions +JQ_VERSION := 1.7.1 OPERATOR_SDK_VERSION := v1.41.1 +OPM_VERSION := v1.66.0 + +OS_KERNEL ?= $(shell uname -s | tr '[:upper:]' '[:lower:]') +OS_MACHINE ?= $(shell uname -m | sed 's/^x86_/amd/') +SYSTEM := $(OS_KERNEL)-$(OS_MACHINE) +TOOLS_DIR := tools/$(SYSTEM) -# Bundle image configuration -BUNDLE_IMG ?= $(IMAGE_TAG_BASE):community-bundle-$(VERSION) +export PATH := $(CURDIR)/$(TOOLS_DIR):$(PATH) -# System detection for tool downloads -UNAME_S := $(shell uname -s) -UNAME_M := $(shell uname -m) -OS_KERNEL := $(shell echo "$(UNAME_S)" | tr '[:upper:]' '[:lower:]') -OS_MACHINE := $(UNAME_M) +# ============================================================================== +# Display +# ============================================================================== -# Display colors GREEN := $(shell tput setaf 2) RESET := $(shell tput sgr0) -# Export variables for generate.sh -export VERSION OPENSHIFT_VERSIONS PACKAGE_CHANNEL MIN_KUBE_VERSION DOCKER_DEFAULT_PLATFORM MODE +define quiet_run +if [[ "$${OLM_VERBOSE:-0}" == "1" || "$${OLM_VERBOSE:-false}" == "true" ]]; then \ + $(2); \ +else \ + output_file="$$(mktemp)"; \ + if $(2) >"$${output_file}" 2>&1; then \ + rm -f "$${output_file}"; \ + else \ + cat "$${output_file}" >&2; \ + rm -f "$${output_file}"; \ + echo "[olm] ERROR: $(1) failed" >&2; \ + exit 1; \ + fi; \ +fi +endef + +export VERSION CSV_VERSION BUNDLE_IMAGE_VERSION IMAGE REDHAT_OPERATOR_IMAGE OPENSHIFT_VERSIONS PACKAGE_CHANNEL MIN_KUBE_VERSION +export PACKAGE_NAME_OVERRIDE CSV_NAME_OVERRIDE DISPLAY_NAME_OVERRIDE +export DOCKER_DEFAULT_PLATFORM BUNDLE_REPO BUNDLE_PLATFORMS SKIP_DIGEST_FAILURE # ============================================================================== # Bundle Targets # ============================================================================== -DISTROS := community redhat marketplace - .PHONY: bundles -bundles: ## Build all OLM bundles (community, redhat, marketplace) -bundles: check-prereqs $(DISTROS:%=bundles/%) +bundles: ## Build all OLM bundles (community, certified) +bundles: check-prereqs $(BUNDLE_TYPES:%=bundle/%) + +.PHONY: bundle/community +bundle/community: ## Build community OLM bundle -.PHONY: $(DISTROS:%=bundles/%) -$(DISTROS:%=bundles/%): bundles/%: tools/operator-sdk +.PHONY: bundle/certified +bundle/certified: ## Build certified OLM bundle + +.PHONY: $(BUNDLE_TYPES:%=bundle/%) +$(BUNDLE_TYPES:%=bundle/%): bundle/%: tools $(KUSTOMIZE) check-version check-csv-version check-tools @echo "$(GREEN)Building $* bundle...$(RESET)" - cd ../../config/manager/$(MODE)/ && $(KUSTOMIZE) edit set image psmdb-operator=$(IMAGE) - ./generate.sh $* - ./tools/operator-sdk bundle validate $@ --select-optional='suite=operatorframework' - $(if $(filter community,$*),./tools/operator-sdk bundle validate $@ --select-optional='name=community' --optional-values='index-path=$@/Dockerfile') - @echo "$(GREEN)✓ Bundle stored in installers/olm/bundles/$*$(RESET)" + @distribution="$*"; \ + operator_image="$(IMAGE)"; \ + if [[ "$*" == "certified" ]]; then \ + distribution="redhat"; \ + operator_image="$(REDHAT_OPERATOR_IMAGE)"; \ + fi; \ + manager_kustomization="../../config/manager/namespace/kustomization.yaml"; \ + original_manager_kustomization="$$(mktemp)"; \ + cp "$${manager_kustomization}" "$${original_manager_kustomization}"; \ + trap 'cp "$${original_manager_kustomization}" "$${manager_kustomization}"; rm -f "$${original_manager_kustomization}"' EXIT; \ + (cd ../../config/manager/namespace/ && $(KUSTOMIZE) edit set image psmdb-operator="$${operator_image}"); \ + BUNDLE_NAME="$*" ./generate.sh "$${distribution}"; \ + $(call quiet_run,Validating $* bundle,operator-sdk bundle validate "bundles/$*" --select-optional='suite=operatorframework'); \ + if [[ "$*" == "community" ]]; then \ + $(call quiet_run,Validating $* community bundle,operator-sdk bundle validate "bundles/$*" --select-optional='name=community' --optional-values='index-path=bundles/$*/Dockerfile'); \ + fi + @echo "$(GREEN)Bundle stored in installers/olm/bundles/$*$(RESET)" + +.PHONY: bundle-dev +bundle-dev: ## Build all OLM bundles and push bundle images to perconalab +bundle-dev: $(BUNDLE_TYPES:%=bundle-dev/%) + +.PHONY: bundle-dev/community +bundle-dev/community: ## Build and push community bundle image to perconalab + +.PHONY: bundle-dev/certified +bundle-dev/certified: ## Build and push certified bundle image to perconalab + +.PHONY: $(BUNDLE_TYPES:%=bundle-dev/%) +$(BUNDLE_TYPES:%=bundle-dev/%): bundle-dev/%: check-version check-csv-version + @package='$(NAME)$(DEV_PACKAGE_SUFFIX)'; \ + if [[ "$*" == "certified" ]]; then \ + package='$(NAME)-certified$(DEV_PACKAGE_SUFFIX)'; \ + fi; \ + csv_name="$${package}.v$(CSV_VERSION)"; \ + $(MAKE) bundle/$* PACKAGE_NAME_OVERRIDE="$${package}" CSV_NAME_OVERRIDE="$${csv_name}" DISPLAY_NAME_OVERRIDE="Percona Distribution for MongoDB Operator $(DEV_DISPLAY_NAME_SUFFIX)" + BUNDLE_REPO='$(DEV_BUNDLE_REPO)' ./build-image.sh '$(CONTAINER)' 'bundles/$*' '$*' '$(BUNDLE_IMAGE_VERSION)' + +# ============================================================================== +# Deploy Targets +# ============================================================================== + +.PHONY: catalog +catalog: ## Build and push all catalog images to perconalab +catalog: $(BUNDLE_TYPES:%=catalog/%) + +.PHONY: catalog/community +catalog/community: ## Build and push community catalog image to perconalab + +.PHONY: catalog/certified +catalog/certified: ## Build and push certified catalog image to perconalab + +.PHONY: $(BUNDLE_TYPES:%=catalog/%) +$(BUNDLE_TYPES:%=catalog/%): catalog/%: tools check-version check-csv-version + @catalog_root='catalogs'; \ + catalog_dir="$${catalog_root}/$*"; \ + catalog_image='$(CATALOG_REPO):$*-catalog'; \ + bundle_image='$(BUNDLE_REPO):$(BUNDLE_IMAGE_VERSION)-$*-bundle'; \ + rm -rf "$${catalog_dir}" "$${catalog_dir}.Dockerfile"; \ + mkdir -p "$${catalog_dir}"; \ + printf '%s\n' \ + 'Schema: olm.semver' \ + 'GenerateMajorChannels: false' \ + 'GenerateMinorChannels: false' \ + 'Stable:' \ + ' Bundles:' \ + " - Image: $${bundle_image}" \ + > "$${catalog_dir}/template.yaml"; \ + opm alpha render-template semver -o yaml < "$${catalog_dir}/template.yaml" > "$${catalog_dir}/catalog.yaml"; \ + opm validate "$${catalog_dir}"; \ + opm generate dockerfile "$${catalog_dir}"; \ + if [[ "$(CONFIRM_PUSH)" != "0" && "$(CONFIRM_PUSH)" != "false" ]]; then \ + read -r -p "Push image $${catalog_image}? [y/N] " answer; \ + [[ "$${answer}" == "y" || "$${answer}" == "Y" || "$${answer}" == "yes" || "$${answer}" == "YES" ]] || exit 1; \ + fi; \ + $(CONTAINER) buildx build \ + --platform '$(CATALOG_PLATFORMS)' \ + -f "$${catalog_dir}.Dockerfile" \ + -t "$${catalog_image}" \ + --push \ + "$${catalog_root}"; \ + echo "$(GREEN)Catalog image pushed: $${catalog_image}$(RESET)" + +.PHONY: deploy +deploy: ## Build, push, and deploy all catalog sources +deploy: $(BUNDLE_TYPES:%=deploy/%) + +.PHONY: deploy/community +deploy/community: ## Build, push, and deploy community bundle + +.PHONY: deploy/certified +deploy/certified: ## Build, push, and deploy certified bundle + +.PHONY: $(BUNDLE_TYPES:%=deploy/%) +$(BUNDLE_TYPES:%=deploy/%): deploy/%: tools check-version check-csv-version + $(MAKE) bundle/$* + BUNDLE_REPO='$(DEPLOY_BUNDLE_REPO)' ./build-image.sh '$(CONTAINER)' 'bundles/$*' '$*' '$(BUNDLE_IMAGE_VERSION)' + $(MAKE) catalog/$* BUNDLE_REPO='$(DEPLOY_BUNDLE_REPO)' + $(MAKE) apply-catalog/$* + +.PHONY: catalog-dev +catalog-dev: ## Build and push all development catalog images +catalog-dev: $(BUNDLE_TYPES:%=catalog-dev/%) + +.PHONY: catalog-dev/community +catalog-dev/community: ## Build and push community development catalog image + +.PHONY: catalog-dev/certified +catalog-dev/certified: ## Build and push certified development catalog image + +.PHONY: $(BUNDLE_TYPES:%=catalog-dev/%) +$(BUNDLE_TYPES:%=catalog-dev/%): catalog-dev/%: bundle-dev/% + $(MAKE) catalog/$* BUNDLE_REPO='$(DEV_BUNDLE_REPO)' + +.PHONY: deploy-dev +deploy-dev: ## Build, push, and deploy all development catalog sources +deploy-dev: $(BUNDLE_TYPES:%=deploy-dev/%) + +.PHONY: deploy-dev/community +deploy-dev/community: ## Build, push, and deploy community development catalog source + +.PHONY: deploy-dev/certified +deploy-dev/certified: ## Build, push, and deploy certified development catalog source + +.PHONY: $(BUNDLE_TYPES:%=deploy-dev/%) +$(BUNDLE_TYPES:%=deploy-dev/%): deploy-dev/%: catalog-dev/% + $(MAKE) apply-catalog/$* CATALOG_SOURCE_NAME='$*-dev' + +.PHONY: $(BUNDLE_TYPES:%=apply-catalog/%) +$(BUNDLE_TYPES:%=apply-catalog/%): apply-catalog/%: + @set -e; \ + catalog_source='$(CATALOG_SOURCE_NAME)'; \ + if [[ -z "$${catalog_source}" ]]; then \ + catalog_source='$(NAME)'; \ + if [[ "$*" == "certified" ]]; then catalog_source='$(NAME)-certified'; fi; \ + fi; \ + catalog_image='$(CATALOG_REPO):$*-catalog'; \ + catalog_dir='catalogs/$*'; \ + catalog_namespace='$(CATALOG_NAMESPACE)'; \ + legacy_catalog_source=''; \ + if [[ -n '$(CATALOG_SOURCE_NAME)' ]]; then \ + legacy_catalog_source='$(NAME)'; \ + if [[ "$*" == "certified" ]]; then legacy_catalog_source='$(NAME)-certified'; fi; \ + fi; \ + if ! kubectl get namespace "$${catalog_namespace}" >/dev/null 2>&1; then \ + kubectl create namespace "$${catalog_namespace}"; \ + fi; \ + printf '%s\n' \ + 'apiVersion: operators.coreos.com/v1alpha1' \ + 'kind: CatalogSource' \ + 'metadata:' \ + " name: $${catalog_source}" \ + " namespace: $${catalog_namespace}" \ + 'spec:' \ + " displayName: $${catalog_source}" \ + ' sourceType: grpc' \ + " image: $${catalog_image}" \ + ' imagePullPolicy: Always' \ + > "$${catalog_dir}/catalog-source.yaml"; \ + kubectl apply --validate=false -f "$${catalog_dir}/catalog-source.yaml"; \ + kubectl delete pod -n "$${catalog_namespace}" -l "olm.catalogSource=$${catalog_source}" --ignore-not-found; \ + if [[ -n "$${legacy_catalog_source}" && "$${legacy_catalog_source}" != "$${catalog_source}" ]]; then \ + kubectl delete catalogsource "$${legacy_catalog_source}" -n "$${catalog_namespace}" --ignore-not-found; \ + fi; \ + echo "$(GREEN)CatalogSource deployed: $${catalog_source} in $${catalog_namespace} -> $${catalog_image}$(RESET)" # ============================================================================== -# Docker Build & Push Targets +# Image Targets # ============================================================================== .PHONY: build -build: ## Build community bundle Docker image -build: - @echo "$(GREEN)Building bundle Docker image...$(RESET)" - docker build -f bundles/community/Dockerfile -t $(BUNDLE_IMG) --platform=linux/amd64 bundles/community - @echo "$(GREEN)✓ Bundle image built: $(BUNDLE_IMG)$(RESET)" +build: ## Build bundle image (set BUNDLE_TYPE=community|certified) +build: check-version check-csv-version + @echo "$(GREEN)Building bundle image...$(RESET)" + $(CONTAINER) build -t $(BUNDLE_IMG) --platform=$(DOCKER_DEFAULT_PLATFORM) bundles/$(BUNDLE_TYPE) + @echo "$(GREEN)Bundle image built: $(BUNDLE_IMG)$(RESET)" .PHONY: push -push: ## Push bundle Docker image to registry +push: ## Push bundle image to registry +push: check-version check-csv-version @echo "$(GREEN)Pushing bundle image to registry...$(RESET)" - docker push $(BUNDLE_IMG) - @echo "$(GREEN)✓ Bundle image pushed: $(BUNDLE_IMG)$(RESET)" + @if [[ "$(CONFIRM_PUSH)" != "0" && "$(CONFIRM_PUSH)" != "false" ]]; then \ + read -r -p "Push image $(BUNDLE_IMG)? [y/N] " answer; \ + [[ "$${answer}" == "y" || "$${answer}" == "Y" || "$${answer}" == "yes" || "$${answer}" == "YES" ]] || exit 1; \ + fi + $(CONTAINER) push $(BUNDLE_IMG) + @echo "$(GREEN)Bundle image pushed: $(BUNDLE_IMG)$(RESET)" + +.PHONY: build-bundle-images +build-bundle-images: check-version check-csv-version $(BUNDLE_TYPES:%=build-%-image) + +build-%-image: + ./build-image.sh '$(CONTAINER)' 'bundles/$*' '$*' '$(BUNDLE_IMAGE_VERSION)' + +# ============================================================================== +# Validation Targets +# ============================================================================== + +.PHONY: validate +validate: ## Run bundle image and directory validation +validate: tools +validate: $(BUNDLE_TYPES:%=validate-%-image) +validate: $(BUNDLE_TYPES:%=validate-%-directory) + +.PHONY: validate/community validate/certified +validate/community validate/certified: validate/%: tools + ./validate-image.sh '$(CONTAINER)' 'bundles/$*' + ./validate-directory.sh 'bundles/$*' + +validate-%-directory: + ./validate-directory.sh 'bundles/$*' + +validate-%-image: + ./validate-image.sh '$(CONTAINER)' 'bundles/$*' # ============================================================================== # Utility Targets # ============================================================================== +.PHONY: install-olm +install-olm: ## Install OLM in Kubernetes cluster +install-olm: tools + operator-sdk olm install + +.PHONY: clean +clean: ## Remove generated files and downloaded tools + rm -rf ./bundles ./catalogs ./projects ./tools + +.PHONY: help +help: ## Show this help message + @awk 'BEGIN {FS = ": ## "; printf "\n$(GREEN)Usage:$(RESET)\n make [target]\n\n$(GREEN)Targets:$(RESET)\n"} /^[a-zA-Z0-9_\/%-]+: ## / {printf " %-28s %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +# ============================================================================== +# Validation Helpers +# ============================================================================== + .PHONY: check-prereqs -check-prereqs: check-version check-git check-tools +check-prereqs: check-version check-csv-version check-tools tools $(KUSTOMIZE) .PHONY: check-version check-version: @@ -100,85 +362,42 @@ ifndef VERSION $(error VERSION is not set) endif -.PHONY: check-git -check-git: - @if ! git rev-parse --git-dir > /dev/null 2>&1; then \ - echo "Error: Not in a git repository"; \ +.PHONY: check-csv-version +check-csv-version: +ifndef CSV_VERSION + $(error CSV_VERSION is not set and could not be parsed from VERSION=$(VERSION)) +endif + @if ! [[ "$(CSV_VERSION)" =~ ^[0-9]+\.[0-9]+\.[0-9]+$$ ]]; then \ + echo "CSV_VERSION must be a semver without prerelease/build metadata, got: $(CSV_VERSION)"; \ exit 1; \ fi .PHONY: check-tools check-tools: - @for cmd in gawk gcsplit yq; do \ + @for cmd in gawk gcsplit yq yamllint envsubst; do \ if ! command -v $$cmd >/dev/null 2>&1; then \ echo "Error: $$cmd is required but not installed"; \ exit 1; \ fi; \ done -.PHONY: install-olm -install-olm: ## Install OLM in Kubernetes cluster -install-olm: tools/operator-sdk - ./tools/operator-sdk olm install - -.PHONY: clean -clean: ## Remove generated files and downloaded tools - rm -rf ./bundles ./projects ./tools - -.PHONY: help -help: ## Show this help message - @awk 'BEGIN {FS = ": ## "; printf "\n$(GREEN)Usage:$(RESET)\n make [target]\n\n$(GREEN)Targets:$(RESET)\n"} /^[a-zA-Z_-]+: ## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST) - -# ============================================================================== -# Tool Management -# ============================================================================== +$(KUSTOMIZE): + $(MAKE) -C ../.. bin/kustomize .PHONY: tools -tools: ## Download required tools -tools: tools/operator-sdk - -# Download operator-sdk -tools/operator-sdk: - @echo "Downloading operator-sdk $(OPERATOR_SDK_VERSION)..." - @install -d tools - @curl -fSL --fail -o '$@' \ - 'https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS_KERNEL)_$(OS_MACHINE)' \ - || { rm -f '$@'; echo "Failed to download operator-sdk"; exit 1; } - @chmod +x '$@' - @echo "✓ operator-sdk installed" - -# ============================================================================== -# Development Targets -# ============================================================================== +tools: $(TOOLS_DIR)/jq $(TOOLS_DIR)/operator-sdk $(TOOLS_DIR)/opm -.PHONY: validate -validate: ## Validate existing bundles without rebuilding - @for distro in $(DISTROS); do \ - if [ -d "bundles/$$distro" ]; then \ - echo "Validating $$distro bundle..."; \ - ./tools/operator-sdk bundle validate "bundles/$$distro" --select-optional='suite=operatorframework' || exit 1; \ - fi; \ - done - @echo "$(GREEN)✓ All bundles validated$(RESET)" +$(TOOLS_DIR): + mkdir -p $(TOOLS_DIR) -.PHONY: list-versions -list-versions: ## Show current version information - @echo "Current configuration:" - @echo " VERSION: $(VERSION)" - @echo " IMAGE: $(IMAGE)" - @echo " MODE: $(MODE)" - @echo " OPENSHIFT_VERSIONS: $(OPENSHIFT_VERSIONS)" - @echo " MIN_KUBE_VERSION: $(MIN_KUBE_VERSION)" +$(TOOLS_DIR)/jq: | $(TOOLS_DIR) + curl -sL "https://github.com/jqlang/jq/releases/download/jq-$(JQ_VERSION)/jq-$(OS_KERNEL)-$(OS_MACHINE)" -o $@ + chmod +x $@ -# ============================================================================== -# Kustomize Integration (from root Makefile) -# ============================================================================== - -# Include go-get-tool function from root Makefile if kustomize target is needed -ifneq (,$(findstring kustomize,$(MAKECMDGOALS))) -include ../../Makefile -endif +$(TOOLS_DIR)/operator-sdk: | $(TOOLS_DIR) + curl -sL "https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS_KERNEL)_$(OS_MACHINE)" -o $@ + chmod +x $@ -.PHONY: kustomize -kustomize: ## Download kustomize locally if necessary - $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v4@latest) \ No newline at end of file +$(TOOLS_DIR)/opm: | $(TOOLS_DIR) + curl -sL "https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$(OS_KERNEL)-$(OS_MACHINE)-opm" -o $@ + chmod +x $@ diff --git a/installers/olm/README.md b/installers/olm/README.md index 8ffbc3ea60..835fe294f7 100644 --- a/installers/olm/README.md +++ b/installers/olm/README.md @@ -1,23 +1,182 @@ -1. To generate bundle correctly please set env variables (default values for these variables you can check in makefile): +# Percona Server for MongoDB Operator OLM bundles + +This directory contains the automation used to generate, validate, build, publish, +and deploy OLM bundle content for the Percona Server for MongoDB Operator. + +Two bundle types are supported: + +- `community` +- `certified` + +Bundles are generated for namespace-scoped installation. Certified bundles keep +`MultiNamespace` and `AllNamespaces` unsupported, matching the current certified +OperatorHub bundle style. + +## Requirements + +Install the host tools checked by `make`: + +```bash +gawk +gcsplit +yq +yamllint +envsubst +kubectl +docker +``` + +The Makefile downloads the OLM helper tools into +`installers/olm/tools/-`: + +```bash +make tools +``` + +Downloaded tools: + +- `jq` +- `operator-sdk` +- `opm` + +## Variables + +Most workflows only need `VERSION`. + +```bash +export VERSION=1.23.0 +``` + +Useful optional variables: + +| Variable | Description | Example | +| --- | --- | --- | +| `CSV_VERSION` | CSV version. Defaults to the first `x.y.z` parsed from `VERSION`. | `1.23.0` | +| `IMAGE` | Community operator image used in generated manifests. | `docker.io/percona/percona-server-mongodb-operator:1.23.0` | +| `REDHAT_OPERATOR_IMAGE` | Certified operator image used in generated manifests. | `registry.connect.redhat.com/percona/percona-server-mongodb-operator:1.23.0` | +| `DEV_BUNDLE_REPO` | Development bundle and catalog image repository. | `docker.io/perconalab/percona-server-mongodb-operator` | +| `CONFIRM_PUSH` | Ask before pushing images. Set to `0` in CI. | `0` | +| `SKIP_DIGEST_FAILURE` | Continue certified generation when a digest cannot be resolved. Missing digests are rendered as ``. | `1` | + +OpenShift versions are resolved from `../../e2e-tests/release_versions` and used +to render `com.redhat.openshift.versions`. Override only when needed: + +```bash +export OPENSHIFT_VERSIONS="v4.18-v4.22" +``` + +## Development + +Development targets publish bundle and catalog images to `perconalab`. + +The development package names are suffixed with `-dev` to avoid colliding with +public OperatorHub packages already present in OpenShift default catalogs: + +- `percona-server-mongodb-operator-dev` +- `percona-server-mongodb-operator-certified-dev` + +The display name is suffixed with `(Dev)` in the OpenShift console. + +Build and push development bundle images: + +```bash +make bundle-dev VERSION=1.23.0 +make bundle-dev/community VERSION=1.23.0 +make bundle-dev/certified VERSION=1.23.0 +``` + +Deploy development catalogs to OpenShift: + +```bash +make deploy-dev VERSION=1.23.0 +make deploy-dev/community VERSION=1.23.0 +make deploy-dev/certified VERSION=1.23.0 +``` + +After deploying, search the OpenShift console for: + +```bash +Percona Distribution for MongoDB Operator (Dev) +``` + +Or check with: + +```bash +kubectl get packagemanifest percona-server-mongodb-operator-dev -n openshift-marketplace +kubectl get packagemanifest percona-server-mongodb-operator-certified-dev -n openshift-marketplace +``` + +## Release Example + +Generate release bundles locally: + +```bash +make bundles VERSION=1.23.0 +make bundle/community VERSION=1.23.0 +make bundle/certified VERSION=1.23.0 +``` + +Build, push, and deploy release catalogs: + +```bash +make deploy VERSION=1.23.0 +make deploy/community VERSION=1.23.0 +make deploy/certified VERSION=1.23.0 +``` + +Release deploy runs the full flow for each bundle type: + +```bash +make bundle/ +./build-image.sh ${CONTAINER} bundles/ ${BUNDLE_IMAGE_VERSION} +make catalog/ BUNDLE_REPO=${DEPLOY_BUNDLE_REPO} +make apply-catalog/ +``` + +## Validation + +Validate all generated bundles: + +```bash +make validate VERSION=1.23.0 +``` + +Validate one bundle: + ```bash -# operator version -export VERSION=1.18.0 -# By default we use perconalab for tag owner. Please update this variable to use another repo -export IMAGE_TAG_OWNER=percona -# Min k8s version -export MIN_KUBE_VERSION=1.27.0 -# Openshift versions: -export OPENSHIFT_VERSIONS="v4.13-v4.16" -# Set namespace or cluster (to generate bundles for cluster-wide) -export MODE=namespace +make validate/community VERSION=1.23.0 +make validate/certified VERSION=1.23.0 ``` -2. Also it could be useful to check variable in makefile and update if you need something extra. For the most cases to update these variables is enough -3. Update spec.description in bundle.csv.yaml with features added in this release. -4. Run bundle generation: + +Validation uses: + +- `validate-image.sh` +- `validate-directory.sh` + +## Certified Metadata + +Certified bundles resolve image metadata and related image digests through +`distributions/redhat.sh`. + +Bundle generation fails when: + +- a required image is missing +- a certified image tag does not match the expected pattern +- a required digest cannot be resolved and `SKIP_DIGEST_FAILURE` is not enabled + +With `SKIP_DIGEST_FAILURE=1`, missing digests are rendered as `` and +reported in the build output. + +## Cleanup + +Remove generated bundles, catalogs, temporary SDK projects, and downloaded tools: + ```bash -# Generate all bundles community redhat and marketplace: -make bundles -# Generate only specific bundle: -make bundles/community +make clean ``` +Show available targets: + +```bash +make help +``` diff --git a/installers/olm/build-image.sh b/installers/olm/build-image.sh new file mode 100755 index 0000000000..dce80fcc41 --- /dev/null +++ b/installers/olm/build-image.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +set -euo pipefail + +confirm_push() { + local image="$1" + local answer + + if [[ "${CONFIRM_PUSH:-1}" == "0" || "${CONFIRM_PUSH:-1}" == "false" ]]; then + return + fi + + read -r -p "Push image ${image}? [y/N] " answer + [[ "${answer}" == "y" || "${answer}" == "Y" || "${answer}" == "yes" || "${answer}" == "YES" ]] +} + +build_image() { + local container="$1" directory="$2" distro="$3" version="$4" + directory=$(cd "${directory}" && pwd) + + local bundle_name="${distro}" + if [[ "${distro}" == "redhat" ]]; then + bundle_name="certified" + fi + + local tag="${version}-${bundle_name}-bundle" + local image="${BUNDLE_REPO}:${tag}" + local platforms="${BUNDLE_PLATFORMS:-linux/amd64,linux/arm64}" + + pushd "${directory}" + + confirm_push "${image}" || exit 1 + "${container}" buildx build \ + --platform "${platforms}" \ + -t "${image}" \ + --push \ + . + + popd +} + +build_image "$@" diff --git a/installers/olm/bundle.annotations.yaml b/installers/olm/bundle.annotations.yaml index b3b558a47d..a0ba51c94e 100644 --- a/installers/olm/bundle.annotations.yaml +++ b/installers/olm/bundle.annotations.yaml @@ -6,7 +6,7 @@ annotations: operators.operatorframework.io.bundle.package.v1: percona-server-mongodb-operator operators.operatorframework.io.bundle.channels.v1: stable operators.operatorframework.io.bundle.channel.default.v1: stable - com.redhat.openshift.versions: 'v4.13' + com.redhat.openshift.versions: '' org.opencontainers.image.authors: info@percona.com org.opencontainers.image.url: https://percona.com org.opencontainers.image.vendor: Percona diff --git a/installers/olm/bundle.csv.yaml b/installers/olm/bundle.csv.yaml index 974c1d8d1e..723d787cf1 100644 --- a/installers/olm/bundle.csv.yaml +++ b/installers/olm/bundle.csv.yaml @@ -18,6 +18,8 @@ metadata: support: percona/percona.com categories: Database capabilities: Deep Insights + operatorframework.io/supported.archs: amd64,arm64 + operatorframework.io/supported.os: linux description: >- Percona Distribution for MongoDB Operator automates the creation, modification, or deletion of items in your Percona Server for MongoDB environment createdAt: "" @@ -39,8 +41,6 @@ spec: ## Percona is Cloud Native The Percona Distribution for MongoDB Kubernetes Operator automates the creation, modification, or deletion of items in your Percona Server for MongoDB environment. - The Operator contains the necessary Kubernetes settings to maintain a consistent Percona Server for MongoDB - instance modification, or deletion of items in your Percona Server for MongoDB environment. The Operator contains the necessary Kubernetes settings to maintain a consistent Percona Server for MongoDB instance. @@ -108,7 +108,6 @@ spec: Add the PSMDB user `Secret` to Kubernetes. User information must be placed in the data section of the `secrets.yaml` - file with Base64-encoded logins and passwords for the user accounts. @@ -139,11 +138,11 @@ spec: links: - name: Percona url: 'https://www.percona.com/' - - name: Percona Kubernetes Operators Landing Page - url: 'https://www.percona.com/software/percona-kubernetes-operators' - name: Documentation - url: 'https://docs.percona.com/percona-operator-for-mongodb/' - - name: Github + url: 'https://docs.percona.com/percona-operator-for-mongodb/index.html' + - name: Cloud Native Landing Page + url: 'https://www.percona.com/cloud-native/' + - name: GitHub url: 'https://github.com/percona/percona-server-mongodb-operator' maintainers: - name: Percona @@ -154,85 +153,7 @@ spec: mediatype: image/svg+xml customresourcedefinitions: - owned: - - description: Instance of a Percona Server for MongoDB replica set - displayName: PerconaServerMongoDB - kind: PerconaServerMongoDB - name: perconaservermongodbs.psmdb.percona.com - version: v1 - specDescriptors: [ ] - statusDescriptors: [ ] - resources: - - version: v1 - kind: Deployment - name: '' - - version: v1 - kind: Service - name: '' - - version: v1 - kind: ReplicaSet - name: '' - - version: v1 - kind: Pod - name: '' - - version: v1 - kind: Secret - name: '' - - version: v1 - kind: ConfigMap - name: '' - - description: Instance of a Percona Server for MongoDB Backup - displayName: PerconaServerMongoDBBackup - kind: PerconaServerMongoDBBackup - name: perconaservermongodbbackups.psmdb.percona.com - version: v1 - specDescriptors: [ ] - statusDescriptors: [ ] - resources: - - version: v1 - kind: Deployment - name: '' - - version: v1 - kind: Service - name: '' - - version: v1 - kind: ReplicaSet - name: '' - - version: v1 - kind: Pod - name: '' - - version: v1 - kind: Secret - name: '' - - version: v1 - kind: ConfigMap - name: '' - - description: Instance of a Percona Server for MongoDB Restore - displayName: PerconaServerMongoDBRestore - kind: PerconaServerMongoDBRestore - name: perconaservermongodbrestores.psmdb.percona.com - version: v1 - specDescriptors: [ ] - statusDescriptors: [ ] - resources: - - version: v1 - kind: Deployment - name: '' - - version: v1 - kind: Service - name: '' - - version: v1 - kind: ReplicaSet - name: '' - - version: v1 - kind: Pod - name: '' - - version: v1 - kind: Secret - name: '' - - version: v1 - kind: ConfigMap - name: '' + owned: [ ] required: [ ] install: strategy: deployment @@ -250,4 +171,4 @@ spec: strategy: deployment spec: permissions: - deployments: \ No newline at end of file + deployments: diff --git a/installers/olm/bundle.relatedImages.yaml b/installers/olm/bundle.relatedImages.yaml deleted file mode 100644 index 6036e5a2d7..0000000000 --- a/installers/olm/bundle.relatedImages.yaml +++ /dev/null @@ -1,16 +0,0 @@ -- name: mongod8.0 - image: registry.connect.redhat.com/percona/percona-server-mongodb-operator-containers@sha256: -- name: mongod7.0 - image: registry.connect.redhat.com/percona/percona-server-mongodb-operator-containers@sha256: -- name: mongod6.0 - image: registry.connect.redhat.com/percona/percona-server-mongodb-operator-containers@sha256: -- name: backup - image: registry.connect.redhat.com/percona/percona-server-mongodb-operator-containers@sha256: -- name: pmm - image: registry.connect.redhat.com/percona/percona-server-mongodb-operator-containers@sha256: -- name: pmm3 - image: registry.connect.redhat.com/percona/percona-server-mongodb-operator-containers@sha256: -- name: logcollector - image: registry.connect.redhat.com/percona/percona-server-mongodb-operator-containers@sha256: -- name: operator - image: registry.connect.redhat.com/percona/percona-server-mongodb-operator@sha256: diff --git a/installers/olm/distributions/redhat.sh b/installers/olm/distributions/redhat.sh new file mode 100755 index 0000000000..c5495d3439 --- /dev/null +++ b/installers/olm/distributions/redhat.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2016 + +redhat_release="${VERSION}" +redhat_registry="${REDHAT_REGISTRY:-registry.connect.redhat.com}" +redhat_catalog_api="${REDHAT_CATALOG_API:-https://catalog.redhat.com/api/containers/v1}" +redhat_catalog_curl_timeout="${REDHAT_CATALOG_CURL_TIMEOUT:-20}" +redhat_operator_repository="${REDHAT_OPERATOR_REPOSITORY:-percona/percona-server-mongodb-operator}" +redhat_containers_repository="${REDHAT_CONTAINERS_REPOSITORY:-percona/percona-server-mongodb-operator-containers}" +redhat_operator_tag="${REDHAT_OPERATOR_TAG:-${redhat_release}}" +redhat_related_images="[]" +redhat_missing_digests=() + +image_tag() { + local image="$1" + + printf '%s\n' "${image##*:}" +} + +digest_key() { + printf '%s' "$1" \ + | sed -E 's/[^[:alnum:]]+/_/g' \ + | tr '[:lower:]' '[:upper:]' +} + +catalog_digest() { + local repository="$1" + local tag="$2" + local digest + + debug "Resolving Red Hat digest for ${redhat_registry}/${repository}:${tag}" + + digest="$( + curl -fsSL \ + --connect-timeout 5 \ + --max-time "${redhat_catalog_curl_timeout}" \ + "${redhat_catalog_api}/repositories/registry/${redhat_registry}/repository/${repository}/tag/${tag}" \ + 2>/dev/null \ + | jq -er '.docker_image_digest // .data.docker_image_digest // .data[0].docker_image_digest' 2>/dev/null + )" || digest="$( + curl -fsSL \ + --connect-timeout 5 \ + --max-time "${redhat_catalog_curl_timeout}" \ + "${redhat_catalog_api}/repositories/registry/${redhat_registry}/repository/${repository}/images?page_size=500" \ + 2>/dev/null \ + | jq -er \ + --arg tag "${tag}" \ + 'first(.data[] | select(any(.repositories[]?.tags[]?; .name == $tag)) | .docker_image_digest)' \ + 2>/dev/null + )" || digest="" + + if [[ -n "${digest}" && "${digest}" != "null" ]]; then + printf 'sha256:%s\n' "${digest#sha256:}" + return + fi + + return 1 +} + +image_ref() { + local key="$1" + local name="$2" + local repository="$3" + local tag="$4" + local digest_var="REDHAT_IMAGE_DIGEST_$(digest_key "${key}")" + local digest="${!digest_var:-}" + + if [[ -z "${digest}" ]]; then + digest="$(catalog_digest "${repository}" "${tag}")" || digest="" + fi + + if [[ -z "${digest}" ]]; then + if [[ "${SKIP_DIGEST_FAILURE:-0}" == "1" || "${SKIP_DIGEST_FAILURE:-false}" == "true" ]]; then + digest="" + redhat_missing_digests+=("${name}:${redhat_registry}/${repository}:${tag}") + else + abort "unable to resolve digest for ${redhat_registry}/${repository}:${tag}; set SKIP_DIGEST_FAILURE=1 to continue with " + fi + fi + + if [[ "${digest}" != "" ]]; then + digest="sha256:${digest#sha256:}" + fi + + printf '%s/%s@%s\n' "${redhat_registry}" "${repository}" "${digest}" +} + +validate_certified_tag() { + local key="$1" + local tag="$2" + local source_tag="${3:-}" + local expected="" + + case "${key}" in + IMAGE_OPERATOR) + expected="${redhat_release}" + ;; + IMAGE_MONGOD60|IMAGE_MONGOD70|IMAGE_MONGOD80) + expected="${redhat_release}-psmdb-${source_tag}" + ;; + IMAGE_BACKUP) + expected="${redhat_release}-backup" + ;; + IMAGE_PMM_CLIENT) + expected="${redhat_release}-pmm" + ;; + IMAGE_PMM3_CLIENT) + expected="${redhat_release}-pmm3" + ;; + IMAGE_LOGCOLLECTOR) + expected="${redhat_release}-logcollector-${source_tag}" + ;; + *) + abort "unsupported certified image key: ${key}" + ;; + esac + + [[ "${tag}" == "${expected}" ]] \ + || abort "invalid Red Hat tag for ${key}: got '${tag}', expected '${expected}'" +} + +add_related_image() { + local key="$1" + local name="$2" + local repository="$3" + local tag="$4" + local source_tag="${5:-}" + local image + + validate_certified_tag "${key}" "${tag}" "${source_tag}" + + image="$(image_ref "${key}" "${name}" "${repository}" "${tag}")" + + log "Related image ${name}: ${image}" + + redhat_related_images="$( + jq -c \ + --arg name "${name}" \ + --arg image "${image}" \ + '. + [{ name: $name, image: $image }]' \ + <<<"${redhat_related_images}" + )" +} + +related_image_by_name() { + local name="$1" + + jq --raw-output \ + --arg name "${name}" \ + 'map(select(.name == $name)) | last.image // ""' \ + <<<"${redhat_related_images}" +} + +require_release_image() { + local key="$1" + + if [[ -z "${!key:-}" ]]; then + abort "${key} is required in e2e-tests/release_versions" + fi +} + +report_missing_digests() { + local item + + [[ "${#redhat_missing_digests[@]}" -eq 0 ]] && return + + log "Digest resolution failed for the following image(s); was used because SKIP_DIGEST_FAILURE is enabled:" + for item in "${redhat_missing_digests[@]}"; do + log " - ${item}" + done +} + +build_redhat_related_images() { + local release_versions="${repo_root}/e2e-tests/release_versions" + local mongod60_tag + local mongod70_tag + local mongod80_tag + local logcollector_tag + + log "Building Red Hat related images from ${release_versions}" + + [[ -f "${release_versions}" ]] \ + || abort "release versions file not found: ${release_versions}" + + # shellcheck source=/dev/null + source "${release_versions}" + + for key in \ + IMAGE_MONGOD60 \ + IMAGE_MONGOD70 \ + IMAGE_MONGOD80 \ + IMAGE_BACKUP \ + IMAGE_PMM_CLIENT \ + IMAGE_PMM3_CLIENT \ + IMAGE_LOGCOLLECTOR; do + require_release_image "${key}" + done + + mongod60_tag="$(image_tag "${IMAGE_MONGOD60}")" + mongod70_tag="$(image_tag "${IMAGE_MONGOD70}")" + mongod80_tag="$(image_tag "${IMAGE_MONGOD80}")" + logcollector_tag="$(image_tag "${IMAGE_LOGCOLLECTOR}")" + + add_related_image "IMAGE_MONGOD80" "mongod8.0" "${redhat_containers_repository}" "${redhat_release}-psmdb-${mongod80_tag}" "${mongod80_tag}" + add_related_image "IMAGE_MONGOD70" "mongod7.0" "${redhat_containers_repository}" "${redhat_release}-psmdb-${mongod70_tag}" "${mongod70_tag}" + add_related_image "IMAGE_MONGOD60" "mongod6.0" "${redhat_containers_repository}" "${redhat_release}-psmdb-${mongod60_tag}" "${mongod60_tag}" + add_related_image "IMAGE_BACKUP" "backup" "${redhat_containers_repository}" "${redhat_release}-backup" + add_related_image "IMAGE_PMM_CLIENT" "pmm" "${redhat_containers_repository}" "${redhat_release}-pmm" + add_related_image "IMAGE_PMM3_CLIENT" "pmm3" "${redhat_containers_repository}" "${redhat_release}-pmm3" + add_related_image "IMAGE_LOGCOLLECTOR" "logcollector" "${redhat_containers_repository}" "${redhat_release}-logcollector-${logcollector_tag}" "${logcollector_tag}" + add_related_image "IMAGE_OPERATOR" "operator" "${redhat_operator_repository}" "${redhat_operator_tag}" + + report_missing_digests + + jq -nc \ + --arg operator_image "$(related_image_by_name operator)" \ + --argjson related_images "${redhat_related_images}" \ + '{ + operatorImage: $operator_image, + relatedImages: $related_images + }' +} diff --git a/installers/olm/generate.sh b/installers/olm/generate.sh index e5964dd35a..167e0c1e87 100755 --- a/installers/olm/generate.sh +++ b/installers/olm/generate.sh @@ -1,182 +1,299 @@ #!/usr/bin/env bash -# Install -# brew install gawk coreutils -for command in gawk gcsplit; do - if ! command -v $command &>/dev/null; then - echo "Error: $command is not installed. Please install it: brew install $command" >&2 - exit 1 - fi -done - -set -eu +set -euo pipefail -DISTRIBUTION="$1" +DISTRIBUTION="${1:?Distribution argument required (community|redhat)}" cd "${BASH_SOURCE[0]%/*}" -bundle_directory="bundles/${DISTRIBUTION}" -project_directory="projects/${DISTRIBUTION}" -go_api_directory=$(cd ../../pkg/apis && pwd) - -# The 'operators.operatorframework.io.bundle.package.v1' package name for each -# bundle (updated for the 'certified' and 'marketplace' bundles). -package_name='percona-server-mongodb-operator' - -# The project name used by operator-sdk for initial bundle generation. -project_name='percona-server-mongodb-operator' +repo_root="$(cd ../.. && pwd)" +release_versions_file="${repo_root}/e2e-tests/release_versions" +bundle_name="${BUNDLE_NAME:-${DISTRIBUTION}}" +bundle_directory="bundles/${bundle_name}" +project_directory="projects/${bundle_name}" +go_api_directory="$(cd ../../pkg/apis && pwd)" -# The prefix for the 'clusterserviceversion.yaml' file. -# Per OLM guidance, the filename for the clusterserviceversion.yaml must be prefixed -# with the Operator's package name for the 'redhat' and 'marketplace' bundles. -# https://github.com/redhat-openshift-ecosystem/certification-releases/blob/main/4.9/ga/troubleshooting.md#get-supported-versions -file_name='percona-server-mongodb-operator' +package_name="percona-server-mongodb-operator" +project_name="percona-server-mongodb-operator" +file_name="percona-server-mongodb-operator" NS_RESOURCE_RBAC="../rbac/namespace" -CLUSTER_RESOURCE_RBAC="../rbac/cluster" NS_RESOURCE_OPERATOR="../manager/namespace" -CLUSTER_RESOURCE_OPERATOR="../manager/cluster" KUSTOMIZATION_FILE="../../config/bundle/kustomization.yaml" -if [ "${MODE}" == "cluster" ]; then - suffix="-cw" - mode="Cluster" - rulesLevel="ClusterPermissions" - sed -i '' "s|$NS_RESOURCE_RBAC|$CLUSTER_RESOURCE_RBAC|g" "$KUSTOMIZATION_FILE" - sed -i '' "s|$NS_RESOURCE_OPERATOR|$CLUSTER_RESOURCE_OPERATOR|g" "$KUSTOMIZATION_FILE" -elif [ "${MODE}" == "namespace" ]; then - suffix="" - mode="" - rulesLevel="permissions" - sed -i '' "s|$CLUSTER_RESOURCE_RBAC|$NS_RESOURCE_RBAC|g" "$KUSTOMIZATION_FILE" - sed -i '' "s|$CLUSTER_RESOURCE_OPERATOR|$NS_RESOURCE_OPERATOR|g" "$KUSTOMIZATION_FILE" -else - echo "Please add MODE variable. It could be either namespace or cluster" +rulesLevel="permissions" +relatedImages="[]" +containerImage="" +csv_stem="" +redhat_distribution_images="{}" + +log() { + echo >&2 "[olm] $*" +} + +debug() { + if [[ "${OLM_VERBOSE:-0}" == "1" || "${OLM_VERBOSE:-false}" == "true" ]]; then + log "$@" + fi +} + +abort() { + echo >&2 "[olm] ERROR: $*" exit 1 -fi -# Copy operator file to config: -cp ../../deploy/operator.yaml ../../config/manager/namespace -cp ../../deploy/cw-operator.yaml ../../config/manager/cluster +} -# Copy RBAC: -gcsplit --elide-empty-files -f output- ../../deploy/rbac.yaml "/^---$/" "{*}" -target_dir="../../config/rbac/namespace" -mv output-00 "$target_dir/role.yaml" -mv output-01 "$target_dir/service_account.yaml" -mv output-02 "$target_dir/role_binding.yaml" +run_quiet() { + local description="$1" + shift -# Copy RBAC for CW: -gcsplit --elide-empty-files -f output- ../../deploy/cw-rbac.yaml "/^---$/" "{*}" -target_dir="../../config/rbac/cluster" -mv output-00 "$target_dir/role.yaml" -mv output-01 "$target_dir/service_account.yaml" -mv output-02 "$target_dir/role_binding.yaml" + if [[ "${OLM_VERBOSE:-0}" == "1" || "${OLM_VERBOSE:-false}" == "true" ]]; then + "$@" + return + fi -kubectl kustomize "../../config/${DISTRIBUTION}" >operator_yamls.yaml + local output_file + output_file="$(mktemp)" -export role="${mode}Role" + if "$@" >"${output_file}" 2>&1; then + rm -f "${output_file}" + return + fi -update_yaml_images() { - local yaml_file="$1" + cat "${output_file}" >&2 + rm -f "${output_file}" + abort "${description} failed" +} + +run_quiet_output() { + local output_path="$1" + local description="$2" + shift 2 - if [ ! -f "$yaml_file" ]; then - echo "Error: File '$yaml_file' does not exist." - return 1 + if [[ "${OLM_VERBOSE:-0}" == "1" || "${OLM_VERBOSE:-false}" == "true" ]]; then + "$@" >"${output_path}" + return fi - local temp_file - temp_file=$(mktemp) + local error_file + error_file="$(mktemp)" - sed -E 's/(("image":|"initImage":|containerImage:|image:|initImage:)[ ]*"?)([^"]+)("?)/\1docker.io\/\3\4/g' "$yaml_file" >"$temp_file" - mv "$temp_file" "$yaml_file" + if "$@" >"${output_path}" 2>"${error_file}"; then + rm -f "${error_file}" + return + fi - echo "File '$yaml_file' updated successfully." + cat "${error_file}" >&2 + rm -f "${error_file}" + abort "${description} failed" } -yq eval '. | select(.kind == "CustomResourceDefinition")' operator_yamls.yaml >operator_crds.yaml -yq eval '. | select(.kind == "Deployment")' operator_yamls.yaml >operator_deployments.yaml -yq eval '. | select(.kind == "ServiceAccount")' operator_yamls.yaml >operator_accounts.yaml -yq eval '. | select(.kind == env(role))' operator_yamls.yaml >operator_roles${suffix}.yaml +require() { + if [ $# -eq 1 ]; then + command -v "$1" >/dev/null 2>&1 \ + || abort "$1 not found in PATH" + else + "$@" >/dev/null 2>&1 \ + || abort "Failed running: $*" + fi +} -## Recreate the Operator SDK project. +sed_in_place() { + local expression="$1" + local file="$2" + local tmp_file -[ ! -d "${project_directory}" ] || rm -r "${project_directory}" -install -d "${project_directory}" -( - cd "${project_directory}" - operator-sdk init --fetch-deps='false' --project-name=${project_name} + tmp_file="$(mktemp)" + sed "$expression" "$file" >"$tmp_file" + mv "$tmp_file" "$file" +} - # Generate CRD descriptions from Go markers. - # https://sdk.operatorframework.io/docs/building-operators/golang/references/markers/ - yq eval '[. | {"group": .spec.group, "kind": .spec.names.kind, "version": .spec.versions[].name}]' ../../../../deploy/crd.yaml >crd_gvks.yaml +check_tools() { + local command - yq eval --inplace '.multigroup = true | .resources = load("crd_gvks.yaml" | fromyaml) | .' ./PROJECT + for command in gawk gcsplit yq jq kubectl operator-sdk yamllint envsubst; do + require "$command" + done +} + +release_version_value() { + local key="$1" + + awk -F= -v key="${key}" '$1 == key { print $2 }' "${release_versions_file}" \ + | tr -d '"' \ + | tail -1 +} + +release_image_ref() { + local key="$1" + local image + + image="$(release_version_value "${key}")" + [[ -n "${image}" ]] || abort "${key} is required in ${release_versions_file}" + + case "${image}" in + *.*/*|*:*/*|localhost/*) + printf '%s' "${image}" + ;; + *) + printf 'docker.io/%s' "${image}" + ;; + esac +} + +resolve_openshift_versions() { + local openshift_min + local openshift_max + + if [[ -n "${OPENSHIFT_VERSIONS:-}" ]]; then + printf '%s' "${OPENSHIFT_VERSIONS}" + return + fi + + [[ -f "${release_versions_file}" ]] \ + || abort "OPENSHIFT_VERSIONS is not set and ${release_versions_file} does not exist" + + openshift_min="$(release_version_value "OPENSHIFT_MIN" | awk -F. '{ print "v" $1 "." $2 }')" + openshift_max="$(release_version_value "OPENSHIFT_MAX" | awk -F. '{ print "v" $1 "." $2 }')" + + [[ -n "${openshift_min}" && -n "${openshift_max}" ]] \ + || abort "OPENSHIFT_MIN and OPENSHIFT_MAX must be set in ${release_versions_file}" - ln -s "${go_api_directory}" . - operator-sdk generate kustomize manifests --interactive='false' --verbose -) + printf '%s-%s' "${openshift_min}" "${openshift_max}" +} + +load_distribution_hooks() { + local hook_file="" + + case "${DISTRIBUTION}" in + redhat) + hook_file="distributions/redhat.sh" + ;; + community) + hook_file="distributions/community.sh" + ;; + esac + + if [[ -n "${hook_file}" && -f "${hook_file}" ]]; then + log "Loading distribution hooks from ${hook_file}" + # shellcheck source=/dev/null + source "${hook_file}" + fi +} + +configure_namespace_manifests() { + sed_in_place "s|../rbac/cluster|$NS_RESOURCE_RBAC|g" "$KUSTOMIZATION_FILE" + sed_in_place "s|../manager/cluster|$NS_RESOURCE_OPERATOR|g" "$KUSTOMIZATION_FILE" +} -# Recreate the OLM bundle. -[ ! -d "${bundle_directory}" ] || rm -r "${bundle_directory}" -install -d \ - "${bundle_directory}/manifests" \ - "${bundle_directory}/metadata" +prepare_operator_sources() { + log "Preparing namespace-scoped operator manifests" -# Render bundle annotations and strip comments. -# Per Red Hat we should not include the org.opencontainers annotations in the -# 'redhat' & 'marketplace' annotations.yaml file, so only add them for 'community'. -# - https://coreos.slack.com/team/UP1LZCC1Y + cp ../../deploy/operator.yaml ../../config/manager/namespace -export package="${package_name}" -export package_channel="${PACKAGE_CHANNEL}${suffix}" -export openshift_supported_versions="${OPENSHIFT_VERSIONS}" + gcsplit --elide-empty-files -f output- ../../deploy/rbac.yaml "/^---$/" "{*}" >/dev/null + mv output-00 ../../config/rbac/namespace/role.yaml + mv output-01 ../../config/rbac/namespace/service_account.yaml + mv output-02 ../../config/rbac/namespace/role_binding.yaml +} + +render_operator_manifests() { + log "Rendering operator manifests for ${DISTRIBUTION}" + + run_quiet_output operator_yamls.yaml "Rendering operator manifests" \ + kubectl kustomize "../../config/${DISTRIBUTION}" -yq eval '.annotations["operators.operatorframework.io.bundle.channels.v1"] = env(package_channel) | - .annotations["operators.operatorframework.io.bundle.channel.default.v1"] = env(package_channel) | - .annotations["com.redhat.openshift.versions"] = env(openshift_supported_versions)' \ - bundle.annotations.yaml >"${bundle_directory}/metadata/annotations.yaml" + yq eval '. | select(.kind == "CustomResourceDefinition")' operator_yamls.yaml >operator_crds.yaml + yq eval '. | select(.kind == "Deployment")' operator_yamls.yaml >operator_deployments.yaml + yq eval '. | select(.kind == "ServiceAccount")' operator_yamls.yaml >operator_accounts.yaml + yq eval '. | select(.kind == "Role")' operator_yamls.yaml >operator_roles.yaml +} + +create_sdk_workspace() { + log "Creating Operator SDK workspace" -if [ "${DISTRIBUTION}" == 'community' ]; then - # community-operators - yq eval --inplace ' - .annotations["operators.operatorframework.io.bundle.package.v1"] = "percona-server-mongodb-operator" | - .annotations["org.opencontainers.image.authors"] = "info@percona.com" | - .annotations["org.opencontainers.image.url"] = "https://percona.com" | - .annotations["org.opencontainers.image.vendor"] = "Percona"' \ - "${bundle_directory}/metadata/annotations.yaml" + rm -rf "${project_directory}" + install -d "${project_directory}" -# certified-operators -elif [ "${DISTRIBUTION}" == 'redhat' ]; then - yq eval --inplace ' - .annotations["operators.operatorframework.io.bundle.package.v1"] = "percona-server-mongodb-operator-certified" ' \ - "${bundle_directory}/metadata/annotations.yaml" + ( + cd "${project_directory}" + run_quiet "Creating Operator SDK workspace" \ + operator-sdk init --fetch-deps="false" --project-name="${project_name}" -# redhat-marketplace -elif [ "${DISTRIBUTION}" == 'marketplace' ]; then - yq eval --inplace ' - .annotations["operators.operatorframework.io.bundle.package.v1"] = "percona-server-mongodb-operator-certified-rhmp" ' \ - "${bundle_directory}/metadata/annotations.yaml" -fi + yq eval '[. | {"group": .spec.group, "kind": .spec.names.kind, "version": .spec.versions[].name}]' \ + ../../../../deploy/crd.yaml >crd_gvks.yaml -# Copy annotations into Dockerfile LABELs. + yq eval --inplace '.multigroup = true | .resources = load("crd_gvks.yaml" | fromyaml) | .' ./PROJECT -labels=$(yq eval -r '.annotations | to_entries | map("LABEL " + .key + "=" + (.value | tojson)) | join("\n")' \ - "${bundle_directory}/metadata/annotations.yaml") + ln -s "${go_api_directory}" . + run_quiet "Generating Operator SDK kustomize manifests" \ + operator-sdk generate kustomize manifests --interactive="false" + ) +} -labels="${labels} +create_bundle_directory() { + log "Creating bundle directory ${bundle_directory}" + + rm -rf "${bundle_directory}" + install -d \ + "${bundle_directory}/manifests" \ + "${bundle_directory}/metadata" +} + +render_bundle_metadata() { + log "Rendering bundle metadata" + + export package="${PACKAGE_NAME_OVERRIDE:-${package_name}}" + export package_channel="${PACKAGE_CHANNEL:-stable}" + export openshift_supported_versions + openshift_supported_versions="$(resolve_openshift_versions)" + + if [[ "${DISTRIBUTION}" == "redhat" ]]; then + export package="${PACKAGE_NAME_OVERRIDE:-${package_name}-certified}" + fi + + yq eval '.annotations["operators.operatorframework.io.bundle.channels.v1"] = env(package_channel) | + .annotations["operators.operatorframework.io.bundle.channel.default.v1"] = env(package_channel) | + .annotations["operators.operatorframework.io.bundle.package.v1"] = env(package) | + .annotations["com.redhat.openshift.versions"] = env(openshift_supported_versions)' \ + bundle.annotations.yaml >"${bundle_directory}/metadata/annotations.yaml" + + case "${DISTRIBUTION}" in + community) + yq eval --inplace ' + .annotations["operators.operatorframework.io.bundle.package.v1"] = env(package) | + .annotations["org.opencontainers.image.authors"] = "info@percona.com" | + .annotations["org.opencontainers.image.url"] = "https://percona.com" | + .annotations["org.opencontainers.image.vendor"] = "Percona"' \ + "${bundle_directory}/metadata/annotations.yaml" + ;; + redhat) + ;; + esac +} + +render_bundle_dockerfile() { + local labels + + labels="$(yq eval -r '.annotations | to_entries | map("LABEL " + .key + "=" + (.value | tojson)) | join("\n")' \ + "${bundle_directory}/metadata/annotations.yaml")" + + labels="${labels} LABEL com.redhat.delivery.backport=true LABEL com.redhat.delivery.operator.bundle=true" -echo "$labels" + LABELS="${labels}" envsubst "${bundle_directory}/Dockerfile" + awk '{gsub(/^[ \t]+/, " "); print}' "${bundle_directory}/Dockerfile" >"${bundle_directory}/Dockerfile.new" + mv "${bundle_directory}/Dockerfile.new" "${bundle_directory}/Dockerfile" +} -LABELS="${labels}" envsubst "${bundle_directory}/Dockerfile" +write_crd_manifests() { + local crd_names -awk '{gsub(/^[ \t]+/, " "); print}' "${bundle_directory}/Dockerfile" >"${bundle_directory}/Dockerfile.new" && mv "${bundle_directory}/Dockerfile.new" "${bundle_directory}/Dockerfile" + log "Writing CRD manifests" -# Include CRDs as manifests. -crd_names=$(yq eval -o=tsv '.metadata.name' ../../deploy/crd.yaml) + crd_names="$(yq eval -o=tsv '.metadata.name' ../../deploy/crd.yaml)" -gawk -v names="${crd_names}" -v bundle_directory="${bundle_directory}" ' + gawk -v names="${crd_names}" -v bundle_directory="${bundle_directory}" ' BEGIN { split(names, name_array, " "); idx=1; @@ -197,111 +314,295 @@ BEGIN { } ' ../../deploy/crd.yaml -find "${bundle_directory}/manifests" -type f -name "*.crd.yaml" -exec sed -i '' '1s/^/---\n/; ${/^---$/d;}' {} + + find "${bundle_directory}/manifests" -type f -name "*.crd.yaml" -print0 | while IFS= read -r -d '' file; do + sed_in_place '1s/^/---\ +/; ${/^---$/d;}' "$file" + done +} -abort() { - echo >&2 "$@" - exit 1 +validate_manifest_inputs() { + yq eval -i '[.]' operator_deployments.yaml + yq eval 'length == 1' operator_deployments.yaml --exit-status >/dev/null \ + || abort "too many deployments accounts: $(yq eval . operator_deployments.yaml)" + + yq eval -i '[.]' operator_accounts.yaml + yq eval 'length == 1' operator_accounts.yaml --exit-status >/dev/null \ + || abort "too many service accounts: $(yq eval . operator_accounts.yaml)" + + yq eval -i '[.]' operator_roles.yaml + yq eval 'length == 1' operator_roles.yaml --exit-status >/dev/null \ + || abort "too many roles: $(yq eval . operator_roles.yaml)" +} + +build_examples() { + local cr_example + local backup_example + local image_backup + local image_logcollector + local image_mongod + local image_operator + local image_pmm + local restore_example + + image_backup="$(release_image_ref "IMAGE_BACKUP")" + image_logcollector="$(release_image_ref "IMAGE_LOGCOLLECTOR")" + image_mongod="$(release_image_ref "IMAGE_MONGOD80")" + image_operator="$(release_image_ref "IMAGE_OPERATOR")" + image_pmm="$(release_image_ref "IMAGE_PMM3_CLIENT")" + + if [[ "${DISTRIBUTION}" == "redhat" ]]; then + image_backup="$(jq -r '.relatedImages[] | select(.name == "backup").image' <<<"${redhat_distribution_images}")" + image_logcollector="$(jq -r '.relatedImages[] | select(.name == "logcollector").image' <<<"${redhat_distribution_images}")" + image_mongod="$(jq -r '.relatedImages[] | select(.name == "mongod8.0").image' <<<"${redhat_distribution_images}")" + image_operator="$(jq -r '.operatorImage' <<<"${redhat_distribution_images}")" + image_pmm="$(jq -r '.relatedImages[] | select(.name == "pmm3").image' <<<"${redhat_distribution_images}")" + fi + + cr_example="$( + yq eval -o=json ../../deploy/cr.yaml | + jq \ + --arg imageBackup "${image_backup}" \ + --arg imageLogcollector "${image_logcollector}" \ + --arg imageMongod "${image_mongod}" \ + --arg imageOperator "${image_operator}" \ + --arg imagePmm "${image_pmm}" \ + ' + def insert_after($k; $new): + to_entries as $e + | reduce $e[] as $item ({}; + . + {($item.key): $item.value} + | if $item.key == $k then . + $new else . end + ); + + .spec |= ( + if has("initImage") then del(.initImage) else . end + | .image = $imageMongod + | insert_after("image"; {"initImage": $imageOperator}) + | if has("initImage") then . else . + {"initImage": $imageOperator} end + | .pmm.image = $imagePmm + | .backup.image = $imageBackup + | .logcollector.image = $imageLogcollector + ) + ' + )" + backup_example="$(yq eval -o=json ../../deploy/backup/backup.yaml)" + restore_example="$(yq eval -o=json ../../deploy/backup/restore.yaml)" + + jq -n "[${cr_example}, ${backup_example}, ${restore_example}]" +} + +build_managed_resources() { + yq eval -o=json '.' operator_roles.yaml | + jq ' + def kind: + { + "certificaterequests": "CertificateRequest", + "certificates": "Certificate", + "configmaps": "ConfigMap", + "cronjobs": "CronJob", + "deployments": "Deployment", + "issuers": "Issuer", + "persistentvolumeclaims": "PersistentVolumeClaim", + "poddisruptionbudgets": "PodDisruptionBudget", + "pods": "Pod", + "replicasets": "ReplicaSet", + "secrets": "Secret", + "serviceexports": "ServiceExport", + "serviceimports": "ServiceImport", + "services": "Service", + "statefulsets": "StatefulSet", + "volumesnapshots": "VolumeSnapshot" + }[.] // .; + + def version($apiGroup): + if $apiGroup == "" then "v1" + else $apiGroup + "/v1" + end; + + [ + (if type == "array" then . else [.] end)[].rules[] + | select((.verbs // []) | any(. == "create" or . == "update" or . == "patch" or . == "delete" or . == "deletecollection")) + | .apiGroups[] as $apiGroup + | select($apiGroup != "psmdb.percona.com") + | .resources[] + | select((contains("/") | not) and . != "events" and . != "leases") + | { + "version": version($apiGroup), + "kind": kind, + "name": "" + } + ] | unique_by(.version + "/" + .kind) | sort_by(.version, .kind) + ' } -dump() { yq --color-output; } -# The first command render yaml correctly and the second extract data. +build_owned_crds() { + local managed_resources + + managed_resources="$(build_managed_resources)" + + yq eval-all -o=json '[.]' ../../deploy/crd.yaml | + jq --argjson managed_resources "${managed_resources}" ' + def crd_description: + { + "PerconaServerMongoDB": "Instance of a Percona Server for MongoDB replica set", + "PerconaServerMongoDBBackup": "Instance of a Percona Server for MongoDB Backup", + "PerconaServerMongoDBRestore": "Instance of a Percona Server for MongoDB Restore", + "PerconaServerMongoDBClusterSync": "Instance of a Percona Server for MongoDB Cluster Sync" + }[.spec.names.kind] // ("Instance of a " + .spec.names.kind); + + [ + .[] + | select(.kind == "CustomResourceDefinition") + | { + "description": crd_description, + "displayName": .spec.names.kind, + "kind": .spec.names.kind, + "name": .metadata.name, + "version": (.spec.versions[] | select(.storage == true) | .name), + "specDescriptors": [], + "statusDescriptors": [], + "resources": (if .spec.names.kind == "PerconaServerMongoDB" then $managed_resources else [] end) + } + ] + ' +} -yq eval -i '[.]' operator_deployments.yaml && yq eval 'length == 1' operator_deployments.yaml --exit-status >/dev/null || abort "too many deployments accounts!" $'\n'"$(yq eval . operator_deployments.yaml)" +update_yaml_images() { + local yaml_file="$1" + local temp_file -yq eval -i '[.]' operator_accounts.yaml && yq eval 'length == 1' operator_accounts.yaml --exit-status >/dev/null || abort "too many service accounts!" $'\n'"$(yq eval . operator_accounts.yaml)" + [[ -f "$yaml_file" ]] \ + || abort "file '$yaml_file' does not exist" -yq eval -i '[.]' operator_roles${suffix}.yaml && yq eval 'length == 1' operator_roles${suffix}.yaml --exit-status >/dev/null || abort "too many roles!" $'\n'"$(yq eval . operator_roles${suffix}.yaml)" + temp_file="$(mktemp)" + sed -E 's/(("image":|"initImage":|containerImage:|image:|initImage:)[ ]*"?)([^"]+)("?)/\1docker.io\/\3\4/g' "$yaml_file" >"$temp_file" + mv "$temp_file" "$yaml_file" +} -# Render bundle CSV and strip comments. -csv_stem=$(yq -r '.projectName' "${project_directory}/PROJECT") +prepare_distribution_images() { + if [[ "${DISTRIBUTION}" != "redhat" ]]; then + relatedImages="[]" + containerImage="${IMAGE}" + return + fi -deployment=$(yq eval operator_deployments.yaml) -containerImage="$(yq eval '.[0].spec.template.spec.containers[0].image' operator_deployments.yaml)" + redhat_distribution_images="$(build_redhat_related_images)" + containerImage="$(jq -r '.operatorImage' <<<"${redhat_distribution_images}")" + relatedImages="$(jq -r '.relatedImages' <<<"${redhat_distribution_images}")" +} -# Include initImage in the example CR. -# Keep it adjacent to spec.image for readability. -cr_example=$( - yq eval -o=json ../../deploy/cr.yaml | - jq --arg initImage "$containerImage" ' - def insert_after($k; $new): - to_entries as $e - | reduce $e[] as $item ({}; - . + {($item.key): $item.value} - | if $item.key == $k then . + $new else . end - ); +render_csv() { + local account + local deployment + local examples + local owned_crds + local rules + local timestamp + local version + local csv_file + local icon_base64 + + log "Rendering CSV" + + csv_stem="$(yq -r '.projectName' "${project_directory}/PROJECT")" + deployment="$(yq eval operator_deployments.yaml)" + + if [[ -z "${containerImage}" ]]; then + containerImage="$(yq eval '.[0].spec.template.spec.containers[0].image' operator_deployments.yaml)" + else + deployment="$( + IMAGE="${containerImage}" yq eval '.[0].spec.template.spec.containers[0].image = env(IMAGE)' \ + <<<"${deployment}" + )" + fi - .spec |= ( - if has("initImage") then del(.initImage) else . end - | insert_after("image"; {"initImage": $initImage}) - | if has("initImage") then . else . + {"initImage": $initImage} end - ) - ' -) -backup_example=$(yq eval -o=json ../../deploy/backup/backup.yaml) -restore_example=$(yq eval -o=json ../../deploy/backup/restore.yaml) -full_example=$(jq -n "[${cr_example}, ${backup_example}, ${restore_example}]") -account=$(yq eval '.[] | .metadata.name' operator_accounts.yaml) -rules=$(yq eval '.[] | .rules' operator_roles${suffix}.yaml) -version="${VERSION}${suffix}" - -timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - -relatedImages=$(yq eval bundle.relatedImages.yaml) - -export examples="${full_example}" -export deployment=$deployment -export account=$account -export rules=$rules -export version="${version}" -export stem="${csv_stem}" -export timestamp=$timestamp -export name="${csv_stem}.v${VERSION}${suffix}" -export name_certified="${csv_stem}-certified.v${VERSION}${suffix}" -export name_certified_rhmp="${csv_stem}-certified-rhmp.v${VERSION}${suffix}" -export skip_range="<${VERSION}" -export containerImage="$containerImage" -export relatedImages=$relatedImages -export rulesLevel=${rulesLevel} - -yq eval ' - .metadata.annotations["alm-examples"] = strenv(examples) | - .metadata.annotations["containerImage"] = env(containerImage) | - .metadata.annotations["olm.skipRange"] = env(skip_range) | - .metadata.annotations["createdAt"] = strenv(timestamp) | - .metadata.name = env(name) | - .spec.version = env(version) | - .spec.install.spec[strenv(rulesLevel)] = [{ "serviceAccountName": env(account), "rules": env(rules) }] | - .spec.install.spec.deployments = [( env(deployment) | .[] |{ "name": .metadata.name, "spec": .spec} )]' bundle.csv.yaml >"${bundle_directory}/manifests/${file_name}.clusterserviceversion.yaml" - -if [ "${DISTRIBUTION}" == "community" ]; then - update_yaml_images "bundles/$DISTRIBUTION/manifests/${file_name}.clusterserviceversion.yaml" -elif [ "${DISTRIBUTION}" == "redhat" ]; then - - yq eval --inplace ' - .spec.relatedImages = env(relatedImages) | - .metadata.annotations.certified = "true" | - .metadata.annotations["containerImage"] = "registry.connect.redhat.com/percona/percona-server-mongodb-operator@sha256:" | - .metadata.name = strenv(name_certified)' \ - "${bundle_directory}/manifests/${file_name}.clusterserviceversion.yaml" - -elif [ "${DISTRIBUTION}" == "marketplace" ]; then - # Annotations needed when targeting Red Hat Marketplace - export package_url="https://marketplace.redhat.com/en-us/operators/${file_name}" - yq --inplace ' - .metadata.name = env(name_certified_rhmp) | - .metadata.annotations["containerImage"] = "registry.connect.redhat.com/percona/percona-server-mongodb-operator@sha256:" | - .metadata.annotations["marketplace.openshift.io/remote-workflow"] = - "https://marketplace.redhat.com/en-us/operators/percona-server-mongodb-operator-certified-rhmp/pricing?utm_source=openshift_console" | - .metadata.annotations["marketplace.openshift.io/support-workflow"] = - "https://marketplace.redhat.com/en-us/operators/percona-server-mongodb-operator-certified-rhmp/support?utm_source=openshift_console" | - .spec.relatedImages = env(relatedImages)' \ - "${bundle_directory}/manifests/${file_name}.clusterserviceversion.yaml" -fi - -# delete blank lines. -sed -i '' '/^$/d' "${bundle_directory}/manifests/${file_name}.clusterserviceversion.yaml" - -if >/dev/null command -v tree; then tree -C "${bundle_directory}"; fi - -yamllint -d '{extends: default, rules: {line-length: disable, indentation: disable}}' bundles/"$DISTRIBUTION" \ No newline at end of file + examples="$(build_examples)" + owned_crds="$(build_owned_crds)" + account="$(yq eval '.[] | .metadata.name' operator_accounts.yaml)" + rules="$(yq eval '.[] | .rules' operator_roles.yaml)" + version="${CSV_VERSION:-${VERSION}}" + timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + csv_file="${bundle_directory}/manifests/${file_name}.clusterserviceversion.yaml" + icon_base64="$(base64 <"${repo_root}/kubernetes.svg" | tr -d '\n')" + + export examples + export owned_crds + export deployment + export account + export rules + export version + export stem="${csv_stem}" + export timestamp + export name="${CSV_NAME_OVERRIDE:-${csv_stem}.v${version}}" + export name_certified="${CSV_NAME_OVERRIDE:-${csv_stem}-certified.v${version}}" + export display_name_override="${DISPLAY_NAME_OVERRIDE:-}" + export skip_range="<${version}" + export containerImage + export relatedImages + export rulesLevel + export icon_base64 + + yq -P eval ' + .metadata.annotations["alm-examples"] = strenv(examples) | + .metadata.annotations["containerImage"] = env(containerImage) | + .metadata.annotations["olm.skipRange"] = env(skip_range) | + .metadata.annotations["createdAt"] = strenv(timestamp) | + .metadata.name = env(name) | + .spec.version = env(version) | + .spec.icon = [{ "base64data": strenv(icon_base64), "mediatype": "image/svg+xml" }] | + .spec.customresourcedefinitions.owned = (strenv(owned_crds) | from_json) | + .spec.install.spec[strenv(rulesLevel)] = [{ "serviceAccountName": env(account), "rules": env(rules) }] | + .spec.install.spec.deployments = (env(deployment) | [.[] | { "name": .metadata.name, "spec": .spec }])' \ + bundle.csv.yaml >"${csv_file}" + + if [[ -n "${display_name_override}" ]]; then + yq eval --inplace '.spec.displayName = strenv(display_name_override)' "${csv_file}" + fi + + case "${DISTRIBUTION}" in + community) + ;; + redhat) + yq -P eval --inplace ' + .spec.relatedImages = (strenv(relatedImages) | from_json) | + .metadata.annotations.certified = "true" | + .metadata.annotations["features.operators.openshift.io/disconnected"] = "true" | + .metadata.name = strenv(name_certified)' \ + "${csv_file}" + ;; + esac + +} + +validate_bundle() { + if [[ "${OLM_VERBOSE:-0}" == "1" || "${OLM_VERBOSE:-false}" == "true" ]] && command -v tree >/dev/null 2>&1; then + tree -C "${bundle_directory}" + fi + + run_quiet "YAML validation" \ + yamllint -d '{extends: default, rules: {line-length: disable, indentation: disable}}' "${bundle_directory}" +} + +normalize_bundle_permissions() { + chmod -R a+rX "${bundle_directory}" +} + +main() { + check_tools + load_distribution_hooks + configure_namespace_manifests + prepare_operator_sources + render_operator_manifests + create_sdk_workspace + create_bundle_directory + render_bundle_metadata + render_bundle_dockerfile + write_crd_manifests + validate_manifest_inputs + prepare_distribution_images + render_csv + normalize_bundle_permissions + validate_bundle +} + +main "$@" diff --git a/installers/olm/validate-directory.sh b/installers/olm/validate-directory.sh new file mode 100755 index 0000000000..4a9e7f93a5 --- /dev/null +++ b/installers/olm/validate-directory.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +validate_bundle_directory() { + local directory="$1" + + operator-sdk bundle validate "${directory}" --select-optional='suite=operatorframework' +} + +validate_bundle_directory "$@" diff --git a/installers/olm/validate-image.sh b/installers/olm/validate-image.sh new file mode 100755 index 0000000000..607c6b7b50 --- /dev/null +++ b/installers/olm/validate-image.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -eu + +push_trap_exit() { + local -a array + eval "array=($(trap -p EXIT))" + # shellcheck disable=SC2064 + trap "$1;${array[2]-}" EXIT +} + +TMPDIR=$(mktemp -d) +push_trap_exit "rm -rf '${TMPDIR}'" +export TMPDIR + +validate_bundle_image() { + local container="$1" directory="$2" + directory=$(cd "${directory}" && pwd) + + local image port registry + registry=$(${container} run --detach --publish-all docker.io/library/registry:latest) + push_trap_exit "echo -n 'Removing '; ${container} rm '${registry}'" + push_trap_exit "echo -n 'Stopping '; ${container} stop '${registry}'" + + port=$(${container} inspect "${registry}" \ + --format='{{ (index .NetworkSettings.Ports "5000/tcp" 0).HostPort }}') + image="localhost:${port}/psmdb-operator-bundle:latest" + + ${container} build --platform="${DOCKER_DEFAULT_PLATFORM:-linux/amd64}" -t "${image}" "${directory}" + ${container} push "${image}" + + opm alpha bundle validate --use-http --image-builder="${container}" \ + --optional-validators='operatorhub,bundle-objects' \ + --tag="${image}" +} + +validate_bundle_image "$@" diff --git a/kubernetes.svg b/kubernetes.svg index e826807da3..301e5bf866 100644 --- a/kubernetes.svg +++ b/kubernetes.svg @@ -1,14 +1,21 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file From 1d3dcb57aa05adbe6cbe621081749666b2a4b739 Mon Sep 17 00:00:00 2001 From: valmiranogueira Date: Fri, 24 Jul 2026 17:41:23 -0300 Subject: [PATCH 2/5] Add clustersync image and -prod rules --- installers/olm/Makefile | 297 ++++++++++++------------- installers/olm/README.md | 140 ++++++++---- installers/olm/build-image.sh | 45 +++- installers/olm/distributions/redhat.sh | 7 +- installers/olm/generate.sh | 20 +- 5 files changed, 290 insertions(+), 219 deletions(-) diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 311613d435..71aab6ae83 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -6,10 +6,6 @@ .SUFFIXES: SHELL := /bin/bash -# ============================================================================== -# Project Configuration -# ============================================================================== - NAME ?= percona-server-mongodb-operator BUNDLE_TYPES := community certified @@ -22,10 +18,6 @@ PACKAGE_CHANNEL ?= stable MIN_KUBE_VERSION ?= "" OPENSHIFT_VERSIONS ?= $(shell awk -F= '$$1 == "OPENSHIFT_MIN" { split($$2, v, "."); min = "v" v[1] "." v[2] } $$1 == "OPENSHIFT_MAX" { split($$2, v, "."); max = "v" v[1] "." v[2] } END { if (min && max) print min "-" max }' '$(RELEASE_VERSIONS)') -# ============================================================================== -# Image Configuration -# ============================================================================== - COMMUNITY_REGISTRY ?= docker.io IMAGE_TAG_OWNER ?= percona IMAGE_TAG_BASE ?= $(IMAGE_TAG_OWNER)/$(NAME) @@ -39,7 +31,6 @@ DOCKER_DEFAULT_PLATFORM ?= linux/amd64 BUNDLE_TYPE ?= community BUNDLE_REPO ?= $(IMAGE_TAG_BASE) DEV_BUNDLE_REPO ?= docker.io/perconalab/$(NAME) -DEV_PACKAGE_SUFFIX ?= -dev DEV_DISPLAY_NAME_SUFFIX ?= (Dev) DEPLOY_BUNDLE_REPO ?= $(DEV_BUNDLE_REPO) BUNDLE_IMAGE_VERSION ?= $(CSV_VERSION) @@ -55,28 +46,24 @@ CONFIRM_PUSH ?= 1 SKIP_DIGEST_FAILURE ?= 0 RUN_BUNDLE_TIMEOUT ?= 10m -# ============================================================================== -# Tool Configuration -# ============================================================================== - REPO_ROOT := $(shell git rev-parse --show-toplevel) KUSTOMIZE := $(REPO_ROOT)/bin/kustomize -JQ_VERSION := 1.7.1 -OPERATOR_SDK_VERSION := v1.41.1 -OPM_VERSION := v1.66.0 - OS_KERNEL ?= $(shell uname -s | tr '[:upper:]' '[:lower:]') -OS_MACHINE ?= $(shell uname -m | sed 's/^x86_/amd/') +OS_MACHINE ?= $(shell uname -m | sed -e 's/^x86_64$$/amd64/' -e 's/^x86_/amd/' -e 's/^aarch64$$/arm64/') SYSTEM := $(OS_KERNEL)-$(OS_MACHINE) TOOLS_DIR := tools/$(SYSTEM) +JQ_VERSION := 1.7.1 +JQ_PLATFORM := $(if $(filter darwin,$(OS_KERNEL)),macos,$(OS_KERNEL)) +OPERATOR_SDK_VERSION := v1.41.1 +OPM_VERSION := v1.66.0 + export PATH := $(CURDIR)/$(TOOLS_DIR):$(PATH) # ============================================================================== -# Display +# Helpers # ============================================================================== - GREEN := $(shell tput setaf 2) RESET := $(shell tput sgr0) @@ -101,21 +88,123 @@ export PACKAGE_NAME_OVERRIDE CSV_NAME_OVERRIDE DISPLAY_NAME_OVERRIDE export DOCKER_DEFAULT_PLATFORM BUNDLE_REPO BUNDLE_PLATFORMS SKIP_DIGEST_FAILURE # ============================================================================== -# Bundle Targets +# Validation and Tool Helpers # ============================================================================== +.PHONY: check-prereqs +check-prereqs: check-version check-csv-version check-tools tools $(KUSTOMIZE) + +.PHONY: check-version +check-version: +ifndef VERSION + $(error VERSION is not set) +endif + +.PHONY: check-csv-version +check-csv-version: +ifndef CSV_VERSION + $(error CSV_VERSION is not set and could not be parsed from VERSION=$(VERSION)) +endif + @if ! [[ "$(CSV_VERSION)" =~ ^[0-9]+\.[0-9]+\.[0-9]+$$ ]]; then \ + echo "CSV_VERSION must be a semver without prerelease/build metadata, got: $(CSV_VERSION)"; \ + exit 1; \ + fi + +.PHONY: check-tools +check-tools: + @for cmd in gawk gcsplit yq yamllint envsubst; do \ + if ! command -v $$cmd >/dev/null 2>&1; then \ + echo "Error: $$cmd is required but not installed"; \ + exit 1; \ + fi; \ + done + +$(KUSTOMIZE): + $(MAKE) -C ../.. bin/kustomize + +.PHONY: tools +tools: $(TOOLS_DIR) $(TOOLS_DIR)/jq $(TOOLS_DIR)/operator-sdk $(TOOLS_DIR)/opm + +$(TOOLS_DIR): + mkdir -p $(TOOLS_DIR) + +$(TOOLS_DIR)/jq: + curl -fL "https://github.com/jqlang/jq/releases/download/jq-$(JQ_VERSION)/jq-$(JQ_PLATFORM)-$(OS_MACHINE)" -o $@ + chmod +x $@ -.PHONY: bundles -bundles: ## Build all OLM bundles (community, certified) -bundles: check-prereqs $(BUNDLE_TYPES:%=bundle/%) +$(TOOLS_DIR)/operator-sdk: | $(TOOLS_DIR) + curl -sL "https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS_KERNEL)_$(OS_MACHINE)" -o $@ + chmod +x $@ + +$(TOOLS_DIR)/opm: | $(TOOLS_DIR) + curl -sL "https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$(OS_KERNEL)-$(OS_MACHINE)-opm" -o $@ + chmod +x $@ + +# ============================================================================== +# Development Rules +# ============================================================================== +.PHONY: bundle +bundle: ## Build all development OLM bundles and push bundle images to perconalab +bundle: $(BUNDLE_TYPES:%=bundle/%) .PHONY: bundle/community -bundle/community: ## Build community OLM bundle +bundle/community: ## Build and push community bundle image to perconalab .PHONY: bundle/certified -bundle/certified: ## Build certified OLM bundle +bundle/certified: ## Build and push certified bundle image to perconalab .PHONY: $(BUNDLE_TYPES:%=bundle/%) -$(BUNDLE_TYPES:%=bundle/%): bundle/%: tools $(KUSTOMIZE) check-version check-csv-version check-tools +$(BUNDLE_TYPES:%=bundle/%): bundle/%: check-version check-csv-version + @package='$(NAME)'; \ + if [[ "$*" == "certified" ]]; then \ + package='$(NAME)-certified'; \ + fi; \ + csv_name="$${package}.v$(CSV_VERSION)"; \ + $(MAKE) bundle-prod/$* PACKAGE_NAME_OVERRIDE="$${package}" CSV_NAME_OVERRIDE="$${csv_name}" DISPLAY_NAME_OVERRIDE="Percona Distribution for MongoDB Operator $(DEV_DISPLAY_NAME_SUFFIX)" + BUNDLE_REPO='$(DEV_BUNDLE_REPO)' ./build-image.sh '$(CONTAINER)' 'bundles/$*' '$*' '$(BUNDLE_IMAGE_VERSION)' + +.PHONY: catalog +catalog: ## Build and push all development catalog images +catalog: $(BUNDLE_TYPES:%=catalog/%) + +.PHONY: catalog/community +catalog/community: ## Build and push community development catalog image + +.PHONY: catalog/certified +catalog/certified: ## Build and push certified development catalog image + +.PHONY: $(BUNDLE_TYPES:%=catalog/%) +$(BUNDLE_TYPES:%=catalog/%): catalog/%: bundle/% + $(MAKE) catalog-prod/$* BUNDLE_REPO='$(DEV_BUNDLE_REPO)' + +.PHONY: deploy +deploy: ## Build, push, and deploy all development catalog sources +deploy: $(BUNDLE_TYPES:%=deploy/%) + +.PHONY: deploy/community +deploy/community: ## Build, push, and deploy community development catalog source + +.PHONY: deploy/certified +deploy/certified: ## Build, push, and deploy certified development catalog source + +.PHONY: $(BUNDLE_TYPES:%=deploy/%) +$(BUNDLE_TYPES:%=deploy/%): deploy/%: catalog/% + $(MAKE) apply-catalog/$* CATALOG_SOURCE_NAME='$*-dev' + +# ============================================================================== +# Production Rules +# ============================================================================== +.PHONY: bundles-prod +bundles-prod: ## Build all production OLM bundles (community, certified) +bundles-prod: check-prereqs $(BUNDLE_TYPES:%=bundle-prod/%) + +.PHONY: bundle-prod/community +bundle-prod/community: ## Build community OLM bundle + +.PHONY: bundle-prod/certified +bundle-prod/certified: ## Build certified OLM bundle + +.PHONY: $(BUNDLE_TYPES:%=bundle-prod/%) +$(BUNDLE_TYPES:%=bundle-prod/%): bundle-prod/%: tools $(KUSTOMIZE) check-version check-csv-version check-tools @echo "$(GREEN)Building $* bundle...$(RESET)" @distribution="$*"; \ operator_image="$(IMAGE)"; \ @@ -135,42 +224,18 @@ $(BUNDLE_TYPES:%=bundle/%): bundle/%: tools $(KUSTOMIZE) check-version check-csv fi @echo "$(GREEN)Bundle stored in installers/olm/bundles/$*$(RESET)" -.PHONY: bundle-dev -bundle-dev: ## Build all OLM bundles and push bundle images to perconalab -bundle-dev: $(BUNDLE_TYPES:%=bundle-dev/%) - -.PHONY: bundle-dev/community -bundle-dev/community: ## Build and push community bundle image to perconalab - -.PHONY: bundle-dev/certified -bundle-dev/certified: ## Build and push certified bundle image to perconalab - -.PHONY: $(BUNDLE_TYPES:%=bundle-dev/%) -$(BUNDLE_TYPES:%=bundle-dev/%): bundle-dev/%: check-version check-csv-version - @package='$(NAME)$(DEV_PACKAGE_SUFFIX)'; \ - if [[ "$*" == "certified" ]]; then \ - package='$(NAME)-certified$(DEV_PACKAGE_SUFFIX)'; \ - fi; \ - csv_name="$${package}.v$(CSV_VERSION)"; \ - $(MAKE) bundle/$* PACKAGE_NAME_OVERRIDE="$${package}" CSV_NAME_OVERRIDE="$${csv_name}" DISPLAY_NAME_OVERRIDE="Percona Distribution for MongoDB Operator $(DEV_DISPLAY_NAME_SUFFIX)" - BUNDLE_REPO='$(DEV_BUNDLE_REPO)' ./build-image.sh '$(CONTAINER)' 'bundles/$*' '$*' '$(BUNDLE_IMAGE_VERSION)' - -# ============================================================================== -# Deploy Targets -# ============================================================================== +.PHONY: catalog-prod +catalog-prod: ## Build and push all production catalog images +catalog-prod: $(BUNDLE_TYPES:%=catalog-prod/%) -.PHONY: catalog -catalog: ## Build and push all catalog images to perconalab -catalog: $(BUNDLE_TYPES:%=catalog/%) +.PHONY: catalog-prod/community +catalog-prod/community: ## Build and push community catalog image to perconalab -.PHONY: catalog/community -catalog/community: ## Build and push community catalog image to perconalab +.PHONY: catalog-prod/certified +catalog-prod/certified: ## Build and push certified catalog image to perconalab -.PHONY: catalog/certified -catalog/certified: ## Build and push certified catalog image to perconalab - -.PHONY: $(BUNDLE_TYPES:%=catalog/%) -$(BUNDLE_TYPES:%=catalog/%): catalog/%: tools check-version check-csv-version +.PHONY: $(BUNDLE_TYPES:%=catalog-prod/%) +$(BUNDLE_TYPES:%=catalog-prod/%): catalog-prod/%: tools check-version check-csv-version @catalog_root='catalogs'; \ catalog_dir="$${catalog_root}/$*"; \ catalog_image='$(CATALOG_REPO):$*-catalog'; \ @@ -200,51 +265,23 @@ $(BUNDLE_TYPES:%=catalog/%): catalog/%: tools check-version check-csv-version "$${catalog_root}"; \ echo "$(GREEN)Catalog image pushed: $${catalog_image}$(RESET)" -.PHONY: deploy -deploy: ## Build, push, and deploy all catalog sources -deploy: $(BUNDLE_TYPES:%=deploy/%) +.PHONY: deploy-prod +deploy-prod: ## Build, push, and deploy all production catalog sources +deploy-prod: $(BUNDLE_TYPES:%=deploy-prod/%) -.PHONY: deploy/community -deploy/community: ## Build, push, and deploy community bundle +.PHONY: deploy-prod/community +deploy-prod/community: ## Build, push, and deploy community bundle -.PHONY: deploy/certified -deploy/certified: ## Build, push, and deploy certified bundle +.PHONY: deploy-prod/certified +deploy-prod/certified: ## Build, push, and deploy certified bundle -.PHONY: $(BUNDLE_TYPES:%=deploy/%) -$(BUNDLE_TYPES:%=deploy/%): deploy/%: tools check-version check-csv-version - $(MAKE) bundle/$* +.PHONY: $(BUNDLE_TYPES:%=deploy-prod/%) +$(BUNDLE_TYPES:%=deploy-prod/%): deploy-prod/%: tools check-version check-csv-version + $(MAKE) bundle-prod/$* BUNDLE_REPO='$(DEPLOY_BUNDLE_REPO)' ./build-image.sh '$(CONTAINER)' 'bundles/$*' '$*' '$(BUNDLE_IMAGE_VERSION)' - $(MAKE) catalog/$* BUNDLE_REPO='$(DEPLOY_BUNDLE_REPO)' + $(MAKE) catalog-prod/$* BUNDLE_REPO='$(DEPLOY_BUNDLE_REPO)' $(MAKE) apply-catalog/$* -.PHONY: catalog-dev -catalog-dev: ## Build and push all development catalog images -catalog-dev: $(BUNDLE_TYPES:%=catalog-dev/%) - -.PHONY: catalog-dev/community -catalog-dev/community: ## Build and push community development catalog image - -.PHONY: catalog-dev/certified -catalog-dev/certified: ## Build and push certified development catalog image - -.PHONY: $(BUNDLE_TYPES:%=catalog-dev/%) -$(BUNDLE_TYPES:%=catalog-dev/%): catalog-dev/%: bundle-dev/% - $(MAKE) catalog/$* BUNDLE_REPO='$(DEV_BUNDLE_REPO)' - -.PHONY: deploy-dev -deploy-dev: ## Build, push, and deploy all development catalog sources -deploy-dev: $(BUNDLE_TYPES:%=deploy-dev/%) - -.PHONY: deploy-dev/community -deploy-dev/community: ## Build, push, and deploy community development catalog source - -.PHONY: deploy-dev/certified -deploy-dev/certified: ## Build, push, and deploy certified development catalog source - -.PHONY: $(BUNDLE_TYPES:%=deploy-dev/%) -$(BUNDLE_TYPES:%=deploy-dev/%): deploy-dev/%: catalog-dev/% - $(MAKE) apply-catalog/$* CATALOG_SOURCE_NAME='$*-dev' - .PHONY: $(BUNDLE_TYPES:%=apply-catalog/%) $(BUNDLE_TYPES:%=apply-catalog/%): apply-catalog/%: @set -e; \ @@ -284,9 +321,8 @@ $(BUNDLE_TYPES:%=apply-catalog/%): apply-catalog/%: echo "$(GREEN)CatalogSource deployed: $${catalog_source} in $${catalog_namespace} -> $${catalog_image}$(RESET)" # ============================================================================== -# Image Targets +# Image Rules # ============================================================================== - .PHONY: build build: ## Build bundle image (set BUNDLE_TYPE=community|certified) build: check-version check-csv-version @@ -312,9 +348,8 @@ build-%-image: ./build-image.sh '$(CONTAINER)' 'bundles/$*' '$*' '$(BUNDLE_IMAGE_VERSION)' # ============================================================================== -# Validation Targets +# Validation Rules # ============================================================================== - .PHONY: validate validate: ## Run bundle image and directory validation validate: tools @@ -333,9 +368,8 @@ validate-%-image: ./validate-image.sh '$(CONTAINER)' 'bundles/$*' # ============================================================================== -# Utility Targets +# Utility Rules # ============================================================================== - .PHONY: install-olm install-olm: ## Install OLM in Kubernetes cluster install-olm: tools @@ -348,56 +382,3 @@ clean: ## Remove generated files and downloaded tools .PHONY: help help: ## Show this help message @awk 'BEGIN {FS = ": ## "; printf "\n$(GREEN)Usage:$(RESET)\n make [target]\n\n$(GREEN)Targets:$(RESET)\n"} /^[a-zA-Z0-9_\/%-]+: ## / {printf " %-28s %s\n", $$1, $$2}' $(MAKEFILE_LIST) - -# ============================================================================== -# Validation Helpers -# ============================================================================== - -.PHONY: check-prereqs -check-prereqs: check-version check-csv-version check-tools tools $(KUSTOMIZE) - -.PHONY: check-version -check-version: -ifndef VERSION - $(error VERSION is not set) -endif - -.PHONY: check-csv-version -check-csv-version: -ifndef CSV_VERSION - $(error CSV_VERSION is not set and could not be parsed from VERSION=$(VERSION)) -endif - @if ! [[ "$(CSV_VERSION)" =~ ^[0-9]+\.[0-9]+\.[0-9]+$$ ]]; then \ - echo "CSV_VERSION must be a semver without prerelease/build metadata, got: $(CSV_VERSION)"; \ - exit 1; \ - fi - -.PHONY: check-tools -check-tools: - @for cmd in gawk gcsplit yq yamllint envsubst; do \ - if ! command -v $$cmd >/dev/null 2>&1; then \ - echo "Error: $$cmd is required but not installed"; \ - exit 1; \ - fi; \ - done - -$(KUSTOMIZE): - $(MAKE) -C ../.. bin/kustomize - -.PHONY: tools -tools: $(TOOLS_DIR)/jq $(TOOLS_DIR)/operator-sdk $(TOOLS_DIR)/opm - -$(TOOLS_DIR): - mkdir -p $(TOOLS_DIR) - -$(TOOLS_DIR)/jq: | $(TOOLS_DIR) - curl -sL "https://github.com/jqlang/jq/releases/download/jq-$(JQ_VERSION)/jq-$(OS_KERNEL)-$(OS_MACHINE)" -o $@ - chmod +x $@ - -$(TOOLS_DIR)/operator-sdk: | $(TOOLS_DIR) - curl -sL "https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS_KERNEL)_$(OS_MACHINE)" -o $@ - chmod +x $@ - -$(TOOLS_DIR)/opm: | $(TOOLS_DIR) - curl -sL "https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$(OS_KERNEL)-$(OS_MACHINE)-opm" -o $@ - chmod +x $@ diff --git a/installers/olm/README.md b/installers/olm/README.md index 835fe294f7..add4c4a39a 100644 --- a/installers/olm/README.md +++ b/installers/olm/README.md @@ -26,8 +26,11 @@ kubectl docker ``` -The Makefile downloads the OLM helper tools into -`installers/olm/tools/-`: +The Makefile downloads the OLM helper tools into: + +```text +installers/olm/tools/- +``` ```bash make tools @@ -41,7 +44,7 @@ Downloaded tools: ## Variables -Most workflows only need `VERSION`. +Most workflows only require `VERSION`. ```bash export VERSION=1.23.0 @@ -54,94 +57,132 @@ Useful optional variables: | `CSV_VERSION` | CSV version. Defaults to the first `x.y.z` parsed from `VERSION`. | `1.23.0` | | `IMAGE` | Community operator image used in generated manifests. | `docker.io/percona/percona-server-mongodb-operator:1.23.0` | | `REDHAT_OPERATOR_IMAGE` | Certified operator image used in generated manifests. | `registry.connect.redhat.com/percona/percona-server-mongodb-operator:1.23.0` | -| `DEV_BUNDLE_REPO` | Development bundle and catalog image repository. | `docker.io/perconalab/percona-server-mongodb-operator` | -| `CONFIRM_PUSH` | Ask before pushing images. Set to `0` in CI. | `0` | +| `DEV_BUNDLE_REPO` | Repository used by development bundle and catalog images. | `docker.io/perconalab/percona-server-mongodb-operator` | +| `DEPLOY_BUNDLE_REPO` | Repository used by release bundle and catalog images. | `docker.io/percona/percona-server-mongodb-operator` | +| `CONFIRM_PUSH` | Ask before pushing images. Development targets default to local builds; deploy targets enable push automatically. Set to `0` in CI. | `0` | | `SKIP_DIGEST_FAILURE` | Continue certified generation when a digest cannot be resolved. Missing digests are rendered as ``. | `1` | OpenShift versions are resolved from `../../e2e-tests/release_versions` and used -to render `com.redhat.openshift.versions`. Override only when needed: +to render `com.redhat.openshift.versions`. + +Override only when necessary: ```bash export OPENSHIFT_VERSIONS="v4.18-v4.22" ``` -## Development +--- + +# Development + +Development targets build local bundles and catalogs by default. Images are only +pushed when running a deploy target (or when explicitly enabling push). -Development targets publish bundle and catalog images to `perconalab`. +Development package names are suffixed with `-dev` to avoid colliding with the +public OperatorHub packages shipped with OpenShift. -The development package names are suffixed with `-dev` to avoid colliding with -public OperatorHub packages already present in OpenShift default catalogs: +Packages: - `percona-server-mongodb-operator-dev` - `percona-server-mongodb-operator-certified-dev` -The display name is suffixed with `(Dev)` in the OpenShift console. +Display names are suffixed with **(Dev)**. -Build and push development bundle images: +## Generate bundles ```bash -make bundle-dev VERSION=1.23.0 -make bundle-dev/community VERSION=1.23.0 -make bundle-dev/certified VERSION=1.23.0 +make bundles VERSION=1.23.0 +make bundle/community VERSION=1.23.0 +make bundle/certified VERSION=1.23.0 ``` -Deploy development catalogs to OpenShift: +## Build catalogs ```bash -make deploy-dev VERSION=1.23.0 -make deploy-dev/community VERSION=1.23.0 -make deploy-dev/certified VERSION=1.23.0 +make catalog/community VERSION=1.23.0 +make catalog/certified VERSION=1.23.0 ``` -After deploying, search the OpenShift console for: +## Deploy catalogs + +Deploy automatically enables image push (with confirmation when enabled). ```bash -Percona Distribution for MongoDB Operator (Dev) +make deploy/community VERSION=1.23.0 +make deploy/certified VERSION=1.23.0 +make deploy VERSION=1.23.0 ``` -Or check with: +After deployment, verify the packages: ```bash kubectl get packagemanifest percona-server-mongodb-operator-dev -n openshift-marketplace kubectl get packagemanifest percona-server-mongodb-operator-certified-dev -n openshift-marketplace ``` -## Release Example +or search in the OpenShift console for: + +```text +Percona Distribution for MongoDB Operator (Dev) +``` + +--- -Generate release bundles locally: +# Release (`*-prod`) + +Release targets use the `-prod` suffix. + +## Generate release bundles ```bash -make bundles VERSION=1.23.0 -make bundle/community VERSION=1.23.0 -make bundle/certified VERSION=1.23.0 +make bundles-prod VERSION=1.23.0 +make bundle-prod/community VERSION=1.23.0 +make bundle-prod/certified VERSION=1.23.0 ``` -Build, push, and deploy release catalogs: +## Build release catalogs ```bash -make deploy VERSION=1.23.0 -make deploy/community VERSION=1.23.0 -make deploy/certified VERSION=1.23.0 +make catalog-prod/community VERSION=1.23.0 +make catalog-prod/certified VERSION=1.23.0 ``` -Release deploy runs the full flow for each bundle type: +## Deploy release catalogs ```bash -make bundle/ -./build-image.sh ${CONTAINER} bundles/ ${BUNDLE_IMAGE_VERSION} -make catalog/ BUNDLE_REPO=${DEPLOY_BUNDLE_REPO} -make apply-catalog/ +make deploy-prod/community VERSION=1.23.0 +make deploy-prod/certified VERSION=1.23.0 +make deploy-prod VERSION=1.23.0 ``` -## Validation +Each deploy performs the complete release workflow: -Validate all generated bundles: +```text +bundle-prod/* + ↓ +build-image.sh + ↓ +catalog-prod/* + ↓ +apply-catalog/* +``` + +Unlike the development targets, release bundles use the production package names: + +- `percona-server-mongodb-operator` +- `percona-server-mongodb-operator-certified` + +--- + +# Validation + +Validate every generated bundle: ```bash make validate VERSION=1.23.0 ``` -Validate one bundle: +Or validate a single bundle: ```bash make validate/community VERSION=1.23.0 @@ -153,29 +194,34 @@ Validation uses: - `validate-image.sh` - `validate-directory.sh` -## Certified Metadata +--- + +# Certified Metadata Certified bundles resolve image metadata and related image digests through `distributions/redhat.sh`. Bundle generation fails when: -- a required image is missing -- a certified image tag does not match the expected pattern -- a required digest cannot be resolved and `SKIP_DIGEST_FAILURE` is not enabled +- a required image is missing; +- a certified image tag does not match the expected pattern; +- a required digest cannot be resolved and `SKIP_DIGEST_FAILURE` is disabled. -With `SKIP_DIGEST_FAILURE=1`, missing digests are rendered as `` and +When `SKIP_DIGEST_FAILURE=1`, missing digests are rendered as `` and reported in the build output. -## Cleanup +--- + +# Cleanup -Remove generated bundles, catalogs, temporary SDK projects, and downloaded tools: +Remove generated bundles, catalogs, temporary SDK projects, and downloaded +tools: ```bash make clean ``` -Show available targets: +Display all available targets: ```bash make help diff --git a/installers/olm/build-image.sh b/installers/olm/build-image.sh index dce80fcc41..a55ebf75ed 100755 --- a/installers/olm/build-image.sh +++ b/installers/olm/build-image.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -euo pipefail @@ -6,16 +6,31 @@ confirm_push() { local image="$1" local answer - if [[ "${CONFIRM_PUSH:-1}" == "0" || "${CONFIRM_PUSH:-1}" == "false" ]]; then - return - fi - - read -r -p "Push image ${image}? [y/N] " answer - [[ "${answer}" == "y" || "${answer}" == "Y" || "${answer}" == "yes" || "${answer}" == "YES" ]] + case "${CONFIRM_PUSH:-0}" in + 1|true|TRUE|yes|YES) + read -r -p "Push image ${image}? [y/N] " answer + + case "${answer}" in + y|Y|yes|YES) + return 0 + ;; + *) + return 1 + ;; + esac + ;; + *) + return 1 + ;; + esac } build_image() { - local container="$1" directory="$2" distro="$3" version="$4" + local container="$1" + local directory="$2" + local distro="$3" + local version="$4" + directory=$(cd "${directory}" && pwd) local bundle_name="${distro}" @@ -26,17 +41,23 @@ build_image() { local tag="${version}-${bundle_name}-bundle" local image="${BUNDLE_REPO}:${tag}" local platforms="${BUNDLE_PLATFORMS:-linux/amd64,linux/arm64}" + local build_action="--load" + + if confirm_push "${image}"; then + build_action="--push" + else + echo "Push skipped. Building image locally." + fi - pushd "${directory}" + pushd "${directory}" >/dev/null - confirm_push "${image}" || exit 1 "${container}" buildx build \ --platform "${platforms}" \ -t "${image}" \ - --push \ + "${build_action}" \ . - popd + popd >/dev/null } build_image "$@" diff --git a/installers/olm/distributions/redhat.sh b/installers/olm/distributions/redhat.sh index c5495d3439..845e24c373 100755 --- a/installers/olm/distributions/redhat.sh +++ b/installers/olm/distributions/redhat.sh @@ -111,6 +111,9 @@ validate_certified_tag() { IMAGE_LOGCOLLECTOR) expected="${redhat_release}-logcollector-${source_tag}" ;; + IMAGE_CLUSTERSYNC) + expected="${redhat_release}-clustersync" + ;; *) abort "unsupported certified image key: ${key}" ;; @@ -193,7 +196,8 @@ build_redhat_related_images() { IMAGE_BACKUP \ IMAGE_PMM_CLIENT \ IMAGE_PMM3_CLIENT \ - IMAGE_LOGCOLLECTOR; do + IMAGE_LOGCOLLECTOR \ + IMAGE_CLUSTERSYNC; do require_release_image "${key}" done @@ -208,6 +212,7 @@ build_redhat_related_images() { add_related_image "IMAGE_BACKUP" "backup" "${redhat_containers_repository}" "${redhat_release}-backup" add_related_image "IMAGE_PMM_CLIENT" "pmm" "${redhat_containers_repository}" "${redhat_release}-pmm" add_related_image "IMAGE_PMM3_CLIENT" "pmm3" "${redhat_containers_repository}" "${redhat_release}-pmm3" + add_related_image "IMAGE_CLUSTERSYNC" "clustersync" "${redhat_containers_repository}" "${redhat_release}-clustersync" add_related_image "IMAGE_LOGCOLLECTOR" "logcollector" "${redhat_containers_repository}" "${redhat_release}-logcollector-${logcollector_tag}" "${logcollector_tag}" add_related_image "IMAGE_OPERATOR" "operator" "${redhat_operator_repository}" "${redhat_operator_tag}" diff --git a/installers/olm/generate.sh b/installers/olm/generate.sh index 167e0c1e87..541a5c3014 100755 --- a/installers/olm/generate.sh +++ b/installers/olm/generate.sh @@ -337,11 +337,13 @@ validate_manifest_inputs() { build_examples() { local cr_example local backup_example + local clustersync_example local image_backup local image_logcollector local image_mongod local image_operator local image_pmm + local image_clustersync local restore_example image_backup="$(release_image_ref "IMAGE_BACKUP")" @@ -349,6 +351,7 @@ build_examples() { image_mongod="$(release_image_ref "IMAGE_MONGOD80")" image_operator="$(release_image_ref "IMAGE_OPERATOR")" image_pmm="$(release_image_ref "IMAGE_PMM3_CLIENT")" + image_clustersync="$(release_image_ref "IMAGE_CLUSTERSYNC")" if [[ "${DISTRIBUTION}" == "redhat" ]]; then image_backup="$(jq -r '.relatedImages[] | select(.name == "backup").image' <<<"${redhat_distribution_images}")" @@ -356,6 +359,7 @@ build_examples() { image_mongod="$(jq -r '.relatedImages[] | select(.name == "mongod8.0").image' <<<"${redhat_distribution_images}")" image_operator="$(jq -r '.operatorImage' <<<"${redhat_distribution_images}")" image_pmm="$(jq -r '.relatedImages[] | select(.name == "pmm3").image' <<<"${redhat_distribution_images}")" + image_clustersync="$(jq -r '.relatedImages[] | select(.name == "clustersync").image' <<<"${redhat_distribution_images}")" fi cr_example="$( @@ -385,10 +389,24 @@ build_examples() { ) ' )" + + clustersync_example="$( + yq eval -o=json ../../deploy/clustersync.yaml | + jq -s \ + --arg imageClustersync "${image_clustersync}" \ + ' + map( + select(.kind == "PerconaServerMongoDBClusterSync") + | .spec.image = $imageClustersync + ) + | first + ' + )" + backup_example="$(yq eval -o=json ../../deploy/backup/backup.yaml)" restore_example="$(yq eval -o=json ../../deploy/backup/restore.yaml)" - jq -n "[${cr_example}, ${backup_example}, ${restore_example}]" + jq -n "[${cr_example}, ${backup_example}, ${restore_example}, ${clustersync_example}]" } build_managed_resources() { From c4f535edabbe74d6c39f39562fabb7abe6ec92e5 Mon Sep 17 00:00:00 2001 From: valmiranogueira Date: Fri, 24 Jul 2026 20:11:25 -0300 Subject: [PATCH 3/5] Add skips for certified bundle --- installers/olm/distributions/redhat.sh | 40 ++++++++++++++++++++++++++ installers/olm/generate.sh | 11 +++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/installers/olm/distributions/redhat.sh b/installers/olm/distributions/redhat.sh index 845e24c373..f93ad22f9a 100755 --- a/installers/olm/distributions/redhat.sh +++ b/installers/olm/distributions/redhat.sh @@ -3,6 +3,7 @@ # shellcheck disable=SC2016 redhat_release="${VERSION}" +redhat_skips_min_version="${REDHAT_SKIPS_MIN_VERSION:-1.17.0}" redhat_registry="${REDHAT_REGISTRY:-registry.connect.redhat.com}" redhat_catalog_api="${REDHAT_CATALOG_API:-https://catalog.redhat.com/api/containers/v1}" redhat_catalog_curl_timeout="${REDHAT_CATALOG_CURL_TIMEOUT:-20}" @@ -226,3 +227,42 @@ build_redhat_related_images() { relatedImages: $related_images }' } + +build_redhat_skips() { + local min_version="${redhat_skips_min_version}" + local current_version="v${redhat_release#v}" + + min_version="v${min_version#v}" + + log "Building Red Hat skips from ${min_version} up to ${current_version}" + + git -C "${repo_root}" tag --list 'v*' \ + | jq -Rsc \ + --arg min_version "${min_version}" \ + --arg current_version "${current_version}" \ + --arg package_name "percona-server-mongodb-operator-certified" \ + ' + def version_parts: + ltrimstr("v") + | split(".") + | map(tonumber); + + ($min_version | version_parts) as $min + | ($current_version | version_parts) as $current + | split("\n") + | map(select(length > 0)) + | map(select(test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))) + | map({ + tag: ., + version: version_parts + }) + | map( + select( + .version >= $min + and .version < $current + ) + ) + | sort_by(.version) + | map($package_name + "." + .tag) + ' +} diff --git a/installers/olm/generate.sh b/installers/olm/generate.sh index 541a5c3014..b0f3abbfb8 100755 --- a/installers/olm/generate.sh +++ b/installers/olm/generate.sh @@ -23,6 +23,7 @@ KUSTOMIZATION_FILE="../../config/bundle/kustomization.yaml" rulesLevel="permissions" relatedImages="[]" +skips="[]" containerImage="" csv_stem="" redhat_distribution_images="{}" @@ -499,7 +500,6 @@ update_yaml_images() { prepare_distribution_images() { if [[ "${DISTRIBUTION}" != "redhat" ]]; then - relatedImages="[]" containerImage="${IMAGE}" return fi @@ -507,6 +507,7 @@ prepare_distribution_images() { redhat_distribution_images="$(build_redhat_related_images)" containerImage="$(jq -r '.operatorImage' <<<"${redhat_distribution_images}")" relatedImages="$(jq -r '.relatedImages' <<<"${redhat_distribution_images}")" + skips="$(build_redhat_skips)" } render_csv() { @@ -557,13 +558,13 @@ render_csv() { export skip_range="<${version}" export containerImage export relatedImages + export skips export rulesLevel export icon_base64 yq -P eval ' .metadata.annotations["alm-examples"] = strenv(examples) | .metadata.annotations["containerImage"] = env(containerImage) | - .metadata.annotations["olm.skipRange"] = env(skip_range) | .metadata.annotations["createdAt"] = strenv(timestamp) | .metadata.name = env(name) | .spec.version = env(version) | @@ -579,17 +580,21 @@ render_csv() { case "${DISTRIBUTION}" in community) + yq -P eval --inplace ' + .metadata.annotations["olm.skipRange"] = env(skip_range)' \ + "${csv_file}" ;; redhat) yq -P eval --inplace ' .spec.relatedImages = (strenv(relatedImages) | from_json) | + .spec.skips = (strenv(skips) | from_json) | .metadata.annotations.certified = "true" | .metadata.annotations["features.operators.openshift.io/disconnected"] = "true" | .metadata.name = strenv(name_certified)' \ + \ "${csv_file}" ;; esac - } validate_bundle() { From 0c2836b745e2ab2be83e9edb55031c49329ce7c2 Mon Sep 17 00:00:00 2001 From: valmiranogueira Date: Tue, 28 Jul 2026 17:29:50 -0300 Subject: [PATCH 4/5] Fix comments and add previous versions to the catalog --- Makefile | 8 - config/marketplace/kustomization.yaml | 6 - installers/olm/Makefile | 361 +++++++----------- installers/olm/README.md | 132 +++++-- .../olm/{build-image.sh => build-bundle.sh} | 49 ++- installers/olm/build-catalog.sh | 335 ++++++++++++++++ installers/olm/bundle.csv.yaml | 77 ++-- installers/olm/distributions/community.sh | 59 +++ installers/olm/distributions/redhat.sh | 101 +++-- installers/olm/generate.sh | 299 +++++---------- installers/olm/validate-image.sh | 51 ++- 11 files changed, 929 insertions(+), 549 deletions(-) delete mode 100644 config/marketplace/kustomization.yaml rename installers/olm/{build-image.sh => build-bundle.sh} (52%) create mode 100755 installers/olm/build-catalog.sh create mode 100644 installers/olm/distributions/community.sh diff --git a/Makefile b/Makefile index d11f17eac6..0b5e41f2ca 100644 --- a/Makefile +++ b/Makefile @@ -75,14 +75,6 @@ undeploy: ## Undeploy operator test: envtest generate ## Run tests. DISABLE_TELEMETRY=true KUBEBUILDER_ASSETS="$(shell $(ENVTEST) --arch=amd64 use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out -.PHONY: validate -validate: ## Validate OLM bundles - $(MAKE) -C installers/olm validate VERSION=$(VERSION) - -.PHONY: validate/community validate/certified -validate/community validate/certified: validate/%: ## Validate one OLM bundle - $(MAKE) -C installers/olm validate/$* VERSION=$(VERSION) - # go-get-tool will 'go get' any package $2 and install it to $1. PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) define go-get-tool diff --git a/config/marketplace/kustomization.yaml b/config/marketplace/kustomization.yaml deleted file mode 100644 index 42bd0a3da5..0000000000 --- a/config/marketplace/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -- ../operator - diff --git a/installers/olm/Makefile b/installers/olm/Makefile index 71aab6ae83..eabf2a5d9d 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -6,6 +6,9 @@ .SUFFIXES: SHELL := /bin/bash +REGISTRY ?= docker.io +PROD_REPOSITORY ?= percona +DEV_REPOSITORY ?= perconalab NAME ?= percona-server-mongodb-operator BUNDLE_TYPES := community certified @@ -15,36 +18,25 @@ CSV_VERSION ?= $(shell printf '%s\n' '$(VERSION)' | $(SED) -nE 's/.*([0-9]+)[.-] RELEASE_VERSIONS ?= ../../e2e-tests/release_versions PACKAGE_CHANNEL ?= stable -MIN_KUBE_VERSION ?= "" -OPENSHIFT_VERSIONS ?= $(shell awk -F= '$$1 == "OPENSHIFT_MIN" { split($$2, v, "."); min = "v" v[1] "." v[2] } $$1 == "OPENSHIFT_MAX" { split($$2, v, "."); max = "v" v[1] "." v[2] } END { if (min && max) print min "-" max }' '$(RELEASE_VERSIONS)') +OPENSHIFT_MIN_VERSION := $(shell awk -F= '$$1 == "OPENSHIFT_MIN" { print $$2 }' '$(RELEASE_VERSIONS)' | cut -d. -f1,2) +OPENSHIFT_MAX_VERSION := $(shell awk -F= '$$1 == "OPENSHIFT_MAX" { print $$2 }' '$(RELEASE_VERSIONS)' | cut -d. -f1,2) +OPENSHIFT_VERSIONS ?= v$(OPENSHIFT_MIN_VERSION)-v$(OPENSHIFT_MAX_VERSION) -COMMUNITY_REGISTRY ?= docker.io -IMAGE_TAG_OWNER ?= percona -IMAGE_TAG_BASE ?= $(IMAGE_TAG_OWNER)/$(NAME) -IMAGE ?= $(COMMUNITY_REGISTRY)/$(IMAGE_TAG_BASE):$(VERSION) - -REDHAT_OPERATOR_IMAGE ?= registry.connect.redhat.com/percona/percona-server-mongodb-operator:$(VERSION) - -CONTAINER ?= docker -DOCKER_DEFAULT_PLATFORM ?= linux/amd64 +DEV_REPOSITORY_BASE ?= $(REGISTRY)/$(DEV_REPOSITORY) +PROD_REPOSITORY_BASE ?= $(REGISTRY)/$(PROD_REPOSITORY) BUNDLE_TYPE ?= community -BUNDLE_REPO ?= $(IMAGE_TAG_BASE) -DEV_BUNDLE_REPO ?= docker.io/perconalab/$(NAME) -DEV_DISPLAY_NAME_SUFFIX ?= (Dev) -DEPLOY_BUNDLE_REPO ?= $(DEV_BUNDLE_REPO) BUNDLE_IMAGE_VERSION ?= $(CSV_VERSION) BUNDLE_PLATFORMS ?= linux/amd64,linux/arm64 -BUNDLE_TAG_TYPE = $(if $(filter redhat certified,$(BUNDLE_TYPE)),certified,$(BUNDLE_TYPE)) -BUNDLE_IMG ?= $(BUNDLE_REPO):$(BUNDLE_IMAGE_VERSION)-$(BUNDLE_TAG_TYPE)-bundle -CATALOG_REPO ?= $(DEV_BUNDLE_REPO) +BUNDLE_BUILD_PUSH ?= 1 +SKIP_DIGEST_FAILURE ?= 0 +DEV_BUNDLE_REPO ?= $(DEV_REPOSITORY_BASE)/$(NAME) +PROD_BUNDLE_REPO ?= $(PROD_REPOSITORY_BASE)/$(NAME) + +CATALOG_BUNDLE_LIMIT ?= 2 CATALOG_NAMESPACE ?= openshift-marketplace CATALOG_PLATFORMS ?= linux/amd64,linux/arm64 -CATALOG_SOURCE_SUFFIX ?= - -CONFIRM_PUSH ?= 1 -SKIP_DIGEST_FAILURE ?= 0 -RUN_BUNDLE_TIMEOUT ?= 10m +CATALOG_BUILD_PUSH ?= 1 REPO_ROOT := $(shell git rev-parse --show-toplevel) KUSTOMIZE := $(REPO_ROOT)/bin/kustomize @@ -54,6 +46,7 @@ OS_MACHINE ?= $(shell uname -m | sed -e 's/^x86_64$$/amd64/' -e 's/^x86_/amd/' - SYSTEM := $(OS_KERNEL)-$(OS_MACHINE) TOOLS_DIR := tools/$(SYSTEM) +CONTAINER ?= docker JQ_VERSION := 1.7.1 JQ_PLATFORM := $(if $(filter darwin,$(OS_KERNEL)),macos,$(OS_KERNEL)) OPERATOR_SDK_VERSION := v1.41.1 @@ -67,25 +60,20 @@ export PATH := $(CURDIR)/$(TOOLS_DIR):$(PATH) GREEN := $(shell tput setaf 2) RESET := $(shell tput sgr0) -define quiet_run -if [[ "$${OLM_VERBOSE:-0}" == "1" || "$${OLM_VERBOSE:-false}" == "true" ]]; then \ - $(2); \ -else \ - output_file="$$(mktemp)"; \ - if $(2) >"$${output_file}" 2>&1; then \ - rm -f "$${output_file}"; \ - else \ - cat "$${output_file}" >&2; \ - rm -f "$${output_file}"; \ - echo "[olm] ERROR: $(1) failed" >&2; \ - exit 1; \ - fi; \ -fi +bundle_distribution = $(if $(filter certified,$(1)),redhat,$(1)) + +define catalog_build_env +BUNDLE_IMAGE_VERSION='$(BUNDLE_IMAGE_VERSION)' \ +BUNDLE_PLATFORMS='$(BUNDLE_PLATFORMS)' \ +CATALOG_BUNDLE_LIMIT='$(CATALOG_BUNDLE_LIMIT)' \ +CATALOG_PLATFORMS='$(CATALOG_PLATFORMS)' \ +CONTAINER='$(CONTAINER)' \ +CONFIRM_BUILD_PUSH='$(CONFIRM_BUILD_PUSH)' endef -export VERSION CSV_VERSION BUNDLE_IMAGE_VERSION IMAGE REDHAT_OPERATOR_IMAGE OPENSHIFT_VERSIONS PACKAGE_CHANNEL MIN_KUBE_VERSION -export PACKAGE_NAME_OVERRIDE CSV_NAME_OVERRIDE DISPLAY_NAME_OVERRIDE -export DOCKER_DEFAULT_PLATFORM BUNDLE_REPO BUNDLE_PLATFORMS SKIP_DIGEST_FAILURE +export VERSION CSV_VERSION BUNDLE_IMAGE_VERSION OPENSHIFT_VERSIONS PACKAGE_CHANNEL +export PACKAGE_NAME_OVERRIDE CSV_NAME_OVERRIDE +export BUNDLE_PLATFORMS BUNDLE_BUILD_PUSH CONFIRM_BUILD_PUSH SKIP_DIGEST_FAILURE # ============================================================================== # Validation and Tool Helpers @@ -128,228 +116,169 @@ $(TOOLS_DIR): mkdir -p $(TOOLS_DIR) $(TOOLS_DIR)/jq: - curl -fL "https://github.com/jqlang/jq/releases/download/jq-$(JQ_VERSION)/jq-$(JQ_PLATFORM)-$(OS_MACHINE)" -o $@ + curl -fsSL "https://github.com/jqlang/jq/releases/download/jq-$(JQ_VERSION)/jq-$(JQ_PLATFORM)-$(OS_MACHINE)" -o $@ chmod +x $@ $(TOOLS_DIR)/operator-sdk: | $(TOOLS_DIR) - curl -sL "https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS_KERNEL)_$(OS_MACHINE)" -o $@ + curl -fsSL "https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS_KERNEL)_$(OS_MACHINE)" -o $@ chmod +x $@ $(TOOLS_DIR)/opm: | $(TOOLS_DIR) - curl -sL "https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$(OS_KERNEL)-$(OS_MACHINE)-opm" -o $@ + curl -fsSL "https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$(OS_KERNEL)-$(OS_MACHINE)-opm" -o $@ chmod +x $@ +# ============================================================================== +# Generic Bundle Rules +# ============================================================================== +.PHONY: generate-bundle check-bundle-type + +check-bundle-type: + @if [[ "$(BUNDLE_TYPE)" != "community" && "$(BUNDLE_TYPE)" != "certified" ]]; then \ + echo "BUNDLE_TYPE must be community or certified"; \ + exit 1; \ + fi + +generate-bundle: check-bundle-type tools $(KUSTOMIZE) check-version check-csv-version check-tools + @echo "$(GREEN)Building $(BUNDLE_TYPE) bundle...$(RESET)" + @set -euo pipefail; \ + distribution='$(BUNDLE_TYPE)'; \ + if [[ '$(BUNDLE_TYPE)' == 'certified' ]]; then \ + distribution='redhat'; \ + fi; \ + manager_kustomization='../../config/manager/namespace/kustomization.yaml'; \ + original_manager_kustomization="$$(mktemp)"; \ + cp "$${manager_kustomization}" "$${original_manager_kustomization}"; \ + trap 'cp "$${original_manager_kustomization}" "$${manager_kustomization}"; rm -f "$${original_manager_kustomization}"' EXIT; \ + ./generate.sh "$${distribution}" && echo "$(GREEN)Bundle stored in installers/olm/bundles/$${distribution}$(RESET)" + # ============================================================================== # Development Rules # ============================================================================== -.PHONY: bundle -bundle: ## Build all development OLM bundles and push bundle images to perconalab -bundle: $(BUNDLE_TYPES:%=bundle/%) -.PHONY: bundle/community -bundle/community: ## Build and push community bundle image to perconalab +## Build all development OLM bundles and push bundle images to DEV_REPOSITORY +.PHONY: bundles bundle $(BUNDLE_TYPES:%=bundle/%) +bundles: ## Build all development OLM bundles and push bundle images +bundles: bundle +bundle: $(BUNDLE_TYPES:%=bundle/%) -.PHONY: bundle/certified -bundle/certified: ## Build and push certified bundle image to perconalab +bundle/community: +bundle/certified: -.PHONY: $(BUNDLE_TYPES:%=bundle/%) $(BUNDLE_TYPES:%=bundle/%): bundle/%: check-version check-csv-version @package='$(NAME)'; \ if [[ "$*" == "certified" ]]; then \ package='$(NAME)-certified'; \ fi; \ csv_name="$${package}.v$(CSV_VERSION)"; \ - $(MAKE) bundle-prod/$* PACKAGE_NAME_OVERRIDE="$${package}" CSV_NAME_OVERRIDE="$${csv_name}" DISPLAY_NAME_OVERRIDE="Percona Distribution for MongoDB Operator $(DEV_DISPLAY_NAME_SUFFIX)" - BUNDLE_REPO='$(DEV_BUNDLE_REPO)' ./build-image.sh '$(CONTAINER)' 'bundles/$*' '$*' '$(BUNDLE_IMAGE_VERSION)' - -.PHONY: catalog -catalog: ## Build and push all development catalog images -catalog: $(BUNDLE_TYPES:%=catalog/%) - -.PHONY: catalog/community -catalog/community: ## Build and push community development catalog image - -.PHONY: catalog/certified -catalog/certified: ## Build and push certified development catalog image - -.PHONY: $(BUNDLE_TYPES:%=catalog/%) -$(BUNDLE_TYPES:%=catalog/%): catalog/%: bundle/% - $(MAKE) catalog-prod/$* BUNDLE_REPO='$(DEV_BUNDLE_REPO)' + $(MAKE) generate-bundle \ + BUNDLE_TYPE='$*' \ + PACKAGE_NAME_OVERRIDE="$${package}" \ + CSV_NAME_OVERRIDE="$${csv_name}" + $(MAKE) build \ + BUNDLE_TYPE='$*' + +## Build and push all development catalog images to DEV_REPOSITORY +.PHONY: catalog-build-push $(BUNDLE_TYPES:%=catalog-build-push/%) +catalog-build-push: ## Build and push all development catalog images +catalog-build-push: $(BUNDLE_TYPES:%=catalog-build-push/%) + +catalog-build-push/community: +catalog-build-push/certified: + +$(BUNDLE_TYPES:%=catalog-build-push/%): catalog-build-push/%: tools check-version check-csv-version + $(MAKE) bundle/$* BUNDLE_BUILD_PUSH=1 + @if [[ "$(CATALOG_BUILD_PUSH)" == "true" || "$(CATALOG_BUILD_PUSH)" == "1" ]]; then \ + $(catalog_build_env) \ + ./build-catalog.sh build \ + '$*' \ + '$(DEV_BUNDLE_REPO)' \ + '$(DEV_BUNDLE_REPO)'; \ + else \ + echo "[olm] Skipping catalog build and push"; \ + fi -.PHONY: deploy +## Build, push, and deploy all development catalog sources +.PHONY: deploy $(BUNDLE_TYPES:%=deploy/%) deploy: ## Build, push, and deploy all development catalog sources deploy: $(BUNDLE_TYPES:%=deploy/%) -.PHONY: deploy/community -deploy/community: ## Build, push, and deploy community development catalog source - -.PHONY: deploy/certified -deploy/certified: ## Build, push, and deploy certified development catalog source +deploy/community: +deploy/certified: -.PHONY: $(BUNDLE_TYPES:%=deploy/%) -$(BUNDLE_TYPES:%=deploy/%): deploy/%: catalog/% - $(MAKE) apply-catalog/$* CATALOG_SOURCE_NAME='$*-dev' +$(BUNDLE_TYPES:%=deploy/%): deploy/%: catalog-build-push/% + CATALOG_NAMESPACE='$(CATALOG_NAMESPACE)' \ + NAME='$(NAME)' \ + bash ./build-catalog.sh apply '$*' '$(DEV_BUNDLE_REPO)' # ============================================================================== # Production Rules # ============================================================================== -.PHONY: bundles-prod -bundles-prod: ## Build all production OLM bundles (community, certified) -bundles-prod: check-prereqs $(BUNDLE_TYPES:%=bundle-prod/%) -.PHONY: bundle-prod/community -bundle-prod/community: ## Build community OLM bundle +## Generate all production OLM bundles. +.PHONY: bundles-prod $(BUNDLE_TYPES:%=bundle-prod/%) +bundles-prod: ## Generate all production OLM bundles +bundles-prod: $(BUNDLE_TYPES:%=bundle-prod/%) -.PHONY: bundle-prod/certified -bundle-prod/certified: ## Build certified OLM bundle +bundle-prod/community: +bundle-prod/certified: -.PHONY: $(BUNDLE_TYPES:%=bundle-prod/%) -$(BUNDLE_TYPES:%=bundle-prod/%): bundle-prod/%: tools $(KUSTOMIZE) check-version check-csv-version check-tools - @echo "$(GREEN)Building $* bundle...$(RESET)" - @distribution="$*"; \ - operator_image="$(IMAGE)"; \ - if [[ "$*" == "certified" ]]; then \ - distribution="redhat"; \ - operator_image="$(REDHAT_OPERATOR_IMAGE)"; \ - fi; \ - manager_kustomization="../../config/manager/namespace/kustomization.yaml"; \ - original_manager_kustomization="$$(mktemp)"; \ - cp "$${manager_kustomization}" "$${original_manager_kustomization}"; \ - trap 'cp "$${original_manager_kustomization}" "$${manager_kustomization}"; rm -f "$${original_manager_kustomization}"' EXIT; \ - (cd ../../config/manager/namespace/ && $(KUSTOMIZE) edit set image psmdb-operator="$${operator_image}"); \ - BUNDLE_NAME="$*" ./generate.sh "$${distribution}"; \ - $(call quiet_run,Validating $* bundle,operator-sdk bundle validate "bundles/$*" --select-optional='suite=operatorframework'); \ - if [[ "$*" == "community" ]]; then \ - $(call quiet_run,Validating $* community bundle,operator-sdk bundle validate "bundles/$*" --select-optional='name=community' --optional-values='index-path=bundles/$*/Dockerfile'); \ - fi - @echo "$(GREEN)Bundle stored in installers/olm/bundles/$*$(RESET)" - -.PHONY: catalog-prod -catalog-prod: ## Build and push all production catalog images -catalog-prod: $(BUNDLE_TYPES:%=catalog-prod/%) - -.PHONY: catalog-prod/community -catalog-prod/community: ## Build and push community catalog image to perconalab - -.PHONY: catalog-prod/certified -catalog-prod/certified: ## Build and push certified catalog image to perconalab - -.PHONY: $(BUNDLE_TYPES:%=catalog-prod/%) -$(BUNDLE_TYPES:%=catalog-prod/%): catalog-prod/%: tools check-version check-csv-version - @catalog_root='catalogs'; \ - catalog_dir="$${catalog_root}/$*"; \ - catalog_image='$(CATALOG_REPO):$*-catalog'; \ - bundle_image='$(BUNDLE_REPO):$(BUNDLE_IMAGE_VERSION)-$*-bundle'; \ - rm -rf "$${catalog_dir}" "$${catalog_dir}.Dockerfile"; \ - mkdir -p "$${catalog_dir}"; \ - printf '%s\n' \ - 'Schema: olm.semver' \ - 'GenerateMajorChannels: false' \ - 'GenerateMinorChannels: false' \ - 'Stable:' \ - ' Bundles:' \ - " - Image: $${bundle_image}" \ - > "$${catalog_dir}/template.yaml"; \ - opm alpha render-template semver -o yaml < "$${catalog_dir}/template.yaml" > "$${catalog_dir}/catalog.yaml"; \ - opm validate "$${catalog_dir}"; \ - opm generate dockerfile "$${catalog_dir}"; \ - if [[ "$(CONFIRM_PUSH)" != "0" && "$(CONFIRM_PUSH)" != "false" ]]; then \ - read -r -p "Push image $${catalog_image}? [y/N] " answer; \ - [[ "$${answer}" == "y" || "$${answer}" == "Y" || "$${answer}" == "yes" || "$${answer}" == "YES" ]] || exit 1; \ - fi; \ - $(CONTAINER) buildx build \ - --platform '$(CATALOG_PLATFORMS)' \ - -f "$${catalog_dir}.Dockerfile" \ - -t "$${catalog_image}" \ - --push \ - "$${catalog_root}"; \ - echo "$(GREEN)Catalog image pushed: $${catalog_image}$(RESET)" - -.PHONY: deploy-prod -deploy-prod: ## Build, push, and deploy all production catalog sources -deploy-prod: $(BUNDLE_TYPES:%=deploy-prod/%) +$(BUNDLE_TYPES:%=bundle-prod/%): bundle-prod/%: + $(MAKE) generate-bundle BUNDLE_TYPE='$*' -.PHONY: deploy-prod/community -deploy-prod/community: ## Build, push, and deploy community bundle +## Build production bundles, optionally build/push the catalog, and deploy it +.PHONY: deploy-prod $(BUNDLE_TYPES:%=deploy-prod/%) +deploy-prod: ## Build production bundles, catalog images, and deploy catalog sources +deploy-prod: $(BUNDLE_TYPES:%=deploy-prod/%) -.PHONY: deploy-prod/certified -deploy-prod/certified: ## Build, push, and deploy certified bundle +deploy-prod/community: +deploy-prod/certified: -.PHONY: $(BUNDLE_TYPES:%=deploy-prod/%) $(BUNDLE_TYPES:%=deploy-prod/%): deploy-prod/%: tools check-version check-csv-version $(MAKE) bundle-prod/$* - BUNDLE_REPO='$(DEPLOY_BUNDLE_REPO)' ./build-image.sh '$(CONTAINER)' 'bundles/$*' '$*' '$(BUNDLE_IMAGE_VERSION)' - $(MAKE) catalog-prod/$* BUNDLE_REPO='$(DEPLOY_BUNDLE_REPO)' - $(MAKE) apply-catalog/$* - -.PHONY: $(BUNDLE_TYPES:%=apply-catalog/%) -$(BUNDLE_TYPES:%=apply-catalog/%): apply-catalog/%: - @set -e; \ - catalog_source='$(CATALOG_SOURCE_NAME)'; \ - if [[ -z "$${catalog_source}" ]]; then \ - catalog_source='$(NAME)'; \ - if [[ "$*" == "certified" ]]; then catalog_source='$(NAME)-certified'; fi; \ - fi; \ - catalog_image='$(CATALOG_REPO):$*-catalog'; \ - catalog_dir='catalogs/$*'; \ - catalog_namespace='$(CATALOG_NAMESPACE)'; \ - legacy_catalog_source=''; \ - if [[ -n '$(CATALOG_SOURCE_NAME)' ]]; then \ - legacy_catalog_source='$(NAME)'; \ - if [[ "$*" == "certified" ]]; then legacy_catalog_source='$(NAME)-certified'; fi; \ - fi; \ - if ! kubectl get namespace "$${catalog_namespace}" >/dev/null 2>&1; then \ - kubectl create namespace "$${catalog_namespace}"; \ - fi; \ - printf '%s\n' \ - 'apiVersion: operators.coreos.com/v1alpha1' \ - 'kind: CatalogSource' \ - 'metadata:' \ - " name: $${catalog_source}" \ - " namespace: $${catalog_namespace}" \ - 'spec:' \ - " displayName: $${catalog_source}" \ - ' sourceType: grpc' \ - " image: $${catalog_image}" \ - ' imagePullPolicy: Always' \ - > "$${catalog_dir}/catalog-source.yaml"; \ - kubectl apply --validate=false -f "$${catalog_dir}/catalog-source.yaml"; \ - kubectl delete pod -n "$${catalog_namespace}" -l "olm.catalogSource=$${catalog_source}" --ignore-not-found; \ - if [[ -n "$${legacy_catalog_source}" && "$${legacy_catalog_source}" != "$${catalog_source}" ]]; then \ - kubectl delete catalogsource "$${legacy_catalog_source}" -n "$${catalog_namespace}" --ignore-not-found; \ - fi; \ - echo "$(GREEN)CatalogSource deployed: $${catalog_source} in $${catalog_namespace} -> $${catalog_image}$(RESET)" + $(MAKE) build-prod BUNDLE_TYPE='$*' BUNDLE_BUILD_PUSH=1 + @if [[ "$(CATALOG_BUILD_PUSH)" == "true" || "$(CATALOG_BUILD_PUSH)" == "1" ]]; then \ + $(catalog_build_env) \ + bash ./build-catalog.sh build \ + '$*' \ + '$(DEV_BUNDLE_REPO)' \ + '$(PROD_BUNDLE_REPO)'; \ + else \ + echo "[olm] Skipping catalog build and push"; \ + fi + CATALOG_NAMESPACE='$(CATALOG_NAMESPACE)' \ + NAME='$(NAME)' \ + bash ./build-catalog.sh apply '$*' '$(DEV_BUNDLE_REPO)' # ============================================================================== # Image Rules # ============================================================================== + .PHONY: build -build: ## Build bundle image (set BUNDLE_TYPE=community|certified) -build: check-version check-csv-version - @echo "$(GREEN)Building bundle image...$(RESET)" - $(CONTAINER) build -t $(BUNDLE_IMG) --platform=$(DOCKER_DEFAULT_PLATFORM) bundles/$(BUNDLE_TYPE) - @echo "$(GREEN)Bundle image built: $(BUNDLE_IMG)$(RESET)" - -.PHONY: push -push: ## Push bundle image to registry -push: check-version check-csv-version - @echo "$(GREEN)Pushing bundle image to registry...$(RESET)" - @if [[ "$(CONFIRM_PUSH)" != "0" && "$(CONFIRM_PUSH)" != "false" ]]; then \ - read -r -p "Push image $(BUNDLE_IMG)? [y/N] " answer; \ - [[ "$${answer}" == "y" || "$${answer}" == "Y" || "$${answer}" == "yes" || "$${answer}" == "YES" ]] || exit 1; \ - fi - $(CONTAINER) push $(BUNDLE_IMG) - @echo "$(GREEN)Bundle image pushed: $(BUNDLE_IMG)$(RESET)" +build: ## Build and optionally push bundle image (set BUNDLE_TYPE=community|certified) +build: build-dev + +.PHONY: build-dev build-prod +build-dev: BUNDLE_REPO = $(DEV_BUNDLE_REPO) +build-prod: BUNDLE_REPO = $(PROD_BUNDLE_REPO) +build-dev build-prod: check-bundle-type check-version check-csv-version + BUNDLE_BUILD_PUSH='$(BUNDLE_BUILD_PUSH)' \ + ./build-bundle.sh \ + '$(CONTAINER)' \ + 'bundles/$(call bundle_distribution,$(BUNDLE_TYPE))' \ + '$(call bundle_distribution,$(BUNDLE_TYPE))' \ + '$(BUNDLE_IMAGE_VERSION)' \ + '$(BUNDLE_REPO)' \ + '$(BUNDLE_TAG_SUFFIX)' .PHONY: build-bundle-images -build-bundle-images: check-version check-csv-version $(BUNDLE_TYPES:%=build-%-image) - -build-%-image: - ./build-image.sh '$(CONTAINER)' 'bundles/$*' '$*' '$(BUNDLE_IMAGE_VERSION)' +build-bundle-images: check-version check-csv-version + $(MAKE) build BUNDLE_TYPE=community + $(MAKE) build BUNDLE_TYPE=certified # ============================================================================== # Validation Rules # ============================================================================== + .PHONY: validate validate: ## Run bundle image and directory validation validate: tools @@ -370,10 +299,6 @@ validate-%-image: # ============================================================================== # Utility Rules # ============================================================================== -.PHONY: install-olm -install-olm: ## Install OLM in Kubernetes cluster -install-olm: tools - operator-sdk olm install .PHONY: clean clean: ## Remove generated files and downloaded tools diff --git a/installers/olm/README.md b/installers/olm/README.md index add4c4a39a..91a611590d 100644 --- a/installers/olm/README.md +++ b/installers/olm/README.md @@ -55,12 +55,19 @@ Useful optional variables: | Variable | Description | Example | | --- | --- | --- | | `CSV_VERSION` | CSV version. Defaults to the first `x.y.z` parsed from `VERSION`. | `1.23.0` | -| `IMAGE` | Community operator image used in generated manifests. | `docker.io/percona/percona-server-mongodb-operator:1.23.0` | -| `REDHAT_OPERATOR_IMAGE` | Certified operator image used in generated manifests. | `registry.connect.redhat.com/percona/percona-server-mongodb-operator:1.23.0` | -| `DEV_BUNDLE_REPO` | Repository used by development bundle and catalog images. | `docker.io/perconalab/percona-server-mongodb-operator` | -| `DEPLOY_BUNDLE_REPO` | Repository used by release bundle and catalog images. | `docker.io/percona/percona-server-mongodb-operator` | -| `CONFIRM_PUSH` | Ask before pushing images. Development targets default to local builds; deploy targets enable push automatically. Set to `0` in CI. | `0` | -| `SKIP_DIGEST_FAILURE` | Continue certified generation when a digest cannot be resolved. Missing digests are rendered as ``. | `1` | +| `REGISTRY` | Registry used for operator, bundle, and catalog images. | `docker.io` | +| `PROD_REPOSITORY` | Repository namespace used for production bundle images. | `percona` | +| `DEV_REPOSITORY` | Repository namespace used for development bundle images and all catalog images. | `perconalab` | +| `DEV_BUNDLE_REPO` | Repository used by development bundle images. | `docker.io/perconalab/percona-server-mongodb-operator` | +| `PROD_BUNDLE_REPO` | Repository used by production bundle images. | `docker.io/percona/percona-server-mongodb-operator` | +| `BUNDLE_BUILD_PUSH` | Build and push bundle images when enabled. | `1` | +| `CONFIRM_BUILD_PUSH` | Ask for confirmation before pushing bundle and catalog images. Set to `0` for non-interactive runs. | `1` | +| `DEV_CATALOG_BUNDLE_TAG_SUFFIX` | Tag suffix used for previous OperatorHub bundle images built as development catalog inputs. The current release bundle keeps the normal bundle tag. | `dev-catalog` | +| `DEV_CATALOG_TAG_SUFFIX` | Tag suffix used for catalog images. Catalog images are always development images. | `dev-catalog` | +| `CATALOG_BUNDLE_LIMIT` | Number of previous OperatorHub bundle versions to include in rendered catalogs, in addition to the current release bundle. | `2` | +| `CATALOG_BUILD_PUSH` | Build and push catalog images during deploy targets when enabled. | `1` | +| `GITHUB_TOKEN` / `GH_TOKEN` | Optional token for GitHub API requests when building catalogs. | `` | +| `SKIP_DIGEST_FAILURE` | Continue certified generation when a non-required digest cannot be resolved. Missing digests are rendered as ``. | `1` | OpenShift versions are resolved from `../../e2e-tests/release_versions` and used to render `com.redhat.openshift.versions`. @@ -75,20 +82,23 @@ export OPENSHIFT_VERSIONS="v4.18-v4.22" # Development -Development targets build local bundles and catalogs by default. Images are only -pushed when running a deploy target (or when explicitly enabling push). +Development targets generate bundle directories and build bundle images. Bundle +image push is controlled by `BUNDLE_BUILD_PUSH` and defaults to enabled. -Development package names are suffixed with `-dev` to avoid colliding with the -public OperatorHub packages shipped with OpenShift. +Development catalog sources are suffixed with `-dev`, but the package names keep +the production names. Packages: -- `percona-server-mongodb-operator-dev` -- `percona-server-mongodb-operator-certified-dev` +- `percona-server-mongodb-operator` +- `percona-server-mongodb-operator-certified` -Display names are suffixed with **(Dev)**. +Catalog sources: -## Generate bundles +- `community-dev` +- `certified-dev` + +## Build bundles ```bash make bundles VERSION=1.23.0 @@ -96,11 +106,53 @@ make bundle/community VERSION=1.23.0 make bundle/certified VERSION=1.23.0 ``` +Build locally without pushing: + +```bash +make bundle/community VERSION=1.23.0 BUNDLE_BUILD_PUSH=0 +``` + ## Build catalogs +Catalog build targets first generate, build, and push the current release bundle +image to `DEV_BUNDLE_REPO` with the normal bundle tag, then render and push the +catalog image to `DEV_BUNDLE_REPO` with the `DEV_CATALOG_TAG_SUFFIX` suffix. + +For the latest `CATALOG_BUNDLE_LIMIT` previous versions already published in +OperatorHub, `build-catalog.sh` downloads the bundle manifests from GitHub, +builds bundle images with the `DEV_CATALOG_BUNDLE_TAG_SUFFIX`, and pushes them +before rendering the catalog. + +The current bundle is rendered into the default package channel from +`PACKAGE_CHANNEL` (`stable` by default). Previous OperatorHub versions are +rendered into versioned channels such as `stable-v1.22` so the OpenShift +Console can show version-specific catalog metadata. + +```bash +make catalog-build-push/community VERSION=1.23.0 +make catalog-build-push/certified VERSION=1.23.0 +``` + +## Personal catalog testing + +Override `DEV_REPOSITORY` to build and push development bundles and catalog +images to a personal repository namespace. Use `CATALOG_NAMESPACE=olm` on +clusters where the OpenShift console reads the software catalog from `olm`. + ```bash -make catalog/community VERSION=1.23.0 -make catalog/certified VERSION=1.23.0 +make deploy/community \ + VERSION=1.23.0 \ + CSV_VERSION=1.23.0 \ + DEV_REPOSITORY=valmiranogueira \ + CATALOG_NAMESPACE=olm +``` + +This publishes images such as: + +```text +docker.io/valmiranogueira/percona-server-mongodb-operator:1.23.0-community-bundle +docker.io/valmiranogueira/percona-server-mongodb-operator:1.22.0-community-bundle-dev-catalog +docker.io/valmiranogueira/percona-server-mongodb-operator:community-dev-catalog ``` ## Deploy catalogs @@ -116,14 +168,14 @@ make deploy VERSION=1.23.0 After deployment, verify the packages: ```bash -kubectl get packagemanifest percona-server-mongodb-operator-dev -n openshift-marketplace -kubectl get packagemanifest percona-server-mongodb-operator-certified-dev -n openshift-marketplace +kubectl get packagemanifest percona-server-mongodb-operator -n openshift-marketplace +kubectl get packagemanifest percona-server-mongodb-operator-certified -n openshift-marketplace ``` or search in the OpenShift console for: ```text -Percona Distribution for MongoDB Operator (Dev) +Percona Distribution for MongoDB Operator ``` --- @@ -140,15 +192,14 @@ make bundle-prod/community VERSION=1.23.0 make bundle-prod/certified VERSION=1.23.0 ``` -## Build release catalogs - -```bash -make catalog-prod/community VERSION=1.23.0 -make catalog-prod/certified VERSION=1.23.0 -``` - ## Deploy release catalogs +Release deploy targets generate production bundles, build and push production +bundle images, and build and push catalog images through +`build-catalog.sh build-push` when `CATALOG_BUILD_PUSH=1`. Catalog images always +use `DEV_BUNDLE_REPO` with the `DEV_CATALOG_TAG_SUFFIX` suffix; production +deploys only switch the bundle image repository to `PROD_BUNDLE_REPO`. + ```bash make deploy-prod/community VERSION=1.23.0 make deploy-prod/certified VERSION=1.23.0 @@ -158,13 +209,13 @@ make deploy-prod VERSION=1.23.0 Each deploy performs the complete release workflow: ```text -bundle-prod/* +generate production bundle ↓ -build-image.sh +build and push production bundle image ↓ -catalog-prod/* +build and push development catalog image pointing to the production bundle ↓ -apply-catalog/* +apply CatalogSource ``` Unlike the development targets, release bundles use the production package names: @@ -201,14 +252,29 @@ Validation uses: Certified bundles resolve image metadata and related image digests through `distributions/redhat.sh`. +The public certified bundle type maps to the internal `redhat` distribution. +Generated certified bundle files are written under: + +```text +installers/olm/bundles/redhat +``` + +The bundle image tag still uses the public `certified` name, for example: + +```text +:1.23.0-certified-bundle +``` + Bundle generation fails when: - a required image is missing; - a certified image tag does not match the expected pattern; -- a required digest cannot be resolved and `SKIP_DIGEST_FAILURE` is disabled. +- the required `clustersync` Red Hat tag digest cannot be resolved. -When `SKIP_DIGEST_FAILURE=1`, missing digests are rendered as `` and -reported in the build output. +When `SKIP_DIGEST_FAILURE=1`, missing non-`clustersync` digests are rendered as +`` and reported in the build output. The `clustersync` digest is always +resolved strictly so a missing `registry.connect.redhat.com/...:-clustersync` +tag stops generation immediately. --- diff --git a/installers/olm/build-image.sh b/installers/olm/build-bundle.sh similarity index 52% rename from installers/olm/build-image.sh rename to installers/olm/build-bundle.sh index a55ebf75ed..cbb67195c4 100755 --- a/installers/olm/build-image.sh +++ b/installers/olm/build-bundle.sh @@ -2,22 +2,10 @@ set -euo pipefail -confirm_push() { - local image="$1" - local answer - - case "${CONFIRM_PUSH:-0}" in - 1|true|TRUE|yes|YES) - read -r -p "Push image ${image}? [y/N] " answer - - case "${answer}" in - y|Y|yes|YES) - return 0 - ;; - *) - return 1 - ;; - esac +is_true() { + case "${1:-}" in + 1|true|TRUE|yes|YES|y|Y) + return 0 ;; *) return 1 @@ -25,11 +13,29 @@ confirm_push() { esac } +confirm_build_push() { + local image="$1" + local answer + + if ! is_true "${CONFIRM_BUILD_PUSH:-1}"; then + return 0 + fi + + if [[ -r /dev/tty ]]; then + read -r -p "Build and push bundle image ${image}? [y/N] " answer $0 build \ + + + $0 apply \ + + +Examples: + BUNDLE_IMAGE_VERSION=1.23.0 $0 build \ + community \ + docker.io/perconalab/percona-server-mongodb-operator \ + docker.io/perconalab/percona-server-mongodb-operator + + $0 apply \ + community \ + docker.io/perconalab/percona-server-mongodb-operator + + $0 apply \ + certified \ + docker.io/percona/percona-server-mongodb-operator + # -> CatalogSource name: certified-prod +EOF +} + +die() { + echo "[olm] ERROR: $*" >&2 + exit 1 +} + +require() { + command -v "$1" >/dev/null 2>&1 || die "$1 is required" +} + +is_true() { + case "${1:-}" in + 1|true|TRUE|yes|YES|y|Y) return 0 ;; + *) return 1 ;; + esac +} + +configure_bundle_type() { + case "$BUNDLE_TYPE" in + community) + GITHUB_REPO="k8s-operatorhub/community-operators" + OPERATOR_PATH="operators/percona-server-mongodb-operator" + ;; + certified) + GITHUB_REPO="redhat-openshift-ecosystem/certified-operators" + OPERATOR_PATH="operators/percona-server-mongodb-operator-certified" + ;; + *) + usage + die "unsupported bundle type: ${BUNDLE_TYPE:-empty}" + ;; + esac +} + +catalog_image() { + echo -n "${CATALOG_REPO}:${BUNDLE_TYPE}-${CATALOG_ENV}" +} + +current_bundle_image() { + echo -n "${BUNDLE_REPO}:${BUNDLE_IMAGE_VERSION}-${BUNDLE_TYPE}-bundle" +} + +previous_bundle_image() { + echo -n "${CATALOG_REPO}:$1-${BUNDLE_TYPE}-bundle-${CATALOG_ENV}" +} + +# CatalogSource name, e.g. certified-dev, certified-prod, community-dev. +catalog_source_name() { + echo -n "${BUNDLE_TYPE}-${CATALOG_ENV}" +} + +list_bundle_images() { + local prefix="$1" + local version + + for version in "${OLD_VERSIONS[@]}"; do + echo "${prefix}$(previous_bundle_image "$version")" + done + + echo "${prefix}$(current_bundle_image)" +} + +download_operatorhub() { + local archive="${TMP_DIR}/operatorhub.tar.gz" + + echo "[olm] Downloading ${GITHUB_REPO}" + + mkdir -p "$OPERATORHUB_DIR" + + curl -fsSL \ + "https://github.com/${GITHUB_REPO}/archive/refs/heads/main.tar.gz" \ + -o "$archive" + + tar -xzf "$archive" \ + -C "$OPERATORHUB_DIR" \ + --strip-components=1 +} + +find_previous_versions() { + local operator_dir="${OPERATORHUB_DIR}/${OPERATOR_PATH}" + + [[ -d "$operator_dir" ]] || + die "operator directory not found: ${operator_dir}" + + find "$operator_dir" \ + -mindepth 1 \ + -maxdepth 1 \ + -type d \ + -exec basename {} \; | + grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | + awk -v current="$BUNDLE_IMAGE_VERSION" ' + $0 != current && !seen[$0]++ { print } + ' | + sort -t. -k1,1n -k2,2n -k3,3n | + tail -n "$CATALOG_BUNDLE_LIMIT" +} + +confirm_build() { + local answer + local tty_source=/dev/stdin + + is_true "$CONFIRM_PUSH" || return 0 + + [[ -r /dev/tty ]] && tty_source=/dev/tty + + echo + echo "[olm] Images to build and push:" + list_bundle_images " - " + echo + + read -r -p "Build and push these images? [y/N] " answer <"$tty_source" + + case "$answer" in + y|Y|yes|YES) ;; + *) + echo "[olm] Build and push skipped" + exit 0 + ;; + esac +} + +write_bundle_dockerfile() { + local bundle_dir="$1" + local annotations="${bundle_dir}/metadata/annotations.yaml" + + [[ -f "$annotations" ]] || + die "bundle annotations not found: ${annotations}" + + [[ -d "${bundle_dir}/manifests" ]] || + die "bundle manifests not found: ${bundle_dir}/manifests" + + { + echo "FROM scratch" + + yq -o=json '.annotations' "$annotations" | + jq -r ' + to_entries[] | + "LABEL \(.key)=\(.value | tostring | @json)" + ' + + echo + echo "COPY manifests/ /manifests/" + echo "COPY metadata/ /metadata/" + } >"${bundle_dir}/Dockerfile" +} + +build_previous_bundle() { + local version="$1" + local bundle_dir="${OPERATORHUB_DIR}/${OPERATOR_PATH}/${version}" + local image + + image="$(previous_bundle_image "$version")" + + echo "[olm] Building and pushing bundle ${image}" + + write_bundle_dockerfile "$bundle_dir" + + "$CONTAINER" buildx build \ + --platform "$BUNDLE_PLATFORMS" \ + --tag "$image" \ + --push \ + "$bundle_dir" +} + +write_catalog_template() { + { + echo "Schema: olm.semver" + echo "GenerateMajorChannels: false" + echo "GenerateMinorChannels: false" + echo "Stable:" + echo " Bundles:" + list_bundle_images " - Image: " + } >"$CATALOG_TEMPLATE" +} + +build_catalog() { + local image + + image="$(catalog_image)" + + mkdir -p "$CATALOG_DIR" + write_catalog_template + + echo "[olm] Catalog bundles:" + list_bundle_images " - " + echo "[olm] Rendering ${BUNDLE_TYPE} catalog" + + opm alpha render-template semver \ + -o yaml \ + <"$CATALOG_TEMPLATE" \ + >"${CATALOG_DIR}/catalog.yaml" + + echo "[olm] Validating ${BUNDLE_TYPE} catalog" + opm validate "$CATALOG_DIR" + + echo "[olm] Generating catalog Dockerfile" + opm generate dockerfile "$CATALOG_DIR" + + echo "[olm] Building and pushing catalog ${image}" + + "$CONTAINER" buildx build \ + --platform "$CATALOG_PLATFORMS" \ + --file "${CATALOG_DIR}.Dockerfile" \ + --tag "$image" \ + --push \ + "$CATALOG_ROOT" + + echo "[olm] Catalog image pushed: ${image}" +} + +run_build() { + local version + + BUNDLE_IMAGE_VERSION="${BUNDLE_IMAGE_VERSION:-}" + [[ -n "$CATALOG_REPO" ]] || die "catalog repository is required" + [[ -n "$BUNDLE_REPO" ]] || die "bundle repository is required" + [[ -n "$BUNDLE_IMAGE_VERSION" ]] || die "BUNDLE_IMAGE_VERSION is required" + [[ "$CATALOG_BUNDLE_LIMIT" =~ ^[0-9]+$ ]] || + die "CATALOG_BUNDLE_LIMIT must be a non-negative integer" + + for command in curl tar jq yq opm "$CONTAINER"; do + require "$command" + done + + TMP_DIR="$(mktemp -d)" + OPERATORHUB_DIR="${TMP_DIR}/operatorhub" + CATALOG_ROOT="${TMP_DIR}/catalog-build" + CATALOG_DIR="${CATALOG_ROOT}/catalog" + CATALOG_TEMPLATE="${TMP_DIR}/catalog-template.yaml" + + trap 'rm -rf "$TMP_DIR"' EXIT + + download_operatorhub + + OLD_VERSIONS=() + + while IFS= read -r version; do + [[ -n "$version" ]] && OLD_VERSIONS+=("$version") + done < <(find_previous_versions) + + [[ "${#OLD_VERSIONS[@]}" -eq "$CATALOG_BUNDLE_LIMIT" ]] || + die "expected ${CATALOG_BUNDLE_LIMIT} previous versions, found ${#OLD_VERSIONS[@]}" + + confirm_build + + for version in "${OLD_VERSIONS[@]}"; do + build_previous_bundle "$version" + done + + build_catalog +} + +run_apply() { + local source_name + local image + + require kubectl + [[ -n "$CATALOG_REPO" ]] || die "catalog repository is required" + + source_name="$(catalog_source_name)" + image="$(catalog_image)" + + echo "[olm] Applying CatalogSource ${source_name}" + echo "[olm] Namespace: ${CATALOG_NAMESPACE}" + echo "[olm] Image: ${image}" + + kubectl apply -f - <" + if [[ "${key}" == "IMAGE_CLUSTERSYNC" ]]; then + abort "unable to resolve digest for required clustersync tag ${redhat_registry}/${repository}:${tag}" fi + + digest="" + redhat_missing_digests+=("${name}:${redhat_registry}/${repository}:${tag}") fi if [[ "${digest}" != "" ]]; then digest="sha256:${digest#sha256:}" fi - printf '%s/%s@%s\n' "${redhat_registry}" "${repository}" "${digest}" + echo -n "${redhat_registry}/${repository}@${digest}" } validate_certified_tag() { @@ -134,7 +138,7 @@ add_related_image() { validate_certified_tag "${key}" "${tag}" "${source_tag}" - image="$(image_ref "${key}" "${name}" "${repository}" "${tag}")" + image="$(set_image_ref "${key}" "${name}" "${repository}" "${tag}")" log "Related image ${name}: ${image}" @@ -160,7 +164,7 @@ require_release_image() { local key="$1" if [[ -z "${!key:-}" ]]; then - abort "${key} is required in e2e-tests/release_versions" + abort "${key} is required in ${release_versions_file}" fi } @@ -176,19 +180,18 @@ report_missing_digests() { } build_redhat_related_images() { - local release_versions="${repo_root}/e2e-tests/release_versions" local mongod60_tag local mongod70_tag local mongod80_tag local logcollector_tag - log "Building Red Hat related images from ${release_versions}" + log "Building Red Hat related images from ${release_versions_file}" - [[ -f "${release_versions}" ]] \ - || abort "release versions file not found: ${release_versions}" + [[ -f "${release_versions_file}" ]] \ + || abort "release versions file not found: ${release_versions_file}" # shellcheck source=/dev/null - source "${release_versions}" + source "${release_versions_file}" for key in \ IMAGE_MONGOD60 \ @@ -197,8 +200,7 @@ build_redhat_related_images() { IMAGE_BACKUP \ IMAGE_PMM_CLIENT \ IMAGE_PMM3_CLIENT \ - IMAGE_LOGCOLLECTOR \ - IMAGE_CLUSTERSYNC; do + IMAGE_LOGCOLLECTOR; do require_release_image "${key}" done @@ -240,7 +242,7 @@ build_redhat_skips() { | jq -Rsc \ --arg min_version "${min_version}" \ --arg current_version "${current_version}" \ - --arg package_name "percona-server-mongodb-operator-certified" \ + --arg package_name "${component_name}-certified" \ ' def version_parts: ltrimstr("v") @@ -266,3 +268,54 @@ build_redhat_skips() { | map($package_name + "." + .tag) ' } + +distribution_package_name() { + echo -n "${component_name}-certified" +} + +customize_csv() { + yq -P eval --inplace ' + .spec.relatedImages = (strenv(relatedImages) | from_json) | + .spec.skips = (strenv(skips) | from_json) | + .metadata.annotations.certified = "true" | + .metadata.annotations["features.operators.openshift.io/disconnected"] = "true" | + .metadata.name = strenv(name_certified) + ' "$1" +} + +build_distribution_data() { + local redhat_data + local images + local related_images + local skips + + redhat_data="$(build_redhat_related_images)" || return + + jq -e \ + '.operatorImage and (.relatedImages | type == "array")' \ + >/dev/null \ + <<<"${redhat_data}" \ + || abort "Invalid Red Hat image data" + + images="$( + jq -c ' + .operatorImage as $operator + | reduce .relatedImages[] as $item + ({}; .[$item.name] = $item.image) + | .operator = $operator + ' <<<"${redhat_data}" + )" + + related_images="$(jq -c '.relatedImages' <<<"${redhat_data}")" + skips="$(build_redhat_skips)" + + jq -nc \ + --argjson images "${images}" \ + --argjson related_images "${related_images}" \ + --argjson skips "${skips}" \ + '{ + images: $images, + relatedImages: $related_images, + skips: $skips + }' +} \ No newline at end of file diff --git a/installers/olm/generate.sh b/installers/olm/generate.sh index b0f3abbfb8..ef39732dc2 100755 --- a/installers/olm/generate.sh +++ b/installers/olm/generate.sh @@ -1,11 +1,13 @@ #!/usr/bin/env bash - set -euo pipefail +shopt -s inherit_errexit 2>/dev/null || true DISTRIBUTION="${1:?Distribution argument required (community|redhat)}" cd "${BASH_SOURCE[0]%/*}" +sed=$(command -v gsed || command -v sed) +date=$(command -v gdate || command -v date) repo_root="$(cd ../.. && pwd)" release_versions_file="${repo_root}/e2e-tests/release_versions" bundle_name="${BUNDLE_NAME:-${DISTRIBUTION}}" @@ -13,78 +15,64 @@ bundle_directory="bundles/${bundle_name}" project_directory="projects/${bundle_name}" go_api_directory="$(cd ../../pkg/apis && pwd)" -package_name="percona-server-mongodb-operator" -project_name="percona-server-mongodb-operator" -file_name="percona-server-mongodb-operator" +# Used both as the operator-sdk project name and as the CSV file stem. +component_name="percona-server-mongodb-operator" NS_RESOURCE_RBAC="../rbac/namespace" NS_RESOURCE_OPERATOR="../manager/namespace" KUSTOMIZATION_FILE="../../config/bundle/kustomization.yaml" -rulesLevel="permissions" relatedImages="[]" skips="[]" containerImage="" csv_stem="" -redhat_distribution_images="{}" +distribution_data="{}" log() { echo >&2 "[olm] $*" } +abort() { + echo >&2 "[olm] ERROR: $*" + exit 1 +} + debug() { if [[ "${OLM_VERBOSE:-0}" == "1" || "${OLM_VERBOSE:-false}" == "true" ]]; then log "$@" fi } -abort() { - echo >&2 "[olm] ERROR: $*" - exit 1 -} - run_quiet() { local description="$1" shift - if [[ "${OLM_VERBOSE:-0}" == "1" || "${OLM_VERBOSE:-false}" == "true" ]]; then - "$@" - return + local output_path="" + if [[ "$1" == "-o" ]]; then + output_path="$2" + shift 2 fi - local output_file - output_file="$(mktemp)" - - if "$@" >"${output_file}" 2>&1; then - rm -f "${output_file}" - return - fi - - cat "${output_file}" >&2 - rm -f "${output_file}" - abort "${description} failed" -} - -run_quiet_output() { - local output_path="$1" - local description="$2" - shift 2 - if [[ "${OLM_VERBOSE:-0}" == "1" || "${OLM_VERBOSE:-false}" == "true" ]]; then - "$@" >"${output_path}" + if [[ -n "${output_path}" ]]; then + "$@" >"${output_path}" + else + "$@" + fi return fi - local error_file - error_file="$(mktemp)" + local capture_file + capture_file="$(mktemp)" - if "$@" >"${output_path}" 2>"${error_file}"; then - rm -f "${error_file}" - return + if [[ -n "${output_path}" ]]; then + "$@" >"${output_path}" 2>"${capture_file}" && { rm -f "${capture_file}"; return; } + else + "$@" >"${capture_file}" 2>&1 && { rm -f "${capture_file}"; return; } fi - cat "${error_file}" >&2 - rm -f "${error_file}" + cat "${capture_file}" >&2 + rm -f "${capture_file}" abort "${description} failed" } @@ -104,7 +92,7 @@ sed_in_place() { local tmp_file tmp_file="$(mktemp)" - sed "$expression" "$file" >"$tmp_file" + "$sed" "$expression" "$file" >"$tmp_file" mv "$tmp_file" "$file" } @@ -119,26 +107,16 @@ check_tools() { release_version_value() { local key="$1" - awk -F= -v key="${key}" '$1 == key { print $2 }' "${release_versions_file}" \ + local version + version="$(awk -F= -v key="${key}" '$1 == key { print $2 }' "${release_versions_file}" \ | tr -d '"' \ - | tail -1 -} + | tail -1)" -release_image_ref() { - local key="$1" - local image - - image="$(release_version_value "${key}")" - [[ -n "${image}" ]] || abort "${key} is required in ${release_versions_file}" - - case "${image}" in - *.*/*|*:*/*|localhost/*) - printf '%s' "${image}" - ;; - *) - printf 'docker.io/%s' "${image}" - ;; - esac + if [[ -z "${version}" ]]; then + abort "Missing ${key} in ${release_versions_file}" + fi + + echo -n "${version}" } resolve_openshift_versions() { @@ -146,7 +124,7 @@ resolve_openshift_versions() { local openshift_max if [[ -n "${OPENSHIFT_VERSIONS:-}" ]]; then - printf '%s' "${OPENSHIFT_VERSIONS}" + echo -n "${OPENSHIFT_VERSIONS}" return fi @@ -159,26 +137,23 @@ resolve_openshift_versions() { [[ -n "${openshift_min}" && -n "${openshift_max}" ]] \ || abort "OPENSHIFT_MIN and OPENSHIFT_MAX must be set in ${release_versions_file}" - printf '%s-%s' "${openshift_min}" "${openshift_max}" + echo -n "${openshift_min}-${openshift_max}" } load_distribution_hooks() { - local hook_file="" - - case "${DISTRIBUTION}" in - redhat) - hook_file="distributions/redhat.sh" - ;; - community) - hook_file="distributions/community.sh" - ;; - esac - - if [[ -n "${hook_file}" && -f "${hook_file}" ]]; then - log "Loading distribution hooks from ${hook_file}" - # shellcheck source=/dev/null - source "${hook_file}" - fi + local hook_file="distributions/${DISTRIBUTION}.sh" + + [[ "${DISTRIBUTION}" == "community" || "${DISTRIBUTION}" == "redhat" ]] \ + || abort "Unknown distribution: ${DISTRIBUTION}" + [[ -f "${hook_file}" ]] || abort "Distribution hooks not found: ${hook_file}" + + log "Loading distribution hooks from ${hook_file}" + # shellcheck source=/dev/null + source "${hook_file}" + + for hook in build_distribution_data distribution_package_name customize_csv; do + declare -F "${hook}" >/dev/null || abort "Missing distribution hook: ${hook}" + done } configure_namespace_manifests() { @@ -200,7 +175,7 @@ prepare_operator_sources() { render_operator_manifests() { log "Rendering operator manifests for ${DISTRIBUTION}" - run_quiet_output operator_yamls.yaml "Rendering operator manifests" \ + run_quiet "Rendering operator manifests" -o operator_yamls.yaml \ kubectl kustomize "../../config/${DISTRIBUTION}" yq eval '. | select(.kind == "CustomResourceDefinition")' operator_yamls.yaml >operator_crds.yaml @@ -218,7 +193,7 @@ create_sdk_workspace() { ( cd "${project_directory}" run_quiet "Creating Operator SDK workspace" \ - operator-sdk init --fetch-deps="false" --project-name="${project_name}" + operator-sdk init --fetch-deps="false" --project-name="${component_name}" yq eval '[. | {"group": .spec.group, "kind": .spec.names.kind, "version": .spec.versions[].name}]' \ ../../../../deploy/crd.yaml >crd_gvks.yaml @@ -243,33 +218,20 @@ create_bundle_directory() { render_bundle_metadata() { log "Rendering bundle metadata" - export package="${PACKAGE_NAME_OVERRIDE:-${package_name}}" + export package="${PACKAGE_NAME_OVERRIDE:-$(distribution_package_name)}" export package_channel="${PACKAGE_CHANNEL:-stable}" export openshift_supported_versions openshift_supported_versions="$(resolve_openshift_versions)" - if [[ "${DISTRIBUTION}" == "redhat" ]]; then - export package="${PACKAGE_NAME_OVERRIDE:-${package_name}-certified}" - fi - - yq eval '.annotations["operators.operatorframework.io.bundle.channels.v1"] = env(package_channel) | - .annotations["operators.operatorframework.io.bundle.channel.default.v1"] = env(package_channel) | - .annotations["operators.operatorframework.io.bundle.package.v1"] = env(package) | - .annotations["com.redhat.openshift.versions"] = env(openshift_supported_versions)' \ - bundle.annotations.yaml >"${bundle_directory}/metadata/annotations.yaml" - - case "${DISTRIBUTION}" in - community) - yq eval --inplace ' - .annotations["operators.operatorframework.io.bundle.package.v1"] = env(package) | - .annotations["org.opencontainers.image.authors"] = "info@percona.com" | - .annotations["org.opencontainers.image.url"] = "https://percona.com" | - .annotations["org.opencontainers.image.vendor"] = "Percona"' \ - "${bundle_directory}/metadata/annotations.yaml" - ;; - redhat) - ;; - esac + yq eval ' + .annotations["operators.operatorframework.io.bundle.channels.v1"] = env(package_channel) | + .annotations["operators.operatorframework.io.bundle.channel.default.v1"] = env(package_channel) | + .annotations["operators.operatorframework.io.bundle.package.v1"] = env(package) | + .annotations["com.redhat.openshift.versions"] = env(openshift_supported_versions) | + .annotations["org.opencontainers.image.authors"] = "info@percona.com" | + .annotations["org.opencontainers.image.url"] = "https://percona.com" | + .annotations["org.opencontainers.image.vendor"] = "Percona" + ' bundle.annotations.yaml >"${bundle_directory}/metadata/annotations.yaml" } render_bundle_dockerfile() { @@ -316,6 +278,7 @@ BEGIN { ' ../../deploy/crd.yaml find "${bundle_directory}/manifests" -type f -name "*.crd.yaml" -print0 | while IFS= read -r -d '' file; do + # shellcheck disable=SC2016 sed_in_place '1s/^/---\ /; ${/^---$/d;}' "$file" done @@ -339,38 +302,15 @@ build_examples() { local cr_example local backup_example local clustersync_example - local image_backup - local image_logcollector - local image_mongod - local image_operator - local image_pmm - local image_clustersync + local images local restore_example - image_backup="$(release_image_ref "IMAGE_BACKUP")" - image_logcollector="$(release_image_ref "IMAGE_LOGCOLLECTOR")" - image_mongod="$(release_image_ref "IMAGE_MONGOD80")" - image_operator="$(release_image_ref "IMAGE_OPERATOR")" - image_pmm="$(release_image_ref "IMAGE_PMM3_CLIENT")" - image_clustersync="$(release_image_ref "IMAGE_CLUSTERSYNC")" - - if [[ "${DISTRIBUTION}" == "redhat" ]]; then - image_backup="$(jq -r '.relatedImages[] | select(.name == "backup").image' <<<"${redhat_distribution_images}")" - image_logcollector="$(jq -r '.relatedImages[] | select(.name == "logcollector").image' <<<"${redhat_distribution_images}")" - image_mongod="$(jq -r '.relatedImages[] | select(.name == "mongod8.0").image' <<<"${redhat_distribution_images}")" - image_operator="$(jq -r '.operatorImage' <<<"${redhat_distribution_images}")" - image_pmm="$(jq -r '.relatedImages[] | select(.name == "pmm3").image' <<<"${redhat_distribution_images}")" - image_clustersync="$(jq -r '.relatedImages[] | select(.name == "clustersync").image' <<<"${redhat_distribution_images}")" - fi + images="$(jq -c '.images' <<<"${distribution_data}")" cr_example="$( yq eval -o=json ../../deploy/cr.yaml | jq \ - --arg imageBackup "${image_backup}" \ - --arg imageLogcollector "${image_logcollector}" \ - --arg imageMongod "${image_mongod}" \ - --arg imageOperator "${image_operator}" \ - --arg imagePmm "${image_pmm}" \ + --argjson images "${images}" \ ' def insert_after($k; $new): to_entries as $e @@ -381,33 +321,36 @@ build_examples() { .spec |= ( if has("initImage") then del(.initImage) else . end - | .image = $imageMongod - | insert_after("image"; {"initImage": $imageOperator}) - | if has("initImage") then . else . + {"initImage": $imageOperator} end - | .pmm.image = $imagePmm - | .backup.image = $imageBackup - | .logcollector.image = $imageLogcollector + | .image = $images["mongod8.0"] + | insert_after("image"; {"initImage": $images.operator}) + | if has("initImage") then . else . + {"initImage": $images.operator} end + | .pmm.image = $images.pmm3 + | .backup.image = $images.backup + | .logcollector.image = $images.logcollector ) ' )" - clustersync_example="$( - yq eval -o=json ../../deploy/clustersync.yaml | - jq -s \ - --arg imageClustersync "${image_clustersync}" \ - ' - map( - select(.kind == "PerconaServerMongoDBClusterSync") - | .spec.image = $imageClustersync - ) - | first - ' - )" + clustersync_example="null" + if jq -e '.clustersync? | strings | length > 0' >/dev/null <<<"${images}"; then + clustersync_example="$( + yq eval -o=json ../../deploy/clustersync.yaml | + jq -s \ + --argjson images "${images}" \ + ' + map( + select(.kind == "PerconaServerMongoDBClusterSync") + | .spec.image = $images.clustersync + ) + | first + ' + )" + fi backup_example="$(yq eval -o=json ../../deploy/backup/backup.yaml)" restore_example="$(yq eval -o=json ../../deploy/backup/restore.yaml)" - jq -n "[${cr_example}, ${backup_example}, ${restore_example}, ${clustersync_example}]" + jq -n "[${cr_example}, ${backup_example}, ${restore_example}, ${clustersync_example}] | map(select(. != null))" } build_managed_resources() { @@ -486,28 +429,18 @@ build_owned_crds() { ' } -update_yaml_images() { - local yaml_file="$1" - local temp_file - - [[ -f "$yaml_file" ]] \ - || abort "file '$yaml_file' does not exist" +prepare_distribution() { + distribution_data="$(build_distribution_data)" || exit $? - temp_file="$(mktemp)" - sed -E 's/(("image":|"initImage":|containerImage:|image:|initImage:)[ ]*"?)([^"]+)("?)/\1docker.io\/\3\4/g' "$yaml_file" >"$temp_file" - mv "$temp_file" "$yaml_file" -} - -prepare_distribution_images() { - if [[ "${DISTRIBUTION}" != "redhat" ]]; then - containerImage="${IMAGE}" - return - fi + jq -e ' + (.images | type == "object") and + ((["operator", "backup", "logcollector", "mongod8.0", "pmm3"] - (.images | keys)) | length == 0) + ' >/dev/null <<<"${distribution_data}" \ + || abort "Distribution data is missing required images" - redhat_distribution_images="$(build_redhat_related_images)" - containerImage="$(jq -r '.operatorImage' <<<"${redhat_distribution_images}")" - relatedImages="$(jq -r '.relatedImages' <<<"${redhat_distribution_images}")" - skips="$(build_redhat_skips)" + containerImage="$(jq -er '.images.operator' <<<"${distribution_data}")" + relatedImages="$(jq -c '.relatedImages // []' <<<"${distribution_data}")" + skips="$(jq -c '.skips // []' <<<"${distribution_data}")" } render_csv() { @@ -540,8 +473,8 @@ render_csv() { account="$(yq eval '.[] | .metadata.name' operator_accounts.yaml)" rules="$(yq eval '.[] | .rules' operator_roles.yaml)" version="${CSV_VERSION:-${VERSION}}" - timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" - csv_file="${bundle_directory}/manifests/${file_name}.clusterserviceversion.yaml" + timestamp="$("$date" -u +"%Y-%m-%dT%H:%M:%SZ")" + csv_file="${bundle_directory}/manifests/${component_name}.clusterserviceversion.yaml" icon_base64="$(base64 <"${repo_root}/kubernetes.svg" | tr -d '\n')" export examples @@ -554,12 +487,10 @@ render_csv() { export timestamp export name="${CSV_NAME_OVERRIDE:-${csv_stem}.v${version}}" export name_certified="${CSV_NAME_OVERRIDE:-${csv_stem}-certified.v${version}}" - export display_name_override="${DISPLAY_NAME_OVERRIDE:-}" export skip_range="<${version}" export containerImage export relatedImages export skips - export rulesLevel export icon_base64 yq -P eval ' @@ -570,31 +501,11 @@ render_csv() { .spec.version = env(version) | .spec.icon = [{ "base64data": strenv(icon_base64), "mediatype": "image/svg+xml" }] | .spec.customresourcedefinitions.owned = (strenv(owned_crds) | from_json) | - .spec.install.spec[strenv(rulesLevel)] = [{ "serviceAccountName": env(account), "rules": env(rules) }] | + .spec.install.spec.permissions = [{ "serviceAccountName": env(account), "rules": env(rules) }] | .spec.install.spec.deployments = (env(deployment) | [.[] | { "name": .metadata.name, "spec": .spec }])' \ bundle.csv.yaml >"${csv_file}" - if [[ -n "${display_name_override}" ]]; then - yq eval --inplace '.spec.displayName = strenv(display_name_override)' "${csv_file}" - fi - - case "${DISTRIBUTION}" in - community) - yq -P eval --inplace ' - .metadata.annotations["olm.skipRange"] = env(skip_range)' \ - "${csv_file}" - ;; - redhat) - yq -P eval --inplace ' - .spec.relatedImages = (strenv(relatedImages) | from_json) | - .spec.skips = (strenv(skips) | from_json) | - .metadata.annotations.certified = "true" | - .metadata.annotations["features.operators.openshift.io/disconnected"] = "true" | - .metadata.name = strenv(name_certified)' \ - \ - "${csv_file}" - ;; - esac + customize_csv "${csv_file}" } validate_bundle() { @@ -622,7 +533,7 @@ main() { render_bundle_dockerfile write_crd_manifests validate_manifest_inputs - prepare_distribution_images + prepare_distribution render_csv normalize_bundle_permissions validate_bundle diff --git a/installers/olm/validate-image.sh b/installers/olm/validate-image.sh index 607c6b7b50..5c57e5f8df 100755 --- a/installers/olm/validate-image.sh +++ b/installers/olm/validate-image.sh @@ -1,35 +1,58 @@ #!/usr/bin/env bash -set -eu +set -euo pipefail push_trap_exit() { local -a array + eval "array=($(trap -p EXIT))" + # shellcheck disable=SC2064 trap "$1;${array[2]-}" EXIT } -TMPDIR=$(mktemp -d) +TMPDIR="$(mktemp -d)" push_trap_exit "rm -rf '${TMPDIR}'" export TMPDIR validate_bundle_image() { - local container="$1" directory="$2" - directory=$(cd "${directory}" && pwd) + local container="$1" + local directory="$2" + local image + local port + local registry + + directory="$(cd "${directory}" && pwd)" + + export DOCKER_DEFAULT_PLATFORM="${DOCKER_DEFAULT_PLATFORM:-linux/amd64}" - local image port registry - registry=$(${container} run --detach --publish-all docker.io/library/registry:latest) - push_trap_exit "echo -n 'Removing '; ${container} rm '${registry}'" - push_trap_exit "echo -n 'Stopping '; ${container} stop '${registry}'" + registry="$( + "${container}" run \ + --detach \ + --publish-all \ + docker.io/library/registry:latest + )" + + push_trap_exit "echo -n 'Removing '; '${container}' rm '${registry}'" + push_trap_exit "echo -n 'Stopping '; '${container}' stop '${registry}'" + + port="$( + "${container}" inspect "${registry}" \ + --format='{{ (index .NetworkSettings.Ports "5000/tcp" 0).HostPort }}' + )" - port=$(${container} inspect "${registry}" \ - --format='{{ (index .NetworkSettings.Ports "5000/tcp" 0).HostPort }}') image="localhost:${port}/psmdb-operator-bundle:latest" - ${container} build --platform="${DOCKER_DEFAULT_PLATFORM:-linux/amd64}" -t "${image}" "${directory}" - ${container} push "${image}" + "${container}" build \ + --platform="${DOCKER_DEFAULT_PLATFORM}" \ + --tag "${image}" \ + "${directory}" + + "${container}" push "${image}" - opm alpha bundle validate --use-http --image-builder="${container}" \ - --optional-validators='operatorhub,bundle-objects' \ + opm alpha bundle validate \ + --use-http \ + --image-builder="${container}" \ + --optional-validators="operatorhub,bundle-objects" \ --tag="${image}" } From a70b2214828229401d0752067c40696d82580f45 Mon Sep 17 00:00:00 2001 From: valmiranogueira Date: Wed, 29 Jul 2026 10:53:32 -0300 Subject: [PATCH 5/5] Fix comments --- installers/olm/Makefile | 139 +++++++++++++---------- installers/olm/README.md | 146 ++++++++++++++++++------- installers/olm/build-bundle.sh | 98 +++++++++++------ installers/olm/build-catalog.sh | 30 +++-- installers/olm/distributions/redhat.sh | 12 +- installers/olm/generate.sh | 4 +- installers/olm/validate-image.sh | 22 +++- 7 files changed, 304 insertions(+), 147 deletions(-) diff --git a/installers/olm/Makefile b/installers/olm/Makefile index eabf2a5d9d..2d9c9feca2 100644 --- a/installers/olm/Makefile +++ b/installers/olm/Makefile @@ -17,25 +17,19 @@ VERSION ?= $(shell git rev-parse --abbrev-ref HEAD | $(SED) -e 's^/^-^g; s^[.]^- CSV_VERSION ?= $(shell printf '%s\n' '$(VERSION)' | $(SED) -nE 's/.*([0-9]+)[.-]([0-9]+)[.-]([0-9]+).*/\1.\2.\3/p') RELEASE_VERSIONS ?= ../../e2e-tests/release_versions -PACKAGE_CHANNEL ?= stable -OPENSHIFT_MIN_VERSION := $(shell awk -F= '$$1 == "OPENSHIFT_MIN" { print $$2 }' '$(RELEASE_VERSIONS)' | cut -d. -f1,2) -OPENSHIFT_MAX_VERSION := $(shell awk -F= '$$1 == "OPENSHIFT_MAX" { print $$2 }' '$(RELEASE_VERSIONS)' | cut -d. -f1,2) -OPENSHIFT_VERSIONS ?= v$(OPENSHIFT_MIN_VERSION)-v$(OPENSHIFT_MAX_VERSION) - -DEV_REPOSITORY_BASE ?= $(REGISTRY)/$(DEV_REPOSITORY) -PROD_REPOSITORY_BASE ?= $(REGISTRY)/$(PROD_REPOSITORY) +CONFIRM_PUSH ?= 1 BUNDLE_TYPE ?= community BUNDLE_IMAGE_VERSION ?= $(CSV_VERSION) -BUNDLE_PLATFORMS ?= linux/amd64,linux/arm64 -BUNDLE_BUILD_PUSH ?= 1 -SKIP_DIGEST_FAILURE ?= 0 -DEV_BUNDLE_REPO ?= $(DEV_REPOSITORY_BASE)/$(NAME) -PROD_BUNDLE_REPO ?= $(PROD_REPOSITORY_BASE)/$(NAME) +BUNDLE_PLATFORM ?= linux/amd64 +BUNDLE_PACKAGE_CHANNEL ?= stable +BUNDLE_SKIP_DIGEST_FAILURE ?= 0 +BUNDLE_DEV_REPO ?= $(REGISTRY)/$(DEV_REPOSITORY)/$(NAME) +BUNDLE_PROD_REPO ?= $(REGISTRY)/$(PROD_REPOSITORY)/$(NAME) CATALOG_BUNDLE_LIMIT ?= 2 +CATALOG_PLATFORM ?= linux/amd64 CATALOG_NAMESPACE ?= openshift-marketplace -CATALOG_PLATFORMS ?= linux/amd64,linux/arm64 CATALOG_BUILD_PUSH ?= 1 REPO_ROOT := $(shell git rev-parse --show-toplevel) @@ -62,18 +56,9 @@ RESET := $(shell tput sgr0) bundle_distribution = $(if $(filter certified,$(1)),redhat,$(1)) -define catalog_build_env -BUNDLE_IMAGE_VERSION='$(BUNDLE_IMAGE_VERSION)' \ -BUNDLE_PLATFORMS='$(BUNDLE_PLATFORMS)' \ -CATALOG_BUNDLE_LIMIT='$(CATALOG_BUNDLE_LIMIT)' \ -CATALOG_PLATFORMS='$(CATALOG_PLATFORMS)' \ -CONTAINER='$(CONTAINER)' \ -CONFIRM_BUILD_PUSH='$(CONFIRM_BUILD_PUSH)' -endef - -export VERSION CSV_VERSION BUNDLE_IMAGE_VERSION OPENSHIFT_VERSIONS PACKAGE_CHANNEL +export VERSION CSV_VERSION BUNDLE_IMAGE_VERSION BUNDLE_PACKAGE_CHANNEL export PACKAGE_NAME_OVERRIDE CSV_NAME_OVERRIDE -export BUNDLE_PLATFORMS BUNDLE_BUILD_PUSH CONFIRM_BUILD_PUSH SKIP_DIGEST_FAILURE +export BUNDLE_PLATFORM CONFIRM_PUSH BUNDLE_SKIP_DIGEST_FAILURE # ============================================================================== # Validation and Tool Helpers @@ -99,7 +84,7 @@ endif .PHONY: check-tools check-tools: - @for cmd in gawk gcsplit yq yamllint envsubst; do \ + @for cmd in curl gawk gcsplit yq yamllint envsubst '$(CONTAINER)'; do \ if ! command -v $$cmd >/dev/null 2>&1; then \ echo "Error: $$cmd is required but not installed"; \ exit 1; \ @@ -116,15 +101,18 @@ $(TOOLS_DIR): mkdir -p $(TOOLS_DIR) $(TOOLS_DIR)/jq: - curl -fsSL "https://github.com/jqlang/jq/releases/download/jq-$(JQ_VERSION)/jq-$(JQ_PLATFORM)-$(OS_MACHINE)" -o $@ + curl -fsSL "https://github.com/jqlang/jq/releases/download/jq-$(JQ_VERSION)/jq-$(JQ_PLATFORM)-$(OS_MACHINE)" -o $@.tmp + mv $@.tmp $@ chmod +x $@ $(TOOLS_DIR)/operator-sdk: | $(TOOLS_DIR) - curl -fsSL "https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS_KERNEL)_$(OS_MACHINE)" -o $@ + curl -fsSL "https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS_KERNEL)_$(OS_MACHINE)" -o $@.tmp + mv $@.tmp $@ chmod +x $@ $(TOOLS_DIR)/opm: | $(TOOLS_DIR) - curl -fsSL "https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$(OS_KERNEL)-$(OS_MACHINE)-opm" -o $@ + curl -fsSL "https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$(OS_KERNEL)-$(OS_MACHINE)-opm" -o $@.tmp + mv $@.tmp $@ chmod +x $@ # ============================================================================== @@ -155,9 +143,9 @@ generate-bundle: check-bundle-type tools $(KUSTOMIZE) check-version check-csv-ve # Development Rules # ============================================================================== -## Build all development OLM bundles and push bundle images to DEV_REPOSITORY +## Generate all development OLM bundles .PHONY: bundles bundle $(BUNDLE_TYPES:%=bundle/%) -bundles: ## Build all development OLM bundles and push bundle images +bundles: ## Generate all development OLM bundles bundles: bundle bundle: $(BUNDLE_TYPES:%=bundle/%) @@ -174,8 +162,6 @@ $(BUNDLE_TYPES:%=bundle/%): bundle/%: check-version check-csv-version BUNDLE_TYPE='$*' \ PACKAGE_NAME_OVERRIDE="$${package}" \ CSV_NAME_OVERRIDE="$${csv_name}" - $(MAKE) build \ - BUNDLE_TYPE='$*' ## Build and push all development catalog images to DEV_REPOSITORY .PHONY: catalog-build-push $(BUNDLE_TYPES:%=catalog-build-push/%) @@ -186,13 +172,16 @@ catalog-build-push/community: catalog-build-push/certified: $(BUNDLE_TYPES:%=catalog-build-push/%): catalog-build-push/%: tools check-version check-csv-version - $(MAKE) bundle/$* BUNDLE_BUILD_PUSH=1 @if [[ "$(CATALOG_BUILD_PUSH)" == "true" || "$(CATALOG_BUILD_PUSH)" == "1" ]]; then \ - $(catalog_build_env) \ + BUNDLE_IMAGE_VERSION='$(BUNDLE_IMAGE_VERSION)' \ + CATALOG_BUNDLE_LIMIT='$(CATALOG_BUNDLE_LIMIT)' \ + CATALOG_PLATFORM='$(CATALOG_PLATFORM)' \ + CONTAINER='$(CONTAINER)' \ + CONFIRM_PUSH='$(CONFIRM_PUSH)' \ ./build-catalog.sh build \ '$*' \ - '$(DEV_BUNDLE_REPO)' \ - '$(DEV_BUNDLE_REPO)'; \ + '$(BUNDLE_DEV_REPO)' \ + '$(BUNDLE_DEV_REPO)'; \ else \ echo "[olm] Skipping catalog build and push"; \ fi @@ -205,10 +194,14 @@ deploy: $(BUNDLE_TYPES:%=deploy/%) deploy/community: deploy/certified: -$(BUNDLE_TYPES:%=deploy/%): deploy/%: catalog-build-push/% +$(BUNDLE_TYPES:%=deploy/%): deploy/%: tools check-version check-csv-version + $(MAKE) bundle/$* + $(MAKE) build/$* + $(MAKE) push/$* + $(MAKE) catalog-build-push/$* CATALOG_NAMESPACE='$(CATALOG_NAMESPACE)' \ NAME='$(NAME)' \ - bash ./build-catalog.sh apply '$*' '$(DEV_BUNDLE_REPO)' + bash ./build-catalog.sh apply '$*' '$(BUNDLE_DEV_REPO)' # ============================================================================== # Production Rules @@ -235,40 +228,72 @@ deploy-prod/certified: $(BUNDLE_TYPES:%=deploy-prod/%): deploy-prod/%: tools check-version check-csv-version $(MAKE) bundle-prod/$* - $(MAKE) build-prod BUNDLE_TYPE='$*' BUNDLE_BUILD_PUSH=1 + $(MAKE) build-prod BUNDLE_TYPE='$*' + $(MAKE) push-prod BUNDLE_TYPE='$*' @if [[ "$(CATALOG_BUILD_PUSH)" == "true" || "$(CATALOG_BUILD_PUSH)" == "1" ]]; then \ - $(catalog_build_env) \ + BUNDLE_IMAGE_VERSION='$(BUNDLE_IMAGE_VERSION)' \ + CATALOG_BUNDLE_LIMIT='$(CATALOG_BUNDLE_LIMIT)' \ + CATALOG_PLATFORM='$(CATALOG_PLATFORM)' \ + CONTAINER='$(CONTAINER)' \ + CONFIRM_PUSH='$(CONFIRM_PUSH)' \ bash ./build-catalog.sh build \ '$*' \ - '$(DEV_BUNDLE_REPO)' \ - '$(PROD_BUNDLE_REPO)'; \ + '$(BUNDLE_DEV_REPO)' \ + '$(BUNDLE_PROD_REPO)'; \ else \ echo "[olm] Skipping catalog build and push"; \ fi CATALOG_NAMESPACE='$(CATALOG_NAMESPACE)' \ NAME='$(NAME)' \ - bash ./build-catalog.sh apply '$*' '$(DEV_BUNDLE_REPO)' + bash ./build-catalog.sh apply '$*' '$(BUNDLE_DEV_REPO)' # ============================================================================== # Image Rules # ============================================================================== -.PHONY: build -build: ## Build and optionally push bundle image (set BUNDLE_TYPE=community|certified) -build: build-dev +.PHONY: build build-prod $(BUNDLE_TYPES:%=build/%) $(BUNDLE_TYPES:%=build-prod/%) +build: ## Build bundle image locally (set BUNDLE_TYPE=community|certified) +build-prod: ## Build production bundle image locally (set BUNDLE_TYPE=community|certified) + +$(BUNDLE_TYPES:%=build/%): build/%: + $(MAKE) build BUNDLE_TYPE='$*' + +$(BUNDLE_TYPES:%=build-prod/%): build-prod/%: + $(MAKE) build-prod BUNDLE_TYPE='$*' + +build: BUNDLE_REPO = $(BUNDLE_DEV_REPO) +build-prod: BUNDLE_REPO = $(BUNDLE_PROD_REPO) +build build-prod: check-bundle-type check-version check-csv-version + BUNDLE_PLATFORM='$(BUNDLE_PLATFORM)' \ + ./build-bundle.sh \ + build \ + '$(CONTAINER)' \ + 'bundles/$(call bundle_distribution,$(BUNDLE_TYPE))' \ + '$(call bundle_distribution,$(BUNDLE_TYPE))' \ + '$(BUNDLE_IMAGE_VERSION)' \ + '$(BUNDLE_REPO)' + + +.PHONY: push push-prod $(BUNDLE_TYPES:%=push/%) $(BUNDLE_TYPES:%=push-prod/%) +push: ## Push existing bundle image (set BUNDLE_TYPE=community|certified) +push-prod: ## Push existing production bundle image (set BUNDLE_TYPE=community|certified) + +$(BUNDLE_TYPES:%=push/%): push/%: + $(MAKE) push BUNDLE_TYPE='$*' + +$(BUNDLE_TYPES:%=push-prod/%): push-prod/%: + $(MAKE) push-prod BUNDLE_TYPE='$*' -.PHONY: build-dev build-prod -build-dev: BUNDLE_REPO = $(DEV_BUNDLE_REPO) -build-prod: BUNDLE_REPO = $(PROD_BUNDLE_REPO) -build-dev build-prod: check-bundle-type check-version check-csv-version - BUNDLE_BUILD_PUSH='$(BUNDLE_BUILD_PUSH)' \ +push: BUNDLE_REPO = $(BUNDLE_DEV_REPO) +push-prod: BUNDLE_REPO = $(BUNDLE_PROD_REPO) +push push-prod: check-bundle-type check-version check-csv-version ./build-bundle.sh \ + push \ '$(CONTAINER)' \ 'bundles/$(call bundle_distribution,$(BUNDLE_TYPE))' \ '$(call bundle_distribution,$(BUNDLE_TYPE))' \ '$(BUNDLE_IMAGE_VERSION)' \ - '$(BUNDLE_REPO)' \ - '$(BUNDLE_TAG_SUFFIX)' + '$(BUNDLE_REPO)' .PHONY: build-bundle-images build-bundle-images: check-version check-csv-version @@ -287,14 +312,14 @@ validate: $(BUNDLE_TYPES:%=validate-%-directory) .PHONY: validate/community validate/certified validate/community validate/certified: validate/%: tools - ./validate-image.sh '$(CONTAINER)' 'bundles/$*' - ./validate-directory.sh 'bundles/$*' + ./validate-image.sh '$(CONTAINER)' 'bundles/$(call bundle_distribution,$*)' + ./validate-directory.sh 'bundles/$(call bundle_distribution,$*)' validate-%-directory: - ./validate-directory.sh 'bundles/$*' + ./validate-directory.sh 'bundles/$(call bundle_distribution,$*)' validate-%-image: - ./validate-image.sh '$(CONTAINER)' 'bundles/$*' + ./validate-image.sh '$(CONTAINER)' 'bundles/$(call bundle_distribution,$*)' # ============================================================================== # Utility Rules diff --git a/installers/olm/README.md b/installers/olm/README.md index 91a611590d..358b507e0c 100644 --- a/installers/olm/README.md +++ b/installers/olm/README.md @@ -58,16 +58,15 @@ Useful optional variables: | `REGISTRY` | Registry used for operator, bundle, and catalog images. | `docker.io` | | `PROD_REPOSITORY` | Repository namespace used for production bundle images. | `percona` | | `DEV_REPOSITORY` | Repository namespace used for development bundle images and all catalog images. | `perconalab` | -| `DEV_BUNDLE_REPO` | Repository used by development bundle images. | `docker.io/perconalab/percona-server-mongodb-operator` | -| `PROD_BUNDLE_REPO` | Repository used by production bundle images. | `docker.io/percona/percona-server-mongodb-operator` | -| `BUNDLE_BUILD_PUSH` | Build and push bundle images when enabled. | `1` | -| `CONFIRM_BUILD_PUSH` | Ask for confirmation before pushing bundle and catalog images. Set to `0` for non-interactive runs. | `1` | -| `DEV_CATALOG_BUNDLE_TAG_SUFFIX` | Tag suffix used for previous OperatorHub bundle images built as development catalog inputs. The current release bundle keeps the normal bundle tag. | `dev-catalog` | -| `DEV_CATALOG_TAG_SUFFIX` | Tag suffix used for catalog images. Catalog images are always development images. | `dev-catalog` | +| `BUNDLE_DEV_REPO` | Repository used by development bundle images. | `docker.io/perconalab/percona-server-mongodb-operator` | +| `BUNDLE_PROD_REPO` | Repository used by production bundle images. | `docker.io/percona/percona-server-mongodb-operator` | +| `BUNDLE_PLATFORM` | Platform used when building current bundle images locally. | `linux/amd64` | +| `BUNDLE_PACKAGE_CHANNEL` | Package channel used for the generated current bundle. | `stable` | +| `CONFIRM_PUSH` | Ask for confirmation before pushing bundle and catalog images. Set to `0` for non-interactive runs. | `1` | | `CATALOG_BUNDLE_LIMIT` | Number of previous OperatorHub bundle versions to include in rendered catalogs, in addition to the current release bundle. | `2` | +| `CATALOG_PLATFORM` | Platform used when building previous OperatorHub bundle images and catalog images. | `linux/amd64` | | `CATALOG_BUILD_PUSH` | Build and push catalog images during deploy targets when enabled. | `1` | -| `GITHUB_TOKEN` / `GH_TOKEN` | Optional token for GitHub API requests when building catalogs. | `` | -| `SKIP_DIGEST_FAILURE` | Continue certified generation when a non-required digest cannot be resolved. Missing digests are rendered as ``. | `1` | +| `BUNDLE_SKIP_DIGEST_FAILURE` | Continue certified bundle generation when a non-required digest cannot be resolved. Missing digests are rendered as ``. | `1` | OpenShift versions are resolved from `../../e2e-tests/release_versions` and used to render `com.redhat.openshift.versions`. @@ -82,11 +81,12 @@ export OPENSHIFT_VERSIONS="v4.18-v4.22" # Development -Development targets generate bundle directories and build bundle images. Bundle -image push is controlled by `BUNDLE_BUILD_PUSH` and defaults to enabled. +Development bundle targets generate bundle directories only. Build bundle +images explicitly with `make build/` and push existing local images with +`make push/`. -Development catalog sources are suffixed with `-dev`, but the package names keep -the production names. +Development catalog sources use `CATALOG_ENV=dev` by default, while package +names keep the production names. Release targets override `CATALOG_ENV=prod`. Packages: @@ -98,7 +98,7 @@ Catalog sources: - `community-dev` - `certified-dev` -## Build bundles +## Generate bundles ```bash make bundles VERSION=1.23.0 @@ -106,27 +106,73 @@ make bundle/community VERSION=1.23.0 make bundle/certified VERSION=1.23.0 ``` -Build locally without pushing: +## Build bundle images + +Build a generated bundle image locally: ```bash -make bundle/community VERSION=1.23.0 BUNDLE_BUILD_PUSH=0 +make build/community VERSION=1.23.0 +make build/certified VERSION=1.23.0 +``` + +## Push bundle images + +Push an existing generated bundle image: + +```bash +make push/community VERSION=1.23.0 +make push/certified VERSION=1.23.0 +``` + +The manual development bundle workflow is: + +```text +make bundle/community + ↓ +make build/community + ↓ +make push/community ``` ## Build catalogs -Catalog build targets first generate, build, and push the current release bundle -image to `DEV_BUNDLE_REPO` with the normal bundle tag, then render and push the -catalog image to `DEV_BUNDLE_REPO` with the `DEV_CATALOG_TAG_SUFFIX` suffix. +Catalog build targets render and push the catalog image to `BUNDLE_DEV_REPO` +with the `-dev-catalog` suffix. The current release bundle image must already +exist in `BUNDLE_DEV_REPO` with the normal bundle tag. For the latest `CATALOG_BUNDLE_LIMIT` previous versions already published in -OperatorHub, `build-catalog.sh` downloads the bundle manifests from GitHub, -builds bundle images with the `DEV_CATALOG_BUNDLE_TAG_SUFFIX`, and pushes them -before rendering the catalog. +OperatorHub, `build-catalog.sh` downloads the bundle manifests from the GitHub +community or certified OperatorHub repositories, builds bundle images with the +`-dev-catalog` suffix, and pushes them before rendering the catalog. + +With the default `CATALOG_BUNDLE_LIMIT=2`, each catalog contains: -The current bundle is rendered into the default package channel from -`PACKAGE_CHANNEL` (`stable` by default). Previous OperatorHub versions are -rendered into versioned channels such as `stable-v1.22` so the OpenShift -Console can show version-specific catalog metadata. +```text +current release bundle +latest previous GitHub OperatorHub bundle +second latest previous GitHub OperatorHub bundle +``` + +For example, with `VERSION=1.23.0` and previous GitHub versions `1.22.0` and +`1.21.2`, the community catalog uses: + +```text +docker.io/perconalab/percona-server-mongodb-operator:1.23.0-community-bundle +docker.io/perconalab/percona-server-mongodb-operator:1.22.0-community-bundle-dev-catalog +docker.io/perconalab/percona-server-mongodb-operator:1.21.2-community-bundle-dev-catalog +``` + +The certified catalog follows the same pattern: + +```text +docker.io/perconalab/percona-server-mongodb-operator:1.23.0-certified-bundle +docker.io/perconalab/percona-server-mongodb-operator:1.22.0-certified-bundle-dev-catalog +docker.io/perconalab/percona-server-mongodb-operator:1.21.2-certified-bundle-dev-catalog +``` + +All bundles are rendered into their own versioned channel. `BUNDLE_PACKAGE_CHANNEL` +sets the channel prefix (`stable` by default), so versions are rendered into +channels such as `stable-v1.23`, `stable-v1.22`, and `stable-v1.21`. ```bash make catalog-build-push/community VERSION=1.23.0 @@ -143,21 +189,22 @@ clusters where the OpenShift console reads the software catalog from `olm`. make deploy/community \ VERSION=1.23.0 \ CSV_VERSION=1.23.0 \ - DEV_REPOSITORY=valmiranogueira \ + DEV_REPOSITORY=my-repository \ CATALOG_NAMESPACE=olm ``` This publishes images such as: ```text -docker.io/valmiranogueira/percona-server-mongodb-operator:1.23.0-community-bundle -docker.io/valmiranogueira/percona-server-mongodb-operator:1.22.0-community-bundle-dev-catalog -docker.io/valmiranogueira/percona-server-mongodb-operator:community-dev-catalog +docker.io/my-repository/percona-server-mongodb-operator:1.23.0-community-bundle +docker.io/my-repository/percona-server-mongodb-operator:1.22.0-community-bundle-dev-catalog +docker.io/my-repository/percona-server-mongodb-operator:community-dev-catalog ``` ## Deploy catalogs -Deploy automatically enables image push (with confirmation when enabled). +Deploy generates the bundle, builds and pushes the current bundle image, builds +and pushes the catalog image, and applies the CatalogSource. ```bash make deploy/community VERSION=1.23.0 @@ -196,9 +243,9 @@ make bundle-prod/certified VERSION=1.23.0 Release deploy targets generate production bundles, build and push production bundle images, and build and push catalog images through -`build-catalog.sh build-push` when `CATALOG_BUILD_PUSH=1`. Catalog images always -use `DEV_BUNDLE_REPO` with the `DEV_CATALOG_TAG_SUFFIX` suffix; production -deploys only switch the bundle image repository to `PROD_BUNDLE_REPO`. +`build-catalog.sh build` when `CATALOG_BUILD_PUSH=1`. Catalog images use +`BUNDLE_DEV_REPO` with the `-prod-catalog` suffix; production deploys switch the +bundle image repository to `BUNDLE_PROD_REPO`. ```bash make deploy-prod/community VERSION=1.23.0 @@ -206,14 +253,30 @@ make deploy-prod/certified VERSION=1.23.0 make deploy-prod VERSION=1.23.0 ``` +Build a production bundle image manually: + +```bash +make build-prod BUNDLE_TYPE=community VERSION=1.23.0 +make build-prod BUNDLE_TYPE=certified VERSION=1.23.0 +``` + +Push a production bundle image manually: + +```bash +make push-prod BUNDLE_TYPE=community VERSION=1.23.0 +make push-prod BUNDLE_TYPE=certified VERSION=1.23.0 +``` + Each deploy performs the complete release workflow: ```text generate production bundle ↓ -build and push production bundle image +build production bundle image + ↓ +push production bundle image ↓ -build and push development catalog image pointing to the production bundle +build and push catalog image pointing to the production bundle ↓ apply CatalogSource ``` @@ -262,19 +325,20 @@ installers/olm/bundles/redhat The bundle image tag still uses the public `certified` name, for example: ```text -:1.23.0-certified-bundle +:1.23.0-certified-bundle ``` Bundle generation fails when: - a required image is missing; - a certified image tag does not match the expected pattern; -- the required `clustersync` Red Hat tag digest cannot be resolved. +- the required `clustersync` tag is not found in the Red Hat repository. -When `SKIP_DIGEST_FAILURE=1`, missing non-`clustersync` digests are rendered as -`` and reported in the build output. The `clustersync` digest is always -resolved strictly so a missing `registry.connect.redhat.com/...:-clustersync` -tag stops generation immediately. +When `BUNDLE_SKIP_DIGEST_FAILURE=1`, missing non-`clustersync` digests are rendered as +`` and reported in the build output. The `clustersync` tag is always +checked strictly, so generation stops immediately when +`registry.connect.redhat.com/...:-clustersync` is not found in the +Red Hat repository. --- diff --git a/installers/olm/build-bundle.sh b/installers/olm/build-bundle.sh index cbb67195c4..56f59d40ac 100755 --- a/installers/olm/build-bundle.sh +++ b/installers/olm/build-bundle.sh @@ -13,31 +13,26 @@ is_true() { esac } -confirm_build_push() { +confirm_push() { local image="$1" local answer - if ! is_true "${CONFIRM_BUILD_PUSH:-1}"; then + if ! is_true "${CONFIRM_PUSH:-1}"; then return 0 fi if [[ -r /dev/tty ]]; then - read -r -p "Build and push bundle image ${image}? [y/N] " answer /dev/null "${container}" buildx build \ --platform "${platforms}" \ -t "${image}" \ - "${build_action}" \ + --load \ . popd >/dev/null } -build_image "$@" +push_image() { + local container="$1" + local image="$2" + + confirm_push "${image}" || { + echo "Bundle image push skipped: ${image}" + exit 1 + } + "${container}" push "${image}" +} + +main() { + local action="${1:-}" + + if [[ "${action}" != "build" && "${action}" != "push" ]]; then + echo "Usage: $0 build|push CONTAINER BUNDLE_DIR DISTRO VERSION BUNDLE_REPO" >&2 + exit 1 + fi + + shift + if [[ "$#" -ne 5 ]]; then + echo "Usage: $0 build|push CONTAINER BUNDLE_DIR DISTRO VERSION BUNDLE_REPO" >&2 + exit 1 + fi + + local container="$1" + local directory="$2" + local distro="$3" + local version="$4" + local bundle_repo="$5" + local image + + image=$(bundle_image "${distro}" "${version}" "${bundle_repo}") + + case "${action}" in + build) + build_image "${container}" "${directory}" "${image}" + ;; + push) + push_image "${container}" "${image}" + ;; + esac +} + +main "$@" diff --git a/installers/olm/build-catalog.sh b/installers/olm/build-catalog.sh index 8cbd8f7849..2e00123d33 100755 --- a/installers/olm/build-catalog.sh +++ b/installers/olm/build-catalog.sh @@ -8,13 +8,12 @@ CATALOG_REPO="${3:-}" BUNDLE_REPO="${4:-}" CONTAINER="${CONTAINER:-docker}" -BUNDLE_PLATFORMS="${BUNDLE_PLATFORMS:-linux/amd64,linux/arm64}" -CATALOG_PLATFORMS="${CATALOG_PLATFORMS:-linux/amd64,linux/arm64}" +CATALOG_PLATFORM="${CATALOG_PLATFORM:-linux/amd64,linux/arm64}" CATALOG_BUNDLE_LIMIT="${CATALOG_BUNDLE_LIMIT:-2}" CATALOG_NAMESPACE="${CATALOG_NAMESPACE:-openshift-marketplace}" -CONFIRM_PUSH="${CONFIRM_PUSH:-${CONFIRM_BUILD_PUSH:-1}}" +CONFIRM_PUSH="${CONFIRM_PUSH:-1}" NAME="${NAME:-percona-server-mongodb-operator}" -CATALOG_ENV="${CATALOG_ENV:-dev-catalog}" +CATALOG_ENV="${CATALOG_ENV:-dev}" usage() { cat <" fi digest="" @@ -173,7 +173,7 @@ report_missing_digests() { [[ "${#redhat_missing_digests[@]}" -eq 0 ]] && return - log "Digest resolution failed for the following image(s); was used because SKIP_DIGEST_FAILURE is enabled:" + log "Digest resolution failed for the following image(s); was used because BUNDLE_SKIP_DIGEST_FAILURE is enabled:" for item in "${redhat_missing_digests[@]}"; do log " - ${item}" done @@ -194,13 +194,15 @@ build_redhat_related_images() { source "${release_versions_file}" for key in \ + IMAGE_OPERATOR \ IMAGE_MONGOD60 \ IMAGE_MONGOD70 \ IMAGE_MONGOD80 \ IMAGE_BACKUP \ IMAGE_PMM_CLIENT \ IMAGE_PMM3_CLIENT \ - IMAGE_LOGCOLLECTOR; do + IMAGE_LOGCOLLECTOR \ + IMAGE_CLUSTERSYNC; do require_release_image "${key}" done @@ -318,4 +320,4 @@ build_distribution_data() { relatedImages: $related_images, skips: $skips }' -} \ No newline at end of file +} diff --git a/installers/olm/generate.sh b/installers/olm/generate.sh index ef39732dc2..da5bcc85f1 100755 --- a/installers/olm/generate.sh +++ b/installers/olm/generate.sh @@ -219,7 +219,7 @@ render_bundle_metadata() { log "Rendering bundle metadata" export package="${PACKAGE_NAME_OVERRIDE:-$(distribution_package_name)}" - export package_channel="${PACKAGE_CHANNEL:-stable}" + export package_channel="${BUNDLE_PACKAGE_CHANNEL:-stable}" export openshift_supported_versions openshift_supported_versions="$(resolve_openshift_versions)" @@ -287,7 +287,7 @@ BEGIN { validate_manifest_inputs() { yq eval -i '[.]' operator_deployments.yaml yq eval 'length == 1' operator_deployments.yaml --exit-status >/dev/null \ - || abort "too many deployments accounts: $(yq eval . operator_deployments.yaml)" + || abort "expected exactly one deployment: $(yq eval . operator_deployments.yaml)" yq eval -i '[.]' operator_accounts.yaml yq eval 'length == 1' operator_accounts.yaml --exit-status >/dev/null \ diff --git a/installers/olm/validate-image.sh b/installers/olm/validate-image.sh index 5c57e5f8df..107337e15a 100755 --- a/installers/olm/validate-image.sh +++ b/installers/olm/validate-image.sh @@ -10,6 +10,21 @@ push_trap_exit() { trap "$1;${array[2]-}" EXIT } +wait_registry() { + local port="$1" + local attempt + + for attempt in $(seq 1 30); do + if curl -fsSL "http://127.0.0.1:${port}/v2/" >/dev/null 2>&1; then + return 0 + fi + sleep 1 + done + + echo "registry container did not become ready" >&2 + return 1 +} + TMPDIR="$(mktemp -d)" push_trap_exit "rm -rf '${TMPDIR}'" export TMPDIR @@ -22,6 +37,10 @@ validate_bundle_image() { local registry directory="$(cd "${directory}" && pwd)" + command -v curl >/dev/null 2>&1 || { + echo "curl is required" >&2 + exit 1 + } export DOCKER_DEFAULT_PLATFORM="${DOCKER_DEFAULT_PLATFORM:-linux/amd64}" @@ -29,7 +48,7 @@ validate_bundle_image() { "${container}" run \ --detach \ --publish-all \ - docker.io/library/registry:latest + docker.io/library/registry:2 )" push_trap_exit "echo -n 'Removing '; '${container}' rm '${registry}'" @@ -39,6 +58,7 @@ validate_bundle_image() { "${container}" inspect "${registry}" \ --format='{{ (index .NetworkSettings.Ports "5000/tcp" 0).HostPort }}' )" + wait_registry "${port}" image="localhost:${port}/psmdb-operator-bundle:latest"