Skip to content

feat(ci): Docker build rework #40

feat(ci): Docker build rework

feat(ci): Docker build rework #40

Workflow file for this run

name: Docker Build
on:
pull_request:
branches: [main, 'release-*', 'pre-release-*']
push:
branches: [main, 'release-*', 'pre-release-*']
workflow_dispatch:
inputs:
projects:
description: 'Comma-separated list of project names to build.'
required: true
concurrency:
group: docker-build-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
defaults:
run:
shell: bash -euo pipefail {0}
env:
PUSH_REGISTRY: ${{ vars.PUSH_REGISTRY }}
jobs:
prepare:
name: Prepare workflow Docker build
runs-on: ec2-runners
container:
image: public.ecr.aws/m3u4c4h9/island-is/actions-runner-public:latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
node-image-tag: ${{ steps.build-args.outputs.node-image-tag }}
docker-registry: ${{ steps.build-args.outputs.docker-registry }}
merge-commit-sha: ${{ steps.merge-commit.outputs.merge-commit-sha }}
merge-commit-sha-short: ${{ steps.merge-commit.outputs.merge-commit-sha-short }}
steps:
- uses: actions/checkout@v4
- name: Build arg prep
id: build-args
run: |
echo node-image-tag="$(./scripts/ci/get-node-version.mjs)" >>"$GITHUB_OUTPUT"
- name: Compute merge commit
id: merge-commit
if: ${{ !github.event.localrun }}
continue-on-error: true
run: |
# Create a temporary merge commit
MERGE_BASE="$(git merge-base HEAD "${{ github.base_ref }}")"
TEMP_BRANCH="temp-merge-$(date +%s)"
git checkout -f -b "$TEMP_BRANCH" "$MERGE_BASE"
git merge --no-commit --no-ff HEAD@{1}
# Get the tree object of the merge result
TREE_OBJECT="$(git write-tree)"
# Create a temporary merge commit
MERGE_COMMIT_SHA="$(git commit-tree $TREE_OBJECT -p HEAD -p HEAD@{1} -m "Temporary merge commit for tagging")"
echo merge-commit-sha="$MERGE_COMMIT_SHA" >>"$GITHUB_OUTPUT"
echo merge-commit-sha-short="${MERGE_COMMIT_SHA:0:7}" >>"$GITHUB_OUTPUT"
# Clean up the temporary branch
git checkout ${{ github.sha }}
git branch -D $TEMP_BRANCH
- name: Create matrix from input
id: set-matrix
env:
localrun: ${{ !!github.event.localrun }}
run: |
# Run smaller sample than all affected when running locally
if [[ ${localrun} == true ]]; then
output="matrix=$(
echo '{
"include":[
{"project":"web","docker":"next"},
{"project":"api","docker":"nest"},
{"project":"service-portal","docker":"static"}
]
}' | jq -c
)"
echo "$output" >>"$GITHUB_OUTPUT"
exit 0
fi
# Create a list of objects of the form:
# [
# {
# "name": "services-my-service",
# "docker": "next|nest|mytype"
# },
# ...
# ]
echo "matrix=$(git ls-files '**/project.json' |
xargs cat | \
jq -s -c '{ include: [
.[]
| {
project: .name,
docker: (.targets | keys | map(select(startswith("docker-") and . != "docker-native")) | map(sub("^docker-"; "")) | .[])
}
]
}')" >>"$GITHUB_OUTPUT"
build:
name: Build ${{ matrix.project }}
runs-on: ec2-runners
container:
image: public.ecr.aws/m3u4c4h9/island-is/actions-runner-public:latest
needs: prepare
strategy:
fail-fast: true
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout
steps:
- name: Prepare and debug inputs
id: inputs
env:
# Append a slash when PUSH_REGISTRY is non-empty
registry: "${{ env.PUSH_REGISTRY }}${{ env.PUSH_REGISTRY && '/' || '' }}"
# We don't use a repository prefix, just the raw project
# repository: '${{ github.repository }}/${{ matrix.project }}'
repository: '${{ matrix.project }}'
localrun: ${{ github.event.localrun }}
run: |
IMAGE_REPOSITORY="$registry$repository"
echo "Labels: ${{ toJSON(github.event.pull_request.labels) }}"
echo "Matrix: ${{ toJSON(matrix) }}"
echo image-repository="$IMAGE_REPOSITORY" >>"$GITHUB_OUTPUT"
if [[ "$localrun" == true ]]; then
echo build-cache="type=local,src=/tmp,dest=/tmp,mode=max" >>"$GITHUB_OUTPUT"
else
echo build-cache="type=registry,image-manifest=true,oci-mediatypes=true,ref=$IMAGE_REPOSITORY:cache,mode=max" >>"$GITHUB_OUTPUT"
fi
- name: Check out repo
uses: actions/checkout@v4
- name: Configure AWS Credentials
if: ${{ !github.event.localrun && env.PUSH_REGISTRY && env.PUSH_REGISTRY != 'ghcr.io' }}
id: aws-creds
uses: aws-actions/configure-aws-credentials@v4
with:
role-session-name: docker-build
aws-region: ${{ vars.AWS_REGION }}
# Identical to usage in push.yml
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# Use this instead of aws-{access,secret}, but needs proper roles & policies
# role-to-assume: ${{ vars.AWS_ECR_ROLE }}
- name: Log in to Amazon ECR
if: ${{ steps.aws-creds.conclusion == 'success' }}
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Log in to Docker
if: ${{ !github.event.localrun && env.PUSH_REGISTRY == 'ghcr.io' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker-container
# driver-opts: image=moby/buildkit:buildx-stable-1
install: true
use: true
- name: Generate Docker metadata
id: meta
if: ${{ !github.event.localrun }}
uses: docker/metadata-action@v5
with:
images: |
${{ steps.inputs.outputs.image-repository }}
tags: |
type=ref,event=branch
# Git SHA
type=sha,format=short,prefix=
type=sha,format=long,prefix=
# SemVer by tag (e.g. v1.2.3)
type=semver,pattern={{version}}
# The final sha for the merge commit
type=raw,value=${{ github.event.pull_request.merge_commit_sha }}
type=raw,value=${{ needs.prepare.outputs.merge-commit-sha }}
type=raw,value=${{ needs.prepare.outputs.merge-commit-sha-short }}
- name: Docker build/cache deps
uses: docker/build-push-action@v6
with:
context: .
file: scripts/ci/Dockerfile
push: false
# labels: ${{ steps.meta.outputs.labels }}
# tags: ${{ steps.meta.outputs.tags }}
target: deps
build-args: |
NODE_IMAGE_TAG=${{ needs.prepare.outputs.node-image-tag }}
cache-from: ${{ steps.inputs.outputs.build-cache }}
cache-to: ${{ steps.inputs.outputs.build-cache }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: scripts/ci/Dockerfile
push: ${{ !github.event.localrun }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
target: output-${{ matrix.docker }}
build-args: |
NODE_IMAGE_TAG=${{ needs.prepare.outputs.node-image-tag }}
APP=${{ matrix.project }}
cache-from: ${{ steps.inputs.outputs.build-cache }}
# cache-to: ${{ steps.inputs.outputs.build-cache }}
status:
name: Collect statuses
runs-on: ec2-runners
container:
image: public.ecr.aws/m3u4c4h9/island-is/actions-runner-public:latest
if: ${{ !cancelled() }}
needs:
- prepare
- build
steps:
- name: Check statuses
env:
dependencies: ${{ toJSON(needs) }}
run: |
# Debug inputs
echo "$dependencies" | jq
echo "# Summary report" >>"$GITHUB_STEP_SUMMARY"
echo "|job|result|" >>"$GITHUB_STEP_SUMMARY"
echo "|---|------|" >>"$GITHUB_STEP_SUMMARY"
success=true
# Loop through each job and ensure .result == "success"
for job in $(echo "$dependencies" | jq -r 'keys[]'); do
result=$(echo "$dependencies" | jq -r ".[\"$job\"].result")
echo "Job $job => $result"
echo "|$job|$result $([[ "$result" == "success" ]] && echo "✅" || echo "❌")|" >>"$GITHUB_STEP_SUMMARY"
if [[ "$result" != "success" ]]; then
success=false
fi
done
if [[ success != true ]]; then
exit 1
fi
echo "All dependencies succeeded!"