name: Release concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false on: workflow_dispatch: permissions: contents: read jobs: release: name: "Promote built artifacts to a release" runs-on: warp-ubuntu-2404-x64-2x environment: production permissions: contents: write actions: read steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Read release inputs from branch id: inputs env: REF_NAME: ${{ github.ref_name }} run: | set -euo pipefail VERSION=$(awk -F'"' '/^version = "[0-9]+\.[0-9]+\.[0-9]+/ {print $2; exit}' Cargo.toml) if [[ -z "$VERSION" ]]; then echo "::error::Could not find workspace 'version = \"X.Y.Z\"' line in Cargo.toml" exit 1 fi if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then echo "::error::Invalid semver in Cargo.toml: $VERSION" exit 1 fi SHA=$(git rev-parse HEAD) SHORT_SHA="${SHA::7}" BRANCH_TAG="${REF_NAME//\//-}-${SHORT_SHA}" echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "sha=$SHA" >> "$GITHUB_OUTPUT" echo "branch-tag=$BRANCH_TAG" >> "$GITHUB_OUTPUT" echo "Releasing version $VERSION from $REF_NAME at $SHORT_SHA" echo "Source image tag: $BRANCH_TAG" - name: Refuse if git tag already exists env: VERSION: ${{ steps.inputs.outputs.version }} run: | set -euo pipefail if git ls-remote --exit-code --tags origin "${VERSION}" >/dev/null 2>&1; then echo "::error::Git tag ${VERSION} already exists on origin." echo "::error::If this was an accidental orphan tag, delete it and re-run. Otherwise the release has already shipped." exit 1 fi - name: Refuse if GitHub release already exists env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ steps.inputs.outputs.version }} run: | set -euo pipefail if gh release view "${VERSION}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then echo "::error::A GitHub release (draft or published) for ${VERSION} already exists." echo "::error::Delete it on the releases page before re-running." exit 1 fi - name: Extract changelog section env: VERSION: ${{ steps.inputs.outputs.version }} run: | set -euo pipefail awk "/^## \\[${VERSION}\\]/{found=1; next} /^## \\[/{if(found) exit} found" CHANGELOG.md > release-notes-raw.md if [ ! -s release-notes-raw.md ]; then echo "::error::No changelog section found for version ${VERSION} in CHANGELOG.md" exit 1 fi - name: Install skopeo run: | sudo apt-get update sudo apt-get install -y skopeo - name: Login to Docker Hub uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PAT }} - name: Verify source images exist env: BRANCH_TAG: ${{ steps.inputs.outputs.branch-tag }} run: | set -euo pipefail for REPO in mpc-node mpc-node-gcp mpc-launcher; do if ! skopeo inspect docker://nearone/$REPO:$BRANCH_TAG > /dev/null 2>&1; then echo "::error::Source image nearone/$REPO:$BRANCH_TAG not found." echo "::error::Has the build workflow finished for this commit? If a new commit was pushed after the build started, wait for the new build to complete." exit 1 fi done - name: Find contract build run id: contract-run env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SHA: ${{ steps.inputs.outputs.sha }} run: | set -euo pipefail RUN_ID=$(gh run list \ --repo "$GITHUB_REPOSITORY" \ --workflow=build_contract.yml \ --commit="$SHA" \ --status=success \ --limit=1 \ --json databaseId \ -q '.[0].databaseId') if [[ -z "$RUN_ID" || "$RUN_ID" == "null" ]]; then echo "::error::No successful build_contract.yml run found for SHA $SHA." echo "::error::Has the contract build finished for this commit?" exit 1 fi echo "run-id=$RUN_ID" >> "$GITHUB_OUTPUT" - name: Download contract artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: contract run-id: ${{ steps.contract-run.outputs.run-id }} github-token: ${{ secrets.GITHUB_TOKEN }} path: contract-artifact - name: Retag images and collect digests id: digests env: BRANCH_TAG: ${{ steps.inputs.outputs.branch-tag }} VERSION: ${{ steps.inputs.outputs.version }} run: | set -euo pipefail for REPO in mpc-node mpc-node-gcp mpc-launcher; do SAFE_NAME="${REPO//-/_}" skopeo copy --preserve-digests --all \ docker://nearone/$REPO:$BRANCH_TAG \ docker://nearone/$REPO:$VERSION DIGEST=$(skopeo inspect docker://nearone/$REPO:$VERSION | jq -r '.Digest') if [[ -z "$DIGEST" || "$DIGEST" == "null" ]]; then echo "::error::Failed to read manifest digest for nearone/$REPO:$VERSION" exit 1 fi echo "${SAFE_NAME}_digest=$DIGEST" >> "$GITHUB_OUTPUT" done - name: Package contract artifact id: contract env: VERSION: ${{ steps.inputs.outputs.version }} run: | set -euo pipefail cd contract-artifact mv mpc_contract.wasm "mpc-contract-v${VERSION}.wasm" tar -czf "mpc-contract-v${VERSION}.tar.gz" "mpc-contract-v${VERSION}.wasm" CONTRACT_HASH=$(sha256sum "mpc-contract-v${VERSION}.wasm" | awk '{print $1}') echo "hash=$CONTRACT_HASH" >> "$GITHUB_OUTPUT" echo "archive=$PWD/mpc-contract-v${VERSION}.tar.gz" >> "$GITHUB_OUTPUT" - name: Compose release notes env: VERSION: ${{ steps.inputs.outputs.version }} NODE_DIGEST: ${{ steps.digests.outputs.mpc_node_digest }} NODE_GCP_DIGEST: ${{ steps.digests.outputs.mpc_node_gcp_digest }} LAUNCHER_DIGEST: ${{ steps.digests.outputs.mpc_launcher_digest }} CONTRACT_HASH: ${{ steps.contract.outputs.hash }} run: | set -euo pipefail cp release-notes-raw.md release-notes.md cat >> release-notes.md <