Skip to content

Build and Publish Dev Container Images #183

Build and Publish Dev Container Images

Build and Publish Dev Container Images #183

name: Build and Publish Dev Container Images
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
schedule:
- cron: "19 15 * * *"
push:
branches: ["main"]
# Publish semver tags as releases.
tags: ["v*.*.*"]
pull_request:
branches: ["main"]
workflow_dispatch:
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
jobs:
preparation:
runs-on: ubuntu-latest
outputs:
folders: ${{ steps.devcontainer-folders.outputs.folders }}
repo: ${{ steps.lowercase-repo.outputs.repo }}
image_timestamp: ${{ steps.timestamp.outputs.image_timestamp }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Get devcontainer folders
id: devcontainer-folders
run: echo "folders=$(ls src | jq -R -s -c 'split("\n")[:-1]')" | tee $GITHUB_OUTPUT
- name: Get lowercase repo name
id: lowercase-repo
run: echo "repo=${GITHUB_REPOSITORY@L}" | tee $GITHUB_OUTPUT
# TODO: this should be replaced by regular calver versioning, but for now we need a unique tag
# to be able to push to ghcr without hitting "already exists" errors, because we want to keep the
# latest tag up to date for users who don't specify a version.
- name: Create timestamp tag
id: timestamp
run: echo "image_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" | tee $GITHUB_OUTPUT
runtime-versions:
name: Get runtime versions
runs-on: ubuntu-latest
needs: preparation
strategy:
matrix:
folder: ${{ fromJson(needs.preparation.outputs.folders) }}
outputs:
runtime_versions: ${{ steps.runtime-versions.outputs.runtime_versions }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Read runtime name from config.json
id: runtime-name
run: |
runtime_name=$(jq -r '.ci.runtime_name' src/${{ matrix.folder }}/config.json)
echo "RUNTIME_NAME=${runtime_name}" | tee $GITHUB_ENV
- name: Load ${{ env.RUNTIME_NAME }} runtime versions to process from config.json
id: runtime-versions
run: |
runtime_versions=$(jq -c --arg runtime_name "${RUNTIME_NAME}_versions" '.ci.[$runtime_name]' src/${{ matrix.folder }}/config.json)
echo "runtime_versions=$runtime_versions" | tee $GITHUB_OUTPUT
env:
RUNTIME_NAME: ${{ env.RUNTIME_NAME }}
build:
runs-on: ubuntu-latest
needs: [preparation, runtime-versions]
strategy:
matrix:
folder: ${{ fromJson(needs.preparation.outputs.folders) }}
runtime_version: ${{ fromJson(needs.runtime-versions.outputs.runtime_versions) }}
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
# read image tag from subdirectory propery file if exists
- name: Install Skopeo
run: |
sudo apt-get update
sudo apt-get install -y skopeo
- name: Load environment variables from .env file
uses: xom9ikk/dotenv@v2.4.0
with:
path: "src/${{ matrix.folder }}"
mode: ''
load-mode: 'skip'
env:
RUNTIME_VERSION: ${{ matrix.runtime_version }}
- name: Get image base digest
id: get-image-base-digest
run: |
if [[ -n "${IMAGE_BASE_TAG}" ]]; then
image_base_reference="${IMAGE_BASE_NAME}:${IMAGE_BASE_TAG}"
else
image_base_reference="${IMAGE_BASE_NAME}"
fi
image_base_digest=$(skopeo inspect --no-creds docker://${image_base_reference} | jq -r .Digest)
echo "IMAGE_BASE_DIGEST=${image_base_digest}" | tee $GITHUB_ENV
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Calculate next calver version
id: calculate-next-calver
run: |
current_calver_major_minor=$(date "+%Y.%m")
last_imagecalvertag=$(skopeo list-tags --no-creds docker://ghcr.io/${TARGET_IMAGE_NAME} | \
jq -r --arg TARGET_IMAGE_BASETAG "$TARGET_IMAGE_BASETAG" '.Tags | sort_by(.) | [ .[] | select(.|startswith($TARGET_IMAGE_BASETAG))]|[last][0]')
last_imagecalver=${last_imagecalvertag##*-}
if [[ ${last_imagecalver} == "${current_calver_major_minor}"* ]]; then
current_patch_number=${last_imagecalver#${current_calver_major_minor}.}
next_patch_number=$((current_patch_number + 1))
next_calver="${current_calver_major_minor}.${next_patch_number}"
else
next_calver="${current_calver_major_minor}.0"
fi
echo "NEXT_CALVER=${next_calver}" | tee $GITHUB_ENV
env:
TARGET_IMAGE_NAME: ${{ env.REGISTRY }}/${{ needs.preparation.outputs.repo }}/${{ matrix.folder }}
TARGET_IMAGE_BASETAG: ${{ env.TARGET_IMAGE_BASETAG }} # set in .env file in each subdirectory
- name: Pre-build dev container image
uses: devcontainers/ci@v0.3
with:
subFolder: src/${{ matrix.folder }}
imageName: ${{ env.TARGET_IMAGE_NAME }}
imageTag: ${{ env.TARGET_IMAGE_BASETAG }}, ${{ env.TARGET_IMAGE_BASETAG }}-${{ env.NEXT_CALVER }}
cacheFrom: ${{ env.TARGET_IMAGE_NAME }}:${{ env.TARGET_IMAGE_BASETAG }}
refFilterForPush: "refs/heads/main"
env:
TARGET_IMAGE_NAME: ${{ env.REGISTRY }}/${{ needs.preparation.outputs.repo }}/${{ matrix.folder }}
TARGET_IMAGE_BASETAG: ${{ env.TARGET_IMAGE_BASETAG }}
IMAGE_BASE_NAME: ${{ env.IMAGE_BASE_NAME }}
IMAGE_BASE_TAG: ${{ env.IMAGE_BASE_TAG }}
IMAGE_BASE_DIGEST: ${{ env.IMAGE_BASE_DIGEST }}
IMAGE_TIMESTAMP: ${{ needs.preparation.outputs.image_timestamp }}
NEXT_CALVER: ${{ env.NEXT_CALVER }}
COMMIT_SHA: ${{ github.sha }}
MATRIX_FOLDER: ${{ matrix.folder }}