Extract console pipeline scripts
This commit is contained in:
parent
9c6cc4ade5
commit
68102491e2
80
.github/workflows/pipeline.yaml
vendored
80
.github/workflows/pipeline.yaml
vendored
@ -79,19 +79,7 @@ jobs:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
INPUT_TARGET_HOST: ${{ inputs.target_host }}
|
||||
INPUT_RUN_APPLY: ${{ inputs.run_apply }}
|
||||
run: |
|
||||
if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then
|
||||
target_host="${INPUT_TARGET_HOST}"
|
||||
run_apply="${INPUT_RUN_APPLY}"
|
||||
else
|
||||
target_host="jp-xhttp-contabo.svc.plus"
|
||||
run_apply="true"
|
||||
fi
|
||||
|
||||
{
|
||||
printf 'target_host=%s\n' "${target_host}"
|
||||
printf 'run_apply=%s\n' "${run_apply}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
run: bash scripts/github-actions/resolve-workflow-inputs.sh
|
||||
|
||||
- name: Compute Image Metadata
|
||||
id: metadata
|
||||
@ -102,42 +90,35 @@ jobs:
|
||||
id: push
|
||||
env:
|
||||
REF: ${{ github.ref }}
|
||||
run: |
|
||||
if [[ "${REF}" == "refs/heads/main" ]]; then
|
||||
echo "push_latest=true" >> "${GITHUB_OUTPUT}"
|
||||
else
|
||||
echo "push_latest=false" >> "${GITHUB_OUTPUT}"
|
||||
fi
|
||||
run: bash scripts/github-actions/resolve-push-latest.sh
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: prep
|
||||
outputs:
|
||||
image_ref: ${{ needs.prep.outputs.image_ref }}
|
||||
image_tag: ${{ needs.prep.outputs.image_tag }}
|
||||
image_ref: ${{ steps.publish.outputs.image_ref }}
|
||||
image_tag: ${{ steps.publish.outputs.image_tag }}
|
||||
steps:
|
||||
- name: Check Out Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set Up Docker Buildx
|
||||
run: |
|
||||
docker buildx create --name console-builder --use >/dev/null 2>&1 || docker buildx use console-builder
|
||||
docker buildx inspect --bootstrap
|
||||
run: bash scripts/github-actions/setup-docker-buildx.sh
|
||||
|
||||
- name: Log In To GHCR
|
||||
env:
|
||||
GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }}
|
||||
run: |
|
||||
echo "${GHCR_TOKEN}" | docker login ghcr.io -u "${GHCR_USERNAME}" --password-stdin
|
||||
run: bash scripts/github-actions/login-ghcr.sh
|
||||
|
||||
- name: Publish Frontend Image
|
||||
id: publish
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.prep.outputs.image_ref }}
|
||||
IMAGE_TAG: ${{ needs.prep.outputs.image_tag }}
|
||||
IMAGE_LATEST_REF: ${{ needs.prep.outputs.image_latest_ref }}
|
||||
PUSH_LATEST: ${{ needs.prep.outputs.push_latest }}
|
||||
run: |
|
||||
bash scripts/github-actions/build-and-push-frontend-image.sh
|
||||
run: bash scripts/github-actions/publish-frontend-image.sh
|
||||
|
||||
deploy:
|
||||
name: Deploy
|
||||
@ -148,7 +129,7 @@ jobs:
|
||||
env:
|
||||
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
|
||||
RUN_APPLY: ${{ needs.prep.outputs.run_apply }}
|
||||
FRONTEND_IMAGE: ${{ needs.prep.outputs.image_ref }}
|
||||
FRONTEND_IMAGE: ${{ needs.build.outputs.image_ref }}
|
||||
steps:
|
||||
- name: Check Out Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@ -174,45 +155,13 @@ jobs:
|
||||
env:
|
||||
SINGLE_NODE_VPS_SSH_PRIVATE_KEY: ${{ secrets.SINGLE_NODE_VPS_SSH_PRIVATE_KEY }}
|
||||
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
printf '%s\n' "${SINGLE_NODE_VPS_SSH_PRIVATE_KEY}" | tr -d '\r' > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -H "${TARGET_HOST}" >> ~/.ssh/known_hosts
|
||||
chmod 644 ~/.ssh/known_hosts
|
||||
run: bash scripts/github-actions/configure-ssh-for-deploy.sh
|
||||
|
||||
- name: Run Deploy Playbook
|
||||
working-directory: playbooks
|
||||
env:
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: |
|
||||
ansible_args=(
|
||||
-i inventory.ini
|
||||
deploy_console_svc_plus.yml
|
||||
-D
|
||||
-l "${TARGET_HOST}"
|
||||
-e "FRONTEND_IMAGE=${FRONTEND_IMAGE}"
|
||||
-e "GHCR_USERNAME=${GHCR_USERNAME}"
|
||||
-e "GHCR_PASSWORD=${GHCR_PASSWORD}"
|
||||
-e "INTERNAL_SERVICE_TOKEN=${INTERNAL_SERVICE_TOKEN}"
|
||||
-e "ACCOUNT_SERVICE_URL=${ACCOUNT_SERVICE_URL}"
|
||||
-e "PRIMARY_DOMAIN=${PRIMARY_DOMAIN}"
|
||||
-e "SECONDARY_DOMAIN=${SECONDARY_DOMAIN}"
|
||||
-e "NEXT_PUBLIC_RUNTIME_ENVIRONMENT=${NEXT_PUBLIC_RUNTIME_ENVIRONMENT}"
|
||||
-e "NEXT_PUBLIC_RUNTIME_REGION=${NEXT_PUBLIC_RUNTIME_REGION}"
|
||||
-e "CLOUDFLARE_ZONE_TAG=${CLOUDFLARE_ZONE_TAG}"
|
||||
-e "CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=${CLOUDFLARE_WEB_ANALYTICS_SITE_TAG}"
|
||||
-e "CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}"
|
||||
-e "CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}"
|
||||
-e "CLOUDFLARE_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN}"
|
||||
)
|
||||
|
||||
if [[ "${RUN_APPLY}" != "true" ]]; then
|
||||
ansible_args=(-C "${ansible_args[@]}")
|
||||
fi
|
||||
|
||||
ansible-playbook "${ansible_args[@]}"
|
||||
run: bash ../scripts/github-actions/run-console-deploy-playbook.sh
|
||||
|
||||
validate:
|
||||
name: Validate
|
||||
@ -227,4 +176,7 @@ jobs:
|
||||
|
||||
- name: Verify Frontend Release
|
||||
run: |
|
||||
bash scripts/github-actions/verify-frontend-release.sh "${PRIMARY_DOMAIN}" "${SECONDARY_DOMAIN}"
|
||||
bash scripts/github-actions/verify-frontend-release.sh \
|
||||
"${PRIMARY_DOMAIN}" \
|
||||
"${SECONDARY_DOMAIN}" \
|
||||
"${{ needs.build.outputs.image_ref }}"
|
||||
|
||||
12
scripts/github-actions/configure-ssh-for-deploy.sh
Normal file
12
scripts/github-actions/configure-ssh-for-deploy.sh
Normal file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
target_host="${TARGET_HOST:?TARGET_HOST is required}"
|
||||
private_key="${SINGLE_NODE_VPS_SSH_PRIVATE_KEY:?SINGLE_NODE_VPS_SSH_PRIVATE_KEY is required}"
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
printf '%s\n' "${private_key}" | tr -d '\r' > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -H "${target_host}" >> ~/.ssh/known_hosts
|
||||
chmod 644 ~/.ssh/known_hosts
|
||||
@ -26,6 +26,15 @@ require_env SECONDARY_DOMAIN
|
||||
|
||||
GHCR_REGISTRY="${GHCR_REGISTRY:-ghcr.io}"
|
||||
|
||||
reject_remote_build_configuration() {
|
||||
local compose_file="$1"
|
||||
|
||||
if grep -Eq '^[[:space:]]*(build|dockerfile):' "${compose_file}"; then
|
||||
echo "Deployment package must reference prebuilt images only; compose build directives are forbidden." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "${WORK_DIR}"' EXIT
|
||||
|
||||
@ -40,6 +49,8 @@ bash "${SCRIPT_DIR}/render-frontend-runtime-env.sh" "${RUNTIME_ENV_FILE}"
|
||||
cp "${DEPLOY_SOURCE_DIR}/docker-compose.yml" "${WORK_DIR}/docker-compose.yml"
|
||||
cp "${DEPLOY_SOURCE_DIR}/Caddyfile" "${WORK_DIR}/Caddyfile"
|
||||
|
||||
reject_remote_build_configuration "${WORK_DIR}/docker-compose.yml"
|
||||
|
||||
tar -C "${WORK_DIR}" -czf "${RELEASE_ARCHIVE}" \
|
||||
docker-compose.yml \
|
||||
Caddyfile \
|
||||
|
||||
7
scripts/github-actions/login-ghcr.sh
Normal file
7
scripts/github-actions/login-ghcr.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ghcr_token="${GHCR_TOKEN:?GHCR_TOKEN is required}"
|
||||
ghcr_username="${GHCR_USERNAME:?GHCR_USERNAME is required}"
|
||||
|
||||
printf '%s' "${ghcr_token}" | docker login ghcr.io -u "${ghcr_username}" --password-stdin
|
||||
7
scripts/github-actions/publish-frontend-image.sh
Normal file
7
scripts/github-actions/publish-frontend-image.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
bash scripts/github-actions/build-and-push-frontend-image.sh
|
||||
|
||||
printf 'image_ref=%s\n' "${IMAGE_REF:?IMAGE_REF is required}" >> "${GITHUB_OUTPUT}"
|
||||
printf 'image_tag=%s\n' "${IMAGE_TAG:?IMAGE_TAG is required}" >> "${GITHUB_OUTPUT}"
|
||||
10
scripts/github-actions/resolve-push-latest.sh
Normal file
10
scripts/github-actions/resolve-push-latest.sh
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ref="${REF:?REF is required}"
|
||||
|
||||
if [[ "${ref}" == "refs/heads/main" ]]; then
|
||||
echo "push_latest=true" >> "${GITHUB_OUTPUT}"
|
||||
else
|
||||
echo "push_latest=false" >> "${GITHUB_OUTPUT}"
|
||||
fi
|
||||
17
scripts/github-actions/resolve-workflow-inputs.sh
Normal file
17
scripts/github-actions/resolve-workflow-inputs.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
event_name="${EVENT_NAME:?EVENT_NAME is required}"
|
||||
input_target_host="${INPUT_TARGET_HOST:-}"
|
||||
input_run_apply="${INPUT_RUN_APPLY:-}"
|
||||
|
||||
if [[ "${event_name}" == "workflow_dispatch" ]]; then
|
||||
target_host="${input_target_host}"
|
||||
run_apply="${input_run_apply}"
|
||||
else
|
||||
target_host="jp-xhttp-contabo.svc.plus"
|
||||
run_apply="true"
|
||||
fi
|
||||
|
||||
printf 'target_host=%s\n' "${target_host}" >> "${GITHUB_OUTPUT}"
|
||||
printf 'run_apply=%s\n' "${run_apply}" >> "${GITHUB_OUTPUT}"
|
||||
33
scripts/github-actions/run-console-deploy-playbook.sh
Normal file
33
scripts/github-actions/run-console-deploy-playbook.sh
Normal file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
target_host="${TARGET_HOST:?TARGET_HOST is required}"
|
||||
run_apply="${RUN_APPLY:?RUN_APPLY is required}"
|
||||
frontend_image="${FRONTEND_IMAGE:?FRONTEND_IMAGE is required}"
|
||||
|
||||
ansible_args=(
|
||||
-i inventory.ini
|
||||
deploy_console_svc_plus.yml
|
||||
-D
|
||||
-l "${target_host}"
|
||||
-e "FRONTEND_IMAGE=${frontend_image}"
|
||||
-e "GHCR_USERNAME=${GHCR_USERNAME:?GHCR_USERNAME is required}"
|
||||
-e "GHCR_PASSWORD=${GHCR_PASSWORD:?GHCR_PASSWORD is required}"
|
||||
-e "INTERNAL_SERVICE_TOKEN=${INTERNAL_SERVICE_TOKEN:?INTERNAL_SERVICE_TOKEN is required}"
|
||||
-e "ACCOUNT_SERVICE_URL=${ACCOUNT_SERVICE_URL:?ACCOUNT_SERVICE_URL is required}"
|
||||
-e "PRIMARY_DOMAIN=${PRIMARY_DOMAIN:?PRIMARY_DOMAIN is required}"
|
||||
-e "SECONDARY_DOMAIN=${SECONDARY_DOMAIN:?SECONDARY_DOMAIN is required}"
|
||||
-e "NEXT_PUBLIC_RUNTIME_ENVIRONMENT=${NEXT_PUBLIC_RUNTIME_ENVIRONMENT:?NEXT_PUBLIC_RUNTIME_ENVIRONMENT is required}"
|
||||
-e "NEXT_PUBLIC_RUNTIME_REGION=${NEXT_PUBLIC_RUNTIME_REGION:?NEXT_PUBLIC_RUNTIME_REGION is required}"
|
||||
-e "CLOUDFLARE_ZONE_TAG=${CLOUDFLARE_ZONE_TAG:?CLOUDFLARE_ZONE_TAG is required}"
|
||||
-e "CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=${CLOUDFLARE_WEB_ANALYTICS_SITE_TAG:?CLOUDFLARE_WEB_ANALYTICS_SITE_TAG is required}"
|
||||
-e "CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID:?CLOUDFLARE_ACCOUNT_ID is required}"
|
||||
-e "CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN is required}"
|
||||
-e "CLOUDFLARE_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN:?CLOUDFLARE_DNS_API_TOKEN is required}"
|
||||
)
|
||||
|
||||
if [[ "${run_apply}" != "true" ]]; then
|
||||
ansible_args=(-C "${ansible_args[@]}")
|
||||
fi
|
||||
|
||||
ansible-playbook "${ansible_args[@]}"
|
||||
5
scripts/github-actions/setup-docker-buildx.sh
Normal file
5
scripts/github-actions/setup-docker-buildx.sh
Normal file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
docker buildx create --name console-builder --use >/dev/null 2>&1 || docker buildx use console-builder
|
||||
docker buildx inspect --bootstrap
|
||||
@ -3,9 +3,22 @@ set -euo pipefail
|
||||
|
||||
PRIMARY_DOMAIN="${1:?usage: verify-frontend-release.sh <primary-domain> <secondary-domain>}"
|
||||
SECONDARY_DOMAIN="${2:?usage: verify-frontend-release.sh <primary-domain> <secondary-domain>}"
|
||||
EXPECTED_IMAGE_REF="${3:?usage: verify-frontend-release.sh <primary-domain> <secondary-domain> <expected-image-ref>}"
|
||||
|
||||
primary_url="https://${PRIMARY_DOMAIN}"
|
||||
secondary_url="https://${SECONDARY_DOMAIN}"
|
||||
expected_image_tag_match="$(printf '%s' "${EXPECTED_IMAGE_REF}" | sed -nE 's#^.+:([^:@]+)$#\1#p')"
|
||||
EXPECTED_IMAGE_TAG="${expected_image_tag_match}"
|
||||
|
||||
if [[ -z "${EXPECTED_IMAGE_TAG}" ]]; then
|
||||
echo "Expected image ref must include a tag, got: ${EXPECTED_IMAGE_REF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "${EXPECTED_IMAGE_TAG}" =~ ^[0-9a-f]{7,40}$ ]]; then
|
||||
echo "Expected image tag must contain a commit id, got: ${EXPECTED_IMAGE_TAG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl -fsSIL "${primary_url}" >/dev/null
|
||||
|
||||
@ -25,3 +38,44 @@ fi
|
||||
|
||||
curl -fsSIL "${primary_url}${asset_path}" >/dev/null
|
||||
printf 'verified static asset: %s%s\n' "${primary_url}" "${asset_path}"
|
||||
|
||||
release_payload="$(curl -fsSL "${primary_url}/api/ping")"
|
||||
release_metadata="$(
|
||||
RELEASE_PAYLOAD="${release_payload}" python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
|
||||
payload = json.loads(os.environ["RELEASE_PAYLOAD"])
|
||||
print(payload.get("releaseImageRef", ""))
|
||||
print(payload.get("releaseImageTag", ""))
|
||||
print(payload.get("releaseCommit", ""))
|
||||
PY
|
||||
)"
|
||||
|
||||
mapfile -t release_lines <<< "${release_metadata}"
|
||||
actual_image_ref="${release_lines[0]-}"
|
||||
actual_image_tag="${release_lines[1]-}"
|
||||
actual_release_commit="${release_lines[2]-}"
|
||||
|
||||
if [[ -z "${actual_image_ref}" || -z "${actual_image_tag}" || -z "${actual_release_commit}" ]]; then
|
||||
echo "Remote release metadata is incomplete: ${release_payload}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${actual_image_ref}" != "${EXPECTED_IMAGE_REF}" ]]; then
|
||||
echo "Remote image ref mismatch: expected ${EXPECTED_IMAGE_REF}, got ${actual_image_ref}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${actual_image_tag}" != "${EXPECTED_IMAGE_TAG}" ]]; then
|
||||
echo "Remote image tag mismatch: expected ${EXPECTED_IMAGE_TAG}, got ${actual_image_tag}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${actual_release_commit}" != "${EXPECTED_IMAGE_TAG}" ]]; then
|
||||
echo "Remote release commit mismatch: expected ${EXPECTED_IMAGE_TAG}, got ${actual_release_commit}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'verified release image: %s\n' "${actual_image_ref}"
|
||||
printf 'verified release commit: %s\n' "${actual_release_commit}"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user