Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 18 additions & 6 deletions .github/workflows/dockerhub-description.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ on:
- 'docker/dockerhub-overview.md'
- 'docker/dockerhub-overview-dev.md'
- 'docker/dockerhub-overview-mongodb.md'
- 'docker/dockerhub-overview-cassandra.md'
workflow_dispatch:
inputs:
image:
Expand All @@ -51,14 +52,15 @@ on:
options:
- extenddb-postgres
- extenddb-mongodb
- extenddb-cassandra
- extenddb-dev

permissions:
contents: read

# One group for every run: publishes are quick and rare, and serializing them
# is simpler and strictly safer than per-image groups now that one push can
# carry both images.
# carry several images.
concurrency:
group: dockerhub-description
cancel-in-progress: false
Expand All @@ -71,8 +73,8 @@ jobs:
# image. Push: the image(s) whose Overview file changed in the push,
# resolved via the compare API rather than a full-history checkout. If the
# push range cannot be resolved (forced push, branch creation), fall back to
# publishing both — the PATCH is idempotent, so over-publishing is harmless
# and strictly better than silently skipping one.
# publishing every image — the PATCH is idempotent, so over-publishing is
# harmless and strictly better than silently skipping one.
plan:
runs-on: ubuntu-latest
timeout-minutes: 5
Expand All @@ -92,7 +94,7 @@ jobs:
set -euo pipefail
if [[ "$EVENT" == "workflow_dispatch" ]]; then
case "$INPUT_IMAGE" in
extenddb-postgres|extenddb-mongodb|extenddb-dev) ;;
extenddb-postgres|extenddb-mongodb|extenddb-cassandra|extenddb-dev) ;;
*) echo "::error::unknown image '$INPUT_IMAGE'"; exit 1 ;;
esac
IMAGES=$(jq -cn --arg i "$INPUT_IMAGE" '[$i]')
Expand All @@ -107,13 +109,15 @@ jobs:
fi
IMAGES='[]'
if [[ -z "$CHANGED" ]]; then
# Unresolvable range: publish both (idempotent).
IMAGES='["extenddb-postgres","extenddb-mongodb","extenddb-dev"]'
# Unresolvable range: publish every image (idempotent).
IMAGES='["extenddb-postgres","extenddb-mongodb","extenddb-cassandra","extenddb-dev"]'
else
grep -qx 'docker/dockerhub-overview.md' <<< "$CHANGED" \
&& IMAGES=$(jq -c '. + ["extenddb-postgres"]' <<< "$IMAGES")
grep -qx 'docker/dockerhub-overview-mongodb.md' <<< "$CHANGED" \
&& IMAGES=$(jq -c '. + ["extenddb-mongodb"]' <<< "$IMAGES")
grep -qx 'docker/dockerhub-overview-cassandra.md' <<< "$CHANGED" \
&& IMAGES=$(jq -c '. + ["extenddb-cassandra"]' <<< "$IMAGES")
grep -qx 'docker/dockerhub-overview-dev.md' <<< "$CHANGED" \
&& IMAGES=$(jq -c '. + ["extenddb-dev"]' <<< "$IMAGES")
fi
Expand Down Expand Up @@ -160,6 +164,10 @@ jobs:
FILE='docker/dockerhub-overview-mongodb.md'
SHORT='DynamoDB-compatible API adapter backed by your own MongoDB 7+ replica set'
;;
extenddb-cassandra)
FILE='docker/dockerhub-overview-cassandra.md'
SHORT='DynamoDB-compatible API adapter backed by your own Apache Cassandra 4.1+ cluster'
;;
extenddb-dev)
FILE='docker/dockerhub-overview-dev.md'
SHORT='DynamoDB-compatible API over SQLite for local dev and CI. Plain HTTP, not for production.'
Expand Down Expand Up @@ -276,6 +284,10 @@ jobs:
FILE='docker/dockerhub-overview-mongodb.md'
SHORT='DynamoDB-compatible API adapter backed by your own MongoDB 7+ replica set'
;;
extenddb-cassandra)
FILE='docker/dockerhub-overview-cassandra.md'
SHORT='DynamoDB-compatible API adapter backed by your own Apache Cassandra 4.1+ cluster'
;;
extenddb-dev)
FILE='docker/dockerhub-overview-dev.md'
SHORT='DynamoDB-compatible API over SQLite for local dev and CI. Plain HTTP, not for production.'
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ghcr-mirror.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ on:
options:
- extenddb-postgres
- extenddb-mongodb
- extenddb-cassandra
default: extenddb-postgres

# Copying between registries needs no repository content at all.
Expand Down Expand Up @@ -86,7 +87,7 @@ jobs:
run: |
set -euo pipefail
case "$RAW_IMAGE" in
extenddb-postgres|extenddb-mongodb) ;;
extenddb-postgres|extenddb-mongodb|extenddb-cassandra) ;;
*) echo "::error::image '$RAW_IMAGE' is not in the allowlist"; exit 1 ;;
esac
KIND=''
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/licenses.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ on:
- 'Cargo.lock'
- 'SOFTWARE-LICENSE-NOTICES.html'
- 'SOFTWARE-LICENSE-NOTICES-MONGODB.html'
- 'SOFTWARE-LICENSE-NOTICES-CASSANDRA.html'
- 'SOFTWARE-LICENSE-NOTICES-DEV.html'
- 'about.toml'
- 'about.hbs'
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/promote-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ on:
# ECR Public does not auto-create repositories on push.
- extenddb-postgres
- extenddb-mongodb
- extenddb-cassandra
default: extenddb-postgres

permissions:
Expand Down Expand Up @@ -117,13 +118,14 @@ jobs:
RAW_TAG: ${{ inputs.candidate_tag }}
RAW_DIGEST: ${{ inputs.expected_digest }}
RAW_MOVE_LATEST: ${{ inputs.move_latest }}
RAW_IMAGE: ${{ inputs.image }}
run: |
set -euo pipefail
# Local allowlist enforcement: API-side choice enforcement is
# undocumented, so the security property is proven here.
case "${{ inputs.image }}" in
extenddb-postgres|extenddb-mongodb) ;;
*) echo "::error::image '${{ inputs.image }}' is not in the allowlist"; exit 1 ;;
case "$RAW_IMAGE" in
extenddb-postgres|extenddb-mongodb|extenddb-cassandra) ;;
*) echo "::error::image '$RAW_IMAGE' is not in the allowlist"; exit 1 ;;
esac
if [[ ! "$RAW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::version '$RAW_VERSION' is not strict semver (MAJOR.MINOR.PATCH)"
Expand Down
48 changes: 33 additions & 15 deletions .github/workflows/release-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ on:
options:
- extenddb-postgres
- extenddb-mongodb
- extenddb-cassandra
default: extenddb-postgres

# Nothing is committed, so no write scope on the repo is needed.
Expand Down Expand Up @@ -146,6 +147,13 @@ jobs:
SMOKE_SCRIPT='./ci/smoke-test-mongodb-container.sh'
SMOKE_ENV='EXTENDDB_MONGODB_IMAGE'
;;
extenddb-cassandra)
IMAGE_REPO='extenddb/extenddb-cassandra'
DOCKERFILE='Dockerfile.cassandra'
LICENSE_BACKEND='cassandra'
SMOKE_SCRIPT='./ci/smoke-test-cassandra-container.sh'
SMOKE_ENV='EXTENDDB_CASSANDRA_IMAGE'
;;
*)
echo "::error::image '$RAW_IMAGE' is not in the allowlist"
exit 1
Expand Down Expand Up @@ -201,17 +209,25 @@ jobs:
# distributed image than in source. The script pins cargo-about itself
# and refuses to run against any other version.
env:
IMAGE: ${{ steps.meta.outputs.image }}
DOCKERFILE: ${{ steps.meta.outputs.dockerfile }}
SMOKE_SCRIPT: ${{ steps.meta.outputs.smoke_script }}
LICENSE_BACKEND: ${{ steps.meta.outputs.license_backend }}
run: |
set -euo pipefail
if [[ "$LICENSE_BACKEND" == mongodb ]] \
&& ! grep -q -- '--backend' devtools/generate-software-license-notices; then
echo "::error::tagged commit does not contain MongoDB image license support; use a tag created after MongoDB container support was merged"
exit 1
fi
# A tag cut before an image's container support merged has no
# Dockerfile or smoke test for it; say so before a build is paid for.
# (Dockerfile.mongodb and the generator's --backend flag arrived
# together, so this also covers the earlier MongoDB-specific check.)
for f in "$DOCKERFILE" "$SMOKE_SCRIPT"; do
if [[ ! -f "$f" ]]; then
echo "::error::tagged commit has no ${f}; use a tag created after ${IMAGE} container support was merged"
exit 1
fi
done
cargo install --locked --version 0.9.0 --features cli cargo-about
./devtools/generate-software-license-notices --check \
--backend '${{ steps.meta.outputs.license_backend }}'
--backend "$LICENSE_BACKEND"

- name: Gate - the release freezes the catalog migrations it ships
# RELEASED_THROUGH must reach the last migration id, so each
Expand Down Expand Up @@ -308,21 +324,23 @@ jobs:
|| { echo "::error::expected ${{ matrix.arch }}, image reports $ACTUAL"; exit 1; }

- name: Smoke test the built image
# EXTENDDB_IMAGE puts ci/smoke-test-container.sh into prebuilt mode: it
# reads VERSION / VCS_REF / BUILD_DATE back off the image labels, runs
# `compose up --no-build`, and asserts every container is running this
# exact image ID.
# Each image has its own smoke script and its own image variable (the
# gate picks both). Setting the variable puts the script into prebuilt
# mode: it reads VERSION / VCS_REF / BUILD_DATE back off the image
# labels, runs `compose up --no-build`, and asserts every container is
# running this exact image ID.
env:
IMAGE: ${{ needs.gate.outputs.image_repo }}:sha-${{ needs.gate.outputs.sha }}-${{ matrix.arch }}
SMOKE_ENV: ${{ needs.gate.outputs.smoke_env }}
SMOKE_SCRIPT: ${{ needs.gate.outputs.smoke_script }}
run: |
set -euo pipefail
if [[ "$SMOKE_ENV" == 'EXTENDDB_MONGODB_IMAGE' ]]; then
EXTENDDB_MONGODB_IMAGE="$IMAGE" "$SMOKE_SCRIPT"
else
EXTENDDB_IMAGE="$IMAGE" "$SMOKE_SCRIPT"
fi
case "$SMOKE_ENV" in
EXTENDDB_IMAGE) EXTENDDB_IMAGE="$IMAGE" "$SMOKE_SCRIPT" ;;
EXTENDDB_MONGODB_IMAGE) EXTENDDB_MONGODB_IMAGE="$IMAGE" "$SMOKE_SCRIPT" ;;
EXTENDDB_CASSANDRA_IMAGE) EXTENDDB_CASSANDRA_IMAGE="$IMAGE" "$SMOKE_SCRIPT" ;;
*) echo "::error::unknown smoke-test variable '$SMOKE_ENV'"; exit 1 ;;
esac

- name: Save the tested image as an artifact
# docker save preserves the image ID, so what publish pushes is exactly
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/sign-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ on:
# signing, which distinguishes tiers automatically.
- extenddb-postgres
- extenddb-mongodb
- extenddb-cassandra
- extenddb-dev
default: extenddb-postgres
reason:
Expand Down Expand Up @@ -80,7 +81,7 @@ jobs:
run: |
set -euo pipefail
case "$RAW_IMAGE" in
extenddb-postgres|extenddb-mongodb|extenddb-dev) ;;
extenddb-postgres|extenddb-mongodb|extenddb-cassandra|extenddb-dev) ;;
*) echo "::error::image '$RAW_IMAGE' is not in the allowlist"; exit 1 ;;
esac
if [[ ! "$RAW_DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]]; then
Expand Down Expand Up @@ -154,9 +155,10 @@ jobs:
# Free text; reaches the shell only through the environment, per
# the repo convention (inputs.image above is allowlist-validated).
REASON: ${{ inputs.reason }}
IMAGE: ${{ inputs.image }}
run: |
{
echo "### ${{ inputs.image }} signed (break-glass path)"
echo "### ${IMAGE} signed (break-glass path)"
echo ""
echo "| field | value |"
echo "|---|---|"
Expand Down
89 changes: 89 additions & 0 deletions .github/workflows/smoke-cassandra-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright 2026 ExtendDB contributors
# SPDX-License-Identifier: Apache-2.0
#
# Builds the Cassandra-backed image and runs ci/smoke-test-cassandra-container.sh
# against it, on both release architectures, whenever a pull request touches
# the image, its reference stack or the smoke test.
#
# release-image.yml runs the same script against the tagged commit, but only at
# release. A script or stack that fails there, on ubuntu-24.04-arm in
# particular, leaves that tag without a Cassandra image and needs a new tag to
# fix. This runs the same thing on the pull request that changes it, in the
# script's build mode (the image built by the script), so a release dispatch
# meets a script that has already passed on both runners. Nothing here is
# published or needs credentials.

name: Cassandra image smoke

on:
pull_request:
paths:
- 'Dockerfile.cassandra'
- 'docker-compose.cassandra.yml'
- 'ci/smoke-test-cassandra-container.sh'
- '.github/workflows/smoke-cassandra-image.yml'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
# Pinned AWS CLI for runners that lack it (the smoke test needs `aws`), as
# in release-image.yml.
AWSCLI_VERSION: 2.31.6
AWSCLI_SHA256_X86_64: 45fdcc3003056b3e85c23776636e74208dc2fc16f26278acdbe8eafe3d4e752b
AWSCLI_SHA256_AARCH64: 517dec4ce83fabe7ace6164bb6189cdb686ef816332ccf83e81999e3fb57cad5

jobs:
smoke:
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm # native arm64, no qemu
name: smoke (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 60 # a hung Rust build must not burn the 6h default
steps:
- name: Check out
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Ensure smoke-test prerequisites
# The AWS CLI is not guaranteed on every runner image. Install is
# pinned to an exact version and verified against a recorded
# checksum: never execute a mutable unverified download.
run: |
set -euo pipefail
if ! command -v aws >/dev/null 2>&1; then
case "$(uname -m)" in
x86_64) EXPECTED="$AWSCLI_SHA256_X86_64"; PKG="awscli-exe-linux-x86_64-${AWSCLI_VERSION}.zip" ;;
aarch64) EXPECTED="$AWSCLI_SHA256_AARCH64"; PKG="awscli-exe-linux-aarch64-${AWSCLI_VERSION}.zip" ;;
*) echo "::error::unsupported architecture $(uname -m)"; exit 1 ;;
esac
curl -fsSL "https://awscli.amazonaws.com/${PKG}" -o /tmp/awscli.zip
echo "${EXPECTED} /tmp/awscli.zip" | sha256sum -c - \
|| { echo "::error::AWS CLI download failed checksum verification"; exit 1; }
unzip -q /tmp/awscli.zip -d /tmp
sudo /tmp/aws/install
fi
for c in docker aws python3 timeout; do
command -v "$c" >/dev/null || { echo "::error::missing $c"; exit 1; }
done
docker compose version

- name: Build the image and run the smoke test
# Build mode: the script builds the image with `docker build` from
# this checkout, then starts the reference stack against it and
# removes the image afterwards.
env:
VCS_REF: ${{ github.event.pull_request.head.sha || github.sha }}
run: ci/smoke-test-cassandra-container.sh
Loading
Loading