diff --git a/.github/workflows/release-traceability.yml b/.github/workflows/release-traceability.yml new file mode 100644 index 0000000..cdc7343 --- /dev/null +++ b/.github/workflows/release-traceability.yml @@ -0,0 +1,53 @@ +name: release-traceability + +on: + push: + branches: + - main + workflow_dispatch: {} + +jobs: + build: + runs-on: ubuntu-latest + outputs: + service_image_ref: ${{ steps.meta.outputs.service_image_ref }} + service_image_tag: ${{ steps.meta.outputs.service_image_tag }} + service_image_commit: ${{ steps.meta.outputs.service_image_commit }} + steps: + - uses: actions/checkout@v4 + + - name: Derive image identity + id: meta + run: bash ./scripts/github-actions/resolve-service-image-ref.sh + + - name: Build image + env: + SERVICE_IMAGE_REF: ${{ steps.meta.outputs.service_image_ref }} + SERVICE_IMAGE_LATEST_REF: ghcr.io/${{ github.repository }}:latest + run: bash ./scripts/github-actions/build-service-image.sh + + - name: Push image + run: bash ./scripts/github-actions/push-image-placeholder.sh + + deploy: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + + - name: Deploy via playbook + env: + IMAGE_REF: ${{ needs.build.outputs.service_image_ref }} + BILLING_SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }} + BILLING_SERVICE_IMAGE_TAG: ${{ needs.build.outputs.service_image_tag }} + BILLING_SERVICE_IMAGE_COMMIT: ${{ needs.build.outputs.service_image_commit }} + run: bash ./scripts/github-actions/deploy-billing-service.sh + + validate: + runs-on: ubuntu-latest + needs: deploy + steps: + - name: Validate runtime traceability + env: + SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }} + run: bash ./scripts/github-actions/validate-release-traceability.sh diff --git a/scripts/github-actions/build-service-image.sh b/scripts/github-actions/build-service-image.sh new file mode 100644 index 0000000..82abc0f --- /dev/null +++ b/scripts/github-actions/build-service-image.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +docker build \ + --tag "${SERVICE_IMAGE_REF:?SERVICE_IMAGE_REF is required}" \ + --tag "${SERVICE_IMAGE_LATEST_REF:?SERVICE_IMAGE_LATEST_REF is required}" \ + . diff --git a/scripts/github-actions/deploy-billing-service.sh b/scripts/github-actions/deploy-billing-service.sh new file mode 100644 index 0000000..ae5c976 --- /dev/null +++ b/scripts/github-actions/deploy-billing-service.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +test -n "${IMAGE_REF:?IMAGE_REF is required}" +ansible-playbook -i inventory playbooks/deploy_billing_service.yml diff --git a/scripts/github-actions/push-image-placeholder.sh b/scripts/github-actions/push-image-placeholder.sh new file mode 100644 index 0000000..febb0ab --- /dev/null +++ b/scripts/github-actions/push-image-placeholder.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Push step is intentionally left as an integration point for the target registry." diff --git a/scripts/github-actions/resolve-service-image-ref.sh b/scripts/github-actions/resolve-service-image-ref.sh new file mode 100644 index 0000000..9fe9c13 --- /dev/null +++ b/scripts/github-actions/resolve-service-image-ref.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +full_sha="${GITHUB_SHA:?GITHUB_SHA is required}" +tag="sha-${full_sha}" +image_ref="ghcr.io/${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}:${tag}" + +printf 'service_image_ref=%s\n' "${image_ref}" >> "${GITHUB_OUTPUT}" +printf 'service_image_tag=%s\n' "${tag}" >> "${GITHUB_OUTPUT}" +printf 'service_image_commit=%s\n' "${full_sha}" >> "${GITHUB_OUTPUT}" diff --git a/scripts/github-actions/validate-release-traceability.sh b/scripts/github-actions/validate-release-traceability.sh new file mode 100644 index 0000000..b1bb474 --- /dev/null +++ b/scripts/github-actions/validate-release-traceability.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +service_image_ref="${SERVICE_IMAGE_REF:?SERVICE_IMAGE_REF is required}" +tag="${service_image_ref##*:}" +commit="${tag#sha-}" + +curl -fsS "https://billing-service.example.com/api/ping" | jq -e \ + --arg image "${service_image_ref}" \ + --arg tag "${tag}" \ + --arg commit "${commit}" \ + '.image == $image and .tag == $tag and .commit == $commit'