feat(ci): Docker build rework #61
Workflow file for this run
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Docker Build | ||
# This workflow builds Docker images for projects in the repository. | ||
# It can be triggered by: | ||
# - Labeling a pull request with "docker build" | ||
# - Being called from another workflow via `workflow_call` | ||
# - Manual dispatch using `workflow_dispatch` | ||
# The workflow uses matrix builds to parallelize the process and leverages caching | ||
# to speed up dependency and final image builds. | ||
on: | ||
pull_request: | ||
types: | ||
- labeled | ||
- synchronize | ||
workflow_call: | ||
inputs: | ||
projects: | ||
description: 'Comma-separated list of project names to build.' | ||
type: string | ||
required: true | ||
runners: | ||
description: 'The runners to run the jobs on.' | ||
type: string | ||
required: false | ||
default: 'arc-runners' | ||
workflow_dispatch: | ||
inputs: | ||
projects: | ||
description: 'Comma-separated list of project names to build.' | ||
type: string | ||
required: true | ||
runners: | ||
description: 'The runners to run the jobs on.' | ||
required: false | ||
type: string | ||
default: 'arc-runners' | ||
concurrency: | ||
group: container-build-${{ github.head_ref || github.run_id }} | ||
cancel-in-progress: true | ||
defaults: | ||
run: | ||
shell: bash -euo pipefail {0} | ||
env: | ||
# Regsitry to for build images and cache | ||
IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY || 'localhost' }} | ||
AWS_ECR_REPO_BASE: ${{ vars.AWS_ECR_REPO_BASE || 'docker.io' }} | ||
is_labeled: ${{ contains(github.event.pull_request.labels.*.name, 'docker build') }} | ||
jobs: | ||
prepare: | ||
name: Prepare | ||
if: ${{ github.event_name != 'pull_request' || env.is_labeled }} | ||
Check failure on line 55 in .github/workflows/docker.yml GitHub Actions / Docker BuildInvalid workflow file
|
||
runs-on: ${{ inputs.runners || 'arc-runners' }} | ||
outputs: | ||
matrix: ${{ steps.set-matrix.outputs.matrix }} | ||
node-image-version: ${{ steps.build-args.outputs.node-image-tag }} | ||
playwright-image-tag: ${{ steps.build-args.outputs.playwright-image-tag }} | ||
container-registry: ${{ steps.build-args.outputs.container-registry }} | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Build arg prep | ||
id: build-args | ||
run: | | ||
echo node-image-version="$(./scripts/ci/get-node-version.mjs)" >>"$GITHUB_OUTPUT" | ||
yarn info --json @playwright/test | jq -r '.children.Version | "playwright-image-tag=v\(.)-focal"' >>"$GITHUB_OUTPUT" | ||
- name: Codegen | ||
if: ${{ env.is_labeled }} | ||
run: | | ||
yarn codegen | ||
- name: Create matrix from input | ||
id: set-matrix | ||
env: | ||
localrun: ${{ !!github.event.localrun }} | ||
projects: ${{ inputs.projects }} | ||
run: | | ||
if [[ "$is_labeled" == true ]]; then | ||
echo "Using small subset for testing docker build (on ${{ github.event.pull_request.base.ref }})" | ||
# A representative sample of various docker build targets | ||
export projects="web,air-discount-scheme-backend,license-api" | ||
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 --arg projects "$projects" '{ include: [ | ||
.[] | ||
| select( .name | IN($projects | split(",") | .[]) ) | ||
| { | ||
project: .name, | ||
docker: (.targets | keys | map(select(startswith("docker-") and . != "docker-native")) | map(sub("^docker-"; "")) | .[]) | ||
} | ||
] | ||
}')" | tee -a "$GITHUB_OUTPUT" | ||
build: | ||
name: Build ${{ matrix.project }} | ||
runs-on: ${{ inputs.runners || 'arc-runners' }} | ||
needs: prepare | ||
strategy: | ||
fail-fast: false | ||
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }} | ||
permissions: | ||
id-token: write # This is required for requesting the JWT for AWS/ECR login | ||
contents: read # This is required for actions/checkout | ||
steps: | ||
- name: Prepare arguments | ||
id: args | ||
env: | ||
# We don't use a repository prefix (e.g. <registry>/island.is/<project>), just the raw project | ||
image_repository: ${{ env.IMAGE_REGISTRY }}/${{ matrix.project }} | ||
localrun: ${{ github.event.localrun }} | ||
run: | | ||
echo image-repository="$image_repository" >>"$GITHUB_OUTPUT" | ||
echo build-cache-local="type=local,src=/tmp,dest=/tmp,mode=max,enabled=${{ !!github.event.localrun }}" >>"$GITHUB_OUTPUT" | ||
echo build-cache="type=registry,image-manifest=true,oci-mediatypes=true,ref=$image_repository:cache,enabled=${{ !github.event.localrun }}" >>"$GITHUB_OUTPUT" | ||
- name: Check out repo | ||
uses: actions/checkout@v4 | ||
- name: Configure AWS Credentials | ||
if: ${{ !github.event.localrun }} | ||
id: aws-creds | ||
uses: aws-actions/configure-aws-credentials@v4 | ||
with: | ||
role-session-name: container-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 }} | ||
# TODO: 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: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v3 | ||
with: | ||
driver: docker-container | ||
# driver-opts: image=moby/buildkit:buildx-stable-1 | ||
driver-opts: | | ||
image=${{ env.AWS_ECR_REPO_BASE }}/moby/buildkit:buildx-stable-1 | ||
install: true | ||
use: true | ||
- name: Generate image metadata | ||
id: meta | ||
# This step requires a valid GitHub token to query the API for e.g. description of repository | ||
if: ${{ !github.event.localrun }} | ||
uses: docker/metadata-action@v5 | ||
with: | ||
images: | | ||
${{ steps.args.outputs.image-repository }} | ||
tags: | | ||
# Edge without prefix should only be used on `main`, so as not to conflict with type=sha | ||
# type=edge | ||
type=edge,prefix=edge- | ||
# SemVer by tag (e.g. v1.2.3) | ||
type=semver,pattern={{version}} | ||
type=pep440,pattern={{version}} | ||
# PR branch/name | ||
# type=ref,event=<branch|tag|pr> Defaults are good | ||
# Git SHA | ||
type=sha,format=short,prefix= | ||
type=sha,format=long,prefix= | ||
# The final sha for the merge commit | ||
# mcgh: Merge Commit GitHub | ||
type=raw,value=mcgh-${{ github.event.pull_request.merge_commit_sha }} | ||
- name: Docker build/cache dependencies | ||
uses: docker/build-push-action@v6 | ||
with: | ||
context: . | ||
file: scripts/ci/Dockerfile | ||
push: false | ||
target: deps | ||
build-args: | | ||
NODE_IMAGE_TAG=${{ needs.prepare.outputs.node-image-version }} | ||
PLAYWRIGHT_IMAGE_TAG=${{ needs.prepare.outputs.playwright-image-tag }} | ||
cache-from: | | ||
${{ steps.args.outputs.build-cache-local }} | ||
${{ steps.args.outputs.build-cache }} | ||
cache-to: | | ||
${{ steps.args.outputs.build-cache-local }},mode=max | ||
${{ steps.args.outputs.build-cache }},mode=max | ||
- name: Docker build/cache output base | ||
uses: docker/build-push-action@v6 | ||
with: | ||
context: . | ||
file: scripts/ci/Dockerfile | ||
push: false | ||
target: output-base | ||
build-args: | | ||
NODE_IMAGE_TAG=${{ needs.prepare.outputs.node-image-version }} | ||
PLAYWRIGHT_IMAGE_TAG=${{ needs.prepare.outputs.playwright-image-tag }} | ||
cache-from: | | ||
${{ steps.args.outputs.build-cache-local }} | ||
${{ steps.args.outputs.build-cache }} | ||
cache-to: | | ||
${{ steps.args.outputs.build-cache-local }},mode=max | ||
${{ steps.args.outputs.build-cache }},mode=max | ||
- 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-version }} | ||
APP=${{ matrix.project }} | ||
PLAYWRIGHT_IMAGE_TAG=${{ needs.prepare.outputs.playwright-image-tag }} | ||
# Caching final images doesn't make sense when this should only run if the project is affected (changed) | ||
cache-from: | | ||
${{ steps.args.outputs.build-cache-local }} | ||
${{ steps.args.outputs.build-cache }} | ||
cache-to: | | ||
${{ steps.args.outputs.build-cache }},mode=min |