-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: build CI Docker images with GH Action
Action to build and deploy Docker images from the CI Dockerfiles in the repo. The action is triggered by a push to the `main`/`master` or `testing` branch when it affects one of the CI `Dockerfile`s (`buildkite/docker/*/Dockerfile`). It can also be triggered manually via the Actions web UI, the GH REST API or the GH CLI tool, e.g.: ```sh gh workflow run build-ci-docker-images ``` When triggered by a `push` event the action will: 1. Determine which `Dockerfile`s are affected. 2. For those `Dockerfile`s, determine its build context (the directory where the `Dockerfile` lives) and the build targets in it. 3. Filter the build targets: * first with `RE_TARGET_INCLUDE`, which defaults to empty so it will match all build targets. * then with `RE_TARGET_EXCLUDE`: set by default to remove some of the build targets the deprecated images like `centos7` and some targets that we don't want to build like the `nojdk` ones. 5. Then, it will also exclude all `testimage` targets because that image is only used for manually testing the workflow. 6. Finally, it will spawn a `docker/build-push-action` job for each of the build targets. For every image built it will push to the registry three image tags: * a `sha` tag with the short hash of the commit that triggered the push * a `date` tag with the current date in ISO format (`YYYYMMDD`) * a `latest` tag When triggered manually (`workflow_dispatch` event) the workflow will default to "running in test mode": it will follow the same steps as a `push` run but with different default values (see `workflow_dispatch.inputs`): * `RE_TARGET_INCLUDE` set to `testimage` (a very simple image to exercise the build that doesn't take much compute) and * `RE_TARGET_EXCLUDE` set to the same pattern as in the `push` event. This effectively limits the build targets to only those in `testimage` not excluded by `RE_TARGET_EXCLUDE` (e.g. `nojdk`). The "test run" also limits the `PLATFORMS` to `linux/amd64`, to further reduce the cost and time of a test run. Finally, it will build those `testimage` targets but **it won't tag `latest` or push any of the image tags to the registry**. This "test mode" behavior can be changed by setting the `workflow_dispatch.inputs` variables: `RE_TARGET_EXCLUDE`, `RE_TARGET_INCLUDE`, `PLATFORMS`, `TAG_DATE`, `TAG_LATEST` and `PUSH`, e.g.: ```sh gh workflow run build-ci-docker-images \ -f RE_TARGET_INCLUDE=ubuntu2404 -f TAG_DATE=20241101 ```
- Loading branch information
Showing
1 changed file
with
284 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
# Action to build and deploy Docker images from the CI Dockerfiles in the | ||
# repo. | ||
# | ||
# The action is triggered by a push to the `main`/`master` or `testing` | ||
# branch when it affects one of the CI `Dockerfile`s | ||
# (`buildkite/docker/*/Dockerfile`). It can also be triggered manually via | ||
# the Actions web UI, the GH REST API or the GH CLI tool, e.g.: | ||
# ```sh | ||
# gh workflow run build-ci-docker-images | ||
# ``` | ||
# | ||
# When triggered by a `push` event the action will: | ||
# | ||
# 1. Determine which `Dockerfile`s are affected. | ||
# | ||
# 2. For those `Dockerfile`s, determine its build context (the directory | ||
# where the `Dockerfile` lives) and the build targets in it. | ||
# | ||
# 3. Filter the build targets: | ||
# * first with `RE_TARGET_INCLUDE`, which defaults to empty so it will | ||
# match all build targets. | ||
# * then with `RE_TARGET_EXCLUDE`: set by default to remove some of the | ||
# build targets the deprecated images like `centos7` and some targets | ||
# that we don't want to build like the `nojdk` ones. | ||
# | ||
# 5. Then, it will also exclude all `testimage` targets because that image | ||
# is only used for manually testing the workflow. | ||
# | ||
# 6. Finally, it will spawn a `docker/build-push-action` job for each of | ||
# the build targets. For every image built it will push to the registry | ||
# three image tags: | ||
# * a `sha` tag with the short hash of the commit that triggered the push | ||
# * a `date` tag with the current date in ISO format (`YYYYMMDD`) | ||
# * a `latest` tag | ||
# | ||
# When triggered manually (`workflow_dispatch` event) the workflow will | ||
# default to "running in test mode": it will follow the same steps as a | ||
# `push` run but with different default values (see | ||
# `workflow_dispatch.inputs`): | ||
# * `RE_TARGET_INCLUDE` set to `testimage` (a very simple image to | ||
# exercise the build that doesn't take much compute) and | ||
# * `RE_TARGET_EXCLUDE` set to the same pattern as in the `push` event. | ||
# | ||
# This effectively limits the build targets to only those in `testimage` not | ||
# excluded by `RE_TARGET_EXCLUDE` (e.g. `nojdk`). | ||
# | ||
# The "test run" also limits the `PLATFORMS` to `linux/amd64`, to further | ||
# reduce the cost and time of a test run. | ||
# | ||
# Finally, it will build those `testimage` targets but **it won't tag | ||
# `latest` or push any of the image tags to the registry**. | ||
# | ||
# This "test mode" behavior can be changed by setting the | ||
# `workflow_dispatch.inputs` variables: `RE_TARGET_EXCLUDE`, | ||
# `RE_TARGET_INCLUDE`, `PLATFORMS`, `TAG_DATE`, `TAG_LATEST` and `PUSH`, | ||
# e.g.: | ||
# ```sh | ||
# gh workflow run build-ci-docker-images \ | ||
# -f RE_TARGET_INCLUDE=ubuntu2404 -f TAG_DATE=20241101 | ||
# ``` | ||
|
||
name: build-ci-docker-images | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
- master | ||
- testing | ||
paths: | ||
# NOTE: keep in-sync with env.SPARSE_PATH | ||
- buildkite/docker/*/Dockerfile | ||
|
||
workflow_dispatch: | ||
inputs: | ||
RE_TARGET_EXCLUDE: | ||
description: |- | ||
Filter out Docker targets matching this extended regex pattern | ||
# NOTE: keep in-sync with env.RE_TARGET_EXCLUDE | ||
default: ^centos7|^ubuntu16|nojdk | ||
RE_TARGET_INCLUDE: | ||
description: |- | ||
Select Docker targets matching this extended regex pattern | ||
# NOTE: keep in-sync with env.TEST_IMAGE | ||
default: testimage | ||
PLATFORMS: | ||
default: linux/amd64 | ||
TAG_DATE: | ||
description: Tag image date in ISO format (YYYYMMDD) | ||
TAG_LATEST: | ||
description: Tag image version as 'latest' | ||
default: false | ||
PUSH: | ||
description: Push images to registry | ||
default: false | ||
|
||
env: | ||
REGISTRY: ghcr.io | ||
SPARSE_PATH: buildkite/docker | ||
TEST_IMAGE: testimage | ||
|
||
GH_EVENT_NAME: ${{ github.event_name }} | ||
GH_REF_NAME: ${{ github.ref_name }} | ||
GH_REPO: ${{ github.repository }} | ||
GH_SHA: ${{ github.sha }} | ||
GH_SHA_BEFORE: ${{ github.event.before }} | ||
|
||
RE_TARGET_EXCLUDE: ${{ inputs.RE_TARGET_EXCLUDE || '^centos7|^ubuntu16|nojdk' }} | ||
RE_TARGET_INCLUDE: ${{ inputs.RE_TARGET_INCLUDE }} | ||
PLATFORMS: ${{ inputs.PLATFORMS || 'linux/amd64,linux/arm64' }} | ||
TAG_DATE: ${{ inputs.TAG_DATE }} | ||
TAG_LATEST: ${{ inputs.TAG_LATEST }} | ||
PUSH: ${{ github.event_name == 'push' || inputs.PUSH }} | ||
|
||
jobs: | ||
setup-targets: | ||
runs-on: ubuntu-latest | ||
|
||
defaults: | ||
run: | ||
shell: bash --noprofile --norc -euo pipefail {0} | ||
|
||
outputs: | ||
targets: ${{ steps.define_targets.outputs.targets }} | ||
targets_length: ${{ steps.define_targets.outputs.targets_length }} | ||
|
||
steps: | ||
- name: Sparse checkout SPARSE_PATH | ||
uses: actions/checkout@v4 | ||
with: | ||
sparse-checkout-cone-mode: false | ||
sparse-checkout: |- | ||
${{ env.SPARSE_PATH }} | ||
- name: Get DOCKERFILES | ||
run: |- | ||
# NOTE: | ||
# GH_SHA_BEFORE is empty on pushing the first commit of a new branch | ||
# or when running manually via workflow_dispatch | ||
if [[ -z $GH_SHA_BEFORE ]]; then | ||
DOCKERFILES="$(ls $SPARSE_PATH/*/Dockerfile)" | ||
else | ||
DOCKERFILES="$( | ||
git diff --name-only $GH_SHA_BEFORE $GH_SHA | | ||
grep Dockerfile || { | ||
# NOTE: | ||
# this grep could fail if e.g. we are force-pushing a stack | ||
# of commits where one or more commits do change Dockerfiles | ||
# but there's no change to any Dockerfile between the last | ||
# commit and this forced push. | ||
echo "WARNING: EMPTY grep" >&2 | ||
true | ||
} | ||
)" | ||
fi | ||
DOCKERFILES_JSON="$(echo -n "$DOCKERFILES" | jq -R '.' | jq -sc '.' )" | ||
echo "DOCKERFILES_JSON=$DOCKERFILES_JSON" | ||
echo "DOCKERFILES_JSON=$DOCKERFILES_JSON" >> $GITHUB_ENV | ||
- name: Define targets | ||
id: define_targets | ||
run: |- | ||
if [[ "$GH_EVENT_NAME" == "push" ]]; then | ||
RE_TARGET_EXCLUDE="$RE_TARGET_EXCLUDE|$TEST_IMAGE" | ||
fi | ||
DOCKERFILES=($(echo "$DOCKERFILES_JSON" | jq -r 'join(" ")')) | ||
if [[ ${#DOCKERFILES[@]} -gt 0 ]]; then | ||
TARGETS_LS="$( | ||
grep -i '^FROM .* AS ' ${DOCKERFILES[@]} | | ||
awk '{print $NF}' | | ||
{ grep -E "$RE_TARGET_INCLUDE" || true; } | | ||
{ grep -vE "$RE_TARGET_EXCLUDE" || true; } | | ||
jq -R '.' | ||
)" | ||
else | ||
TARGETS_LS="" | ||
fi | ||
TARGETS="$(echo "$TARGETS_LS" | jq -sc '.')" | ||
TARGETS_LENGTH="$(echo "$TARGETS_LS" | jq -s 'length')" | ||
echo "targets=$TARGETS" | ||
echo "targets_length=$TARGETS_LENGTH" | ||
echo "targets=$TARGETS" >> "$GITHUB_OUTPUT" | ||
echo "targets_length=$TARGETS_LENGTH" >> "$GITHUB_OUTPUT" | ||
build-and-publish-docker-images: | ||
needs: setup-targets | ||
if: ${{ needs.setup-targets.outputs.targets_length > 0 }} | ||
|
||
runs-on: ubuntu-latest | ||
|
||
strategy: | ||
matrix: | ||
target: ${{ fromJson(needs.setup-targets.outputs.targets) }} | ||
|
||
defaults: | ||
run: | ||
shell: bash --noprofile --norc -euo pipefail {0} | ||
|
||
steps: | ||
- name: Sparse checkout SPARSE_PATH | ||
uses: actions/checkout@v4 | ||
with: | ||
sparse-checkout-cone-mode: false | ||
sparse-checkout: |- | ||
${{ env.SPARSE_PATH }} | ||
- name: Set up dynamic env | ||
env: | ||
MATRIX_TARGET: ${{ matrix.target }} | ||
run: |- | ||
declare -A TAGS | ||
TAGS[sha]="${GH_SHA::7}" | ||
TAGS[date]="$TAG_DATE" | ||
# set default date value if TAG_DATE is not set, is empty | ||
# or is an empty string | ||
if [[ -z "${TAGS[date]+isset}" || -z "${TAGS[date]// }" ]]; then | ||
TAGS[date]="$(date +%Y%m%d)" | ||
fi | ||
if [[ "$GH_EVENT_NAME" == "push" || "$TAG_LATEST" == "true" ]]; then | ||
TAGS[latest]="latest" | ||
fi | ||
if [[ "$REGISTRY" == "gcr.io" ]]; then | ||
IMAGE_PREFIX="bazel-public" | ||
elif [[ "$REGISTRY" == "ghcr.io" ]]; then | ||
IMAGE_PREFIX="$GH_REPO" | ||
else | ||
echo "Invalid registry: $REGISTRY" | ||
exit 1 | ||
fi | ||
if [[ "$GH_REF_NAME" == "testing" ]]; then | ||
IMAGE_PREFIX="$IMAGE_PREFIX/testing" | ||
fi | ||
IMAGE_NAME="$REGISTRY/$IMAGE_PREFIX/$MATRIX_TARGET" | ||
DISTRO="${MATRIX_TARGET%%-*}" | ||
CONTEXT="$SPARSE_PATH/$DISTRO" | ||
{ | ||
for tag in ${!TAGS[@]}; do | ||
echo "IMAGE_TAG_${tag^^}=$IMAGE_NAME:${TAGS[$tag]}" | ||
done | ||
echo "CONTEXT=$CONTEXT" | ||
} >> $GITHUB_ENV | ||
- name: Set up QEMU | ||
uses: docker/setup-qemu-action@v3 | ||
|
||
- name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v3 | ||
|
||
- name: Login to Registry | ||
uses: docker/login-action@v3 | ||
with: | ||
registry: ${{ env.REGISTRY }} | ||
username: ${{ github.actor }} | ||
password: ${{ secrets.GITHUB_TOKEN }} | ||
|
||
- name: Build and push | ||
uses: docker/build-push-action@v6 | ||
with: | ||
context: ${{ env.CONTEXT }} | ||
target: ${{ matrix.target }} | ||
platforms: ${{ env.PLATFORMS }} | ||
tags: |- | ||
${{ env.IMAGE_TAG_SHA }} | ||
${{ env.IMAGE_TAG_DATE }} | ||
${{ env.IMAGE_TAG_LATEST }} | ||
labels: |- | ||
org.opencontainers.image.source=${{ github.repositoryUrl }} | ||
push: ${{ env.PUSH }} |