Initial commit
Deploy / validate-dispatch (push) Successful in 43s
Deploy / scheduled-rebuild-dev (push) Skipped
Deploy / scheduled-rebuild-master (push) Skipped
Deploy / rollback-dev (push) Skipped
Deploy / rollback-master (push) Skipped
Deploy / changes (push) Successful in 13s
Deploy / secret-scan (push) Successful in 9s
Deploy / app-quality (push) Successful in 11m21s
Deploy / security-deps (push) Successful in 14s
Deploy / context (push) Failing after 1s
Deploy / app-image (push) Skipped
Deploy / deploy-dev (push) Skipped
Deploy / deploy-master (push) Skipped
Deploy / validate-dispatch (push) Successful in 43s
Deploy / scheduled-rebuild-dev (push) Skipped
Deploy / scheduled-rebuild-master (push) Skipped
Deploy / rollback-dev (push) Skipped
Deploy / rollback-master (push) Skipped
Deploy / changes (push) Successful in 13s
Deploy / secret-scan (push) Successful in 9s
Deploy / app-quality (push) Successful in 11m21s
Deploy / security-deps (push) Successful in 14s
Deploy / context (push) Failing after 1s
Deploy / app-image (push) Skipped
Deploy / deploy-dev (push) Skipped
Deploy / deploy-master (push) Skipped
This commit is contained in:
@@ -0,0 +1,627 @@
|
||||
name: Deploy
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: website-starter-deploy-${{ github.event_name == 'schedule' && 'scheduled' || github.event.inputs.rollback_target || github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
rollback_target:
|
||||
description: "Rollback target: dev or master. Leave empty for a normal manual deploy."
|
||||
required: false
|
||||
app_image_tag:
|
||||
description: "Next app image tag for rollback."
|
||||
required: false
|
||||
schedule:
|
||||
- cron: "17 3 * * 1"
|
||||
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
NODE_VERSION_FILE: .node-version
|
||||
TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969
|
||||
REGISTRY: ${{ vars.CONTAINER_REGISTRY }}
|
||||
APP_IMAGE_REPOSITORY: ${{ vars.APP_IMAGE_REPOSITORY }}
|
||||
APP_NAME: ${{ vars.APP_NAME }}
|
||||
COMPANY_NAME: ${{ vars.COMPANY_NAME }}
|
||||
CONTACT_EMAIL: ${{ vars.CONTACT_EMAIL }}
|
||||
CONTACT_PHONE: ${{ vars.CONTACT_PHONE }}
|
||||
CONTACT_ADDRESS: ${{ vars.CONTACT_ADDRESS }}
|
||||
|
||||
jobs:
|
||||
validate-dispatch:
|
||||
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Validate workflow dispatch inputs
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
ROLLBACK_TARGET: ${{ github.event.inputs.rollback_target }}
|
||||
APP_IMAGE_TAG: ${{ github.event.inputs.app_image_tag }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
if [ "$EVENT_NAME" != "workflow_dispatch" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
fail() {
|
||||
echo "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
validate_tag() {
|
||||
key="$1"
|
||||
value="$2"
|
||||
case "$value" in
|
||||
[A-Za-z0-9_]* ) ;;
|
||||
*) fail "Invalid Docker image tag for $key." ;;
|
||||
esac
|
||||
case "$value" in
|
||||
*[!A-Za-z0-9_.-]* ) fail "Invalid Docker image tag for $key." ;;
|
||||
esac
|
||||
if [ "${#value}" -gt 128 ]; then
|
||||
fail "Docker image tag for $key exceeds 128 characters."
|
||||
fi
|
||||
}
|
||||
|
||||
case "$REF_NAME" in
|
||||
dev|master) ;;
|
||||
*) fail "Manual deployments must be dispatched from dev or master." ;;
|
||||
esac
|
||||
|
||||
if [ -z "$ROLLBACK_TARGET" ]; then
|
||||
if [ -n "$APP_IMAGE_TAG" ]; then
|
||||
fail "Image tags require rollback_target."
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case "$ROLLBACK_TARGET" in
|
||||
dev|master) ;;
|
||||
*) fail "rollback_target must be dev or master." ;;
|
||||
esac
|
||||
if [ "$ROLLBACK_TARGET" != "$REF_NAME" ]; then
|
||||
fail "rollback_target must match the dispatched branch."
|
||||
fi
|
||||
|
||||
validate_tag APP_IMAGE_TAG "$APP_IMAGE_TAG"
|
||||
|
||||
changes:
|
||||
if: github.event_name != 'schedule' && (github.event_name != 'workflow_dispatch' || github.event.inputs.rollback_target == '')
|
||||
needs: [validate-dispatch]
|
||||
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
deploy: ${{ steps.detect.outputs.deploy }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
lfs: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect changed areas
|
||||
id: detect
|
||||
env:
|
||||
CHANGESET_BEFORE: ${{ github.event.before }}
|
||||
run: sh scripts/detect-ci-changes.sh
|
||||
|
||||
app-quality:
|
||||
if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true'
|
||||
needs: [changes]
|
||||
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
APP_URL: https://example.com
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version-file: ${{ env.NODE_VERSION_FILE }}
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Restore and update image cache
|
||||
env:
|
||||
IMAGE_CACHE_NAMESPACE: ${{ github.ref_name }}
|
||||
run: sh scripts/prepare-image-cache.sh
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Test
|
||||
run: npm run test
|
||||
|
||||
- name: Type check
|
||||
run: npm run typecheck
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
SKIP_IMAGE_OPTIMIZATION: true
|
||||
run: npm run build
|
||||
|
||||
- name: E2E smoke test
|
||||
env:
|
||||
SKIP_IMAGE_OPTIMIZATION: true
|
||||
run: npm run test:e2e
|
||||
|
||||
secret-scan:
|
||||
if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true'
|
||||
needs: [changes]
|
||||
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Scan repository for committed secrets
|
||||
run: sh scripts/trivy-scan.sh secret-fs .
|
||||
|
||||
security-deps:
|
||||
if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true'
|
||||
needs: [changes]
|
||||
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Report app dependency vulnerabilities
|
||||
run: sh scripts/trivy-scan.sh vuln-fs .
|
||||
|
||||
- name: Block high and critical app dependency vulnerabilities
|
||||
env:
|
||||
TRIVY_SEVERITY: HIGH,CRITICAL
|
||||
TRIVY_EXIT_CODE: 1
|
||||
run: sh scripts/trivy-scan.sh vuln-fs .
|
||||
|
||||
context:
|
||||
if: >
|
||||
needs.changes.outputs.deploy == 'true' &&
|
||||
(
|
||||
(github.event_name == 'push' &&
|
||||
github.event.pull_request == null &&
|
||||
(github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/master')) ||
|
||||
(github.event_name == 'workflow_dispatch' &&
|
||||
github.event.pull_request == null &&
|
||||
(github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/master'))
|
||||
)
|
||||
needs: [changes]
|
||||
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
environment_name: ${{ steps.resolve.outputs.environment_name }}
|
||||
primary_domain: ${{ steps.resolve.outputs.primary_domain }}
|
||||
app_url: ${{ steps.resolve.outputs.app_url }}
|
||||
cors_origin: ${{ steps.resolve.outputs.cors_origin }}
|
||||
deploy_stack_name: ${{ steps.resolve.outputs.deploy_stack_name }}
|
||||
traefik_certresolver: ${{ steps.resolve.outputs.traefik_certresolver }}
|
||||
traefik_network_name: ${{ steps.resolve.outputs.traefik_network_name }}
|
||||
steps:
|
||||
- name: Resolve deployment target
|
||||
id: resolve
|
||||
env:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
DEV_PRIMARY_DOMAIN: ${{ vars.DEV_PRIMARY_DOMAIN }}
|
||||
DEV_APP_URL: ${{ vars.DEV_APP_URL }}
|
||||
DEV_CORS_ORIGIN: ${{ vars.DEV_CORS_ORIGIN }}
|
||||
DEV_DEPLOY_STACK_NAME: ${{ vars.DEV_DEPLOY_STACK_NAME }}
|
||||
DEV_TRAEFIK_CERTRESOLVER: ${{ secrets.DEV_TRAEFIK_CERTRESOLVER }}
|
||||
DEV_TRAEFIK_NETWORK_NAME: ${{ vars.DEV_TRAEFIK_NETWORK_NAME }}
|
||||
PROD_PRIMARY_DOMAIN: ${{ vars.PROD_PRIMARY_DOMAIN }}
|
||||
PROD_APP_URL: ${{ vars.PROD_APP_URL }}
|
||||
PROD_CORS_ORIGIN: ${{ vars.PROD_CORS_ORIGIN }}
|
||||
PROD_DEPLOY_STACK_NAME: ${{ vars.PROD_DEPLOY_STACK_NAME }}
|
||||
PROD_TRAEFIK_CERTRESOLVER: ${{ secrets.PROD_TRAEFIK_CERTRESOLVER }}
|
||||
PROD_TRAEFIK_NETWORK_NAME: ${{ vars.PROD_TRAEFIK_NETWORK_NAME }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
set_output() {
|
||||
printf '%s=%s\n' "$1" "$2" >> "$GITHUB_OUTPUT"
|
||||
}
|
||||
|
||||
require_value() {
|
||||
key="$1"
|
||||
value="$2"
|
||||
if [ -z "$value" ]; then
|
||||
echo "Missing required workflow configuration: $key" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "$REF_NAME" in
|
||||
dev)
|
||||
config_prefix="DEV"
|
||||
environment_name="dev"
|
||||
primary_domain="$DEV_PRIMARY_DOMAIN"
|
||||
app_url="${DEV_APP_URL:-}"
|
||||
cors_origin="${DEV_CORS_ORIGIN:-}"
|
||||
deploy_stack_name="${DEV_DEPLOY_STACK_NAME:-website-starter-dev}"
|
||||
traefik_certresolver="$DEV_TRAEFIK_CERTRESOLVER"
|
||||
traefik_network_name="${DEV_TRAEFIK_NETWORK_NAME:-traefik-external}"
|
||||
;;
|
||||
master)
|
||||
config_prefix="PROD"
|
||||
environment_name="production"
|
||||
primary_domain="$PROD_PRIMARY_DOMAIN"
|
||||
app_url="${PROD_APP_URL:-}"
|
||||
cors_origin="${PROD_CORS_ORIGIN:-}"
|
||||
deploy_stack_name="${PROD_DEPLOY_STACK_NAME:-website-starter-prod}"
|
||||
traefik_certresolver="$PROD_TRAEFIK_CERTRESOLVER"
|
||||
traefik_network_name="${PROD_TRAEFIK_NETWORK_NAME:-traefik}"
|
||||
;;
|
||||
*)
|
||||
echo "Branch $REF_NAME is not configured for deployment." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
require_value "${config_prefix}_PRIMARY_DOMAIN" "$primary_domain"
|
||||
require_value "${config_prefix}_TRAEFIK_CERTRESOLVER" "$traefik_certresolver"
|
||||
|
||||
if [ -z "$app_url" ]; then
|
||||
app_url="https://$primary_domain"
|
||||
fi
|
||||
if [ -z "$cors_origin" ]; then
|
||||
cors_origin="$app_url"
|
||||
fi
|
||||
|
||||
set_output "environment_name" "$environment_name"
|
||||
set_output "primary_domain" "$primary_domain"
|
||||
set_output "app_url" "$app_url"
|
||||
set_output "cors_origin" "$cors_origin"
|
||||
set_output "deploy_stack_name" "$deploy_stack_name"
|
||||
set_output "traefik_certresolver" "$traefik_certresolver"
|
||||
set_output "traefik_network_name" "$traefik_network_name"
|
||||
|
||||
app-image:
|
||||
if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true'
|
||||
needs: [changes, context, app-quality, secret-scan, security-deps]
|
||||
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
APP_URL: ${{ needs.context.outputs.app_url }}
|
||||
APP_IMAGE_NAME: ${{ env.APP_IMAGE_REPOSITORY }}:${{ github.ref_name }}-${{ github.sha }}-${{ github.run_id }}
|
||||
APP_BRANCH_IMAGE_NAME: ${{ env.APP_IMAGE_REPOSITORY }}:${{ github.ref_name }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version-file: ${{ env.NODE_VERSION_FILE }}
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Restore and update image cache
|
||||
env:
|
||||
IMAGE_CACHE_NAMESPACE: ${{ github.ref_name }}
|
||||
run: sh scripts/prepare-image-cache.sh
|
||||
|
||||
- name: Build app runtime image
|
||||
run: |
|
||||
docker build --pull \
|
||||
--build-arg BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
--build-arg VCS_REF="${{ github.sha }}" \
|
||||
--build-arg APP_URL="$APP_URL" \
|
||||
--build-arg APP_NAME="${APP_NAME:-Example Website}" \
|
||||
--build-arg COMPANY_NAME="${COMPANY_NAME:-Example Company}" \
|
||||
--build-arg CONTACT_EMAIL="${CONTACT_EMAIL:-info@example.com}" \
|
||||
--build-arg CONTACT_PHONE="${CONTACT_PHONE:-+49 000 000000}" \
|
||||
--build-arg CONTACT_ADDRESS="${CONTACT_ADDRESS:-Example Street 1, 12345 Example City}" \
|
||||
--build-arg SOURCE_URL="${{ github.server_url }}/${{ github.repository }}" \
|
||||
--build-arg SKIP_IMAGE_OPTIMIZATION=true \
|
||||
--build-arg IMAGE_CACHE_NAMESPACE="${{ github.ref_name }}" \
|
||||
-f Dockerfile \
|
||||
-t "$APP_IMAGE_NAME" \
|
||||
-t "$APP_BRANCH_IMAGE_NAME" \
|
||||
.
|
||||
|
||||
- name: Report app runtime image vulnerabilities
|
||||
run: sh scripts/trivy-scan.sh vuln-image "$APP_IMAGE_NAME"
|
||||
|
||||
- name: Block high and critical app runtime vulnerabilities
|
||||
env:
|
||||
TRIVY_SEVERITY: HIGH,CRITICAL
|
||||
TRIVY_EXIT_CODE: 1
|
||||
run: sh scripts/trivy-scan.sh vuln-image "$APP_IMAGE_NAME"
|
||||
|
||||
- name: Push app runtime image
|
||||
env:
|
||||
REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }}
|
||||
REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
if [ -z "${REGISTRY_USERNAME:-}" ] || [ -z "${REGISTRY_TOKEN:-}" ]; then
|
||||
echo "Missing required secrets: PACKAGES_USERNAME and PACKAGES_TOKEN" >&2
|
||||
exit 1
|
||||
fi
|
||||
docker_config="$(mktemp -d)"
|
||||
cleanup() {
|
||||
DOCKER_CONFIG="$docker_config" docker logout "$REGISTRY" >/dev/null 2>&1 || true
|
||||
rm -rf "$docker_config"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 1' HUP INT TERM
|
||||
export DOCKER_CONFIG="$docker_config"
|
||||
echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin
|
||||
docker push "$APP_IMAGE_NAME"
|
||||
docker push "$APP_BRANCH_IMAGE_NAME"
|
||||
|
||||
deploy-dev:
|
||||
if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true' && github.ref == 'refs/heads/dev'
|
||||
needs: [changes, context, app-quality, app-image, secret-scan, security-deps]
|
||||
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
CALENDAR_URL: ${{ secrets.CALENDAR_URL }}
|
||||
PRIMARY_DOMAIN: ${{ needs.context.outputs.primary_domain }}
|
||||
APP_URL: ${{ needs.context.outputs.app_url }}
|
||||
CORS_ORIGIN: ${{ needs.context.outputs.cors_origin }}
|
||||
COMPOSE_PROJECT_NAME: ${{ needs.context.outputs.deploy_stack_name }}
|
||||
TRAEFIK_STACK_NAME: ${{ needs.context.outputs.deploy_stack_name }}
|
||||
TRAEFIK_CERTRESOLVER: ${{ needs.context.outputs.traefik_certresolver }}
|
||||
TRAEFIK_NETWORK_NAME: ${{ needs.context.outputs.traefik_network_name }}
|
||||
DEPLOY_TARGET: dev
|
||||
APP_IMAGE_NAME: ${{ env.APP_IMAGE_REPOSITORY }}:dev-${{ github.sha }}-${{ github.run_id }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Pull app runtime image
|
||||
env:
|
||||
REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }}
|
||||
REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
if [ -z "${REGISTRY_USERNAME:-}" ] || [ -z "${REGISTRY_TOKEN:-}" ]; then
|
||||
echo "Missing required secrets: PACKAGES_USERNAME and PACKAGES_TOKEN" >&2
|
||||
exit 1
|
||||
fi
|
||||
docker_config="$(mktemp -d)"
|
||||
cleanup() {
|
||||
DOCKER_CONFIG="$docker_config" docker logout "$REGISTRY" >/dev/null 2>&1 || true
|
||||
rm -rf "$docker_config"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 1' HUP INT TERM
|
||||
export DOCKER_CONFIG="$docker_config"
|
||||
echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin
|
||||
docker pull "$APP_IMAGE_NAME"
|
||||
|
||||
- name: Validate runtime configuration
|
||||
run: |
|
||||
if [ -z "${CALENDAR_URL:-}" ]; then
|
||||
echo "Missing required secret: CALENDAR_URL" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Print deployment target
|
||||
run: |
|
||||
echo "Environment: ${{ needs.context.outputs.environment_name }}"
|
||||
echo "Site host: $PRIMARY_DOMAIN"
|
||||
echo "Compose project: $COMPOSE_PROJECT_NAME"
|
||||
|
||||
- name: Deploy and verify app
|
||||
run: sh scripts/deploy-and-verify.sh
|
||||
|
||||
deploy-master:
|
||||
if: github.event_name != 'schedule' && needs.changes.outputs.deploy == 'true' && github.ref == 'refs/heads/master'
|
||||
needs: [changes, context, app-quality, app-image, secret-scan, security-deps]
|
||||
runs-on: ${{ vars.PROD_RUNNER || vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
CALENDAR_URL: ${{ secrets.CALENDAR_URL }}
|
||||
PRIMARY_DOMAIN: ${{ needs.context.outputs.primary_domain }}
|
||||
APP_URL: ${{ needs.context.outputs.app_url }}
|
||||
CORS_ORIGIN: ${{ needs.context.outputs.cors_origin }}
|
||||
COMPOSE_PROJECT_NAME: ${{ needs.context.outputs.deploy_stack_name }}
|
||||
TRAEFIK_STACK_NAME: ${{ needs.context.outputs.deploy_stack_name }}
|
||||
TRAEFIK_CERTRESOLVER: ${{ needs.context.outputs.traefik_certresolver }}
|
||||
TRAEFIK_NETWORK_NAME: ${{ needs.context.outputs.traefik_network_name }}
|
||||
DEPLOY_TARGET: master
|
||||
APP_IMAGE_NAME: ${{ env.APP_IMAGE_REPOSITORY }}:master-${{ github.sha }}-${{ github.run_id }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Pull app runtime image
|
||||
env:
|
||||
REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }}
|
||||
REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
if [ -z "${REGISTRY_USERNAME:-}" ] || [ -z "${REGISTRY_TOKEN:-}" ]; then
|
||||
echo "Missing required secrets: PACKAGES_USERNAME and PACKAGES_TOKEN" >&2
|
||||
exit 1
|
||||
fi
|
||||
docker_config="$(mktemp -d)"
|
||||
cleanup() {
|
||||
DOCKER_CONFIG="$docker_config" docker logout "$REGISTRY" >/dev/null 2>&1 || true
|
||||
rm -rf "$docker_config"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 1' HUP INT TERM
|
||||
export DOCKER_CONFIG="$docker_config"
|
||||
echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin
|
||||
docker pull "$APP_IMAGE_NAME"
|
||||
|
||||
- name: Validate runtime configuration
|
||||
run: |
|
||||
if [ -z "${CALENDAR_URL:-}" ]; then
|
||||
echo "Missing required secret: CALENDAR_URL" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Print deployment target
|
||||
run: |
|
||||
echo "Environment: ${{ needs.context.outputs.environment_name }}"
|
||||
echo "Site host: $PRIMARY_DOMAIN"
|
||||
echo "Compose project: $COMPOSE_PROJECT_NAME"
|
||||
|
||||
- name: Deploy and verify app
|
||||
run: sh scripts/deploy-and-verify.sh
|
||||
|
||||
scheduled-rebuild-dev:
|
||||
if: github.event_name == 'schedule'
|
||||
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
DEPLOY_TARGET: dev
|
||||
CALENDAR_URL: ${{ secrets.CALENDAR_URL }}
|
||||
PRIMARY_DOMAIN: ${{ vars.DEV_PRIMARY_DOMAIN }}
|
||||
APP_URL: ${{ vars.DEV_APP_URL }}
|
||||
CORS_ORIGIN: ${{ vars.DEV_CORS_ORIGIN }}
|
||||
COMPOSE_PROJECT_NAME: ${{ vars.DEV_DEPLOY_STACK_NAME }}
|
||||
TRAEFIK_STACK_NAME: ${{ vars.DEV_DEPLOY_STACK_NAME }}
|
||||
TRAEFIK_CERTRESOLVER: ${{ secrets.DEV_TRAEFIK_CERTRESOLVER }}
|
||||
TRAEFIK_NETWORK_NAME: ${{ vars.DEV_TRAEFIK_NETWORK_NAME }}
|
||||
steps:
|
||||
- name: Checkout dev branch
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
ref: dev
|
||||
lfs: true
|
||||
|
||||
- name: Scan repository for committed secrets
|
||||
run: sh scripts/trivy-scan.sh secret-fs .
|
||||
|
||||
- name: Report app dependency vulnerabilities
|
||||
run: sh scripts/trivy-scan.sh vuln-fs .
|
||||
|
||||
- name: Block high and critical app dependency vulnerabilities
|
||||
env:
|
||||
TRIVY_SEVERITY: HIGH,CRITICAL
|
||||
TRIVY_EXIT_CODE: 1
|
||||
run: sh scripts/trivy-scan.sh vuln-fs .
|
||||
|
||||
- name: Rebuild latest base image and deploy dev
|
||||
env:
|
||||
REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }}
|
||||
REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
|
||||
run: sh scripts/scheduled-rebuild-deploy.sh
|
||||
|
||||
scheduled-rebuild-master:
|
||||
if: github.event_name == 'schedule'
|
||||
runs-on: ${{ vars.PROD_RUNNER || vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
DEPLOY_TARGET: master
|
||||
CALENDAR_URL: ${{ secrets.CALENDAR_URL }}
|
||||
PRIMARY_DOMAIN: ${{ vars.PROD_PRIMARY_DOMAIN }}
|
||||
APP_URL: ${{ vars.PROD_APP_URL }}
|
||||
CORS_ORIGIN: ${{ vars.PROD_CORS_ORIGIN }}
|
||||
COMPOSE_PROJECT_NAME: ${{ vars.PROD_DEPLOY_STACK_NAME }}
|
||||
TRAEFIK_STACK_NAME: ${{ vars.PROD_DEPLOY_STACK_NAME }}
|
||||
TRAEFIK_CERTRESOLVER: ${{ secrets.PROD_TRAEFIK_CERTRESOLVER }}
|
||||
TRAEFIK_NETWORK_NAME: ${{ vars.PROD_TRAEFIK_NETWORK_NAME }}
|
||||
steps:
|
||||
- name: Checkout master branch
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
ref: master
|
||||
lfs: true
|
||||
|
||||
- name: Scan repository for committed secrets
|
||||
run: sh scripts/trivy-scan.sh secret-fs .
|
||||
|
||||
- name: Report app dependency vulnerabilities
|
||||
run: sh scripts/trivy-scan.sh vuln-fs .
|
||||
|
||||
- name: Block high and critical app dependency vulnerabilities
|
||||
env:
|
||||
TRIVY_SEVERITY: HIGH,CRITICAL
|
||||
TRIVY_EXIT_CODE: 1
|
||||
run: sh scripts/trivy-scan.sh vuln-fs .
|
||||
|
||||
- name: Rebuild latest base image and deploy master
|
||||
env:
|
||||
REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }}
|
||||
REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
|
||||
run: sh scripts/scheduled-rebuild-deploy.sh
|
||||
|
||||
rollback-dev:
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_target == 'dev' && github.ref == 'refs/heads/dev'
|
||||
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
DEPLOY_TARGET: dev
|
||||
CALENDAR_URL: ${{ secrets.CALENDAR_URL }}
|
||||
PRIMARY_DOMAIN: ${{ vars.DEV_PRIMARY_DOMAIN }}
|
||||
APP_URL: ${{ vars.DEV_APP_URL }}
|
||||
CORS_ORIGIN: ${{ vars.DEV_CORS_ORIGIN }}
|
||||
COMPOSE_PROJECT_NAME: ${{ vars.DEV_DEPLOY_STACK_NAME }}
|
||||
TRAEFIK_STACK_NAME: ${{ vars.DEV_DEPLOY_STACK_NAME }}
|
||||
TRAEFIK_CERTRESOLVER: ${{ secrets.DEV_TRAEFIK_CERTRESOLVER }}
|
||||
TRAEFIK_NETWORK_NAME: ${{ vars.DEV_TRAEFIK_NETWORK_NAME }}
|
||||
APP_IMAGE_TAG: ${{ github.event.inputs.app_image_tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Deploy rollback and verify app
|
||||
env:
|
||||
REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }}
|
||||
REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
|
||||
run: sh scripts/manual-rollback-deploy.sh
|
||||
|
||||
rollback-master:
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_target == 'master' && github.ref == 'refs/heads/master'
|
||||
runs-on: ${{ vars.PROD_RUNNER || vars.CI_RUNNER || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
DEPLOY_TARGET: master
|
||||
CALENDAR_URL: ${{ secrets.CALENDAR_URL }}
|
||||
PRIMARY_DOMAIN: ${{ vars.PROD_PRIMARY_DOMAIN }}
|
||||
APP_URL: ${{ vars.PROD_APP_URL }}
|
||||
CORS_ORIGIN: ${{ vars.PROD_CORS_ORIGIN }}
|
||||
COMPOSE_PROJECT_NAME: ${{ vars.PROD_DEPLOY_STACK_NAME }}
|
||||
TRAEFIK_STACK_NAME: ${{ vars.PROD_DEPLOY_STACK_NAME }}
|
||||
TRAEFIK_CERTRESOLVER: ${{ secrets.PROD_TRAEFIK_CERTRESOLVER }}
|
||||
TRAEFIK_NETWORK_NAME: ${{ vars.PROD_TRAEFIK_NETWORK_NAME }}
|
||||
APP_IMAGE_TAG: ${{ github.event.inputs.app_image_tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Deploy rollback and verify app
|
||||
env:
|
||||
REGISTRY_USERNAME: ${{ secrets.PACKAGES_USERNAME }}
|
||||
REGISTRY_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
|
||||
run: sh scripts/manual-rollback-deploy.sh
|
||||
Reference in New Issue
Block a user