name: CI/CD on: push: branches: - main tags: - "v*" pull_request: branches: - main - fix/dependency-cves permissions: contents: read env: REGISTRY: git.twyco.de jobs: preconditions: if: > !(contains(github.event.head_commit.message, '[no-ci]') || contains(github.event.head_commit.message, '[skip-ci]')) runs-on: ubuntu-latest steps: - name: Conditions met run: | echo "Conditions for CI/CD are met. Proceeding with the workflow." test_and_check_app: runs-on: ubuntu-latest needs: - preconditions steps: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Run required application checks uses: ./.github/tests/test_and_check_app publish: name: Publish immutable image if: >- github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) needs: - preconditions - test_and_check_app runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: fetch-depth: 0 - name: Validate ref and calculate image name id: image shell: bash run: | set -Eeuo pipefail image_path="$(printf '%s' "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]')" image="${REGISTRY}/${image_path}" case "${GITHUB_REF}" in refs/heads/main) printf -v run_attempt '%03d' "${GITHUB_RUN_ATTEMPT}" tag="main-${GITHUB_RUN_NUMBER}-${run_attempt}-${GITHUB_SHA:0:12}" environment="staging" ;; refs/tags/*) tag="${GITHUB_REF#refs/tags/}" if [[ ! "${tag}" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then echo "Production tags must match vMAJOR.MINOR.PATCH." >&2 exit 1 fi git fetch --no-tags origin main:refs/remotes/origin/main if ! git merge-base --is-ancestor "${GITHUB_SHA}" origin/main; then echo "Production tag must point to a commit contained in main." >&2 exit 1 fi environment="production" ;; *) echo "Unsupported ref: ${GITHUB_REF}" >&2 exit 1 ;; esac { echo "image=${image}" echo "tag=${tag}" echo "environment=${environment}" } >> "${GITHUB_OUTPUT}" - name: Log in to private registry shell: bash env: REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} run: | set -Eeuo pipefail if [[ -z "${REGISTRY_USERNAME}" || -z "${REGISTRY_TOKEN}" ]]; then echo "Registry credentials are missing." >&2 exit 1 fi printf '%s' "${REGISTRY_TOKEN}" | docker login \ --username "${REGISTRY_USERNAME}" \ --password-stdin \ "${REGISTRY}" - name: Build and publish runtime image shell: bash env: IMAGE: ${{ steps.image.outputs.image }} IMAGE_TAG: ${{ steps.image.outputs.tag }} DEPLOYMENT_ENVIRONMENT: ${{ steps.image.outputs.environment }} run: | set -Eeuo pipefail if [[ "${DEPLOYMENT_ENVIRONMENT}" == "production" ]] && docker buildx imagetools inspect "${IMAGE}:${IMAGE_TAG}" >/dev/null 2>&1; then echo "Production image ${IMAGE}:${IMAGE_TAG} already exists and will not be overwritten." >&2 exit 1 fi docker buildx build \ --file Dockerfile \ --target runtime \ --label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --label "org.opencontainers.image.revision=${GITHUB_SHA}" \ --label "org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \ --label "de.twyco.environment=${DEPLOYMENT_ENVIRONMENT}" \ --tag "${IMAGE}:${IMAGE_TAG}" \ --push \ . - name: Remove registry credentials if: ${{ always() }} shell: bash run: docker logout "${REGISTRY}" >/dev/null 2>&1 || true