Files
ziti/.github/workflows/release-quickstart.yml

227 lines
9.8 KiB
YAML

name: Release Quickstart Workflow
# Publishes the openziti/quickstart Docker image and deploys the get.openziti.io
# CloudFront function for a given release tag.
#
# Triggers:
# - release: published (new release published -> build + push the versioned image, move :latest, deploy CloudFront)
# - release: edited (e.g. "Latest release" moved -> re-point :latest and CloudFront only; the image is NOT rebuilt)
# - workflow_dispatch (manual; required `release_tag` input)
#
# Both jobs are idempotent and independently re-runnable from the Actions UI.
on:
release:
types: [published, edited]
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag to (re)publish, e.g. v1.6.7'
required: true
type: string
force_latest:
description: 'Force-move :latest to this tag even if GitHub does not mark this release as latest'
required: false
type: boolean
default: false
force_cloudfront:
description: 'Force-deploy the get.openziti.io CloudFront function even if GitHub does not mark this release as latest'
required: false
type: boolean
default: false
permissions:
contents: read
jobs:
resolve-tag:
name: Resolve Release Tag
# only run on the official upstream repo, or on a fork that overrides the
# quickstart image repo to a destination it can push to
if: github.repository_owner == 'openziti' || vars.ZITI_QUICKSTART_IMAGE != ''
runs-on: ubuntu-24.04
outputs:
tag: ${{ steps.resolve.outputs.tag }}
sha: ${{ steps.resolve.outputs.sha }}
is_latest: ${{ steps.resolve.outputs.is_latest }}
eligible: ${{ steps.resolve.outputs.eligible }}
steps:
- name: Resolve release tag and commit SHA
id: resolve
env:
DISPATCH_TAG: ${{ github.event.inputs.release_tag }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -o errexit
set -o pipefail
if [[ -n "${DISPATCH_TAG:-}" ]]; then
TAG="${DISPATCH_TAG}"
elif [[ -n "${RELEASE_TAG:-}" ]]; then
TAG="${RELEASE_TAG}"
else
echo "ERROR: no release tag provided (neither dispatch input nor release event)" >&2
exit 1
fi
if ! [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "INFO: '${TAG}' is not a release semver (prerelease/draft/other edit); skipping image + CloudFront."
echo "eligible=false" | tee -a "$GITHUB_OUTPUT"
echo "is_latest=false" | tee -a "$GITHUB_OUTPUT"
exit 0
fi
echo "eligible=true" | tee -a "$GITHUB_OUTPUT"
# resolve the commit the tag points at; this is the snapshot we will
# check out for both jobs so the Dockerfile, routes.yml, and python
# deploy script all come from the released revision rather than main
SHA="$(gh api "repos/${GITHUB_REPOSITORY}/git/refs/tags/${TAG}" --jq '.object.sha')"
if [[ -z "${SHA}" || "${SHA}" == "null" ]]; then
echo "ERROR: failed to resolve commit SHA for tag '${TAG}'" >&2
exit 1
fi
# if the tag is annotated, dereference one level to the commit object
OBJ_TYPE="$(gh api "repos/${GITHUB_REPOSITORY}/git/tags/${SHA}" --jq '.object.type' 2>/dev/null || echo "commit")"
if [[ "${OBJ_TYPE}" == "commit" ]]; then
SHA="$(gh api "repos/${GITHUB_REPOSITORY}/git/tags/${SHA}" --jq '.object.sha' 2>/dev/null || echo "${SHA}")"
fi
echo "tag=${TAG}" | tee -a "$GITHUB_OUTPUT"
echo "sha=${SHA}" | tee -a "$GITHUB_OUTPUT"
# Determine whether this tag is the repo's "Latest release" (the GitHub
# latest-release endpoint excludes prereleases and drafts). The
# CloudFront job gates on this so an older maintenance release does not
# re-point the shared get.openziti.io function.
LATEST_TAG="$(gh release view --repo "${GITHUB_REPOSITORY}" --json tagName --jq '.tagName' 2>/dev/null || true)"
if [[ "${TAG}" == "${LATEST_TAG}" ]]; then
echo "INFO: ${TAG} is GitHub's latest release."
echo "is_latest=true" | tee -a "$GITHUB_OUTPUT"
else
echo "INFO: GitHub's latest release is '${LATEST_TAG:-<unknown>}', not ${TAG}."
echo "is_latest=false" | tee -a "$GITHUB_OUTPUT"
fi
release-quickstart-image:
# NOTE: the job display name "Release Quickstart Job" is referenced by name
# in .github/workflows/promote-downstreams.yml's `ignore-checks`. Don't
# rename without updating that file too.
name: Release Quickstart Job
needs: resolve-tag
# eligible, AND (a manual dispatch, OR a publish, OR it's now the latest).
# A non-latest edit (e.g. a typo fix on an old release) skips the job entirely.
if: >-
needs.resolve-tag.outputs.eligible == 'true'
&& (github.event_name != 'release'
|| github.event.action == 'published'
|| needs.resolve-tag.outputs.is_latest == 'true')
runs-on: ubuntu-24.04
env:
ZITI_QUICKSTART_IMAGE: ${{ vars.ZITI_QUICKSTART_IMAGE || 'docker.io/openziti/quickstart' }}
steps:
- name: Debug action
uses: hmarr/debug-action@v3.0.0
# Dual checkout: workflow-src/ holds the orchestrator script (may not
# exist on older tags); release-src/ holds the Dockerfile + image
# assets at the release commit.
# ref: on release events github.ref is the tag (would defeat the
# dual-checkout), so force the default branch. On workflow_dispatch
# use github.ref so a human can dispatch from a feature branch to
# test orchestrator changes.
- name: Checkout Workflow Scripts (current branch)
uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'release' && github.event.repository.default_branch || github.ref }}
path: workflow-src
- name: Checkout Release Snapshot
uses: actions/checkout@v6
with:
ref: ${{ needs.resolve-tag.outputs.sha }}
path: release-src
- name: Set Up QEMU
# only needed to build the multi-arch image; an edit just re-points :latest
if: ${{ !(github.event_name == 'release' && github.event.action == 'edited') }}
uses: docker/setup-qemu-action@v4
with:
platforms: amd64,arm64
- name: Set Up Docker BuildKit
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ vars.DOCKER_HUB_API_USER || secrets.DOCKER_HUB_API_USER }}
password: ${{ secrets.DOCKER_HUB_API_TOKEN }}
# By default the script moves :latest only if this tag is GitHub's
# "Latest release". The workflow_dispatch input `force_latest`
# translates to FORCE_LATEST_FLAG below, which conditionally passes
# --force-latest to bypass that check.
- name: Build & Push Multi-Platform Quickstart Image
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
shell: bash
run: |
FORCE_LATEST_FLAG=""
if [[ "${{ github.event.inputs.force_latest }}" == "true" ]]; then
FORCE_LATEST_FLAG="--force-latest"
fi
SKIP_BUILD_FLAG=""
if [[ "${{ github.event_name }}" == "release" && "${{ github.event.action }}" == "edited" ]]; then
# an edit (e.g. moving "Latest release") must not rebuild the image;
# only re-point :latest to the now-latest tag
SKIP_BUILD_FLAG="--skip-build"
fi
bash workflow-src/dist/scripts/release-quickstart-image.sh \
--tag "${{ needs.resolve-tag.outputs.tag }}" \
--image-repo "${ZITI_QUICKSTART_IMAGE}" \
--context-dir release-src/quickstart/docker/image \
${FORCE_LATEST_FLAG} \
${SKIP_BUILD_FLAG}
release-quickstart-cloudfront:
name: Deploy get.openziti.io CloudFront Function
needs: resolve-tag
# Gated on two conditions:
# 1. Official upstream repo only -- forks could push to their own DockerHub
# repo but cannot deploy to someone else's CloudFront distribution.
# 2. This tag is GitHub's "Latest release" (or force_cloudfront was set on a
# manual dispatch). get.openziti.io is a single shared function; only the
# newest release should re-point it. Older maintenance releases skip.
if: >-
github.repository_owner == 'openziti'
&& needs.resolve-tag.outputs.eligible == 'true'
&& (needs.resolve-tag.outputs.is_latest == 'true' || github.event.inputs.force_cloudfront == 'true')
runs-on: ubuntu-24.04
steps:
# Single checkout at the release tag: python, routes.yml, and the
# Jinja template all come from the release snapshot.
- name: Checkout Workspace at Release Commit
uses: actions/checkout@v6
with:
ref: ${{ needs.resolve-tag.outputs.sha }}
- name: Configure Python
shell: bash
run: |
pip install --requirement ./dist/cloudfront/get.openziti.io/requirements.txt
python --version
# GITHUB_SHA must be the resolved tag's commit, not github.sha
# (which on workflow_dispatch is the default branch HEAD). The
# python embeds it into routes.yml's templated raw-GitHub URLs.
- name: Deploy CloudFront Function
shell: bash
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ vars.AWS_REGION || secrets.AWS_REGION }}
GITHUB_SHA: ${{ needs.resolve-tag.outputs.sha }}
run: python ./dist/cloudfront/get.openziti.io/deploy-cloudfront-function.py