accounts/.github/workflows/pipeline.yml

284 lines
10 KiB
YAML

name: Pipeline
on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:
inputs:
target_host:
description: Ansible host or alias
required: false
default: "jp-xhttp-contabo.svc.plus"
type: string
run_apply:
description: Apply deployment
required: true
default: true
type: boolean
internal_service_token:
description: Optional ACP auth token
required: false
default: ""
type: string
permissions:
contents: read
packages: write
id-token: write
concurrency:
group: pipeline-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
env:
DEFAULT_TARGET_HOST: jp-xhttp-contabo.svc.plus
jobs:
prep:
name: Prep
runs-on: ubuntu-latest
outputs:
target_host: ${{ steps.flags.outputs.target_host }}
run_apply: ${{ steps.flags.outputs.run_apply }}
push_image: ${{ steps.flags.outputs.push_image }}
push_latest: ${{ steps.flags.outputs.push_latest }}
steps:
- name: Check Out Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Resolve Pipeline Flags
id: flags
env:
IMAGE_REPO_OWNER: ${{ vars.IMAGE_REPO_OWNER || github.repository_owner }}
DEFAULT_TARGET_HOST: ${{ env.DEFAULT_TARGET_HOST }}
INPUT_TARGET_HOST: ${{ inputs.target_host }}
INPUT_RUN_APPLY: ${{ inputs.run_apply }}
INPUT_PUSH_IMAGE: ${{ inputs.push_image }}
INPUT_PUSH_LATEST: ${{ inputs.push_latest }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
GITHUB_BEFORE: ${{ github.event.before }}
GITHUB_SHA: ${{ github.sha }}
run: bash ./scripts/github-actions/resolve-pipeline-flags.sh >> "$GITHUB_OUTPUT"
build:
name: Build
needs: prep
runs-on: ubuntu-latest
env:
SERVICE_REGISTRY: ghcr.io
SERVICE_IMAGE_REPO_OWNER: ${{ vars.IMAGE_REPO_OWNER || github.repository_owner }}
SERVICE_IMAGE_NAME: accounts
outputs:
service_image_repo: ${{ steps.service_image.outputs.repo }}
service_image_tag: ${{ steps.service_ref.outputs.image_tag }}
service_image_commit: ${{ steps.service_ref.outputs.image_commit }}
service_image_ref: ${{ steps.service_ref.outputs.image_ref }}
steps:
- name: Check Out Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set Up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set Up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Log In To GHCR
if: needs.prep.outputs.push_image == 'true'
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ vars.GHCR_USERNAME || github.repository_owner }}
password: ${{ secrets.GHCR_TOKEN || github.token }}
- name: Resolve Service Image Repository
id: service_image
run: echo "repo=${SERVICE_REGISTRY}/${SERVICE_IMAGE_REPO_OWNER}/${SERVICE_IMAGE_NAME}" >> "$GITHUB_OUTPUT"
- name: Compute Service Image Tags
id: service_meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ${{ steps.service_image.outputs.repo }}
tags: |
type=sha,format=long
type=raw,value=latest,enable=${{ needs.prep.outputs.push_latest == 'true' || github.ref == 'refs/heads/main' }}
- name: Resolve Service Image Ref
id: service_ref
run: |
set -euo pipefail
image_ref="$(bash .github/scripts/utils/preferred-image-ref.sh "${{ steps.service_meta.outputs.tags }}")"
image_no_digest="${image_ref%@*}"
image_tag="${image_no_digest##*:}"
image_commit=""
if [[ "${image_tag}" =~ ^[0-9a-f]{7,40}$ ]]; then
image_commit="${image_tag}"
fi
echo "image_tag=${image_tag}" >> "$GITHUB_OUTPUT"
echo "image_commit=${image_commit}" >> "$GITHUB_OUTPUT"
echo "image_ref=${image_ref}" >> "$GITHUB_OUTPUT"
- name: Build And Push Service Image
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: ${{ needs.prep.outputs.push_image == 'true' }}
tags: ${{ steps.service_meta.outputs.tags }}
labels: ${{ steps.service_meta.outputs.labels }}
deploy:
name: Deploy
needs:
- prep
- build
if: ${{ needs.prep.outputs.push_image == 'true' }}
runs-on: ubuntu-latest
outputs:
image: ${{ needs.build.outputs.service_image_ref }}
image_ref: ${{ needs.build.outputs.service_image_ref }}
run_apply: ${{ needs.prep.outputs.run_apply }}
pushed: "true"
steps:
- name: Check Out Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Resolve Deploy Image Artifact
id: deploy_image
run: |
set -euo pipefail
image_ref="${{ needs.build.outputs.service_image_ref }}"
image_no_digest="${image_ref%@*}"
image_tag="${image_no_digest##*:}"
if [[ -z "${image_ref}" || -z "${image_tag}" ]]; then
echo "invalid deploy image artifact: ${image_ref}" >&2
exit 1
fi
if [[ ! "${image_tag}" =~ ^sha-([0-9a-f]{40})$ && ! "${image_tag}" =~ ^[0-9a-f]{40}$ ]]; then
echo "deploy image artifact must be a full-SHA tag: ${image_ref}" >&2
exit 1
fi
echo "image_ref=${image_ref}" >> "$GITHUB_OUTPUT"
- name: Check Out Playbooks Repository
# Pull latest playbooks HEAD from the default branch.
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: x-evor/playbooks
token: ${{ secrets.WORKSPACE_REPO_TOKEN || github.token }}
path: playbooks
- name: Guard Against Host Image Builds
working-directory: ${{ github.workspace }}/playbooks
run: |
set -euo pipefail
if grep -REn \
-e 'docker build' \
-e 'podman build' \
-e 'docker buildx build' \
-e 'gcloud builds submit' \
deploy_accounts_svc_plus.yml roles/vhosts/accounts_service \
; then
echo "deploy flow must use the build job image artifact and must not build images on the target host" >&2
exit 1
fi
- name: Guard Release Traceability Contract
working-directory: ${{ github.workspace }}/playbooks
run: |
set -euo pipefail
grep -REn "lookup\\('ansible.builtin.env', 'ACCOUNTS_IMAGE_REF'\\)" \
deploy_accounts_svc_plus.yml >/dev/null
grep -REn 'image: "\{\{ accounts_service_image_ref \}\}"' \
roles/vhosts/accounts_service/templates/docker-compose.yml.j2 >/dev/null
grep -REn 'IMAGE: "\{\{ accounts_service_image_ref \}\}"' \
roles/vhosts/accounts_service/templates/docker-compose.yml.j2 >/dev/null
grep -REn '^IMAGE=\{\{ accounts_service_image_ref \}\}$' \
roles/vhosts/accounts_service/templates/app.env.j2 >/dev/null
- name: Set Up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.0.0
with:
python-version: "3.11"
- name: Install Ansible Runtime
run: |
python -m pip install --upgrade pip
python -m pip install "ansible-core==2.18.3"
- name: Prepare Runner SSH Access
env:
SINGLE_NODE_VPS_SSH_PRIVATE_KEY: ${{ secrets.SINGLE_NODE_VPS_SSH_PRIVATE_KEY }}
SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
run: |
bash ./scripts/github-actions/prepare-ssh.sh \
"${{ needs.prep.outputs.target_host }}" \
"${SSH_KNOWN_HOSTS}"
- name: Run Accounts Deploy Playbook
working-directory: ${{ github.workspace }}/playbooks
env:
ACCOUNTS_IMAGE_REF: ${{ steps.deploy_image.outputs.image_ref }}
ACCOUNTS_PULL_IMAGE: "true"
BRIDGE_AUTH_TOKEN: ${{ secrets.BRIDGE_AUTH_TOKEN }}
BRIDGE_REVIEW_AUTH_TOKEN: ${{ secrets.BRIDGE_REVIEW_AUTH_TOKEN }}
BRIDGE_SERVER_URL: https://xworkmate-bridge.svc.plus
run: |
set -euo pipefail
args=(
ansible-playbook
-i inventory.ini
-e "accounts_service_hosts=${{ needs.prep.outputs.target_host }}"
)
if [[ "${{ needs.prep.outputs.run_apply }}" != "true" ]]; then
args+=(-C)
fi
args+=(deploy_accounts_svc_plus.yml)
ANSIBLE_CONFIG="${PWD}/ansible.cfg" \
ACCOUNTS_PULL_IMAGE="${ACCOUNTS_PULL_IMAGE:-true}" \
"${args[@]}"
validate:
name: Validate
needs:
- build
- deploy
if: ${{ always() && needs.deploy.result == 'success' && needs.deploy.outputs.pushed == 'true' && needs.deploy.outputs.run_apply == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Check Out Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate Deployed Accounts Service
run: bash ./scripts/github-actions/validate-deploy.sh "${{ needs.build.outputs.service_image_ref }}" https://accounts.svc.plus
- name: Validate Review XWorkmate Sync Contract
env:
REVIEW_ACCOUNT_EMAIL: review@svc.plus
REVIEW_ACCOUNT_PASSWORD: ${{ secrets.REVIEW_ACCOUNT_PASSWORD }}
BRIDGE_AUTH_TOKEN: ${{ secrets.BRIDGE_AUTH_TOKEN }}
BRIDGE_REVIEW_AUTH_TOKEN: ${{ secrets.BRIDGE_REVIEW_AUTH_TOKEN }}
run: bash ./scripts/github-actions/validate-review-xworkmate-sync.sh https://accounts.svc.plus