-
Notifications
You must be signed in to change notification settings - Fork 22
ci: gh action release automation #2203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
SimonRastikian
wants to merge
26
commits into
main
Choose a base branch
from
simon/gh-action-release-automation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
a554d5d
Automatic release tag creation on release branch merge
SimonRastikian bc3dff9
Renaming fil
SimonRastikian d52b961
Adding automatic Running workflows
SimonRastikian 0b010db
Trigering workflows and storing the digest and image id
SimonRastikian 7abcf95
Building the contract
SimonRastikian f783bf0
Contract hash
SimonRastikian ca7fe4e
create the draft release
SimonRastikian f3ee756
Change the title of the draft
SimonRastikian d1bbd5b
tabs issues
SimonRastikian b69168a
Cleaning up
SimonRastikian dae17c3
Do not use cliff for changelog
SimonRastikian 1c0c5eb
No arbitrary sleep instead use gh watch
SimonRastikian 072c4f6
Better waiting
SimonRastikian 25037f4
Checking versioning and no inspect --raw for skopeo
SimonRastikian 5e5c356
Release notes is not empty
SimonRastikian 64c47d6
Making zizmor pass
SimonRastikian 44a66cd
Avoid creating the tag via the GitHub API
SimonRastikian 1b35dce
Fix critical issue
SimonRastikian 23b7ffc
Merge branch 'main' into simon/gh-action-release-automation
SimonRastikian 6632cff
Trigger on creation of tag or on merge PR
SimonRastikian e07d28a
lookup only
SimonRastikian ae272ce
Merge branch 'main' into simon/gh-action-release-automation
SimonRastikian fc2a03a
Merge branch 'main' into simon/gh-action-release-automation
SimonRastikian d7ebef2
Update .github/workflows/release.yml
SimonRastikian d6bf074
Merge branch 'main' into simon/gh-action-release-automation
SimonRastikian 8475664
Install cargo near
SimonRastikian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,366 @@ | ||
| name: Release | ||
|
|
||
| # Prevent duplicate runs for the same workflow + branch combination. | ||
| # If a new run starts while one is in progress, the older run is cancelled. | ||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| # Trigger this workflow when: | ||
| # 1. A pull request targeting main is closed (filters to merged release PRs via job `if`). | ||
| # 2. A version tag (v*) is pushed directly (e.g. `git push origin v3.6.0`). | ||
| on: | ||
| push: | ||
| tags: | ||
| - '*.*.*' | ||
| pull_request: | ||
| branches: | ||
| - main | ||
| types: | ||
| - closed | ||
|
|
||
| jobs: | ||
| release-info: | ||
| name: "Extract release info" | ||
| # Run when a version tag is pushed OR a merged release/v* PR is closed. | ||
| if: >- | ||
| github.event_name == 'push' | ||
| || (github.event.pull_request.merged == true | ||
| && startsWith(github.event.pull_request.head.ref, 'release/v')) | ||
| runs-on: warp-ubuntu-2404-x64-2x | ||
| # Write permission on contents is required to push a new tag (PR path). | ||
| permissions: | ||
| contents: write | ||
|
|
||
| # Expose version and commit hash to downstream jobs. | ||
| outputs: | ||
| version: ${{ steps.version.outputs.version }} | ||
| tag: ${{ steps.version.outputs.tag }} | ||
| short-sha: ${{ steps.version.outputs.short-sha }} | ||
|
|
||
| steps: | ||
| # Extract the semver version from the tag ref or branch name. | ||
| # Tag push: refs/tags/v3.6.0 -> version="3.6.0", tag="v3.6.0" | ||
| # PR merge: release/v3.6.0 -> version="3.6.0", tag="v3.6.0" | ||
| # Also compute the 7-char commit hash used as the Docker source tag. | ||
| - name: Extract version | ||
| id: version | ||
| env: | ||
| EVENT: ${{ github.event_name }} | ||
| TAG_REF: ${{ github.ref }} | ||
| TAG_SHA: ${{ github.sha }} | ||
| BRANCH: ${{ github.event.pull_request.head.ref }} | ||
| PR_SHA: ${{ github.event.pull_request.merge_commit_sha }} | ||
| run: | | ||
| if [[ "$EVENT" == "push" ]]; then | ||
| VERSION="${TAG_REF#refs/tags/v}" | ||
| SHA="$TAG_SHA" | ||
| else | ||
| VERSION="${BRANCH#release/v}" | ||
| SHA="$PR_SHA" | ||
| fi | ||
| if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then | ||
| echo "::error::Invalid semver: $VERSION" | ||
| exit 1 | ||
| fi | ||
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | ||
| echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT" | ||
| echo "short-sha=${SHA::7}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # Create the tag via the GitHub API so we don't need git credentials. | ||
| # Points the tag at the PR's merge commit SHA. | ||
| # Skipped on tag push since the tag already exists. | ||
| - name: Create tag | ||
| if: github.event_name == 'pull_request' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| TAG: ${{ steps.version.outputs.tag }} | ||
| SHA: ${{ github.event.pull_request.merge_commit_sha }} | ||
| run: | | ||
| gh api "repos/$GITHUB_REPOSITORY/git/refs" \ | ||
| --method POST \ | ||
| -f ref="refs/tags/$TAG" \ | ||
| -f sha="$SHA" | ||
|
|
||
| # Trigger the existing Docker retag workflows for each image. | ||
| # These are the same workflows you'd normally trigger manually from the Actions UI. | ||
| retag-docker-images: | ||
| name: "Retag Docker images" | ||
| needs: release-info | ||
| runs-on: warp-ubuntu-2404-x64-2x | ||
| permissions: | ||
| actions: write | ||
|
|
||
| # Expose image digests to downstream jobs. | ||
| outputs: | ||
| mpc_node_manifest_digest: ${{ steps.digests.outputs.mpc_node_manifest_digest }} | ||
| mpc_node_image_id: ${{ steps.digests.outputs.mpc_node_image_id }} | ||
| mpc_node_gcp_manifest_digest: ${{ steps.digests.outputs.mpc_node_gcp_manifest_digest }} | ||
| mpc_node_gcp_image_id: ${{ steps.digests.outputs.mpc_node_gcp_image_id }} | ||
| mpc_launcher_manifest_digest: ${{ steps.digests.outputs.mpc_launcher_manifest_digest }} | ||
| mpc_launcher_image_id: ${{ steps.digests.outputs.mpc_launcher_image_id }} | ||
|
|
||
| steps: | ||
| # Dispatch each retag workflow and immediately capture its run ID. | ||
| # A brief sleep after each dispatch lets the run appear in the API | ||
| # before we query for it. | ||
| - name: Retag mpc-launcher | ||
| id: launcher | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| REPO: ${{ github.repository }} | ||
| SHORT_SHA: ${{ needs.release-info.outputs.short-sha }} | ||
| VERSION: ${{ needs.release-info.outputs.version }} | ||
| run: | | ||
| gh workflow run docker_launcher_release.yml \ | ||
| --repo "$REPO" \ | ||
| -f source-tag="main-${SHORT_SHA}" \ | ||
| -f release-tag="${VERSION}" | ||
| sleep 5 | ||
| RUN_ID=$(gh run list --workflow=docker_launcher_release.yml --repo "$REPO" --limit=1 --json databaseId -q '.[0].databaseId') | ||
| echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Retag mpc-node-gcp | ||
| id: node-gcp | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| REPO: ${{ github.repository }} | ||
| SHORT_SHA: ${{ needs.release-info.outputs.short-sha }} | ||
| VERSION: ${{ needs.release-info.outputs.version }} | ||
| run: | | ||
| gh workflow run docker_node_release.yml \ | ||
| --repo "$REPO" \ | ||
| -f source-tag="main-${SHORT_SHA}" \ | ||
| -f release-tag="${VERSION}" \ | ||
| -f repository="mpc-node-gcp" | ||
| sleep 5 | ||
| RUN_ID=$(gh run list --workflow=docker_node_release.yml --repo "$REPO" --limit=1 --json databaseId -q '.[0].databaseId') | ||
| echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Retag mpc-node | ||
| id: node | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| REPO: ${{ github.repository }} | ||
| SHORT_SHA: ${{ needs.release-info.outputs.short-sha }} | ||
| VERSION: ${{ needs.release-info.outputs.version }} | ||
| run: | | ||
| gh workflow run docker_node_release.yml \ | ||
| --repo "$REPO" \ | ||
| -f source-tag="main-${SHORT_SHA}" \ | ||
| -f release-tag="${VERSION}" \ | ||
| -f repository="mpc-node" | ||
| sleep 5 | ||
| RUN_ID=$(gh run list --workflow=docker_node_release.yml --repo "$REPO" --limit=1 --json databaseId -q '.[0].databaseId') | ||
| echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # Wait for all three dispatched runs to complete. | ||
| # --exit-status makes gh run watch fail if the workflow itself fails. | ||
| - name: Wait for retag workflows | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| REPO: ${{ github.repository }} | ||
| LAUNCHER_RUN: ${{ steps.launcher.outputs.run_id }} | ||
| NODE_GCP_RUN: ${{ steps.node-gcp.outputs.run_id }} | ||
| NODE_RUN: ${{ steps.node.outputs.run_id }} | ||
| run: | | ||
| for RUN_ID in $LAUNCHER_RUN $NODE_GCP_RUN $NODE_RUN; do | ||
| gh run watch "$RUN_ID" --repo "$REPO" --exit-status | ||
| done | ||
|
|
||
| - name: Install skopeo | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y skopeo | ||
|
|
||
| # Inspect each retagged image and store Manifest digest and Image ID as outputs. | ||
| - name: Get image digests | ||
| id: digests | ||
| env: | ||
| RELEASE_TAG: ${{ needs.release-info.outputs.version }} | ||
| run: | | ||
| for REPO in mpc-launcher mpc-node-gcp mpc-node; do | ||
| SAFE_NAME="${REPO//-/_}" | ||
| MANIFEST_DIGEST=$(skopeo inspect docker://nearone/$REPO:$RELEASE_TAG | jq -r '.Digest') | ||
| # Use --override-os/--override-arch to resolve multi-arch manifest lists | ||
| # to the platform-specific image manifest, where .config.digest is the Image ID. | ||
| IMAGE_ID=$(skopeo inspect --raw --override-os linux --override-arch amd64 docker://nearone/$REPO:$RELEASE_TAG | jq -r '.config.digest') | ||
| echo "${SAFE_NAME}_manifest_digest=$MANIFEST_DIGEST" >> "$GITHUB_OUTPUT" | ||
| echo "${SAFE_NAME}_image_id=$IMAGE_ID" >> "$GITHUB_OUTPUT" | ||
| done | ||
|
|
||
| # Build the smart contract using reproducible builds, rename the artifacts | ||
| # with the release version, and package them into a .tar.gz archive. | ||
| # This job runs in parallel with retag-docker-images since both only | ||
| # depend on release-info. | ||
| build-contract: | ||
| name: "Build contract" | ||
| needs: release-info | ||
| # Uses a 16x runner because the reproducible contract build is resource-intensive. | ||
| runs-on: warp-ubuntu-2404-x64-16x | ||
| permissions: | ||
| contents: read | ||
|
|
||
| # Expose contract hash to downstream jobs. | ||
| outputs: | ||
| contract-hash: ${{ steps.contract-hash.outputs.sha256 }} | ||
|
|
||
| steps: | ||
| # Checkout the release tag so we build the correct code in both trigger paths. | ||
| - name: Checkout | ||
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 | ||
| with: | ||
| ref: ${{ needs.release-info.outputs.tag }} | ||
| persist-credentials: false | ||
|
|
||
| # Rust cache: lookup-only + save-if:false keeps this step inert so that | ||
| # no poisoned cache content can influence release artifacts (zizmor cache-poisoning). | ||
| # The actual build happens inside a Docker container (reproducible-wasm) | ||
| # so host caching provides limited benefit here anyway. | ||
| - name: Cache Rust dependencies | ||
| uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 | ||
| with: | ||
| save-if: false | ||
| cache-provider: "warpbuild" | ||
| lookup-only: true | ||
|
|
||
| - name: Install cargo-binstall | ||
| uses: taiki-e/install-action@d4422f254e595ee762a758628fe4f16ce050fa2e # v2.67.28 | ||
| with: | ||
| tool: cargo-binstall | ||
|
|
||
| - name: Install cargo-near | ||
| run: | | ||
| sudo apt-get update && sudo apt-get install --assume-yes libudev-dev | ||
| cargo binstall --force --no-confirm --locked cargo-near@0.19.1 --pkg-url="{ repo }/releases/download/{ name }-v{ version }/{ name }-{ target }.{ archive-format }" | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Install wasm-opt | ||
| run: | | ||
| cargo binstall --force --no-confirm --locked wasm-opt@0.116.1 | ||
| echo "${HOME}/.cargo/bin" >> $GITHUB_PATH | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| # Build the contract with reproducible-wasm to ensure deterministic output. | ||
| # This produces mpc_contract.wasm and mpc_contract_abi.json | ||
| # under target/near/mpc_contract/. | ||
| - name: Build contract | ||
| run: cargo near build reproducible-wasm --manifest-path crates/contract/Cargo.toml | ||
|
Comment on lines
+249
to
+250
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how will this work without installing cargo-near first? |
||
|
|
||
| # Compute the SHA-256 hash of the compiled contract .wasm before renaming. | ||
| # Used later in the GitHub release notes so users can verify contract integrity. | ||
| - name: Compute contract hash | ||
| id: contract-hash | ||
| run: | | ||
| CONTRACT_HASH=$(sha256sum target/near/mpc_contract/mpc_contract.wasm | awk '{print $1}') | ||
| echo "sha256=$CONTRACT_HASH" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # Rename the build outputs to include the release version and | ||
| # compress them into a single .tar.gz archive for distribution. | ||
| # e.g. mpc-contract-v3.6.0.wasm, mpc-contract-v3.6.0-abi.json | ||
| - name: Package contract artifacts | ||
| env: | ||
| VERSION: ${{ needs.release-info.outputs.version }} | ||
| run: | | ||
| cd target/near/mpc_contract/ | ||
| mv mpc_contract.wasm "mpc-contract-v${VERSION}.wasm" | ||
| mv mpc_contract_abi.json "mpc-contract-v${VERSION}-abi.json" | ||
| tar -czf "mpc-contract-v${VERSION}.tar.gz" "mpc-contract-v${VERSION}.wasm" "mpc-contract-v${VERSION}-abi.json" | ||
|
|
||
| # Upload the .tar.gz as a GitHub Actions artifact so downstream jobs | ||
| # (e.g. creating the GitHub release) can download and attach it. | ||
| - name: Upload contract artifact | ||
| uses: actions/upload-artifact@ea165f8d65b6db9a6b7e75b195db6a7b2bcf4bac # v4.6.2 | ||
| with: | ||
| name: mpc-contract | ||
| path: target/near/mpc_contract/mpc-contract-v${{ needs.release-info.outputs.version }}.tar.gz | ||
|
|
||
| # Create a draft GitHub release with the changelog, Docker image info, | ||
| # and the contract artifact attached. | ||
| # Runs after both retag-docker-images and build-contract complete. | ||
| create-release: | ||
| name: "Create draft GitHub release" | ||
| needs: [release-info, retag-docker-images, build-contract] | ||
| runs-on: warp-ubuntu-2404-x64-2x | ||
| permissions: | ||
| contents: write | ||
|
|
||
| steps: | ||
| # Checkout the release tag to read the correct CHANGELOG.md. | ||
| - name: Checkout | ||
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 | ||
| with: | ||
| ref: ${{ needs.release-info.outputs.tag }} | ||
| persist-credentials: false | ||
|
|
||
| # Extract the changelog section for this version from CHANGELOG.md. | ||
| # Sections are delimited by "## [<version>]" headers, so we print | ||
| # everything between the current version's header and the next one. | ||
| - name: Extract changelog | ||
| env: | ||
| VERSION: ${{ needs.release-info.outputs.version }} | ||
| run: | | ||
| 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 found for version ${VERSION} in CHANGELOG.md" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Download the contract .tar.gz uploaded by the build-contract job. | ||
| - name: Download contract artifact | ||
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | ||
| with: | ||
| name: mpc-contract | ||
|
|
||
| # Compose the full release body: changelog + Docker images + contract hash. | ||
| - name: Compose release body | ||
| env: | ||
| VERSION: ${{ needs.release-info.outputs.version }} | ||
| NODE_MANIFEST: ${{ needs.retag-docker-images.outputs.mpc_node_manifest_digest }} | ||
| NODE_IMAGE_ID: ${{ needs.retag-docker-images.outputs.mpc_node_image_id }} | ||
| NODE_GCP_MANIFEST: ${{ needs.retag-docker-images.outputs.mpc_node_gcp_manifest_digest }} | ||
| NODE_GCP_IMAGE_ID: ${{ needs.retag-docker-images.outputs.mpc_node_gcp_image_id }} | ||
| LAUNCHER_MANIFEST: ${{ needs.retag-docker-images.outputs.mpc_launcher_manifest_digest }} | ||
| LAUNCHER_IMAGE_ID: ${{ needs.retag-docker-images.outputs.mpc_launcher_image_id }} | ||
| CONTRACT_HASH: ${{ needs.build-contract.outputs.contract-hash }} | ||
| run: | | ||
| cp release-notes-raw.md release-notes.md | ||
| cat >> release-notes.md <<EOF | ||
|
|
||
| ## Docker images | ||
|
|
||
| nearone/mpc-node:${VERSION} | ||
| Manifest digest: ${NODE_MANIFEST} | ||
| Image ID: ${NODE_IMAGE_ID} | ||
|
|
||
| nearone/mpc-node-gcp:${VERSION} | ||
| Manifest digest: ${NODE_GCP_MANIFEST} | ||
| Image ID: ${NODE_GCP_IMAGE_ID} | ||
|
|
||
| nearone/mpc-launcher:${VERSION} | ||
| Manifest digest: ${LAUNCHER_MANIFEST} | ||
| Image ID: ${LAUNCHER_IMAGE_ID} | ||
|
|
||
| ## MPC contract | ||
|
|
||
| digest: sha256:${CONTRACT_HASH} | ||
| EOF | ||
|
|
||
| # Create a draft release on GitHub using the gh CLI. | ||
| # --draft: the release is not published yet, allowing manual review before going live. | ||
| # --notes-file: uses the composed release notes (changelog + Docker images + contract hash). | ||
| # The contract .tar.gz is passed as a positional argument, which gh attaches as a release asset. | ||
| - name: Create draft release | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| TAG: ${{ needs.release-info.outputs.tag }} | ||
| VERSION: ${{ needs.release-info.outputs.version }} | ||
| run: | | ||
| gh release create "$TAG" \ | ||
| --repo "$GITHUB_REPOSITORY" \ | ||
| --title "MPC ${VERSION}" \ | ||
| --notes-file release-notes.md \ | ||
| --draft \ | ||
| "mpc-contract-v${VERSION}.tar.gz" | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.