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" 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 }} run: bash ./scripts/github-actions/validate-review-xworkmate-sync.sh https://accounts.svc.plus