accounts/.github/workflows/pipeline.yml
2026-04-10 20:35:20 +08:00

448 lines
17 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:
base_images_exists: ${{ steps.flags.outputs.base_images_exists }}
run_base_images: ${{ steps.flags.outputs.run_base_images }}
push_base_images: ${{ steps.flags.outputs.push_base_images }}
base_image_registry: ${{ steps.flags.outputs.base_image_registry }}
base_image_org: ${{ steps.flags.outputs.base_image_org }}
dockerhub_namespace: ${{ steps.flags.outputs.dockerhub_namespace }}
target_host: ${{ steps.flags.outputs.target_host }}
run_apply: ${{ steps.flags.outputs.run_apply }}
image_tag: ${{ steps.flags.outputs.image_tag }}
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 }}
DOCKERHUB_NAMESPACE: ${{ vars.DOCKERHUB_NAMESPACE || 'cloudneutral' }}
INPUT_TARGET_HOST: ${{ inputs.target_host }}
INPUT_RUN_APPLY: ${{ inputs.run_apply }}
INPUT_IMAGE_TAG: ${{ inputs.image_tag }}
INPUT_PUSH_IMAGE: ${{ inputs.push_image }}
INPUT_PUSH_LATEST: ${{ inputs.push_latest }}
INPUT_RUN_BASE_IMAGES: ${{ inputs.run_base_images }}
INPUT_PUSH_BASE_IMAGES: ${{ inputs.push_base_images }}
INPUT_BASE_IMAGE_REGISTRY: ${{ inputs.base_image_registry }}
INPUT_BASE_IMAGE_ORG: ${{ inputs.base_image_org }}
INPUT_DOCKERHUB_NAMESPACE: ${{ inputs.dockerhub_namespace }}
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:
BASE_REGISTRY: ${{ needs.prep.outputs.base_image_registry }}
BASE_ORG: ${{ needs.prep.outputs.base_image_org }}
PUSH_BASE_IMAGES: ${{ needs.prep.outputs.push_base_images }}
DOCKERHUB_NAMESPACE: ${{ needs.prep.outputs.dockerhub_namespace }}
SERVICE_REGISTRY: ghcr.io
SERVICE_IMAGE_REPO_OWNER: ${{ vars.IMAGE_REPO_OWNER || github.repository_owner }}
SERVICE_IMAGE_NAME: accounts
outputs:
artifact_name: ${{ steps.service_artifact.outputs.name }}
service_image_repo: ${{ steps.service_image.outputs.repo }}
base_images_pushed: ${{ needs.prep.outputs.push_base_images }}
openresty_tag: ${{ steps.openresty_preferred.outputs.tag }}
postgres_tag: ${{ steps.postgres_preferred.outputs.tag }}
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 Base Image Registry
if: needs.prep.outputs.run_base_images == 'true' && env.PUSH_BASE_IMAGES == 'true'
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ${{ env.BASE_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate OpenResty Tags
if: needs.prep.outputs.run_base_images == 'true'
id: openresty_meta
uses: ./.github/actions/auto-tag
with:
image: ${{ env.BASE_REGISTRY }}/${{ env.BASE_ORG }}/openresty-geoip
- name: Resolve OpenResty Preferred Tag
if: needs.prep.outputs.run_base_images == 'true'
id: openresty_preferred
run: |
set -euo pipefail
tag="$(bash .github/scripts/utils/preferred-tag.sh "${{ steps.openresty_meta.outputs.tags }}")"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
- name: Build OpenResty Base Image
if: needs.prep.outputs.run_base_images == 'true'
id: openresty_build
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: .
file: deploy/base-images/openresty-geoip.Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ env.PUSH_BASE_IMAGES == 'true' }}
tags: ${{ steps.openresty_meta.outputs.tags }}
labels: ${{ steps.openresty_meta.outputs.labels }}
- name: Generate Postgres Tags
if: needs.prep.outputs.run_base_images == 'true'
id: postgres_meta
uses: ./.github/actions/auto-tag
with:
image: ${{ env.BASE_REGISTRY }}/${{ env.BASE_ORG }}/postgres-runtime
- name: Resolve Postgres Preferred Tag
if: needs.prep.outputs.run_base_images == 'true'
id: postgres_preferred
run: |
set -euo pipefail
tag="$(bash .github/scripts/utils/preferred-tag.sh "${{ steps.postgres_meta.outputs.tags }}")"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
- name: Build Postgres Base Image
if: needs.prep.outputs.run_base_images == 'true'
id: postgres_build
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: .
file: deploy/base-images/postgres-runtime-wth-extensions.Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ env.PUSH_BASE_IMAGES == 'true' }}
tags: ${{ steps.postgres_meta.outputs.tags }}
labels: ${{ steps.postgres_meta.outputs.labels }}
- 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=raw,value=${{ needs.prep.outputs.image_tag }},enable=${{ needs.prep.outputs.image_tag != '' }}
type=sha,format=short,enable=${{ needs.prep.outputs.image_tag == '' }}
type=raw,value=latest,enable=${{ needs.prep.outputs.push_latest == 'true' || github.ref == 'refs/heads/main' }}
- name: Build Service Image Artifact
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: false
tags: ${{ steps.service_meta.outputs.tags }}
labels: ${{ steps.service_meta.outputs.labels }}
outputs: type=docker,dest=${{ runner.temp }}/accounts-image.tar
- name: Prepare Service Artifact Bundle
id: service_artifact
run: |
set -euo pipefail
bundle_dir="${RUNNER_TEMP}/service-image-artifact"
mkdir -p "${bundle_dir}"
cp "${RUNNER_TEMP}/accounts-image.tar" "${bundle_dir}/accounts-image.tar"
printf '%s\n' "${{ steps.service_meta.outputs.tags }}" > "${bundle_dir}/tags.txt"
echo "name=accounts-image-artifact" >> "$GITHUB_OUTPUT"
- name: Upload Service Artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ steps.service_artifact.outputs.name }}
path: ${{ runner.temp }}/service-image-artifact
deploy:
name: Deploy
needs:
- prep
- build
if: ${{ needs.prep.outputs.push_image == 'true' }}
runs-on: ubuntu-latest
outputs:
image: ${{ needs.build.outputs.service_image_repo }}
preferred_tag: ${{ steps.push.outputs.preferred_tag }}
run_apply: ${{ needs.prep.outputs.run_apply }}
pushed: "true"
steps:
- name: Check Out Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- 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: Download Service Artifact
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: ${{ needs.build.outputs.artifact_name }}
path: ${{ runner.temp }}/service-image-artifact
- name: Log In To GHCR
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: Load And Push Service Image
id: push
run: |
set -euo pipefail
docker load -i "${RUNNER_TEMP}/service-image-artifact/accounts-image.tar"
preferred_tag="$(bash .github/scripts/utils/preferred-tag.sh "$(cat "${RUNNER_TEMP}/service-image-artifact/tags.txt")")"
while IFS= read -r tag; do
[[ -z "${tag}" ]] && continue
docker push "${tag}"
done < "${RUNNER_TEMP}/service-image-artifact/tags.txt"
echo "preferred_tag=${preferred_tag}" >> "$GITHUB_OUTPUT"
- name: Resolve Deploy Image Tag
id: deploy_image_tag
run: |
set -euo pipefail
tag="${{ steps.push.outputs.preferred_tag }}"
echo "value=${tag##*:}" >> "$GITHUB_OUTPUT"
- 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_REPO: ${{ needs.build.outputs.service_image_repo }}
ACCOUNTS_IMAGE_TAG: ${{ steps.deploy_image_tag.outputs.value }}
ACCOUNTS_PULL_IMAGE: "true"
run: |
set -euo pipefail
args=(
ansible-playbook
-i inventory.ini
deploy_accounts_svc_plus.yml
-l "${{ needs.prep.outputs.target_host }}"
)
if [[ "${{ needs.prep.outputs.run_apply }}" != "true" ]]; then
args+=(-C)
fi
ANSIBLE_CONFIG="${PWD}/ansible.cfg" \
ACCOUNTS_PULL_IMAGE="${ACCOUNTS_PULL_IMAGE:-true}" \
"${args[@]}"
- name: Log In To Docker Hub
if: needs.prep.outputs.run_base_images == 'true' && needs.build.outputs.base_images_pushed == 'true'
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: docker.io
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Push Base Images To Docker Hub
if: needs.prep.outputs.run_base_images == 'true' && needs.build.outputs.base_images_pushed == 'true'
env:
BASE_REGISTRY: ${{ needs.prep.outputs.base_image_registry }}
BASE_ORG: ${{ needs.prep.outputs.base_image_org }}
DOCKERHUB_NAMESPACE: ${{ needs.prep.outputs.dockerhub_namespace }}
OPENRESTY_TAG: ${{ needs.build.outputs.openresty_tag }}
POSTGRES_TAG: ${{ needs.build.outputs.postgres_tag }}
run: |
set -euo pipefail
docker pull "${OPENRESTY_TAG}"
docker tag "${OPENRESTY_TAG}" "docker.io/${DOCKERHUB_NAMESPACE}/openresty-geoip:latest"
docker push "docker.io/${DOCKERHUB_NAMESPACE}/openresty-geoip:latest"
docker pull "${POSTGRES_TAG}"
docker tag "${POSTGRES_TAG}" "docker.io/${DOCKERHUB_NAMESPACE}/postgres-runtime:latest"
docker push "docker.io/${DOCKERHUB_NAMESPACE}/postgres-runtime:latest"
validate:
name: Validate
needs:
- prep
- 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 Endpoint
run: bash ./scripts/github-actions/validate-deploy.sh https://accounts.svc.plus
- name: Generate Service SBOM
uses: anchore/sbom-action@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0
with:
image: ${{ needs.deploy.outputs.preferred_tag }}
output-file: sbom-accounts.spdx.json
- name: Upload Service SBOM
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sbom-accounts
path: sbom-accounts.spdx.json
- name: Scan Service Image
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: ${{ needs.deploy.outputs.preferred_tag }}
severity: HIGH,CRITICAL
exit-code: '1'
- name: Install Cosign
uses: sigstore/cosign-installer@2e2f661cd4be3a4b891a882064e49d0fed4b7b88 # v3.9.0
with:
cosign-release: 'v2.4.1'
- name: Sign Service Image
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign sign --yes "${{ needs.deploy.outputs.preferred_tag }}"
- name: Generate OpenResty SBOM
if: needs.prep.outputs.run_base_images == 'true' && needs.build.outputs.base_images_pushed == 'true'
uses: anchore/sbom-action@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0
with:
image: ${{ needs.build.outputs.openresty_tag }}
output-file: sbom-openresty-geoip.spdx.json
- name: Upload OpenResty SBOM
if: needs.prep.outputs.run_base_images == 'true' && needs.build.outputs.base_images_pushed == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sbom-openresty-geoip
path: sbom-openresty-geoip.spdx.json
- name: Scan OpenResty Image
if: needs.prep.outputs.run_base_images == 'true' && needs.build.outputs.base_images_pushed == 'true'
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: ${{ needs.build.outputs.openresty_tag }}
severity: HIGH,CRITICAL
exit-code: '1'
- name: Sign OpenResty Image
if: needs.prep.outputs.run_base_images == 'true' && needs.build.outputs.base_images_pushed == 'true'
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign sign --yes "${{ needs.build.outputs.openresty_tag }}"
- name: Generate Postgres SBOM
if: needs.prep.outputs.run_base_images == 'true' && needs.build.outputs.base_images_pushed == 'true'
uses: anchore/sbom-action@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0
with:
image: ${{ needs.build.outputs.postgres_tag }}
output-file: sbom-postgres-runtime.spdx.json
- name: Upload Postgres SBOM
if: needs.prep.outputs.run_base_images == 'true' && needs.build.outputs.base_images_pushed == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sbom-postgres-runtime
path: sbom-postgres-runtime.spdx.json
- name: Scan Postgres Image
if: needs.prep.outputs.run_base_images == 'true' && needs.build.outputs.base_images_pushed == 'true'
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: ${{ needs.build.outputs.postgres_tag }}
severity: HIGH,CRITICAL
exit-code: '1'
- name: Sign Postgres Image
if: needs.prep.outputs.run_base_images == 'true' && needs.build.outputs.base_images_pushed == 'true'
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign sign --yes "${{ needs.build.outputs.postgres_tag }}"