Separate backend/frontend base-main lanes and add postmortem coverage
Some checks failed
CICD / Build and Publish CICD Base Image (push) Successful in 18s
CICD / Build and Push CICD Image (push) Successful in 20m53s
CICD / Build CICD Image Failure Postmortem (push) Has been skipped
CICD / Backend Tests (push) Successful in 29s
CICD / Frontend Tests (push) Successful in 41s
CICD / Backend Doctests (push) Successful in 18s
CICD / Pre-commit Checks (push) Successful in 8m50s
CICD / Source Lanes Failure Postmortem (push) Has been skipped
CICD / CICD Tests Complete (push) Successful in 6s
CICD / Build Backend Base Image (push) Successful in 4m12s
CICD / Build Integration Tester Image (push) Successful in 1m16s
CICD / Build E2E Tester Image (push) Failing after 23s
CICD / Build Backend Main Image (push) Successful in 1m57s
CICD / Build Frontend Base Image (push) Successful in 7m10s
CICD / Build and Publish Runtime Images (Legacy Disabled) (push) Has been skipped
CICD / Build Frontend Main Image (push) Successful in 4m28s
CICD / Runtime Images Failure Postmortem (push) Successful in 7s
CICD / Runtime Black-Box Integration Tests (push) Has been skipped
CICD / End-to-End Tests (push) Has been skipped
CICD / Integration Tests Failure Postmortem (push) Has been skipped
CICD / Production Images Complete (push) Failing after 11s
CICD / E2E Tests Failure Postmortem (push) Has been skipped
Some checks failed
CICD / Build and Publish CICD Base Image (push) Successful in 18s
CICD / Build and Push CICD Image (push) Successful in 20m53s
CICD / Build CICD Image Failure Postmortem (push) Has been skipped
CICD / Backend Tests (push) Successful in 29s
CICD / Frontend Tests (push) Successful in 41s
CICD / Backend Doctests (push) Successful in 18s
CICD / Pre-commit Checks (push) Successful in 8m50s
CICD / Source Lanes Failure Postmortem (push) Has been skipped
CICD / CICD Tests Complete (push) Successful in 6s
CICD / Build Backend Base Image (push) Successful in 4m12s
CICD / Build Integration Tester Image (push) Successful in 1m16s
CICD / Build E2E Tester Image (push) Failing after 23s
CICD / Build Backend Main Image (push) Successful in 1m57s
CICD / Build Frontend Base Image (push) Successful in 7m10s
CICD / Build and Publish Runtime Images (Legacy Disabled) (push) Has been skipped
CICD / Build Frontend Main Image (push) Successful in 4m28s
CICD / Runtime Images Failure Postmortem (push) Successful in 7s
CICD / Runtime Black-Box Integration Tests (push) Has been skipped
CICD / End-to-End Tests (push) Has been skipped
CICD / Integration Tests Failure Postmortem (push) Has been skipped
CICD / Production Images Complete (push) Failing after 11s
CICD / E2E Tests Failure Postmortem (push) Has been skipped
This commit is contained in:
@@ -273,6 +273,34 @@ jobs:
|
||||
|
||||
- *failure_diagnostics_step
|
||||
|
||||
build-cicd-postmortem:
|
||||
name: Build CICD Image Failure Postmortem
|
||||
runs-on: ubuntu-act
|
||||
needs: build_cicd
|
||||
if: always() && needs.build_cicd.result == 'failure'
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Identify runner
|
||||
run: |
|
||||
echo "=== Runner Identity ==="
|
||||
echo "runner_name=${RUNNER_NAME:-unknown}"
|
||||
echo "runner_hostname=${HOSTNAME:-unknown}"
|
||||
echo "timestamp_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
- name: Build CICD postmortem diagnostics
|
||||
run: |
|
||||
echo "=== Build CICD Postmortem ==="
|
||||
echo "build_cicd_result=${{ needs.build_cicd.result }}"
|
||||
uname -a || true
|
||||
cat /etc/os-release 2>/dev/null || true
|
||||
df -h || true
|
||||
free -h || true
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
docker version || true
|
||||
docker info || true
|
||||
fi
|
||||
timeout 10 curl -fsSIL "https://${GITEA_REGISTRY}/v2/" || true
|
||||
|
||||
backend-tests:
|
||||
name: Backend Tests
|
||||
runs-on: ubuntu-act
|
||||
@@ -488,11 +516,170 @@ jobs:
|
||||
name: CICD Tests Complete
|
||||
runs-on: ubuntu-act
|
||||
needs: [backend-tests, precommit-tests, frontend-tests, xdoctest]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Confirm all source lanes passed
|
||||
run: |
|
||||
set -euo pipefail
|
||||
backend_status="${{ needs.backend-tests.result }}"
|
||||
precommit_status="${{ needs.precommit-tests.result }}"
|
||||
frontend_status="${{ needs.frontend-tests.result }}"
|
||||
xdoctest_status="${{ needs.xdoctest.result }}"
|
||||
|
||||
echo "backend-tests=${backend_status}"
|
||||
echo "precommit-tests=${precommit_status}"
|
||||
echo "frontend-tests=${frontend_status}"
|
||||
echo "xdoctest=${xdoctest_status}"
|
||||
|
||||
if [ "${backend_status}" != "success" ] || [ "${precommit_status}" != "success" ] || [ "${frontend_status}" != "success" ] || [ "${xdoctest_status}" != "success" ]; then
|
||||
echo "❌ One or more CICD source lanes failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Source checks complete"
|
||||
|
||||
- *failure_diagnostics_step
|
||||
|
||||
build-backend-base-image:
|
||||
name: Build Backend Base Image
|
||||
runs-on: ubuntu-act-8gb
|
||||
needs: [build_cicd, cicd-tests-complete]
|
||||
outputs:
|
||||
backend_base_tag_ref: ${{ steps.backend_base_ref.outputs.backend_base_tag_ref }}
|
||||
backend_base_digest_ref: ${{ steps.backend_base_ref.outputs.backend_base_digest_ref }}
|
||||
steps:
|
||||
- name: Resolve head SHA
|
||||
id: meta
|
||||
run: |
|
||||
echo "head_sha=${{ needs.build_cicd.outputs.head_sha }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Minimal checkout for backend base image inputs
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
HEAD_SHA: ${{ steps.meta.outputs.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
trap 'rm -f ~/.ssh/id_rsa' EXIT
|
||||
|
||||
if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then
|
||||
echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts
|
||||
fi
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -p "${GITEA_SSH_PORT}" "${GITEA_SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
|
||||
git clone --depth 1 --no-checkout "${GITEA_REPO_SSH_URL}" .
|
||||
|
||||
if GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
|
||||
git fetch --depth 1 origin "${HEAD_SHA}" >/dev/null 2>&1; then
|
||||
git checkout FETCH_HEAD -- Dockerfile.backend backend/pyproject.toml backend/uv.lock
|
||||
else
|
||||
git checkout HEAD -- Dockerfile.backend backend/pyproject.toml backend/uv.lock
|
||||
fi
|
||||
|
||||
- name: Build and push backend base image
|
||||
id: backend_base_ref
|
||||
env:
|
||||
PACKAGE_ACCESS_TOKEN: ${{ secrets.PACKAGE_ACCESS_TOKEN }}
|
||||
REGISTRY_USER: ${{ github.actor }}
|
||||
HEAD_SHA: ${{ steps.meta.outputs.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "${PACKAGE_ACCESS_TOKEN}" | docker login "http://${GITEA_REGISTRY}" -u "${REGISTRY_USER}" --password-stdin
|
||||
BACKEND_BASE_REPO="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-backend-base"
|
||||
BACKEND_BASE_TAG_REF="${BACKEND_BASE_REPO}:${HEAD_SHA}"
|
||||
|
||||
docker build -f Dockerfile.backend --target dependencies -t plex-playlist-backend-base:"${HEAD_SHA}" .
|
||||
docker tag plex-playlist-backend-base:"${HEAD_SHA}" "${BACKEND_BASE_TAG_REF}"
|
||||
docker push "${BACKEND_BASE_TAG_REF}"
|
||||
docker pull "${BACKEND_BASE_TAG_REF}" >/dev/null
|
||||
|
||||
BACKEND_BASE_DIGEST_REF="$({ docker image inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "${BACKEND_BASE_TAG_REF}" | grep "^${BACKEND_BASE_REPO}@sha256:" | head -n 1; } || true)"
|
||||
if [ -z "${BACKEND_BASE_DIGEST_REF}" ]; then
|
||||
echo "❌ Unable to resolve backend base image digest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "backend_base_tag_ref=${BACKEND_BASE_TAG_REF}" >> "$GITHUB_OUTPUT"
|
||||
echo "backend_base_digest_ref=${BACKEND_BASE_DIGEST_REF}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- *failure_diagnostics_step
|
||||
|
||||
build-frontend-base-image:
|
||||
name: Build Frontend Base Image
|
||||
runs-on: ubuntu-act-8gb
|
||||
needs: [build_cicd, cicd-tests-complete]
|
||||
outputs:
|
||||
frontend_base_tag_ref: ${{ steps.frontend_base_ref.outputs.frontend_base_tag_ref }}
|
||||
frontend_base_digest_ref: ${{ steps.frontend_base_ref.outputs.frontend_base_digest_ref }}
|
||||
steps:
|
||||
- name: Resolve head SHA
|
||||
id: meta
|
||||
run: |
|
||||
echo "head_sha=${{ needs.build_cicd.outputs.head_sha }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Minimal checkout for frontend base image inputs
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
HEAD_SHA: ${{ steps.meta.outputs.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
trap 'rm -f ~/.ssh/id_rsa' EXIT
|
||||
|
||||
if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then
|
||||
echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts
|
||||
fi
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -p "${GITEA_SSH_PORT}" "${GITEA_SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
|
||||
git clone --depth 1 --no-checkout "${GITEA_REPO_SSH_URL}" .
|
||||
|
||||
if GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
|
||||
git fetch --depth 1 origin "${HEAD_SHA}" >/dev/null 2>&1; then
|
||||
git checkout FETCH_HEAD -- Dockerfile.frontend frontend/package.json frontend/yarn.lock frontend/.yarnrc.yml
|
||||
else
|
||||
git checkout HEAD -- Dockerfile.frontend frontend/package.json frontend/yarn.lock frontend/.yarnrc.yml
|
||||
fi
|
||||
|
||||
- name: Build and push frontend base image
|
||||
id: frontend_base_ref
|
||||
env:
|
||||
PACKAGE_ACCESS_TOKEN: ${{ secrets.PACKAGE_ACCESS_TOKEN }}
|
||||
REGISTRY_USER: ${{ github.actor }}
|
||||
HEAD_SHA: ${{ steps.meta.outputs.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "${PACKAGE_ACCESS_TOKEN}" | docker login "http://${GITEA_REGISTRY}" -u "${REGISTRY_USER}" --password-stdin
|
||||
FRONTEND_BASE_REPO="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-frontend-base"
|
||||
FRONTEND_BASE_TAG_REF="${FRONTEND_BASE_REPO}:${HEAD_SHA}"
|
||||
|
||||
docker build -f Dockerfile.frontend --target deps -t plex-playlist-frontend-base:"${HEAD_SHA}" .
|
||||
docker tag plex-playlist-frontend-base:"${HEAD_SHA}" "${FRONTEND_BASE_TAG_REF}"
|
||||
docker push "${FRONTEND_BASE_TAG_REF}"
|
||||
docker pull "${FRONTEND_BASE_TAG_REF}" >/dev/null
|
||||
|
||||
FRONTEND_BASE_DIGEST_REF="$({ docker image inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "${FRONTEND_BASE_TAG_REF}" | grep "^${FRONTEND_BASE_REPO}@sha256:" | head -n 1; } || true)"
|
||||
if [ -z "${FRONTEND_BASE_DIGEST_REF}" ]; then
|
||||
echo "❌ Unable to resolve frontend base image digest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "frontend_base_tag_ref=${FRONTEND_BASE_TAG_REF}" >> "$GITHUB_OUTPUT"
|
||||
echo "frontend_base_digest_ref=${FRONTEND_BASE_DIGEST_REF}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- *failure_diagnostics_step
|
||||
|
||||
build-integration-tester-image:
|
||||
name: Build Integration Tester Image
|
||||
runs-on: ubuntu-act-8gb
|
||||
@@ -696,9 +883,10 @@ jobs:
|
||||
- *failure_diagnostics_step
|
||||
|
||||
runtime_images:
|
||||
name: Build and Publish Runtime Images
|
||||
name: Build and Publish Runtime Images (Legacy Disabled)
|
||||
runs-on: ubuntu-act-8gb
|
||||
needs: [build_cicd, cicd-tests-complete]
|
||||
if: false
|
||||
needs: [build_cicd, cicd-tests-complete, build-backend-base-image, build-frontend-base-image]
|
||||
timeout-minutes: 45
|
||||
outputs:
|
||||
head_sha: ${{ steps.meta.outputs.head_sha }}
|
||||
@@ -903,22 +1091,264 @@ jobs:
|
||||
|
||||
- *failure_diagnostics_step
|
||||
|
||||
build-backend-main-image:
|
||||
name: Build Backend Main Image
|
||||
runs-on: ubuntu-act-8gb
|
||||
needs: [build_cicd, cicd-tests-complete, build-backend-base-image]
|
||||
outputs:
|
||||
head_sha: ${{ steps.meta.outputs.head_sha }}
|
||||
deployable_backend_tag_ref: ${{ steps.deployable_backend_ref.outputs.deployable_backend_tag_ref }}
|
||||
deployable_backend_digest_ref: ${{ steps.deployable_backend_ref.outputs.deployable_backend_digest_ref }}
|
||||
steps:
|
||||
- name: Resolve head SHA
|
||||
id: meta
|
||||
run: |
|
||||
echo "head_sha=${{ needs.build_cicd.outputs.head_sha }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Minimal checkout for backend main image inputs
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
HEAD_SHA: ${{ steps.meta.outputs.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
trap 'rm -f ~/.ssh/id_rsa' EXIT
|
||||
|
||||
if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then
|
||||
echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts
|
||||
fi
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -p "${GITEA_SSH_PORT}" "${GITEA_SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
|
||||
git clone --depth 1 --no-checkout "${GITEA_REPO_SSH_URL}" .
|
||||
|
||||
if GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
|
||||
git fetch --depth 1 origin "${HEAD_SHA}" >/dev/null 2>&1; then
|
||||
git checkout FETCH_HEAD -- \
|
||||
.dockerignore \
|
||||
Dockerfile.backend \
|
||||
backend \
|
||||
scripts/verify-deployable-image-purity.sh
|
||||
else
|
||||
git checkout HEAD -- \
|
||||
.dockerignore \
|
||||
Dockerfile.backend \
|
||||
backend \
|
||||
scripts/verify-deployable-image-purity.sh
|
||||
fi
|
||||
|
||||
- name: Build and push backend main image
|
||||
id: deployable_backend_ref
|
||||
env:
|
||||
PACKAGE_ACCESS_TOKEN: ${{ secrets.PACKAGE_ACCESS_TOKEN }}
|
||||
REGISTRY_USER: ${{ github.actor }}
|
||||
HEAD_SHA: ${{ steps.meta.outputs.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
retry_registry_op() {
|
||||
op_name="$1"
|
||||
image_ref="$2"
|
||||
attempts="${3:-5}"
|
||||
backoff="${4:-3}"
|
||||
attempt=1
|
||||
|
||||
while [ "${attempt}" -le "${attempts}" ]; do
|
||||
echo "${op_name} attempt ${attempt}/${attempts} for ${image_ref}"
|
||||
if [ "${op_name}" = "push" ]; then
|
||||
if docker push "${image_ref}"; then
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
if docker pull "${image_ref}" >/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${attempt}" -lt "${attempts}" ]; then
|
||||
sleep_seconds=$((backoff * attempt))
|
||||
echo "Retrying in ${sleep_seconds}s"
|
||||
sleep "${sleep_seconds}"
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
docker build -f Dockerfile.backend -t deployable-backend:"${HEAD_SHA}" .
|
||||
bash ./scripts/verify-deployable-image-purity.sh --image deployable-backend:"${HEAD_SHA}" --profile backend
|
||||
|
||||
echo "${PACKAGE_ACCESS_TOKEN}" | docker login "http://${GITEA_REGISTRY}" -u "${REGISTRY_USER}" --password-stdin
|
||||
DEPLOYABLE_BACKEND_REPO="${GITEA_REGISTRY}/darkhelm.org/deployable-backend"
|
||||
DEPLOYABLE_BACKEND_TAG_REF="${DEPLOYABLE_BACKEND_REPO}:${HEAD_SHA}"
|
||||
docker tag "deployable-backend:${HEAD_SHA}" "${DEPLOYABLE_BACKEND_TAG_REF}"
|
||||
|
||||
retry_registry_op push "${DEPLOYABLE_BACKEND_TAG_REF}" 5 4
|
||||
retry_registry_op pull "${DEPLOYABLE_BACKEND_TAG_REF}" 5 3
|
||||
|
||||
DEPLOYABLE_BACKEND_DIGEST_REF="$({ docker image inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "${DEPLOYABLE_BACKEND_TAG_REF}" | grep "^${DEPLOYABLE_BACKEND_REPO}@sha256:" | head -n 1; } || true)"
|
||||
if [ -z "${DEPLOYABLE_BACKEND_DIGEST_REF}" ]; then
|
||||
echo "❌ Unable to resolve backend main digest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "deployable_backend_tag_ref=${DEPLOYABLE_BACKEND_TAG_REF}" >> "$GITHUB_OUTPUT"
|
||||
echo "deployable_backend_digest_ref=${DEPLOYABLE_BACKEND_DIGEST_REF}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- *failure_diagnostics_step
|
||||
|
||||
build-frontend-main-image:
|
||||
name: Build Frontend Main Image
|
||||
runs-on: ubuntu-act-8gb
|
||||
needs: [build_cicd, cicd-tests-complete, build-frontend-base-image]
|
||||
outputs:
|
||||
head_sha: ${{ steps.meta.outputs.head_sha }}
|
||||
deployable_frontend_tag_ref: ${{ steps.deployable_frontend_ref.outputs.deployable_frontend_tag_ref }}
|
||||
deployable_frontend_digest_ref: ${{ steps.deployable_frontend_ref.outputs.deployable_frontend_digest_ref }}
|
||||
steps:
|
||||
- name: Resolve head SHA
|
||||
id: meta
|
||||
run: |
|
||||
echo "head_sha=${{ needs.build_cicd.outputs.head_sha }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Minimal checkout for frontend main image inputs
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
HEAD_SHA: ${{ steps.meta.outputs.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
trap 'rm -f ~/.ssh/id_rsa' EXIT
|
||||
|
||||
if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then
|
||||
echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts
|
||||
fi
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -p "${GITEA_SSH_PORT}" "${GITEA_SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
|
||||
git clone --depth 1 --no-checkout "${GITEA_REPO_SSH_URL}" .
|
||||
|
||||
if GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
|
||||
git fetch --depth 1 origin "${HEAD_SHA}" >/dev/null 2>&1; then
|
||||
git checkout FETCH_HEAD -- \
|
||||
.dockerignore \
|
||||
Dockerfile.frontend \
|
||||
frontend \
|
||||
scripts/verify-deployable-image-purity.sh
|
||||
else
|
||||
git checkout HEAD -- \
|
||||
.dockerignore \
|
||||
Dockerfile.frontend \
|
||||
frontend \
|
||||
scripts/verify-deployable-image-purity.sh
|
||||
fi
|
||||
|
||||
- name: Build and push frontend main image
|
||||
id: deployable_frontend_ref
|
||||
env:
|
||||
PACKAGE_ACCESS_TOKEN: ${{ secrets.PACKAGE_ACCESS_TOKEN }}
|
||||
REGISTRY_USER: ${{ github.actor }}
|
||||
HEAD_SHA: ${{ steps.meta.outputs.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
retry_registry_op() {
|
||||
op_name="$1"
|
||||
image_ref="$2"
|
||||
attempts="${3:-5}"
|
||||
backoff="${4:-3}"
|
||||
attempt=1
|
||||
|
||||
while [ "${attempt}" -le "${attempts}" ]; do
|
||||
echo "${op_name} attempt ${attempt}/${attempts} for ${image_ref}"
|
||||
if [ "${op_name}" = "push" ]; then
|
||||
if docker push "${image_ref}"; then
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
if docker pull "${image_ref}" >/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${attempt}" -lt "${attempts}" ]; then
|
||||
sleep_seconds=$((backoff * attempt))
|
||||
echo "Retrying in ${sleep_seconds}s"
|
||||
sleep "${sleep_seconds}"
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
docker build -f Dockerfile.frontend --target production -t deployable-frontend:"${HEAD_SHA}" .
|
||||
bash ./scripts/verify-deployable-image-purity.sh --image deployable-frontend:"${HEAD_SHA}" --profile frontend
|
||||
|
||||
echo "${PACKAGE_ACCESS_TOKEN}" | docker login "http://${GITEA_REGISTRY}" -u "${REGISTRY_USER}" --password-stdin
|
||||
DEPLOYABLE_FRONTEND_REPO="${GITEA_REGISTRY}/darkhelm.org/deployable-frontend"
|
||||
DEPLOYABLE_FRONTEND_TAG_REF="${DEPLOYABLE_FRONTEND_REPO}:${HEAD_SHA}"
|
||||
docker tag "deployable-frontend:${HEAD_SHA}" "${DEPLOYABLE_FRONTEND_TAG_REF}"
|
||||
|
||||
retry_registry_op push "${DEPLOYABLE_FRONTEND_TAG_REF}" 5 4
|
||||
retry_registry_op pull "${DEPLOYABLE_FRONTEND_TAG_REF}" 5 3
|
||||
|
||||
DEPLOYABLE_FRONTEND_DIGEST_REF="$({ docker image inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "${DEPLOYABLE_FRONTEND_TAG_REF}" | grep "^${DEPLOYABLE_FRONTEND_REPO}@sha256:" | head -n 1; } || true)"
|
||||
if [ -z "${DEPLOYABLE_FRONTEND_DIGEST_REF}" ]; then
|
||||
echo "❌ Unable to resolve frontend main digest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "deployable_frontend_tag_ref=${DEPLOYABLE_FRONTEND_TAG_REF}" >> "$GITHUB_OUTPUT"
|
||||
echo "deployable_frontend_digest_ref=${DEPLOYABLE_FRONTEND_DIGEST_REF}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- *failure_diagnostics_step
|
||||
|
||||
production-images-complete:
|
||||
name: Production Images Complete
|
||||
runs-on: ubuntu-act
|
||||
needs: runtime_images
|
||||
runs-on: ubuntu-act-8gb
|
||||
needs: [build-backend-main-image, build-frontend-main-image, build-integration-tester-image, build-e2e-tester-image]
|
||||
if: always()
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Confirm deployable runtime images are published
|
||||
- name: Confirm required production/tester images are complete
|
||||
run: |
|
||||
echo "✅ Deployable backend/frontend runtime images are available"
|
||||
set -euo pipefail
|
||||
backend_main_status="${{ needs.build-backend-main-image.result }}"
|
||||
frontend_main_status="${{ needs.build-frontend-main-image.result }}"
|
||||
integration_tester_status="${{ needs.build-integration-tester-image.result }}"
|
||||
e2e_tester_status="${{ needs.build-e2e-tester-image.result }}"
|
||||
|
||||
echo "build-backend-main-image=${backend_main_status}"
|
||||
echo "build-frontend-main-image=${frontend_main_status}"
|
||||
echo "build-integration-tester-image=${integration_tester_status}"
|
||||
echo "build-e2e-tester-image=${e2e_tester_status}"
|
||||
|
||||
if [ "${backend_main_status}" != "success" ] || [ "${frontend_main_status}" != "success" ] || [ "${integration_tester_status}" != "success" ] || [ "${e2e_tester_status}" != "success" ]; then
|
||||
echo "❌ Production Images Complete gate failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Production, integration tester, and E2E tester images are complete"
|
||||
|
||||
- *failure_diagnostics_step
|
||||
|
||||
runtime-images-postmortem:
|
||||
name: Runtime Images Failure Postmortem
|
||||
# Run on the broader runner pool so we can still capture diagnostics when
|
||||
# runtime_images fails during platform "Set up job" before any step executes.
|
||||
runs-on: ubuntu-act
|
||||
needs: runtime_images
|
||||
if: always() && needs.runtime_images.result == 'failure'
|
||||
runs-on: ubuntu-act-8gb
|
||||
needs: [runtime_images, build-backend-main-image, build-frontend-main-image, build-integration-tester-image, build-e2e-tester-image]
|
||||
if: always() && (needs.runtime_images.result == 'failure' || needs['build-backend-main-image'].result == 'failure' || needs['build-frontend-main-image'].result == 'failure' || needs['build-integration-tester-image'].result == 'failure' || needs['build-e2e-tester-image'].result == 'failure')
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Identify runner
|
||||
@@ -1135,7 +1565,7 @@ jobs:
|
||||
# Pin integration tests to high-memory worker to reduce setup-stage runner churn.
|
||||
runs-on: ubuntu-act-8gb
|
||||
timeout-minutes: 20
|
||||
needs: [runtime_images, production-images-complete, build-integration-tester-image]
|
||||
needs: [build_cicd, production-images-complete, build-backend-main-image, build-frontend-main-image, build-integration-tester-image]
|
||||
steps:
|
||||
- name: Identify runner
|
||||
run: |
|
||||
@@ -1152,9 +1582,9 @@ jobs:
|
||||
- *configure_registry_host_step
|
||||
- name: Run runtime black-box integration checks via compose
|
||||
env:
|
||||
HEAD_SHA: ${{ needs.runtime_images.outputs.head_sha }}
|
||||
DEPLOYABLE_BACKEND_TAG_REF: ${{ needs.runtime_images.outputs.deployable_backend_tag_ref }}
|
||||
DEPLOYABLE_BACKEND_DIGEST_REF: ${{ needs.runtime_images.outputs.deployable_backend_digest_ref }}
|
||||
HEAD_SHA: ${{ needs.build_cicd.outputs.head_sha }}
|
||||
DEPLOYABLE_BACKEND_TAG_REF: ${{ needs.build-backend-main-image.outputs.deployable_backend_tag_ref }}
|
||||
DEPLOYABLE_BACKEND_DIGEST_REF: ${{ needs.build-backend-main-image.outputs.deployable_backend_digest_ref }}
|
||||
INTEGRATION_TESTER_DIGEST_REF: ${{ needs.build-integration-tester-image.outputs.integration_tester_digest_ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1365,12 +1795,33 @@ jobs:
|
||||
|
||||
- *failure_diagnostics_step
|
||||
|
||||
integration-tests-postmortem:
|
||||
name: Integration Tests Failure Postmortem
|
||||
runs-on: ubuntu-act
|
||||
needs: integration-tests
|
||||
if: always() && needs.integration-tests.result == 'failure'
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Integration postmortem diagnostics
|
||||
run: |
|
||||
echo "=== Integration Tests Postmortem ==="
|
||||
echo "integration_tests_result=${{ needs.integration-tests.result }}"
|
||||
uname -a || true
|
||||
cat /etc/os-release 2>/dev/null || true
|
||||
df -h || true
|
||||
free -h || true
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
docker version || true
|
||||
docker info || true
|
||||
fi
|
||||
timeout 10 curl -fsSIL "https://${GITEA_REGISTRY}/v2/" || true
|
||||
|
||||
e2e-tests:
|
||||
name: End-to-End Tests
|
||||
# Use ubuntu-act runner pool for consistent availability.
|
||||
runs-on: ubuntu-act-8gb
|
||||
timeout-minutes: 45
|
||||
needs: [runtime_images, production-images-complete, build-integration-tester-image, build-e2e-tester-image]
|
||||
needs: [build_cicd, production-images-complete, build-backend-main-image, build-frontend-main-image, build-integration-tester-image, build-e2e-tester-image]
|
||||
steps:
|
||||
- name: Identify runner
|
||||
run: |
|
||||
@@ -1387,12 +1838,12 @@ jobs:
|
||||
- *configure_registry_host_step
|
||||
- name: Run E2E tests against runtime services via compose
|
||||
env:
|
||||
HEAD_SHA: ${{ needs.runtime_images.outputs.head_sha }}
|
||||
HEAD_SHA: ${{ needs.build_cicd.outputs.head_sha }}
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
DEPLOYABLE_BACKEND_TAG_REF: ${{ needs.runtime_images.outputs.deployable_backend_tag_ref }}
|
||||
DEPLOYABLE_BACKEND_DIGEST_REF: ${{ needs.runtime_images.outputs.deployable_backend_digest_ref }}
|
||||
DEPLOYABLE_FRONTEND_TAG_REF: ${{ needs.runtime_images.outputs.deployable_frontend_tag_ref }}
|
||||
DEPLOYABLE_FRONTEND_DIGEST_REF: ${{ needs.runtime_images.outputs.deployable_frontend_digest_ref }}
|
||||
DEPLOYABLE_BACKEND_TAG_REF: ${{ needs.build-backend-main-image.outputs.deployable_backend_tag_ref }}
|
||||
DEPLOYABLE_BACKEND_DIGEST_REF: ${{ needs.build-backend-main-image.outputs.deployable_backend_digest_ref }}
|
||||
DEPLOYABLE_FRONTEND_TAG_REF: ${{ needs.build-frontend-main-image.outputs.deployable_frontend_tag_ref }}
|
||||
DEPLOYABLE_FRONTEND_DIGEST_REF: ${{ needs.build-frontend-main-image.outputs.deployable_frontend_digest_ref }}
|
||||
INTEGRATION_TESTER_DIGEST_REF: ${{ needs.build-integration-tester-image.outputs.integration_tester_digest_ref }}
|
||||
E2E_TESTER_DIGEST_REF: ${{ needs.build-e2e-tester-image.outputs.e2e_tester_digest_ref }}
|
||||
run: |
|
||||
@@ -1720,3 +2171,24 @@ jobs:
|
||||
find "${ARTIFACT_DIR}" -maxdepth 2 -type f | sort || true
|
||||
|
||||
- *failure_diagnostics_step
|
||||
|
||||
e2e-tests-postmortem:
|
||||
name: E2E Tests Failure Postmortem
|
||||
runs-on: ubuntu-act
|
||||
needs: e2e-tests
|
||||
if: always() && needs.e2e-tests.result == 'failure'
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: E2E postmortem diagnostics
|
||||
run: |
|
||||
echo "=== E2E Tests Postmortem ==="
|
||||
echo "e2e_tests_result=${{ needs.e2e-tests.result }}"
|
||||
uname -a || true
|
||||
cat /etc/os-release 2>/dev/null || true
|
||||
df -h || true
|
||||
free -h || true
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
docker version || true
|
||||
docker info || true
|
||||
fi
|
||||
timeout 10 curl -fsSIL "https://${GITEA_REGISTRY}/v2/" || true
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# Frontend Dockerfile for Vue/Vite TypeScript project
|
||||
FROM node:24-alpine AS build
|
||||
FROM node:24-alpine AS deps
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY frontend/package.json frontend/yarn.lock frontend/.yarnrc.yml ./
|
||||
RUN corepack enable && yarn install --immutable
|
||||
|
||||
FROM deps AS build
|
||||
|
||||
COPY frontend/ .
|
||||
RUN yarn build
|
||||
|
||||
|
||||
Reference in New Issue
Block a user