From 67a048d184d7f1b87308571520134f7930a7b7fc Mon Sep 17 00:00:00 2001 From: andrpac Date: Sun, 13 Jul 2025 20:43:24 +0100 Subject: [PATCH] feat: create release pipeline --- .github/actions/build-push-image/action.yaml | 2 + .github/actions/image2commit/action.yml | 42 +++ .github/actions/image2commit/entrypoint.sh | 39 +++ .github/actions/set-tag/entrypoint.sh | 4 +- .github/workflows/promote-image.yml | 30 +-- .github/workflows/release-branch.yml | 4 +- .github/workflows/release-image.yml | 270 +++++++++++++++++++ .github/workflows/release-post-merge.yml | 4 +- .github/workflows/sboms-pr.yaml | 4 +- scripts/move-image.sh | 38 ++- 10 files changed, 400 insertions(+), 37 deletions(-) create mode 100644 .github/actions/image2commit/action.yml create mode 100644 .github/actions/image2commit/entrypoint.sh create mode 100644 .github/workflows/release-image.yml diff --git a/.github/actions/build-push-image/action.yaml b/.github/actions/build-push-image/action.yaml index fda56c152a..d62afb41e0 100644 --- a/.github/actions/build-push-image/action.yaml +++ b/.github/actions/build-push-image/action.yaml @@ -99,3 +99,5 @@ runs: sbom: true push: true tags: ${{ inputs.tags }} + labels: | + org.opencontainers.image.revision=${{ github.sha }} diff --git a/.github/actions/image2commit/action.yml b/.github/actions/image2commit/action.yml new file mode 100644 index 0000000000..48484ff17c --- /dev/null +++ b/.github/actions/image2commit/action.yml @@ -0,0 +1,42 @@ +name: image2commit +description: Resolve full commit SHA from a promoted image tag. + +inputs: + register: + description: "Registry (e.g., docker.io, quay.io)" + required: true + repo: + description: "Repository path (e.g., andrpac/my-repo)" + required: true + image_sha: + description: "Short SHA or 'latest'" + required: true + +outputs: + commit_sha: + description: "Resolved full commit SHA" + value: ${{ steps.resolve.outputs.commit_sha }} +runs: + using: "composite" + steps: + - name: Install skopeo and jq + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y skopeo jq + + - name: Resolve commit SHA + id: resolve + shell: bash + run: | + chmod +x ${{ github.action_path }}/entrypoint.sh + full_sha=$(${{ + github.action_path + }}/entrypoint.sh \ + "${{ inputs.register }}" \ + "${{ inputs.repo }}" \ + "${{ inputs.image_sha }}" + ) + + echo "Raw full_sha: $full_sha" + echo "commit_sha=$full_sha" >> $GITHUB_OUTPUT diff --git a/.github/actions/image2commit/entrypoint.sh b/.github/actions/image2commit/entrypoint.sh new file mode 100644 index 0000000000..0d324e1513 --- /dev/null +++ b/.github/actions/image2commit/entrypoint.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# Copyright 2025 MongoDB Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script retrives the git commit sha given an image sha + +set -euo pipefail + +registry="$1" +repo="$2" +image_sha="$3" + +if [[ "$image_sha" == "latest" ]]; then + tag="promoted-latest" +else + tag="promoted-${image_sha}" +fi + +full_image="${registry}/${repo}:${tag}" + +sha=$(skopeo inspect "docker://${full_image}" | jq -r '.Labels["org.opencontainers.image.revision"]') + +if [[ -z "$sha" || "$sha" == "null" ]]; then + echo "Error: Could not extract commit SHA from $full_image" >&2 + exit 1 +fi + +echo "$sha" diff --git a/.github/actions/set-tag/entrypoint.sh b/.github/actions/set-tag/entrypoint.sh index e2978dcd85..a0fc7cedc9 100644 --- a/.github/actions/set-tag/entrypoint.sh +++ b/.github/actions/set-tag/entrypoint.sh @@ -17,12 +17,12 @@ set -eou pipefail git config --global --add safe.directory /github/workspace -# Get the full commit hash and shorten to 6 characters +# Get the full commit hash and shorten to 7 characters full_commit_sha="${INPUT_COMMIT_SHA:-}" if [ -z "$full_commit_sha" ]; then full_commit_sha=$(git rev-parse HEAD) fi -commit_id=$(echo "$full_commit_sha" | cut -c1-6) +commit_id=$(echo "$full_commit_sha" | cut -c1-7) # Get the full branch name branch_name="${INPUT_BRANCH_NAME:-}" diff --git a/.github/workflows/promote-image.yml b/.github/workflows/promote-image.yml index 17009e1384..74fa176df6 100644 --- a/.github/workflows/promote-image.yml +++ b/.github/workflows/promote-image.yml @@ -21,19 +21,6 @@ jobs: - name: Checkout PR commit uses: actions/checkout@v4 - # Note, we have to be careful how we retrive the image. The event that pushed - # the image to the ghcr.io repo was mainly a push/schedule that passed all the - # tests. This event has access to github.ref_name. However, the workflow_run - # event does not have access github.ref_name set up. - # - # Therefore, we need to manually specify the branch as main - - name: Prepare image tag - id: set_tag - uses: ./.github/actions/set-tag - with: - branch_name: ${{ github.event.workflow_run.head_branch }} - commit_sha: ${{ github.event.workflow_run.head_sha }} - - name: Log in to the GitHub Container Registry uses: docker/login-action@v3 with: @@ -41,23 +28,26 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Pull unofficial image from GitHub Container Registry - run: | - docker pull ${{ env.GHCR_REPO }}:${{ steps.set_tag.outputs.tag }} - - name: Login to Docker registry uses: docker/login-action@v3 with: registry: docker.io username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - + - name: Log in to Quay registry uses: docker/login-action@v3 with: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_PASSWORD }} + + - name: Prepare image tag + id: set_tag + uses: ./.github/actions/set-tag + with: + branch_name: ${{ github.event.workflow_run.head_branch }} + commit_sha: ${{ github.event.workflow_run.head_sha }} - name: Prepare tag for promoted image id: promoted_tag @@ -73,6 +63,8 @@ jobs: IMAGE_DEST_REPO: ${{ env.DOCKER_REPO }} IMAGE_SRC_TAG: ${{ steps.set_tag.outputs.tag }} IMAGE_DEST_TAG: ${{ steps.promoted_tag.outputs.tag }} + ALIAS_ENABLED: true + ALIAS_TAG: promoted-latest - name: Move image to Quay run: ./scripts/move-image.sh @@ -81,3 +73,5 @@ jobs: IMAGE_DEST_REPO: ${{ env.QUAY_REPO }} IMAGE_SRC_TAG: ${{ steps.set_tag.outputs.tag }} IMAGE_DEST_TAG: ${{ steps.promoted_tag.outputs.tag }} + ALIAS_ENABLED: true + ALIAS_TAG: promoted-latest diff --git a/.github/workflows/release-branch.yml b/.github/workflows/release-branch.yml index 4099cf0aa4..a10adff92f 100644 --- a/.github/workflows/release-branch.yml +++ b/.github/workflows/release-branch.yml @@ -1,7 +1,7 @@ -# Create Release Branch +# DEPRECATED: Create Release Branch # TODO after GitHub add permission for action-bot to commit to the protected branches - please merge release-* workflow into one -name: Create Release Branch +name: "[Deprecated] Create Release Branch" on: workflow_dispatch: diff --git a/.github/workflows/release-image.yml b/.github/workflows/release-image.yml new file mode 100644 index 0000000000..cc45214bca --- /dev/null +++ b/.github/workflows/release-image.yml @@ -0,0 +1,270 @@ +name: Release Image + +on: + workflow_dispatch: + inputs: + version: + description: "Release version" + required: true + type: string + authors: + description: "Comma-separated list of author emails" + required: true + type: string + image_sha: + description: "7-digit commit SHA used for the promoted image (e.g. 3e79a3f or 'latest')" + required: false + default: "latest" + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + # Image2commit: Creates a mapping between the image_sha given as input and the actual git commit + # This is necassary for the release-image step that requires checking out that exact git commit + image2commit: + name: Resolve Commit SHA from Image + runs-on: ubuntu-latest + environment: release + outputs: + commit_sha: ${{ steps.resolve.outputs.commit_sha }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Log in to Docker registry + uses: docker/login-action@v3 + with: + registry: docker.io + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Run image2commit + id: resolve + uses: ./.github/actions/image2commit + with: + register: docker.io + repo: mongodb/mongodb-atlas-kubernetes-operator-prerelease + image_sha: ${{ github.event.inputs.image_sha }} + + # Check-commit: Outputs the commit used when someone wants to use latest image_sha + # and does not know what version of operator will end up using + check-commit: + name: Check resolved commit + runs-on: ubuntu-latest + needs: image2commit + steps: + - name: Echo resolved commit + run: | + echo "Resolved commit: ${{ needs.image2commit.outputs.commit_sha }}" + + # Release-image: Created and uploads a release for the specified operator version given in the image_sha + # Note, with new releases, all of the release artifacts will be stored withing docs/releases/{release_version} + release-image: + runs-on: ubuntu-latest + environment: release + needs: image2commit + env: + VERSION: ${{ github.event.inputs.version || 'vtest-0.0.0-dev' }} + AUTHORS: ${{ github.event.inputs.authors || 'unknown' }} + IMAGE_SHA: ${{ github.event.inputs.image_sha || 'latest' }} + DOCKER_SIGNATURE_REPO: docker.io/mongodb/signatures + DOCKER_RELEASE_REPO: docker.io/mongodb/mongodb-atlas-kubernetes-operator + DOCKER_PRERELEASE_REPO: docker.io/mongodb/mongodb-atlas-kubernetes-operator-prerelease + QUAY_RELEASE_REPO: quay.io/mongodb/mongodb-atlas-kubernetes-operator + QUAY_PRERELEASE_REPO: quay.io/mongodb/mongodb-atlas-kubernetes-operator-prerelease + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ needs.image2commit.outputs.commit_sha }} + + - name: Generate GitHub App Token + id: generate_token + uses: mongodb/apix-action/token@v8 + with: + app-id: ${{ secrets.AKO_RELEASER_APP_ID }} + private-key: ${{ secrets.AKO_RELEASER_RSA_KEY }} + + # Login in into all registries + - name: Log in to Docker registry + uses: docker/login-action@v3 + with: + registry: docker.io + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Log in to Quay registry + uses: docker/login-action@v3 + with: + registry: quay.io + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_PASSWORD }} + + - name: Log in to Artifactory + uses: docker/login-action@v3 + with: + registry: artifactory.corp.mongodb.com + username: ${{ secrets.MDB_ARTIFACTORY_USERNAME }} + password: ${{ secrets.MDB_ARTIFACTORY_PASSWORD }} + + - name: Install devbox + uses: jetify-com/devbox-install-action@v0.13.0 + + # This step configures all of the dynamic variables needed for later steps + - name: Configure job environment for downstream steps + id: tags + run: | + promoted_tag="promoted-${IMAGE_SHA}" + release_tag="${VERSION}" + certified_tag="certified-${release_tag}" + + docker_image_url="${DOCKER_RELEASE_REPO}:${release_tag}" + quay_image_url="${QUAY_RELEASE_REPO}:${release_tag}" + quay_certified_image_url="${QUAY_RELEASE_REPO}:${certified_tag}" + + echo "promoted_tag=$promoted_tag" >> $GITHUB_OUTPUT + echo "release_tag=$release_tag" >> $GITHUB_OUTPUT + echo "certified_tag=$certified_tag" >> $GITHUB_OUTPUT + echo "docker_image_url=$docker_image_url" >> $GITHUB_OUTPUT + echo "quay_image_url=$quay_image_url" >> $GITHUB_OUTPUT + echo "quay_certified_image_url=$quay_certified_image_url" >> $GITHUB_OUTPUT + + # Trigger the helm release workflow to sync + - name: Trigger helm post release workflow + env: + GH_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + gh workflow run post-atlas-operator-release.yaml \ + --repo mongodb/helm-charts \ + --ref main \ + --fields version="${{ steps.tags.outputs.release_tag }}" \ + + # Move prerelease images to official release registries in Docker Hub and Quay + - name: Move image to Docker registry release from prerelease + run: devbox run -- ./scripts/move-image.sh + env: + IMAGE_SRC_REPO: ${{ env.DOCKER_PRERELEASE_REPO }} + IMAGE_DEST_REPO: ${{ env.DOCKER_RELEASE_REPO }} + IMAGE_SRC_TAG: ${{ steps.tags.outputs.promoted_tag }} + IMAGE_DEST_TAG: ${{ steps.tags.outputs.release_tag }} + + - name: Move image to Quay registry release from prerelease + run: devbox run -- ./scripts/move-image.sh + env: + IMAGE_SRC_REPO: ${{ env.QUAY_PRERELEASE_REPO }} + IMAGE_DEST_REPO: ${{ env.QUAY_RELEASE_REPO }} + IMAGE_SRC_TAG: ${{ steps.tags.outputs.promoted_tag }} + IMAGE_DEST_TAG: ${{ steps.tags.outputs.release_tag }} + + # Create Openshift certified images + - name: Create OpenShift certified image on Quay + run: devbox run -- ./scripts/move-image.sh + env: + IMAGE_SRC_REPO: ${{ env.QUAY_PRERELEASE_REPO }} + IMAGE_DEST_REPO: ${{ env.QUAY_RELEASE_REPO }} + IMAGE_SRC_TAG: ${{ steps.tags.outputs.promoted_tag }} + IMAGE_DEST_TAG: ${{ steps.tags.outputs.certified_tag }} + + - name: Certify Openshift images + uses: ./.github/actions/certify-openshift-images + with: + registry: quay.io + repository: mongodb/mongodb-atlas-kubernetes-operator + version: ${{ steps.tags.outputs.certified_tag }} + registry_password: ${{ secrets.QUAY_PASSWORD }} + rhcc_project: ${{ secrets.RH_CERTIFICATION_OSPID }} + rhcc_token: ${{ secrets.RH_CERTIFICATION_PYXIS_API_TOKEN }} + submit: true + + # Link updates to pr: all-in-one.yml, helm-updates, sdlc requirements + - name: Generate deployment configurations + uses: ./.github/actions/gen-install-scripts + with: + ENV: prod + IMAGE_URL: ${{ steps.tags.outputs.docker_image_url }} + + - name: Bump Helm chart version + run: devbox run -- ./scripts/bump-helm-chart-version.sh + + # Prepare SDLC requirement: signatures, sboms, compliance reports + # Note, signed images will live in mongodb/release and mongodb/signature repos + - name: Sign released images + run: | + devbox run -- make sign IMG="${{ steps.tags.outputs.docker_image_url }}" SIGNATURE_REPO="${{ env.DOCKER_RELEASE_REPO }}" + devbox run -- make sign IMG="${{ steps.tags.outputs.quay_image_url }}" SIGNATURE_REPO="${{ env.QUAY_RELEASE_REPO }}" + devbox run -- make sign IMG="${{ steps.tags.outputs.docker_image_url }}" SIGNATURE_REPO="${{ env.DOCKER_SIGNATURE_REPO }}" + devbox run -- make sign IMG="${{ steps.tags.outputs.quay_certified_image_url }}" SIGNATURE_REPO="${{ env.QUAY_RELEASE_REPO }}" + devbox run -- make sign IMG="${{ steps.tags.outputs.quay_certified_image_url }}" SIGNATURE_REPO="${{ env.DOCKER_SIGNATURE_REPO }}" + env: + PKCS11_URI: ${{ secrets.PKCS11_URI }} + GRS_USERNAME: ${{ secrets.GRS_USERNAME }} + GRS_PASSWORD: ${{ secrets.GRS_PASSWORD }} + + - name: Generate SBOMs + run: devbox run -- make generate-sboms RELEASED_OPERATOR_IMAGE="${{ env.DOCKER_RELEASE_REPO }}" + + - name: Create SDLC report + run: devbox run -- make gen-sdlc-checklist + + # Create PR on release branch with all updates generated + - name: Create release pr with all updated artefacts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + export BRANCH="new-release/${VERSION}" + export COMMIT_MESSAGE="feat: release ${VERSION}" + export RELEASE_DIR="releases/${VERSION}" + + git config --global user.name "${{ steps.generate_token.outputs.user-name }}" + git config --global user.email "${{ steps.generate_token.outputs.user-email }}" + + mkdir -p "$RELEASE_DIR" + cp -r deploy "$RELEASE_DIR/deploy" + cp -r bundle "$RELEASE_DIR/bundle" + cp -r helm-charts "$RELEASE_DIR/helm-charts" + cp bundle.Dockerfile "$RELEASE_DIR/bundle.Dockerfile" + + git fetch origin + git checkout -f -b "$BRANCH" origin/main + git push -f origin "$BRANCH" + + git add -f "$RELEASE_DIR" + scripts/create-signed-commit.sh + + gh pr create \ + --draft \ + --base main \ + --head "$BRANCH" \ + --title "$COMMIT_MESSAGE" \ + --body "This is an autogenerated PR to prepare for the release" + + # Create release artefacts on GitHub by tagging and pushing a tag + - name: Create configuration package + run: | + set -x + tar czvf atlas-operator-all-in-one-${{ env.VERSION }}.tar.gz -C releases/${{ env.VERSION }}/deploy all-in-one.yaml + + - name: Tag the release assets + run: | + git fetch --tags + git tag -f ${{ env.VERSION }} + git push -f origin ${{ env.VERSION }} + + - name: Create release on GitHub + uses: softprops/action-gh-release@v2 + with: + draft: true + prerelease: false + tag_name: "${{ env.VERSION }}" + name: "${{ env.VERSION }}" + token: ${{ secrets.GITHUB_TOKEN }} + body_path: docs/release-notes/release-notes-template.md + files: | + ./atlas-operator-all-in-one-${{ env.VERSION }}.tar.gz + ./docs/releases/v${{ env.VERSION }}/sdlc-compliance.md + ./docs/releases/v${{ env.VERSION }}/linux_amd64.sbom.json + ./docs/releases/v${{ env.VERSION }}/linux_arm64.sbom.json diff --git a/.github/workflows/release-post-merge.yml b/.github/workflows/release-post-merge.yml index a7b7902190..2ea7e3a5e7 100644 --- a/.github/workflows/release-post-merge.yml +++ b/.github/workflows/release-post-merge.yml @@ -1,8 +1,8 @@ -# GitHub workflow for creating release. +# DEPRECATED: GitHub workflow for creating release. # Trigger release branch should be merge into main # TODO add e2e/smoke test for autogen configuration -name: Create Release +name: "[Deprecated] Create Release" on: push: diff --git a/.github/workflows/sboms-pr.yaml b/.github/workflows/sboms-pr.yaml index 7d57ee2ad9..e930dcded2 100644 --- a/.github/workflows/sboms-pr.yaml +++ b/.github/workflows/sboms-pr.yaml @@ -1,5 +1,5 @@ -# GitHub workflow for creating the SDLC SBOMs PR after a release. -name: Create SBOMs PR +# DEPRECATED: GitHub workflow for creating the SDLC SBOMs PR after a release. +name: "[Deprecated] Create SBOMs PR" on: workflow_call: diff --git a/scripts/move-image.sh b/scripts/move-image.sh index 691dc55cf0..1d68375da7 100755 --- a/scripts/move-image.sh +++ b/scripts/move-image.sh @@ -2,7 +2,7 @@ # Copyright 2025 MongoDB Inc # # Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. +# You may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 @@ -13,25 +13,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -# This scripts moves an image from a registry to another with retagging - +# This script moves a multi-arch image from one registry to another using docker buildx. set -euo pipefail -# Required environment variables +# Required env vars : "${IMAGE_SRC_REPO:?Missing IMAGE_SRC_REPO}" : "${IMAGE_SRC_TAG:?Missing IMAGE_SRC_TAG}" : "${IMAGE_DEST_REPO:?Missing IMAGE_DEST_REPO}" : "${IMAGE_DEST_TAG:?Missing IMAGE_DEST_TAG}" +# Optional env vars -> ALIAS TAG can be updated to a list later on +ALIAS_TAG="${ALIAS_TAG:-}" +ALIAS_ENABLED="${ALIAS_ENABLED:-false}" + image_src_url="${IMAGE_SRC_REPO}:${IMAGE_SRC_TAG}" image_dest_url="${IMAGE_DEST_REPO}:${IMAGE_DEST_TAG}" -echo "Checking if ${image_dest_url} already exists..." +echo "Checking if ${image_dest_url} already exists remotely..." if docker manifest inspect "${image_dest_url}" > /dev/null 2>&1; then - echo "${image_dest_url} already exists. Skipping push." -else - echo "Tagging ${image_src_url} -> ${image_dest_url}" - docker tag "${image_src_url}" "${image_dest_url}" - echo "Pushing to ${image_dest_url}..." - docker push "${image_dest_url}" + echo "Image ${image_dest_url} already exists. Skipping transfer." + exit 0 fi + +echo "Transferring multi-arch image:" +echo " From: ${image_src_url}" +echo " To: ${image_dest_url}" + +BUILDER_NAME="tmpbuilder-move-image" +docker buildx create --name "${BUILDER_NAME}" --use > /dev/null +docker buildx imagetools create "${image_src_url}" --tag "${image_dest_url}" + +if [[ "${ALIAS_ENABLED}" == "true" && -n "${ALIAS_TAG}" ]]; then + echo "Aliasing ${image_src_url} as ${IMAGE_DEST_REPO}:${ALIAS_TAG}" + docker buildx imagetools create "${image_src_url}" --tag "${IMAGE_DEST_REPO}:${ALIAS_TAG}" + echo "Successfully aliased as ${IMAGE_DEST_REPO}:${ALIAS_TAG}" +fi + +docker buildx rm "${BUILDER_NAME}" > /dev/null +echo "Successfully moved ${image_src_url} -> ${image_dest_url}"