Deploy billing-service from build artifact
This commit is contained in:
parent
8a18e547fc
commit
7fbc23257a
52
.github/workflows/release-traceability.yml
vendored
52
.github/workflows/release-traceability.yml
vendored
@ -26,6 +26,18 @@ jobs:
|
|||||||
SERVICE_IMAGE_LATEST_REF: ghcr.io/${{ github.repository }}:latest
|
SERVICE_IMAGE_LATEST_REF: ghcr.io/${{ github.repository }}:latest
|
||||||
run: bash ./scripts/github-actions/build-service-image.sh
|
run: bash ./scripts/github-actions/build-service-image.sh
|
||||||
|
|
||||||
|
- name: Build linux binary artifact
|
||||||
|
env:
|
||||||
|
BILLING_SERVICE_BINARY_ARTIFACT: dist/billing-service-linux-amd64
|
||||||
|
run: bash ./scripts/github-actions/build-service-binary.sh
|
||||||
|
|
||||||
|
- name: Upload billing-service binary artifact
|
||||||
|
uses: actions/upload-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||||
|
with:
|
||||||
|
name: billing-service-linux-amd64
|
||||||
|
path: dist/billing-service-linux-amd64
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Push image
|
- name: Push image
|
||||||
run: bash ./scripts/github-actions/push-image-placeholder.sh
|
run: bash ./scripts/github-actions/push-image-placeholder.sh
|
||||||
|
|
||||||
@ -35,12 +47,32 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Download billing-service binary artifact
|
||||||
|
uses: actions/download-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
|
with:
|
||||||
|
name: billing-service-linux-amd64
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
- name: Install ansible
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y ansible
|
||||||
|
|
||||||
|
- name: Configure deploy SSH
|
||||||
|
env:
|
||||||
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
run: |
|
||||||
|
test -n "${SSH_PRIVATE_KEY}"
|
||||||
|
install -d -m 0700 ~/.ssh
|
||||||
|
printf '%s\n' "${SSH_PRIVATE_KEY}" > ~/.ssh/id_ed25519
|
||||||
|
chmod 0600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -H jp-xhttp-contabo.svc.plus >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
- name: Deploy via playbook
|
- name: Deploy via playbook
|
||||||
env:
|
env:
|
||||||
IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}
|
|
||||||
BILLING_SERVICE_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_BINARY_ARTIFACT: dist/billing-service-linux-amd64
|
||||||
BILLING_SERVICE_IMAGE_COMMIT: ${{ needs.build.outputs.service_image_commit }}
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||||
|
INTERNAL_SERVICE_TOKEN: ${{ secrets.INTERNAL_SERVICE_TOKEN }}
|
||||||
|
STACK_TARGET_HOST: jp-xhttp-contabo.svc.plus
|
||||||
run: bash ./scripts/github-actions/deploy-billing-service.sh
|
run: bash ./scripts/github-actions/deploy-billing-service.sh
|
||||||
|
|
||||||
validate:
|
validate:
|
||||||
@ -57,8 +89,18 @@ jobs:
|
|||||||
- name: Verify traceability script cases
|
- name: Verify traceability script cases
|
||||||
run: bash ./scripts/github-actions/test-validate-release-traceability.sh
|
run: bash ./scripts/github-actions/test-validate-release-traceability.sh
|
||||||
|
|
||||||
|
- name: Configure validate SSH
|
||||||
|
env:
|
||||||
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
run: |
|
||||||
|
test -n "${SSH_PRIVATE_KEY}"
|
||||||
|
install -d -m 0700 ~/.ssh
|
||||||
|
printf '%s\n' "${SSH_PRIVATE_KEY}" > ~/.ssh/id_ed25519
|
||||||
|
chmod 0600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -H jp-xhttp-contabo.svc.plus >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
- name: Validate runtime traceability
|
- name: Validate runtime traceability
|
||||||
env:
|
env:
|
||||||
SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}
|
SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}
|
||||||
RUNTIME_PING_URL: https://accounts.svc.plus/api/ping
|
STACK_TARGET_HOST: jp-xhttp-contabo.svc.plus
|
||||||
run: bash ./scripts/github-actions/validate-release-traceability.sh
|
run: bash ./scripts/github-actions/validate-release-traceability-remote.sh
|
||||||
|
|||||||
@ -5,7 +5,9 @@ release identity.
|
|||||||
|
|
||||||
## Runtime contract
|
## Runtime contract
|
||||||
|
|
||||||
- `IMAGE` must contain the full image reference used to start the container.
|
- `IMAGE` must contain the full release image reference produced by the build
|
||||||
|
job, even when the target host runs the service as a systemd binary instead
|
||||||
|
of a container.
|
||||||
- `/api/ping` returns `image`, `tag`, `commit`, and `version`.
|
- `/api/ping` returns `image`, `tag`, `commit`, and `version`.
|
||||||
- `tag`, `commit`, and `version` are derived from `IMAGE`.
|
- `tag`, `commit`, and `version` are derived from `IMAGE`.
|
||||||
- If `IMAGE` is missing or malformed, the derived fields stay empty instead of
|
- If `IMAGE` is missing or malformed, the derived fields stay empty instead of
|
||||||
@ -14,9 +16,13 @@ release identity.
|
|||||||
## Pipeline contract
|
## Pipeline contract
|
||||||
|
|
||||||
- Build must produce `service_image_ref` only from the full `GITHUB_SHA`.
|
- Build must produce `service_image_ref` only from the full `GITHUB_SHA`.
|
||||||
- Deploy must consume `service_image_ref` and pass it through as the runtime
|
- Build must also produce the linux billing-service binary artifact consumed by
|
||||||
image identity.
|
deploy.
|
||||||
- Validate must use `GET https://accounts.svc.plus/api/ping`.
|
- Deploy must consume that build artifact directly and must not rebuild on the
|
||||||
|
target host.
|
||||||
|
- Deploy must pass `service_image_ref` through as the runtime image identity.
|
||||||
|
- Validate must query `billing-service` on the deployment target at
|
||||||
|
`http://127.0.0.1:8081/api/ping` over SSH.
|
||||||
- Validate must derive `tag` and `commit` from `service_image_ref` and compare
|
- Validate must derive `tag` and `commit` from `service_image_ref` and compare
|
||||||
them against `/api/ping`.
|
them against `/api/ping`.
|
||||||
- Validate must fail when runtime `image`, `tag`, `commit`, or `version` is
|
- Validate must fail when runtime `image`, `tag`, `commit`, or `version` is
|
||||||
@ -27,11 +33,11 @@ release identity.
|
|||||||
## External playbook alignment
|
## External playbook alignment
|
||||||
|
|
||||||
The external `playbooks/deploy_billing_service.yml` playbook should accept
|
The external `playbooks/deploy_billing_service.yml` playbook should accept
|
||||||
`IMAGE_REF` (or an equivalent full image reference variable), derive any
|
`BILLING_SERVICE_BINARY_ARTIFACT` and `BILLING_SERVICE_IMAGE_REF`, deploy the
|
||||||
repo/tag helpers from it, and inject `IMAGE=<full image ref>` into the running
|
binary artifact to the target host without rebuilding it there, and inject
|
||||||
container environment for the service exposed through
|
`IMAGE=<full image ref>` into `/etc/default/billing-service` for the runtime
|
||||||
`https://accounts.svc.plus/api/ping`.
|
served through `http://127.0.0.1:8081/api/ping`.
|
||||||
|
|
||||||
If `accounts.svc.plus/api/ping` keeps returning empty runtime metadata, treat
|
If `billing-service /api/ping` keeps returning empty runtime metadata, treat
|
||||||
that as a deployment contract failure: the runtime did not receive the full
|
that as a deployment contract failure: the runtime did not receive the full
|
||||||
`IMAGE` value and release traceability is broken.
|
`IMAGE` value and release traceability is broken.
|
||||||
|
|||||||
14
scripts/github-actions/build-service-binary.sh
Normal file
14
scripts/github-actions/build-service-binary.sh
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
artifact_path="${BILLING_SERVICE_BINARY_ARTIFACT:?BILLING_SERVICE_BINARY_ARTIFACT is required}"
|
||||||
|
target_dir="$(dirname "${artifact_path}")"
|
||||||
|
|
||||||
|
mkdir -p "${target_dir}"
|
||||||
|
|
||||||
|
CGO_ENABLED="${CGO_ENABLED:-0}" \
|
||||||
|
GOOS="${GOOS:-linux}" \
|
||||||
|
GOARCH="${GOARCH:-amd64}" \
|
||||||
|
go build -buildvcs=false -o "${artifact_path}" ./cmd/billing-service
|
||||||
|
|
||||||
|
chmod 0755 "${artifact_path}"
|
||||||
@ -1,5 +1,36 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
test -n "${IMAGE_REF:?IMAGE_REF is required}"
|
target_host="${STACK_TARGET_HOST:?STACK_TARGET_HOST is required}"
|
||||||
ansible-playbook -i inventory playbooks/deploy_billing_service.yml
|
artifact_path="${BILLING_SERVICE_BINARY_ARTIFACT:?BILLING_SERVICE_BINARY_ARTIFACT is required}"
|
||||||
|
image_ref="${BILLING_SERVICE_IMAGE_REF:-${IMAGE_REF:-}}"
|
||||||
|
database_url="${DATABASE_URL:?DATABASE_URL is required}"
|
||||||
|
internal_service_token="${INTERNAL_SERVICE_TOKEN:?INTERNAL_SERVICE_TOKEN is required}"
|
||||||
|
playbooks_repo_url="${PLAYBOOKS_REPO_URL:-https://github.com/x-evor/playbooks.git}"
|
||||||
|
playbooks_repo_ref="${PLAYBOOKS_REPO_REF:-c0f1a1c2ee00e4131db2484c8cc00b2bc4dc1263}"
|
||||||
|
|
||||||
|
if [[ ! -f "${artifact_path}" ]]; then
|
||||||
|
echo "binary artifact not found: ${artifact_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${image_ref}" ]]; then
|
||||||
|
echo "BILLING_SERVICE_IMAGE_REF or IMAGE_REF is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "${workdir}"' EXIT
|
||||||
|
|
||||||
|
git clone --depth 1 "${playbooks_repo_url}" "${workdir}/playbooks"
|
||||||
|
git -C "${workdir}/playbooks" fetch --depth 1 origin "${playbooks_repo_ref}"
|
||||||
|
git -C "${workdir}/playbooks" checkout --detach FETCH_HEAD
|
||||||
|
|
||||||
|
export ANSIBLE_HOST_KEY_CHECKING=false
|
||||||
|
export BILLING_SERVICE_BINARY_ARTIFACT="$(cd "$(dirname "${artifact_path}")" && pwd)/$(basename "${artifact_path}")"
|
||||||
|
export BILLING_SERVICE_IMAGE_REF="${image_ref}"
|
||||||
|
export DATABASE_URL="${database_url}"
|
||||||
|
export INTERNAL_SERVICE_TOKEN="${internal_service_token}"
|
||||||
|
|
||||||
|
cd "${workdir}/playbooks"
|
||||||
|
ansible-playbook -i inventory.ini deploy_billing_service.yml --limit "${target_host}"
|
||||||
|
|||||||
@ -30,6 +30,34 @@ if not any(line.strip() == "- build" for line in validate_block):
|
|||||||
if not any(line.strip() == "- deploy" for line in validate_block):
|
if not any(line.strip() == "- deploy" for line in validate_block):
|
||||||
raise SystemExit("validate job must depend on deploy")
|
raise SystemExit("validate job must depend on deploy")
|
||||||
|
|
||||||
|
build_block = []
|
||||||
|
deploy_block = []
|
||||||
|
current_job = None
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith(" build:"):
|
||||||
|
current_job = "build"
|
||||||
|
elif line.startswith(" deploy:"):
|
||||||
|
current_job = "deploy"
|
||||||
|
elif line.startswith(" validate:"):
|
||||||
|
current_job = "validate"
|
||||||
|
elif line.startswith(" ") and not line.startswith(" "):
|
||||||
|
current_job = None
|
||||||
|
|
||||||
|
if current_job == "build":
|
||||||
|
build_block.append(line)
|
||||||
|
elif current_job == "deploy":
|
||||||
|
deploy_block.append(line)
|
||||||
|
|
||||||
|
if not any("Upload billing-service binary artifact" in line for line in build_block):
|
||||||
|
raise SystemExit("build job must upload the billing-service binary artifact")
|
||||||
|
|
||||||
|
if not any("Download billing-service binary artifact" in line for line in deploy_block):
|
||||||
|
raise SystemExit("deploy job must download the billing-service binary artifact")
|
||||||
|
|
||||||
|
if not any("BILLING_SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}" in line for line in deploy_block):
|
||||||
|
raise SystemExit("deploy job must consume needs.build.outputs.service_image_ref")
|
||||||
|
|
||||||
if not any("SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}" in line for line in validate_block):
|
if not any("SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}" in line for line in validate_block):
|
||||||
raise SystemExit("validate job must consume needs.build.outputs.service_image_ref")
|
raise SystemExit("validate job must consume needs.build.outputs.service_image_ref")
|
||||||
PY
|
PY
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
service_image_ref="${SERVICE_IMAGE_REF:?SERVICE_IMAGE_REF is required}"
|
||||||
|
target_host="${STACK_TARGET_HOST:?STACK_TARGET_HOST is required}"
|
||||||
|
ssh_target="${RUNTIME_SSH_TARGET:-root@${target_host}}"
|
||||||
|
runtime_ping_path="${RUNTIME_PING_PATH:-http://127.0.0.1:8081/api/ping}"
|
||||||
|
tag="${service_image_ref##*:}"
|
||||||
|
commit="${tag#sha-}"
|
||||||
|
|
||||||
|
ssh -o BatchMode=yes "${ssh_target}" "systemctl is-active billing-service >/dev/null"
|
||||||
|
|
||||||
|
runtime_payload="$(ssh -o BatchMode=yes "${ssh_target}" "curl -fsS ${runtime_ping_path}")"
|
||||||
|
|
||||||
|
jq -e \
|
||||||
|
--arg image "${service_image_ref}" \
|
||||||
|
--arg tag "${tag}" \
|
||||||
|
--arg commit "${commit}" \
|
||||||
|
'
|
||||||
|
(.image | type == "string" and length > 0) and
|
||||||
|
(.tag | type == "string" and length > 0) and
|
||||||
|
(.commit | type == "string" and length > 0) and
|
||||||
|
(.version | type == "string" and length > 0) and
|
||||||
|
.image == $image and
|
||||||
|
.tag == $tag and
|
||||||
|
.commit == $commit and
|
||||||
|
.version == $commit
|
||||||
|
' <<<"${runtime_payload}" >/dev/null
|
||||||
Loading…
Reference in New Issue
Block a user