diff --git a/.github/scripts/create_or_update_upstream_pr.sh b/.github/scripts/create_or_update_upstream_pr.sh new file mode 100755 index 00000000..65d64cba --- /dev/null +++ b/.github/scripts/create_or_update_upstream_pr.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -euo pipefail +upstream=redhat-openshift-ecosystem/community-operators-prod +branch="openstack-lightspeed-release-$NEW_VERSION" +title="operator openstack-lightspeed-operator ($NEW_VERSION)" +head="${FORK_REPOSITORY%%/*}:$branch" + +gh auth setup-git --hostname github.com +git config user.name "$AUTHOR_NAME" +git config user.email "$AUTHOR_EMAIL" +git remote add fork "https://github.com/$FORK_REPOSITORY.git" +remote_head=$(git ls-remote fork "refs/heads/$branch" | cut -f1) +git switch -c "$branch" +git add -- "operators/openstack-lightspeed-operator/$NEW_VERSION" +git commit --signoff -m "$title" +# Only replace the branch if it has not changed since we read it. +git push --force-with-lease="refs/heads/$branch:$remote_head" \ + fork "HEAD:refs/heads/$branch" + +body_file="$RUNNER_TEMP/community-release-pr.md" +cat > "$body_file" <> "$GITHUB_OUTPUT" diff --git a/.github/scripts/prepare_community_bundle.sh b/.github/scripts/prepare_community_bundle.sh new file mode 100755 index 00000000..0a2fc42f --- /dev/null +++ b/.github/scripts/prepare_community_bundle.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash + +set -euo pipefail +if [[ "$HAS_TOKEN" != true ]]; then + echo "::error::Set the COMMUNITY_OPERATORS_TOKEN repository secret." + exit 1 +fi +: "${AUTHOR_NAME:?Set the RELEASE_AUTHOR_NAME repository variable.}" +: "${AUTHOR_EMAIL:?Set the RELEASE_AUTHOR_EMAIL repository variable.}" +version_pattern='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' +if [[ ! "$NEW_VERSION" =~ $version_pattern ]]; then + echo "::error::new_version must have the form X.Y.Z." + exit 1 +fi +if [[ ! "$FORK_REPOSITORY" =~ ^[a-zA-Z0-9-]+/[a-zA-Z0-9_.-]+$ ]]; then + echo "::error::Set the COMMUNITY_OPERATORS_FORK repository variable to owner/repository." + exit 1 +fi +if [[ ! "$CONTAINER_IMAGE_TAG" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$ ]]; then + echo "::error::container_image_tag must be a valid container image tag." + exit 1 +fi + +operator_dir=operators/openstack-lightspeed-operator +release_dir="$operator_dir/$NEW_VERSION" +if [[ -e "$release_dir" ]]; then + echo "::error::Version $NEW_VERSION already exists on upstream main." + exit 1 +fi +PREVIOUS_VERSION=$( + { find "$operator_dir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n'; printf '%s\n' "$NEW_VERSION"; } | + grep -E "$version_pattern" | sort -Vu | + awk -v version="$NEW_VERSION" '$0 == version { print previous } { previous = $0 }' +) +: "${PREVIOUS_VERSION:?No existing community release precedes this version.}" + +declare -A image_digests +for repository in operator operator-bundle; do + digest=$(curl --fail --silent --show-error --max-time 30 \ + --get --data-urlencode "specificTag=$CONTAINER_IMAGE_TAG" \ + --data-urlencode 'onlyActiveTags=true' \ + "https://quay.io/api/v1/repository/openstack-lightspeed/$repository/tag/" | + jq -r --arg tag "$CONTAINER_IMAGE_TAG" \ + '[.tags[]? | select(.name == $tag) | .manifest_digest][0] // empty') + if [[ ! "$digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then + echo "::error::No active $repository image with a valid digest for $CONTAINER_IMAGE_TAG." + exit 1 + fi + image_digests[$repository]="$digest" +done +OPERATOR_IMAGE_DIGEST=${image_digests[operator]} +BUNDLE_IMAGE_DIGEST=${image_digests[operator-bundle]} +export PREVIOUS_VERSION OPERATOR_IMAGE_DIGEST + +image="quay.io/openstack-lightspeed/operator-bundle@$BUNDLE_IMAGE_DIGEST" +podman pull "$image" +image_id=$(podman image inspect --format '{{.Id}}' "$image") +# Podman 4 requires a command even though this container is never started. +container_id=$(podman create "$image_id" /bin/true) +trap 'podman rm "$container_id" >/dev/null || true' EXIT +mkdir -p "$release_dir" +for directory in manifests metadata tests; do + podman cp "$container_id:/$directory" "$release_dir/$directory" +done + +yq -i ' + .metadata.name = ("openstack-lightspeed-operator.v" + strenv(NEW_VERSION)) | + .metadata.annotations.containerImage = ("quay.io/openstack-lightspeed/operator@" + strenv(OPERATOR_IMAGE_DIGEST)) | + .metadata.annotations.support = "Community" | + .spec.displayName = "OpenStack Lightspeed (Community)" | + .spec.replaces = ("openstack-lightspeed-operator.v" + strenv(PREVIOUS_VERSION)) | + .spec.version = strenv(NEW_VERSION) +' "$release_dir/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml" + +cat > "$release_dir/release-config.yaml" <> "$GITHUB_OUTPUT" +echo "bundle_digest=$BUNDLE_IMAGE_DIGEST" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/propose-community-release.yaml b/.github/workflows/propose-community-release.yaml new file mode 100644 index 00000000..5e430613 --- /dev/null +++ b/.github/workflows/propose-community-release.yaml @@ -0,0 +1,99 @@ +name: Propose community operator release + +on: + workflow_dispatch: + inputs: + new_version: + description: 'Community release version (X.Y.Z)' + required: true + type: string + container_image_tag: + description: 'Published operator and bundle image tag' + required: true + default: latest + type: string + +permissions: + contents: read + +concurrency: + group: community-release-${{ vars.COMMUNITY_OPERATORS_FORK }}-${{ inputs.new_version }} + cancel-in-progress: false + +jobs: + propose-release: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + defaults: + run: + shell: bash + env: + NEW_VERSION: ${{ inputs.new_version }} + CONTAINER_IMAGE_TAG: ${{ inputs.container_image_tag }} + FORK_REPOSITORY: ${{ vars.COMMUNITY_OPERATORS_FORK }} # owner/repo-name + AUTHOR_NAME: ${{ vars.RELEASE_AUTHOR_NAME }} + AUTHOR_EMAIL: ${{ vars.RELEASE_AUTHOR_EMAIL }} + + steps: + - name: Check out release scripts + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + sparse-checkout: .github + persist-credentials: false + + - name: Check out community operators + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + repository: redhat-openshift-ecosystem/community-operators-prod + ref: main + path: community-operators-prod + # Keep history for pushing to forks that are behind upstream. + fetch-depth: 0 + sparse-checkout: operators/openstack-lightspeed-operator + persist-credentials: false + + - name: Prepare community bundle + id: bundle + working-directory: community-operators-prod + env: + # The preparation script only needs to know whether the PAT is configured. + HAS_TOKEN: ${{ secrets.COMMUNITY_OPERATORS_TOKEN != '' }} + run: bash "$GITHUB_WORKSPACE/.github/scripts/prepare_community_bundle.sh" + + - name: Install operator-sdk + uses: redhat-actions/openshift-tools-installer@37eb3cfb20ac2df6f0511ffa71cd917e4d49cabf # v3 + with: + source: github + operator-sdk: 1.42.3 + + - name: Validate community bundle + working-directory: community-operators-prod + run: operator-sdk bundle validate "./operators/openstack-lightspeed-operator/$NEW_VERSION" + + # Scorecard executes its tests in Kubernetes pods. + - name: Create cluster for scorecard + uses: helm/kind-action@1544676c07bb3570fcb70166beaa07c4528fd4aa # v1.15.1 + with: + cluster_name: community-scorecard + wait: 120s + + - name: Run community bundle scorecard + working-directory: community-operators-prod + run: operator-sdk scorecard "./operators/openstack-lightspeed-operator/$NEW_VERSION" --wait-time 5m + + - name: Create or update upstream pull request + id: pr + working-directory: community-operators-prod + env: + GH_TOKEN: ${{ secrets.COMMUNITY_OPERATORS_TOKEN }} + GH_PROMPT_DISABLED: "1" + GIT_TERMINAL_PROMPT: "0" + OPERATOR_DIGEST: ${{ steps.bundle.outputs.operator_digest }} + BUNDLE_DIGEST: ${{ steps.bundle.outputs.bundle_digest }} + run: bash "$GITHUB_WORKSPACE/.github/scripts/create_or_update_upstream_pr.sh" + + - name: Show pull request + env: + PR_URL: ${{ steps.pr.outputs.pull-request-url }} + run: | + printf 'Release pull request: %s\n' "$PR_URL" >> "$GITHUB_STEP_SUMMARY" diff --git a/docs/development.md b/docs/development.md index 4b34c505..08fad12e 100644 --- a/docs/development.md +++ b/docs/development.md @@ -23,6 +23,44 @@ cd ../.. `PULL_SECRET` is the same pull secret described in the [installation guide](install_guide.md#access-to-registry-images). +## Releasing to the community catalog + +The **Propose community operator release** GitHub Actions workflow uses +already-published operator and bundle images from Quay. It resolves the selected +tag to image digests, extracts the bundle, and updates its version, community +metadata, and upgrade link to the preceding release. It runs bundle validation +and scorecard tests (in a temporary Kind cluster), then opens or updates a PR in +[community-operators-prod](https://github.com/redhat-openshift-ecosystem/community-operators-prod). +The current targets are the `alpha` channel on OpenShift 4.16, 4.18, 4.22 configured +in `.github/scripts/prepare_community_bundle.sh`. + +Before the first release, a repository administrator must configure these +Actions secrets and variables: + +- Secret `COMMUNITY_OPERATORS_TOKEN`: a bot account token that can push to the + fork and create or update upstream PRs. +- Variable `COMMUNITY_OPERATORS_FORK`: the existing fork, in `owner/repository` + format (for example, `openstack-lightspeed-bot/community-operators-prod`). +- Variables `RELEASE_AUTHOR_NAME` and `RELEASE_AUTHOR_EMAIL`: the bot's Git + commit identity. + +To make a release: + +1. Wait for **Build and push operator container image** to finish successfully + for the code you want to release. +2. In the `lightspeed-operator` repository, open **Actions → Propose community + operator release → Run workflow**. +3. Set `new_version` to the next community version, such as `0.0.3` (without a + `v` prefix). It must not already exist in the catalog. Set + `container_image_tag` to `latest` (the default, from the latest main build), + or a specific build tag such as `v0.1.0-`. +4. Click **Run workflow**. If validation and scorecard pass, the run summary + contains the upstream PR link. Follow that PR's checks and review through + merge; the upstream pipeline handles publication to the catalog. + +After publication, verify installation and a Lightspeed query on an OpenShift +cluster using the [installation guide](install_guide.md). + ## Architecture ```mermaid