Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 190 additions & 33 deletions .github/workflows/ci-publish.yml
Original file line number Diff line number Diff line change
@@ -1,38 +1,141 @@
name: CI Publish

on:
push:
tags: [ 'v*.*.*' ]
workflow_dispatch:
inputs:
sha:
description: 'Commit sha to release (must be an ancestor of main for a live run)'
required: true
type: string
dry_run:
description: 'Dry run: build and verify without pushing or publishing'
required: false
default: true
type: boolean

concurrency:
group: publish
cancel-in-progress: false

permissions:
contents: read
packages: read

env:
IMAGE_NAME: docker.io/bitshock/switchgear

jobs:
preflight:
name: Preflight
runs-on: ubuntu-latest
container:
image: ghcr.io/${{ github.repository }}/ci-rust:1.97.1
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

outputs:
version: ${{ steps.version.outputs.version }}
dry_run: ${{ steps.version.outputs.dry_run }}
have_docker: ${{ steps.creds.outputs.have_docker }}
have_crates: ${{ steps.creds.outputs.have_crates }}

steps:
- name: Checkout release commit
uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
fetch-depth: 0

- name: Configure git
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

- name: Resolve workspace version
id: version
env:
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version')
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
echo "::error::could not resolve workspace version from cargo metadata"
exit 1
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "dry_run=${DRY_RUN}" >> "$GITHUB_OUTPUT"

- name: Detect credentials
id: creds
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -euo pipefail
if [ -n "${DOCKERHUB_TOKEN:-}" ] && [ -n "${DOCKERHUB_USERNAME:-}" ]; then
echo "have_docker=true" >> "$GITHUB_OUTPUT"
else
echo "have_docker=false" >> "$GITHUB_OUTPUT"
fi
if [ -n "${CARGO_REGISTRY_TOKEN:-}" ]; then
echo "have_crates=true" >> "$GITHUB_OUTPUT"
else
echo "have_crates=false" >> "$GITHUB_OUTPUT"
fi

- name: Assert sha is an ancestor of main
if: ${{ !inputs.dry_run }}
env:
SHA: ${{ inputs.sha }}
run: |
set -euo pipefail
git fetch --no-tags origin main
if ! git merge-base --is-ancestor "$SHA" FETCH_HEAD; then
echo "::error::$SHA is not an ancestor of main"
exit 1
fi
echo "$SHA is an ancestor of main"

- name: Summary
env:
VERSION: ${{ steps.version.outputs.version }}
DRY_RUN: ${{ steps.version.outputs.dry_run }}
HAVE_DOCKER: ${{ steps.creds.outputs.have_docker }}
HAVE_CRATES: ${{ steps.creds.outputs.have_crates }}
SHA: ${{ inputs.sha }}
run: |
set -euo pipefail
{
echo "### Publish \`$VERSION\`"
echo
echo "| | |"
echo "|---|---|"
echo "| sha | \`$SHA\` |"
echo "| dry run | \`$DRY_RUN\` |"
echo "| docker credentials | \`$HAVE_DOCKER\` |"
echo "| crates.io credentials | \`$HAVE_CRATES\` |"
} >> "$GITHUB_STEP_SUMMARY"

tests:
name: Rust Tests For Publish
uses: ./.github/workflows/rust-tests-workflow.yml

# tests:
# name: Call Rust Tests
# runs-on: ubuntu-latest
# steps:
# - name: Skip tests (temporary)
# run: echo "Tests skipped for debugging"

docker:
name: Docker Publish
needs: tests
needs: [ preflight, tests ]
runs-on: ubuntu-latest
if: vars.PUBLISH_DRY_RUN == 'true' || github.repository == 'bitshock-src/switchgear'

steps:
- name: Checkout repository
- name: Checkout release commit
uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
if: vars.PUBLISH_DRY_RUN != 'true'
if: needs.preflight.outputs.have_docker == 'true' && needs.preflight.outputs.dry_run == 'false'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
Expand All @@ -44,45 +147,99 @@ jobs:
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=raw,value=${{ needs.preflight.outputs.version }}
type=raw,value=latest

- name: Log dry run
if: vars.PUBLISH_DRY_RUN == 'true'
- name: Log skipped push
if: ${{ !(needs.preflight.outputs.have_docker == 'true' && needs.preflight.outputs.dry_run == 'false') }}
env:
TAGS: ${{ steps.meta.outputs.tags }}
HAVE_DOCKER: ${{ needs.preflight.outputs.have_docker }}
DRY_RUN: ${{ needs.preflight.outputs.dry_run }}
run: |
echo "== DOCKER PUSH DRY RUN =="
echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n' | while read -r tag; do
echo " - $tag"
set -euo pipefail
if [ "$DRY_RUN" != "true" ] && [ "$HAVE_DOCKER" != "true" ]; then
echo "::warning::DOCKERHUB_USERNAME/DOCKERHUB_TOKEN are not set, building without pushing"
fi
echo "== DOCKER PUSH SKIPPED =="
printf '%s\n' "$TAGS" | while read -r tag; do
if [ -n "$tag" ]; then
echo " - $tag"
fi
done

- name: Build and push multi-arch image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ vars.PUBLISH_DRY_RUN != 'true' }}
push: ${{ needs.preflight.outputs.have_docker == 'true' && needs.preflight.outputs.dry_run == 'false' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}



cargo:
name: Cargo Publish
needs: tests
needs: [ preflight, docker ]
runs-on: ubuntu-latest
environment: publish
container:
image: ghcr.io/bitshock-src/switchgear/ci-rust:1.0.0
image: ghcr.io/${{ github.repository }}/ci-rust:1.97.1
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

if: vars.PUBLISH_DRY_RUN == 'true' || github.repository == 'bitshock-src/switchgear'
steps:
- name: Checkout repository
- name: Checkout release commit
uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}

- name: Publish dry run
if: vars.PUBLISH_DRY_RUN == 'true'
run: cargo publish --dry-run

- name: Publish crates.io
if: vars.PUBLISH_DRY_RUN != 'true'
run: cargo publish
- name: Publish to crates.io
env:
HAVE_CRATES: ${{ needs.preflight.outputs.have_crates }}
DRY_RUN: ${{ needs.preflight.outputs.dry_run }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -euo pipefail
if [ "$HAVE_CRATES" = "true" ] && [ "$DRY_RUN" = "false" ]; then
cargo publish --workspace
else
if [ "$DRY_RUN" != "true" ] && [ "$HAVE_CRATES" != "true" ]; then
echo "::warning::CARGO_REGISTRY_TOKEN is not set, running a dry run instead"
fi
cargo publish --workspace --dry-run
fi

git-tag:
name: Git Tag
needs: [ preflight, cargo ]
runs-on: ubuntu-latest
if: needs.preflight.outputs.dry_run == 'false'
permissions:
contents: write

steps:
- name: Checkout release commit
uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
fetch-depth: 0

- name: Tag release
env:
VERSION: ${{ needs.preflight.outputs.version }}
SHA: ${{ inputs.sha }}
run: |
set -euo pipefail
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'

if git ls-remote --exit-code --tags origin "v$VERSION" > /dev/null 2>&1; then
echo "::error::tag v$VERSION already exists on origin"
exit 1
fi

git tag "v$VERSION" "$SHA"
git push origin "v$VERSION"

echo "### Tagged \`v$VERSION\` at \`$SHA\`" >> "$GITHUB_STEP_SUMMARY"
3 changes: 2 additions & 1 deletion .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ on:

permissions:
contents: read
packages: read
statuses: write

jobs:
tests:
name: Rust Tests For Branch Push
uses: ./.github/workflows/rust-tests-workflow.yml
uses: ./.github/workflows/rust-tests-workflow.yml
104 changes: 104 additions & 0 deletions .github/workflows/release-branch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Release Branch

on:
workflow_dispatch:
inputs:
version:
description: 'Explicit semver (n.n.n). Leave blank to bump by level.'
required: false
type: string
level:
description: 'Bump level, used when version is blank'
required: false
default: patch
type: choice
options:
- patch
- minor
- major

permissions:
contents: write
packages: read

jobs:
release-branch:
name: Create Release Branch
runs-on: ubuntu-latest
container:
image: ghcr.io/${{ github.repository }}/ci-rust:1.97.1
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

steps:
- name: Checkout main
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0

- name: Configure git
run: |
set -euo pipefail
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'

- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

- name: Bump workspace version
id: bump
env:
VERSION: ${{ inputs.version }}
LEVEL: ${{ inputs.level }}
run: |
set -euo pipefail
if [ -n "$VERSION" ]; then
cargo set-version --workspace "$VERSION"
else
cargo set-version --workspace --bump "$LEVEL"
fi
cargo build
NEW=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version')
echo "version=${NEW}" >> "$GITHUB_OUTPUT"
echo "branch=release-${NEW}" >> "$GITHUB_OUTPUT"

- name: Create release branch
env:
NEW: ${{ steps.bump.outputs.version }}
BRANCH: ${{ steps.bump.outputs.branch }}
run: |
set -euo pipefail

if git ls-remote --exit-code --heads origin "$BRANCH" > /dev/null 2>&1; then
echo "::error::branch $BRANCH already exists on origin"
exit 1
fi

if git diff --quiet -- Cargo.lock '*Cargo.toml'; then
echo "::error::version is already $NEW, nothing to commit"
exit 1
fi

git checkout -b "$BRANCH"
git add -- Cargo.lock '*Cargo.toml'
git commit -m "release $NEW"
git push origin "$BRANCH"

- name: Summary
env:
NEW: ${{ steps.bump.outputs.version }}
BRANCH: ${{ steps.bump.outputs.branch }}
run: |
set -euo pipefail
{
echo "### Release branch \`$BRANCH\`"
echo
echo "Version \`$NEW\` off \`main\`."
echo
echo "[Open the pull request]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/compare/main...$BRANCH?expand=1) to trigger CI on it."
} >> "$GITHUB_STEP_SUMMARY"
Loading
Loading