404 lines
15 KiB
YAML
404 lines
15 KiB
YAML
name: Promote to Production
|
||
|
||
on:
|
||
workflow_dispatch:
|
||
inputs:
|
||
commit_sha:
|
||
description: 'Specific commit SHA to cherry-pick (leave empty to promote everything in main)'
|
||
type: string
|
||
required: false
|
||
default: ''
|
||
worker_container_increment:
|
||
description: 'Worker container version increment. Defaults to patch.'
|
||
type: choice
|
||
options:
|
||
- major
|
||
- minor
|
||
- patch
|
||
required: false
|
||
default: 'patch'
|
||
|
||
jobs:
|
||
promote:
|
||
runs-on: ubuntu-latest
|
||
permissions:
|
||
contents: write
|
||
pull-requests: write
|
||
|
||
steps:
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
token: ${{ secrets.PROMOTE_PAT_TOKEN }}
|
||
|
||
- name: Configure Git
|
||
run: |
|
||
git config --global user.name "github-actions[bot]"
|
||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||
|
||
- name: Setup branches
|
||
run: |
|
||
# Ensure we have latest changes
|
||
git fetch origin
|
||
|
||
# Create production branch if it doesn't exist
|
||
if ! git show-ref --verify --quiet refs/remotes/origin/production; then
|
||
echo "Creating production branch from main"
|
||
git checkout -b production origin/main
|
||
git push origin production
|
||
else
|
||
echo "Production branch already exists"
|
||
git checkout production
|
||
git pull origin production
|
||
fi
|
||
|
||
# Ensure main is up to date
|
||
git checkout main
|
||
git pull origin main
|
||
|
||
- name: Cherry-pick specific commit
|
||
if: ${{ inputs.commit_sha != '' }}
|
||
run: |
|
||
echo "Cherry-picking specific commit: ${{ inputs.commit_sha }}"
|
||
|
||
# Validate commit exists in main
|
||
if ! git cat-file -e "${{ inputs.commit_sha }}^{commit}"; then
|
||
echo "Error: Commit ${{ inputs.commit_sha }} does not exist"
|
||
exit 1
|
||
fi
|
||
|
||
# Check if commit is in main branch
|
||
if ! git merge-base --is-ancestor "${{ inputs.commit_sha }}" main; then
|
||
echo "Error: Commit ${{ inputs.commit_sha }} is not in main branch"
|
||
exit 1
|
||
fi
|
||
|
||
# Switch to production and cherry-pick
|
||
git checkout production
|
||
|
||
# Check if commit is already in production
|
||
if git merge-base --is-ancestor "${{ inputs.commit_sha }}" production; then
|
||
echo "Commit ${{ inputs.commit_sha }} is already in production branch"
|
||
exit 0
|
||
fi
|
||
|
||
# Cherry-pick the commit
|
||
COMMIT_MSG=$(git log -1 --pretty=format:"%s" "${{ inputs.commit_sha }}")
|
||
echo "Cherry-picking: $COMMIT_MSG"
|
||
|
||
if git cherry-pick "${{ inputs.commit_sha }}"; then
|
||
echo "✅ Successfully cherry-picked commit"
|
||
git push origin production
|
||
echo "commit_promoted=true" >> $GITHUB_ENV
|
||
else
|
||
echo "❌ Cherry-pick failed - conflicts may need resolution"
|
||
git cherry-pick --abort
|
||
exit 1
|
||
fi
|
||
|
||
- name: Recreate production from main
|
||
if: ${{ inputs.commit_sha == '' }}
|
||
run: |
|
||
echo "Recreating production branch from main"
|
||
|
||
# Get current commit on main for summary
|
||
MAIN_COMMIT=$(git rev-parse main)
|
||
MAIN_COMMIT_MSG=$(git log -1 --pretty=format:"%s" main)
|
||
echo "Main branch is at: $MAIN_COMMIT ($MAIN_COMMIT_MSG)"
|
||
|
||
# Check if production branch exists remotely
|
||
if git show-ref --verify --quiet refs/remotes/origin/production; then
|
||
PROD_COMMIT=$(git rev-parse origin/production)
|
||
PROD_COMMIT_MSG=$(git log -1 --pretty=format:"%s" origin/production)
|
||
echo "Current production is at: $PROD_COMMIT ($PROD_COMMIT_MSG)"
|
||
|
||
# Check if they're already the same
|
||
if [ "$MAIN_COMMIT" = "$PROD_COMMIT" ]; then
|
||
echo "Production is already up to date with main"
|
||
echo "production_updated=false" >> $GITHUB_ENV
|
||
exit 0
|
||
fi
|
||
|
||
echo "Deleting existing production branch"
|
||
git push origin --delete production
|
||
|
||
# Also delete local production branch if it exists
|
||
if git show-ref --verify --quiet refs/heads/production; then
|
||
echo "Deleting local production branch"
|
||
git branch -D production
|
||
fi
|
||
else
|
||
echo "Production branch doesn't exist, will create new one"
|
||
fi
|
||
|
||
# Create new production branch from main
|
||
echo "Creating new production branch from main"
|
||
git checkout main
|
||
git checkout -b production
|
||
git push origin production
|
||
|
||
echo "✅ Successfully recreated production branch from main"
|
||
echo "production_updated=true" >> $GITHUB_ENV
|
||
echo "main_commit=$MAIN_COMMIT" >> $GITHUB_ENV
|
||
|
||
- name: Create summary
|
||
if: always()
|
||
run: |
|
||
echo "## Promotion Summary" >> $GITHUB_STEP_SUMMARY
|
||
|
||
if [ "${{ inputs.commit_sha }}" != "" ]; then
|
||
echo "### Single Commit Promotion" >> $GITHUB_STEP_SUMMARY
|
||
echo "**Target Commit:** \`${{ inputs.commit_sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||
|
||
if [ "${commit_promoted:-false}" = "true" ]; then
|
||
echo "✅ **Status:** Successfully promoted to production" >> $GITHUB_STEP_SUMMARY
|
||
else
|
||
echo "❌ **Status:** Promotion failed or commit already existed" >> $GITHUB_STEP_SUMMARY
|
||
fi
|
||
else
|
||
echo "### Bulk Promotion" >> $GITHUB_STEP_SUMMARY
|
||
|
||
if [ "${production_updated:-false}" = "true" ]; then
|
||
echo "✅ **Status:** Successfully updated production branch" >> $GITHUB_STEP_SUMMARY
|
||
else
|
||
echo "ℹ️ **Status:** No new commits to promote or production already up to date" >> $GITHUB_STEP_SUMMARY
|
||
fi
|
||
fi
|
||
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "### Branches" >> $GITHUB_STEP_SUMMARY
|
||
echo "- **Source:** main" >> $GITHUB_STEP_SUMMARY
|
||
echo "- **Target:** production" >> $GITHUB_STEP_SUMMARY
|
||
|
||
calculate-version:
|
||
runs-on: ubuntu-latest
|
||
needs: promote
|
||
outputs:
|
||
version: ${{ steps.calculate_version.outputs.version }}
|
||
previous_version: ${{ steps.calculate_version.outputs.previous_version }}
|
||
steps:
|
||
- name: Checkout production branch
|
||
uses: actions/checkout@v4
|
||
with:
|
||
ref: production
|
||
fetch-depth: 0
|
||
|
||
- name: Get latest release and calculate next version
|
||
id: calculate_version
|
||
run: |
|
||
# Get the latest release version from GitHub
|
||
LATEST_RELEASE=$(gh release view --json tagName --jq .tagName 2>/dev/null || echo "")
|
||
|
||
if [ -z "$LATEST_RELEASE" ]; then
|
||
echo "No previous releases found, starting at 0.0.0"
|
||
CURRENT_VERSION="0.0.0"
|
||
else
|
||
# Remove 'v' prefix if present
|
||
CURRENT_VERSION=${LATEST_RELEASE#v}
|
||
echo "Latest release: $LATEST_RELEASE (version: $CURRENT_VERSION)"
|
||
fi
|
||
|
||
# Split version into components
|
||
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
|
||
|
||
# Increment based on input
|
||
case "${{ inputs.worker_container_increment }}" in
|
||
major)
|
||
MAJOR=$((MAJOR + 1))
|
||
MINOR=0
|
||
PATCH=0
|
||
;;
|
||
minor)
|
||
MINOR=$((MINOR + 1))
|
||
PATCH=0
|
||
;;
|
||
patch)
|
||
PATCH=$((PATCH + 1))
|
||
;;
|
||
esac
|
||
|
||
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
|
||
echo "New version: $NEW_VERSION"
|
||
|
||
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||
echo "previous_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
build-and-push:
|
||
needs: calculate-version
|
||
strategy:
|
||
matrix:
|
||
include:
|
||
- arch: amd64
|
||
os: ubuntu-latest
|
||
- arch: arm64
|
||
os: linux-arm64
|
||
runs-on: ${{ matrix.os }}
|
||
permissions:
|
||
contents: write
|
||
packages: write
|
||
env:
|
||
VERSION: ${{ needs.calculate-version.outputs.version }}
|
||
REGISTRY: ghcr.io
|
||
|
||
steps:
|
||
- name: Checkout production branch
|
||
uses: actions/checkout@v4
|
||
with:
|
||
ref: production
|
||
fetch-depth: 0
|
||
|
||
- name: Install uv
|
||
uses: astral-sh/setup-uv@v5
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v2
|
||
|
||
- name: Log in to GitHub Container Registry
|
||
uses: docker/login-action@v2
|
||
with:
|
||
registry: ${{ env.REGISTRY }}
|
||
username: ${{ github.actor }}
|
||
password: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
- name: Build distributions
|
||
run: |
|
||
echo "Building full distribution for production release..."
|
||
make full-dist
|
||
|
||
- name: Build and push Worker images
|
||
run: |
|
||
# Build both worker and worker-base images
|
||
make docker VERSION=${{ env.VERSION }} ARCH=${{ matrix.arch }}
|
||
make docker-base VERSION=${{ env.VERSION }} ARCH=${{ matrix.arch }}
|
||
|
||
# Push to GHCR only
|
||
make publish-ghcr VERSION=${{ env.VERSION }} ARCH=${{ matrix.arch }}
|
||
|
||
push-manifest:
|
||
runs-on: ubuntu-latest
|
||
needs: [calculate-version, build-and-push]
|
||
permissions:
|
||
contents: write
|
||
packages: write
|
||
env:
|
||
VERSION: ${{ needs.calculate-version.outputs.version }}
|
||
REGISTRY: ghcr.io
|
||
steps:
|
||
- name: Checkout production branch
|
||
uses: actions/checkout@v4
|
||
with:
|
||
ref: production
|
||
fetch-depth: 0
|
||
|
||
- name: Log in to GitHub Container Registry
|
||
uses: docker/login-action@v2
|
||
with:
|
||
registry: ${{ env.REGISTRY }}
|
||
username: ${{ github.actor }}
|
||
password: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
- name: Push manifest to GHCR
|
||
working-directory: ./docker
|
||
run: |
|
||
make ghcr-manifest VERSION=${{ env.VERSION }}
|
||
make ghcr-manifest INSTALL_TOOLKITS=false VERSION=${{ env.VERSION }}
|
||
|
||
create-release:
|
||
runs-on: ubuntu-latest
|
||
needs: [calculate-version, push-manifest]
|
||
permissions:
|
||
contents: write
|
||
env:
|
||
VERSION: ${{ needs.calculate-version.outputs.version }}
|
||
PREVIOUS_VERSION: ${{ needs.calculate-version.outputs.previous_version }}
|
||
steps:
|
||
- name: Checkout production branch
|
||
uses: actions/checkout@v4
|
||
with:
|
||
ref: production
|
||
fetch-depth: 0
|
||
token: ${{ secrets.PROMOTE_PAT_TOKEN }}
|
||
|
||
- name: Generate release notes
|
||
id: generate_release_notes
|
||
run: |
|
||
echo "# Release ${{ env.VERSION }}" > release_notes.md
|
||
echo "" >> release_notes.md
|
||
echo "## Container Images" >> release_notes.md
|
||
echo "" >> release_notes.md
|
||
echo "### GitHub Container Registry (GHCR)" >> release_notes.md
|
||
echo "\`\`\`bash" >> release_notes.md
|
||
echo "docker pull ghcr.io/arcadeai/worker:${{ env.VERSION }}" >> release_notes.md
|
||
echo "docker pull ghcr.io/arcadeai/worker-base:${{ env.VERSION }}" >> release_notes.md
|
||
echo "\`\`\`" >> release_notes.md
|
||
echo "" >> release_notes.md
|
||
echo "## What's Changed" >> release_notes.md
|
||
echo "" >> release_notes.md
|
||
|
||
if [ "${{ inputs.commit_sha }}" != "" ]; then
|
||
# Case 1: Specific commit SHA to cherry-pick was provided
|
||
echo "### Cherry-picked commit:" >> release_notes.md
|
||
echo "" >> release_notes.md
|
||
git log -1 --pretty=format:"- **%h** %s (%an)" "${{ inputs.commit_sha }}" >> release_notes.md
|
||
echo "" >> release_notes.md
|
||
echo "_This release contains a single cherry-picked commit from main._" >> release_notes.md
|
||
else
|
||
# Case 2: Bulk promotion (production == main)
|
||
echo "### Commits in this release:" >> release_notes.md
|
||
echo "" >> release_notes.md
|
||
|
||
# Get the list of previous releases to find the last release
|
||
PREV_RELEASE_DATE=$(gh release list --limit 5 --json publishedAt,tagName --jq '.[1].publishedAt // empty' 2>/dev/null || echo "")
|
||
|
||
if [ -z "$PREV_RELEASE_DATE" ]; then
|
||
# First promotion ever
|
||
echo "**Initial release** - Recent commits from main branch:" >> release_notes.md
|
||
echo "" >> release_notes.md
|
||
git log -20 --pretty=format:"- **%h** %s (%an)" --reverse >> release_notes.md
|
||
else
|
||
# Show commits since the previous promotion
|
||
echo "Changes since previous release ($(echo $PREV_RELEASE_DATE | cut -d'T' -f1)):" >> release_notes.md
|
||
echo "" >> release_notes.md
|
||
git log --since="$PREV_RELEASE_DATE" --pretty=format:"- **%h** %s (%an)" --reverse >> release_notes.md
|
||
echo "" >> release_notes.md
|
||
|
||
# Fallback if no commits since last release (shouldn't happen, but just in case)
|
||
if [ $(git log --since="$PREV_RELEASE_DATE" --oneline | wc -l) -eq 0 ]; then
|
||
echo "_No commits found since previous release. Showing recent commits:_" >> release_notes.md
|
||
echo "" >> release_notes.md
|
||
git log -10 --pretty=format:"- **%h** %s (%an)" --reverse >> release_notes.md
|
||
fi
|
||
fi
|
||
echo "" >> release_notes.md
|
||
echo "_This release includes all changes from the main branch._" >> release_notes.md
|
||
fi
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
- name: Create GitHub Release
|
||
uses: actions/create-release@v1
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
with:
|
||
tag_name: v${{ env.VERSION }}
|
||
release_name: Release v${{ env.VERSION }}
|
||
body_path: release_notes.md
|
||
draft: false
|
||
prerelease: false
|
||
|
||
- name: Update workflow summary
|
||
run: |
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "## 🚀 Container Release" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "### Version Information" >> $GITHUB_STEP_SUMMARY
|
||
echo "- **Previous Version:** v${{ env.PREVIOUS_VERSION }}" >> $GITHUB_STEP_SUMMARY
|
||
echo "- **New Version:** v${{ env.VERSION }}" >> $GITHUB_STEP_SUMMARY
|
||
echo "- **Increment Type:** ${{ inputs.worker_container_increment }}" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "### Container Images Published to GHCR" >> $GITHUB_STEP_SUMMARY
|
||
echo "- ✅ ghcr.io/arcadeai/worker:${{ env.VERSION }}" >> $GITHUB_STEP_SUMMARY
|
||
echo "- ✅ ghcr.io/arcadeai/worker-base:${{ env.VERSION }}" >> $GITHUB_STEP_SUMMARY
|