Skip to content
24 changes: 19 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@ permissions:
contents: read

jobs:
wait-for-image:
name: Wait for Docker image to be pushed to Docker Hub
uses: ./.github/workflows/wait-for-docker-image.yml
with:
image: >-
${{
github.event_name == 'pull_request'
&& format('ryanmillard/img2num-dev:pr-{0}', github.event.pull_request.number)
|| 'ryanmillard/img2num-dev:main'
}}

lint:
name: Lint & Validate Code
needs: wait-for-image
runs-on: ubuntu-latest
container:
image: >-
${{
github.event_name == 'pull_request'
&& format('ryanmillard/img2num-dev:pr-{0}', github.event.pull_request.number)
|| 'ryanmillard/img2num-dev:latest'
|| 'ryanmillard/img2num-dev:main'
}}

steps:
Expand All @@ -44,36 +56,38 @@ jobs:
cmake-build:
name: Build C/C++
uses: ./.github/workflows/cmake-build.yml
needs: wait-for-image
with:
image: >-
${{
github.event_name == 'pull_request'
&& format('ryanmillard/img2num-dev:pr-{0}', github.event.pull_request.number)
|| 'ryanmillard/img2num-dev:latest'
|| 'ryanmillard/img2num-dev:main'
}}

build-react-app:
name: Build React App
uses: ./.github/workflows/build-react-app.yml
needs: cmake-build
needs: [cmake-build, wait-for-image]
with:
image: >-
${{
github.event_name == 'pull_request'
&& format('ryanmillard/img2num-dev:pr-{0}', github.event.pull_request.number)
|| 'ryanmillard/img2num-dev:latest'
|| 'ryanmillard/img2num-dev:main'
}}
wasm-artifacts-path: packages/js/build-wasm/

build-docs:
name: Build Documentation Site
uses: ./.github/workflows/build-docs.yml
needs: wait-for-image
with:
image: >-
${{
github.event_name == 'pull_request'
&& format('ryanmillard/img2num-dev:pr-{0}', github.event.pull_request.number)
|| 'ryanmillard/img2num-dev:latest'
|| 'ryanmillard/img2num-dev:main'
}}
secrets:
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
Expand Down
23 changes: 21 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,47 @@ permissions:
contents: write

jobs:
setup:
runs-on: ubuntu-latest
outputs:
image: ${{ steps.set.outputs.image }}
steps:
- id: set
run: echo "image=ryanmillard/img2num-dev:main" >> $GITHUB_OUTPUT

wait-for-image:
name: Wait for Docker image to be pushed to Docker Hub
uses: ./.github/workflows/wait-for-docker-image.yml

cmake-build:
name: Build WASM
needs: [wait-for-image, setup]
uses: ./.github/workflows/cmake-build.yml
with:
image: ${{ steps.set.outputs.image }}

build-react-app:
name: Build React App
uses: ./.github/workflows/build-react-app.yml
needs: cmake-build
needs: [cmake-build, wait-for-image, setup]
with:
wasm-artifacts-path: packages/js/build-wasm/
image: ${{ steps.set.outputs.image }}

build-docs:
name: Build Documentation Site
needs: [wait-for-image, setup]
uses: ./.github/workflows/build-docs.yml
with:
image: ${{ steps.set.outputs.image }}
secrets:
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
ALGOLIA_INDEX_NAME: ${{ secrets.ALGOLIA_INDEX_NAME }}

deploy:
name: Deploy site to GH Pages
needs: [build-react-app, build-docs]
needs: [build-react-app, build-docs, wait-for-image]
runs-on: ubuntu-latest
steps:
- name: Download React build artifacts
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/docker-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ jobs:
file: Dockerfile.dev
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=registry,ref=${{ steps.meta.outputs.cache_ref }}
cache-from: |
type=registry,ref=${{ steps.meta.outputs.cache_ref }}
type=registry,ref=${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ steps.meta.outputs.cache_ref }},mode=max


Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-auto-label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ jobs:

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
81 changes: 81 additions & 0 deletions .github/workflows/wait-for-docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# What is the point in this file if it is just an exponential backoff?
#
# The file is used to prevent race conditions between docker-ci.yml and any workflow that
# depends on the image it pushes. Without this, workflows like ci.yml may pull an outdated
# or unavailable image — most likely on a fresh PR where the image does not exist yet —
# causing failures that have nothing to do with the code being tested.

name: Wait for Docker Image

on:
workflow_call:
inputs:
image:
required: false
type: string
default: ryanmillard/img2num-dev:main
description: >
Docker image that this workflow waits for. It will check Docker Hub for this image. Defaults to: ryanmillard/img2num-dev:main
max_attempts:
required: false
type: number
default: 8
description: >
Maximum number of pull attempts before giving up.
With the default of 8 attempts and an initial delay of 15s,
the total worst-case wait is ~63 minutes (15+30+60+120+240+480+960+1920s).
initial_delay_seconds:
required: false
type: number
default: 15
description: >
Initial delay in seconds between attempts. Doubles after each failure.
With the default of 15s and 8 attempts, worst-case wait is ~63 minutes.

permissions: {}

jobs:
wait-for-image:
name: Wait for Docker Image
runs-on: ubuntu-latest
steps:
- name: Wait for image to be available
run: |
# Retries with exponential backoff.
# Defaults: 8 attempts, 15s initial delay -> delays of 15, 30, 60, 120, 240, 480, 960, 1920 (all in seconds)
# Total worst-case wait with defaults: ~63 minutes (enough for Dawn to typically be built into the image)
IMAGE="${{ inputs.image }}"
MAX_ATTEMPTS=${{ inputs.max_attempts }}
DELAY=${{ inputs.initial_delay_seconds }}
IMAGE_NAME="${IMAGE%%:*}"
TAG="${IMAGE##*:}"
WORKFLOW_STARTED_AT="${{ github.event.repository.updated_at }}"
WORKFLOW_START_TS=$(date -d "$WORKFLOW_STARTED_AT" +%s)

for i in $(seq 1 $MAX_ATTEMPTS); do
echo "Attempt $i of $MAX_ATTEMPTS: checking $IMAGE..."

LAST_PUSHED=$(curl -sf "https://hub.docker.com/v2/repositories/${IMAGE_NAME}/tags/${TAG}/" \
| jq -r '.tag_last_pushed // empty')

if [[ -n "$LAST_PUSHED" ]]; then
LAST_PUSHED_TS=$(date -d "$LAST_PUSHED" +%s)
echo "Image last pushed at: $LAST_PUSHED (workflow started at: $WORKFLOW_STARTED_AT)"

if [[ "$LAST_PUSHED_TS" -ge "$WORKFLOW_START_TS" ]]; then
echo "Image is fresh, proceeding."
exit 0
else
echo "Image exists but is stale."
fi
else
echo "Image not found yet."
fi

echo "Waiting ${DELAY}s..."
sleep $DELAY
DELAY=$((DELAY * 2))
done
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

echo "Image never became fresh after $MAX_ATTEMPTS attempts"
exit 1
Loading