Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ef7cf5e
Update GitHub Actions for Node 24
stephanos May 25, 2026
473e209
Replace Node 20 workflow actions
stephanos May 25, 2026
9858d06
Integrate Docker cache into compose action
stephanos May 25, 2026
edc7e82
Generalize docker compose action
stephanos May 25, 2026
50f06fe
Rename docker compose teardown steps
stephanos May 25, 2026
10a6368
Clean up docker compose action branching
stephanos May 25, 2026
1dad919
Split docker compose start and stop actions
stephanos May 25, 2026
8e98afb
Infer docker compose cache key
stephanos May 25, 2026
b138aaa
Simplify docker compose start scripts
stephanos May 25, 2026
2250c61
Document docker compose cache scripting
stephanos May 25, 2026
eff4d78
Simplify docker compose stop condition
stephanos May 25, 2026
fd5b8f7
Simplify single test compose guards
stephanos May 25, 2026
864469d
Remove redundant compose start guard
stephanos May 25, 2026
68130a7
Use raw compose services for cache key
stephanos May 25, 2026
b97c900
Use hashFiles for compose cache key
stephanos May 25, 2026
6c6ede8
Simplify compose start cache guards
stephanos May 25, 2026
b162966
Deduplicate compose cache key
stephanos May 25, 2026
0f58b87
Avoid expression interpolation in compose script
stephanos May 25, 2026
28045ad
Update github-script to v9
stephanos May 25, 2026
cc7b57a
Add permissions to promote docker workflow
stephanos May 25, 2026
5ce7ce7
Wait for compose services on start
stephanos May 26, 2026
2219337
Align compose cache with requested services
stephanos May 26, 2026
acce778
Make compose up flags configurable
stephanos May 26, 2026
6a56486
Pass compose wait flag explicitly
stephanos May 26, 2026
3a632e5
Rename compose start flags input
stephanos May 26, 2026
bbca562
Add docker compose healthy action
stephanos May 26, 2026
cd1a623
Move compose wait to healthy action
stephanos May 26, 2026
1289548
Reuse integration compose services
stephanos May 26, 2026
a9f0a5d
Reuse matrix compose services
stephanos May 26, 2026
dacc257
Always run compose healthy action
stephanos May 26, 2026
ebeb79c
Fix docker compose service inputs
stephanos May 26, 2026
06f41a2
🐍
stephanos May 27, 2026
0fd4c7b
this, too
stephanos May 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/actions/docker-compose-healthy/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Docker Compose Healthy
description: Wait for Docker Compose services to become healthy.

inputs:
compose_file:
description: Docker Compose file to use.
required: true
services:
description: Whitespace-delimited list of services to wait for.
default: ""

runs:
using: composite
steps:
- name: Wait for services
shell: bash
env:
COMPOSE_FILE: ${{ inputs.compose_file }}
SERVICES: ${{ inputs.services }}
run: |
if [[ -z "${SERVICES//[[:space:]]/}" ]]; then
echo "No services configured for docker compose healthy."
exit 0
fi

read -r -a services <<< "$SERVICES"
docker compose -f "$COMPOSE_FILE" up --wait "${services[@]}"
49 changes: 0 additions & 49 deletions .github/actions/docker-compose-pull/action.yml

This file was deleted.

131 changes: 131 additions & 0 deletions .github/actions/docker-compose-start/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
name: Docker Compose Start
description: Restore Docker image cache, pull Docker Compose services with retries, then start them.

inputs:
compose_file:
description: Docker Compose file to use.
required: true
services:
description: Whitespace-delimited list of services to start.
default: ""
attempts:
description: Number of pull attempts.
default: "5"
delay_seconds:
description: Delay between failed attempts.
default: "5"
timeout_minutes:
description: Timeout for each pull attempt.
default: "1"
flags:
description: Additional options to pass to docker compose up.
default: ""
cache:
description: Set to "true" to cache Docker images.
default: "false"
cache_path:
description: Directory used to store the Docker image archive.
default: ${{ runner.temp }}/docker-image-cache

runs:
using: composite
steps:
- name: Prepare Docker image cache
id: cache
if: ${{ inputs.cache == 'true' }}
shell: bash
env:
COMPOSE_HASH: ${{ hashFiles(inputs.compose_file) }}
SERVICES: ${{ inputs.services }}
run: |
services_hash="$(printf '%s' "$SERVICES" | shasum -a 256 | cut -d ' ' -f 1)"

echo "key=docker-${RUNNER_OS}${RUNNER_ARCH}-${COMPOSE_HASH}-${services_hash}" >> "$GITHUB_OUTPUT"

- name: Restore Docker image cache
id: restore-cache
if: ${{ inputs.cache == 'true' }}
uses: actions/cache/restore@v5
with:
path: ${{ inputs.cache_path }}
key: ${{ steps.cache.outputs.key }}

- name: Load Docker images
if: ${{ inputs.cache == 'true' && steps.restore-cache.outputs.cache-hit == 'true' }}
shell: bash
env:
CACHE_PATH: ${{ inputs.cache_path }}
run: |
if [[ -f "$CACHE_PATH/images.tar" ]]; then
docker load --input "$CACHE_PATH/images.tar"
else
echo "Docker image cache hit did not include images.tar."
fi

- name: Pull services
if: ${{ steps.restore-cache.outputs.cache-hit != 'true' }}
uses: nick-fields/retry@v4
env:
COMPOSE_FILE: ${{ inputs.compose_file }}
SERVICES: ${{ inputs.services }}
with:
max_attempts: ${{ inputs.attempts }}
retry_wait_seconds: ${{ inputs.delay_seconds }}
timeout_minutes: ${{ inputs.timeout_minutes }}
shell: bash
command: |
if [[ -z "${SERVICES//[[:space:]]/}" ]]; then
echo "No services configured for docker compose pull."
exit 0
fi
docker compose -f "$COMPOSE_FILE" pull $SERVICES

- name: Start services
shell: bash
env:
COMPOSE_FILE: ${{ inputs.compose_file }}
SERVICES: ${{ inputs.services }}
FLAGS: ${{ inputs.flags }}
run: |
if [[ -z "${SERVICES//[[:space:]]/}" ]]; then
echo "No services configured for docker compose up."
exit 0
fi
docker compose -f "$COMPOSE_FILE" up -d $FLAGS $SERVICES

- name: Save Docker images to archive
id: archive
if: ${{ inputs.cache == 'true' && steps.restore-cache.outputs.cache-hit != 'true' }}
shell: bash
env:
CACHE_PATH: ${{ inputs.cache_path }}
COMPOSE_FILE: ${{ inputs.compose_file }}
SERVICES: ${{ inputs.services }}
run: |
mkdir -p "$CACHE_PATH"
images_file="$CACHE_PATH/images.txt"
: > "$images_file"

# Only cache images that were actually pulled for this job's services.
while IFS= read -r image; do
if docker image inspect "$image" >/dev/null 2>&1; then
printf '%s\n' "$image" >> "$images_file"
fi
done < <(docker compose -f "$COMPOSE_FILE" config --images $SERVICES | sort -u)

if [[ ! -s "$images_file" ]]; then
echo "No Docker images are available to cache."
echo "cacheable=false" >> "$GITHUB_OUTPUT"
exit 0
fi

mapfile -t images < "$images_file"
docker save --output "$CACHE_PATH/images.tar" "${images[@]}"
echo "cacheable=true" >> "$GITHUB_OUTPUT"

- name: Save Docker image cache
if: ${{ inputs.cache == 'true' && steps.archive.outputs.cacheable == 'true' }}
uses: actions/cache/save@v5
with:
path: ${{ inputs.cache_path }}
key: ${{ steps.cache.outputs.key }}
21 changes: 21 additions & 0 deletions .github/actions/docker-compose-stop/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Docker Compose Stop
description: Stop Docker Compose services.

inputs:
compose_file:
description: Docker Compose file to use.
required: true
flags:
description: Additional options to pass to docker compose down.
default: ""

runs:
using: composite
steps:
- name: Stop services
shell: bash
env:
COMPOSE_FILE: ${{ inputs.compose_file }}
FLAGS: ${{ inputs.flags }}
run: |
docker compose -f "$COMPOSE_FILE" down $FLAGS
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could arguably be inlined, but I like the parity of the start/stop actions.

24 changes: 12 additions & 12 deletions .github/actions/post-test-reporting/action.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
name: Post Test Reporting
description: Generate test reports and upload post-test artifacts
inputs:
artifact-name-suffix:
artifact_name_suffix:
description: Suffix to use for uploaded artifact names
required: true
codecov-flag:
codecov_flag:
description: Codecov flag for coverage and test result uploads
required: false
default: ""
debug-log-path:
debug_log_path:
description: Optional debug log path to upload
required: false
default: ""
job-name:
job_name:
description: Exact job name to resolve for artifact names
required: true
runs:
Expand Down Expand Up @@ -44,8 +44,8 @@ runs:
if: ${{ !cancelled() }}
shell: bash
env:
CODECOV_FLAG: ${{ inputs.codecov-flag }}
ARTIFACT_NAME_SUFFIX: ${{ inputs.artifact-name-suffix }}
CODECOV_FLAG: ${{ inputs.codecov_flag }}
ARTIFACT_NAME_SUFFIX: ${{ inputs.artifact_name_suffix }}
run: |
flag="$CODECOV_FLAG"
if [ -z "$flag" ]; then
Expand Down Expand Up @@ -74,15 +74,15 @@ runs:
id: get_job_id
uses: ./.github/actions/get-job-id
with:
job_name: ${{ inputs.job-name }}
job_name: ${{ inputs.job_name }}
run_id: ${{ github.run_id }}

- name: Upload test results to GitHub
# Can't pin to major because the action linter doesn't recognize the include-hidden-files flag.
uses: actions/upload-artifact@v6
if: ${{ !cancelled() }}
with:
name: junit-xml--${{ github.run_id }}--${{ steps.get_job_id.outputs.job_id }}--${{ github.run_attempt }}--${{ inputs.artifact-name-suffix }}
name: junit-xml--${{ github.run_id }}--${{ steps.get_job_id.outputs.job_id }}--${{ github.run_attempt }}--${{ inputs.artifact_name_suffix }}
path: ./.testoutput/junit.*.xml
include-hidden-files: true
retention-days: 28
Expand All @@ -91,16 +91,16 @@ runs:
uses: actions/upload-artifact@v6
if: ${{ !cancelled() }}
with:
name: test-summary-json--${{ github.run_id }}--${{ steps.get_job_id.outputs.job_id }}--${{ github.run_attempt }}--${{ inputs.artifact-name-suffix }}
name: test-summary-json--${{ github.run_id }}--${{ steps.get_job_id.outputs.job_id }}--${{ github.run_attempt }}--${{ inputs.artifact_name_suffix }}
path: ./.testoutput/test-summary.json
if-no-files-found: ignore
retention-days: 28

- name: Upload debug logs
uses: actions/upload-artifact@v6
if: ${{ !cancelled() && inputs.debug-log-path != '' }}
if: ${{ !cancelled() && inputs.debug_log_path != '' }}
with:
name: debug-logs--${{ github.run_id }}--${{ steps.get_job_id.outputs.job_id }}--${{ github.run_attempt }}--${{ inputs.artifact-name-suffix }}
path: ${{ inputs.debug-log-path }}
name: debug-logs--${{ github.run_id }}--${{ steps.get_job_id.outputs.job_id }}--${{ github.run_attempt }}--${{ inputs.artifact_name_suffix }}
path: ${{ inputs.debug_log_path }}
if-no-files-found: ignore
retention-days: 14
2 changes: 1 addition & 1 deletion .github/workflows/check-pr-placeholders.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:

steps:
- name: Validate PR description for placeholder lines or empty sections
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const pr = await github.rest.pulls.get({
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/promote-docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ on:
DOCKERHUB_TOKEN:
required: true

permissions:
contents: read

jobs:
validate-inputs:
runs-on: ubuntu-latest
Expand All @@ -30,7 +33,7 @@ jobs:
steps:
- name: Validate input tags
id: validate
uses: actions/github-script@v8
uses: actions/github-script@v9
env:
SOURCE_TAG: ${{ inputs.source-tag }}
TARGET_TAGS: ${{ inputs.target-tags }}
Expand Down Expand Up @@ -94,7 +97,7 @@ jobs:
sudo mv crane /usr/local/bin/
- name: Promote image
uses: actions/github-script@v8
uses: actions/github-script@v9
env:
SOURCE_TAG: ${{ needs.validate-inputs.outputs.source-tag-safe }}
TARGET_TAGS: ${{ needs.validate-inputs.outputs.target-tags-safe }}
Expand Down
24 changes: 19 additions & 5 deletions .github/workflows/run-single-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ jobs:
env:
PERSISTENCE_TYPE: ${{ matrix.persistence_type }}
PERSISTENCE_DRIVER: ${{ matrix.persistence_driver }}
CONTAINERS: ${{ join(matrix.containers, ' ') }}
steps:
- uses: actions/checkout@v6
if: ${{ contains(fromJSON(inputs.test_dbs), matrix.name) }}
Expand All @@ -132,12 +133,18 @@ jobs:
ref: ${{ env.COMMIT }}

- name: Start containerized dependencies
if: ${{ contains(fromJSON(inputs.test_dbs), matrix.name) && toJson(matrix.containers) != '[]' }}
uses: hoverkraft-tech/compose-action@v2.0.1
if: ${{ contains(fromJSON(inputs.test_dbs), matrix.name) }}
uses: ./.github/actions/docker-compose-start
with:
compose_file: ${{ env.DOCKER_COMPOSE_FILE }}
services: ${{ env.CONTAINERS }}

- name: Wait for containerized dependencies to be healthy
if: ${{ contains(fromJSON(inputs.test_dbs), matrix.name) }}
uses: ./.github/actions/docker-compose-healthy
with:
compose-file: ${{ env.DOCKER_COMPOSE_FILE }}
services: "${{ join(matrix.containers, '\n') }}"
down-flags: -v
compose_file: ${{ env.DOCKER_COMPOSE_FILE }}
services: ${{ env.CONTAINERS }}

- uses: actions/setup-go@v6
if: ${{ contains(fromJSON(inputs.test_dbs), matrix.name) }}
Expand All @@ -151,3 +158,10 @@ jobs:
env:
TEST_ARGS: "-run ${{ inputs.test_name }} -count ${{ inputs.n_runs }}"
TEST_TIMEOUT: "${{ inputs.timeout_minutes }}m"

- name: Stop containerized dependencies
if: ${{ always() && contains(fromJSON(inputs.test_dbs), matrix.name) }}
uses: ./.github/actions/docker-compose-stop
with:
compose_file: ${{ env.DOCKER_COMPOSE_FILE }}
flags: -v
Loading
Loading