diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..df5d66d5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,251 @@ +# testing : https://github.com/postgis/docker-postgis/pull/432 +# original author: https://github.com/BowlesCR + +# To pin GitHub Actions versions by commit hash: 'pinact run -u --min-age 7' ( https://github.com/suzuki-shunsuke/pinact ) + +name: TEST - Docker PostGIS CI +# Serialize publish-capable runs per ref so scheduled/push/manual runs queue instead of racing. +concurrency: + group: test-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +on: + push: + pull_request: + workflow_dispatch: + schedule: + - cron: '15 13 * * *' + +# ============================================================================ +# FOR FORKING: Modify these settings in your forked repository +# ============================================================================ +env: + DOCKERHUB_REPO: postgis/docker-postgis-test + GITHUB_REPO: postgis/docker-postgis + LATEST_VERSION: 17-3.5 + DOCKERHUB_SHORT_DESCRIPTION: "TEST REPO - PostGIS Docker" + DOCKERHUB_README_PREFIX: "# WARNING: This is a TEST repository ONLY\n\n" + RUNNER_PLATFORMS_JSON: '["ubuntu-24.04","ubuntu-24.04-arm"]' # Runner platforms used to expand the build matrix +# +# Also add these secrets in your repository settings: +# https://github.com/${GITHUB_REPO}/settings/secrets/actions +# - secrets.DOCKERHUB_USERNAME +# - secrets.DOCKERHUB_ACCESS_TOKEN ( READ, Write, Delete access ) +# ============================================================================ + +defaults: + run: + shell: bash --noprofile --norc -euo pipefail {0} + +jobs: + + setup: + # This job sets up configuration constants and loads the CI matrix from matrix.yml. + # - Constants: CANONICAL_REPO and SHOULD_PUBLISH flag (fork-friendly configuration) + # - Matrix: BUILD_TARGETS and BUILD_INCLUDE arrays (automatically generated by ./update.sh) + name: Setup and Load Configuration + runs-on: ubuntu-latest + outputs: + CANONICAL_REPO: ${{ steps.constants.outputs.CANONICAL_REPO }} + SHOULD_PUBLISH: ${{ steps.constants.outputs.SHOULD_PUBLISH }} + BUILD_INCLUDE: ${{ steps.matrix.outputs.BUILD_INCLUDE }} + BUILD_TARGETS: ${{ steps.matrix.outputs.BUILD_TARGETS }} + steps: + - name: Set constants + id: constants + env: + VAR_CANONICAL: ${{ vars.CANONICAL_REPO }} + run: | + CANONICAL_REPO="${VAR_CANONICAL:-$GITHUB_REPO}" + echo "CANONICAL_REPO=$CANONICAL_REPO" >> "$GITHUB_OUTPUT" + + # Compute if we should publish + if [[ "${{ github.repository }}" == "$CANONICAL_REPO" ]] && \ + [[ "${{ github.ref }}" == "refs/heads/master" ]] && \ + [[ "${{ github.event_name }}" != "pull_request" ]]; then + echo "SHOULD_PUBLISH=true" >> "$GITHUB_OUTPUT" + else + echo "SHOULD_PUBLISH=false" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Load and validate matrix + id: matrix + run: bash ci/matrix.sh + + make-docker-images: + needs: setup + strategy: + matrix: + include: ${{ fromJSON(needs.setup.outputs.BUILD_INCLUDE) }} + + name: "Build:${{ matrix.postgres }}-${{ matrix.postgis }}-${{ matrix.variant }} (${{ contains(matrix.runner-platform, 'arm') && 'arm64' || 'x86-64' }}) Docker image" + runs-on: ${{ matrix.runner-platform }} + continue-on-error: ${{ matrix.postgis == 'master' }} + env: + VERSION: ${{ matrix.postgres }}-${{ matrix.postgis }} + VARIANT: ${{ matrix.variant }} + # the "postgis/postgis" name is the expected test name; ( via ./test/postgis-config.sh ) + # changing it will break the official-images test script + # this is only for CI test and not for Docker hub publishing + CI_IMAGE_TAG: postgis/postgis:ci-${{ github.run_id }}-${{ matrix.postgres }}-${{ matrix.postgis }}-${{ matrix.variant }}-${{ contains(matrix.runner-platform, 'arm') && 'arm' || 'x64' }} + + steps: + - name: Checkout source + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + + - name: Compute build directory + run: | + if [[ "$VARIANT" == "alpine" ]]; then + echo "BUILD_DIR=$VERSION/alpine" >> "$GITHUB_ENV" + else + echo "BUILD_DIR=$VERSION" >> "$GITHUB_ENV" + fi + + - name: Build Docker image for ${{ env.VERSION }} ${{ env.VARIANT }} + id: build + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + with: + context: ${{ env.BUILD_DIR }} + file: ${{ env.BUILD_DIR }}/Dockerfile + tags: ${{ env.CI_IMAGE_TAG }} + load: true + push: false # don't push until after testing + + - name: Check out official-images repo + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + repository: docker-library/official-images + path: official-images + sparse-checkout: | + test + + - name: Test image with official-images + run: bash ci/test-image.sh "$CI_IMAGE_TAG" + + - name: Login to dockerhub + id: login-dockerhub + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + if: ${{ needs.setup.outputs.SHOULD_PUBLISH == 'true' }} + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + + - name: Push image by digest + id: push + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + if: ${{ needs.setup.outputs.SHOULD_PUBLISH == 'true' && steps.login-dockerhub.outcome == 'success' }} + with: + context: ${{ env.BUILD_DIR }} + file: ${{ env.BUILD_DIR }}/Dockerfile + outputs: type=image,"name=${{ env.DOCKERHUB_REPO }}",push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + if: ${{ steps.push.outcome == 'success' }} + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.push.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digests + if: ${{ steps.push.outcome == 'success' }} + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: digests-${{ github.run_id }}-${{ env.VERSION }}-${{ env.VARIANT }}-${{ matrix.runner-platform }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 10 + + merge-manifests: + name: "Merge:${{ matrix.postgres }}-${{ matrix.postgis }}-${{ matrix.variant }} manifests and push to DockerHub" + needs: [setup, make-docker-images] + runs-on: ubuntu-24.04-arm # Run on arm runner for manifest merge + if: ${{ needs.setup.outputs.SHOULD_PUBLISH == 'true' }} + # Ensure each tag variant is published by only one workflow run at a time to keep manifests consistent. + concurrency: + group: merge-${{ matrix.postgres }}-${{ matrix.postgis }}-${{ matrix.variant }} + cancel-in-progress: false + continue-on-error: ${{ matrix.postgis == 'master' }} + env: + VERSION: ${{ matrix.postgres }}-${{ matrix.postgis }} + VARIANT: ${{ matrix.variant }} + strategy: + matrix: + include: ${{ fromJSON(needs.setup.outputs.BUILD_TARGETS) }} + + steps: + - name: Checkout source + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Login to dockerhub + id: login-dockerhub + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + + - name: Download digests + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + path: ${{ runner.temp }}/digests + pattern: digests-${{ github.run_id }}-${{ env.VERSION }}-${{ env.VARIANT }}-* + merge-multiple: true + + - name: Verify digests for all platforms + working-directory: ${{ runner.temp }}/digests + run: | + expected="$(jq 'length' <<< "$RUNNER_PLATFORMS_JSON")" + found="$(find . -maxdepth 1 -type f | wc -l | tr -d '[:space:]')" + if [[ "$found" -ne "$expected" ]]; then + echo "ERROR: Expected ${expected} digest(s) (one per runner platform), found ${found}." + echo "RUNNER_PLATFORMS_JSON=${RUNNER_PLATFORMS_JSON}" + ls -la + exit 1 + fi + echo "[OK] Found ${found}/${expected} digests" + + - name: Create manifest list and push + env: + MATRIX_TAGS: ${{ matrix.tags }} + run: | + build_month="$(date -u +%Y%m)" + read -r -a tags_arr <<< "$MATRIX_TAGS" + extra_tags="" + if [[ "${#tags_arr[@]}" -ge 2 ]]; then + extra_tags+=" ${tags_arr[1]}-${build_month}" + fi + bash ci/push-manifest.sh "${{ env.DOCKERHUB_REPO }}" "${MATRIX_TAGS}${extra_tags}" "${{ runner.temp }}/digests" + + - name: Inspect image # Purely for debugging + run: | + sleep 5 + docker buildx imagetools inspect ${{ env.DOCKERHUB_REPO }}:${{ env.VERSION }}${{ env.VARIANT == 'alpine' && '-alpine' || ''}} + + dockerHubDescription: + needs: [merge-manifests] + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Prepare README with prefix (create ./_DOCKER-HUB-README.md ) + run: bash ci/prepare-dockerhub-readme.sh && ls -la ./_DOCKER-HUB-README.md + + - name: Debug ./_DOCKER-HUB-README.md + run: cat ./_DOCKER-HUB-README.md + - name: Update Docker Hub Description + uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + repository: ${{ env.DOCKERHUB_REPO }} + short-description: "${{ env.DOCKERHUB_SHORT_DESCRIPTION }}" + readme-filepath: ./_DOCKER-HUB-README.md diff --git a/ci/matrix.sh b/ci/matrix.sh new file mode 100755 index 00000000..bacc1c4b --- /dev/null +++ b/ci/matrix.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# +# matrix.sh - Parse matrix.yml and output build targets for CI workflows +# +# Called by: .github/workflows/*.yml +# Outputs: BUILD_TARGETS and BUILD_INCLUDE for GitHub Actions matrix strategy +# +set -Eeuo pipefail + +# --- Logging (CI-only, no colors) --- +log_info() { echo "[INFO] $*" >&2; } +log_warn() { echo "[WARN] $*" >&2; } +log_error() { echo "[ERROR] $*" >&2; } +die() { log_error "$1"; exit "${2:-1}"; } + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +readonly MATRIX_FILE="matrix.yml" + +# Environment variables (set by CI workflow) +runner_platforms_json="${RUNNER_PLATFORMS_JSON:-}" +github_output_file="${GITHUB_OUTPUT:-}" + +# Write a line to GitHub Actions output file, or stdout if not in CI +set_github_output() { + local output_line="$1" + if [[ -n "$github_output_file" ]]; then + echo "$output_line" >> "$github_output_file" + else + echo "$output_line" + fi +} + +# --- Input Validation --- +if [[ ! -f "$MATRIX_FILE" ]]; then + die "$MATRIX_FILE not found in repo root" +fi + +if [[ -z "$runner_platforms_json" ]]; then + die "RUNNER_PLATFORMS_JSON environment variable is required" +fi + +if ! command -v ruby >/dev/null 2>&1; then + die "ruby is required to parse ${MATRIX_FILE}" +fi + +# --- Parse Matrix File --- +build_targets="$( + ruby -ryaml -rjson -e ' + matrix = YAML.load_file(ARGV.fetch(0)) + targets = matrix.fetch("build_targets") + puts JSON.generate(targets) + ' "$MATRIX_FILE" +)" +set_github_output "BUILD_TARGETS=$build_targets" + +# Expand build_targets with runner platforms to create full build matrix. +# Each target is combined with each platform to create BUILD_INCLUDE entries. +runner_platforms="$(jq -c '.' <<< "$runner_platforms_json")" +build_include="$(jq -c --argjson platforms "$runner_platforms" ' + [ .[] as $combo | $platforms[] | $combo + {"runner-platform": .} ] +' <<< "$build_targets")" +set_github_output "BUILD_INCLUDE=$build_include" + +target_count="$(jq 'length' <<< "$build_targets")" +include_count="$(jq 'length' <<< "$build_include")" +log_info "Loaded BUILD_TARGETS with ${target_count} entries" +log_info "Expanded BUILD_INCLUDE with ${include_count} entries (targets x platforms)" + +# --- Validation --- +log_info "Validating ./${MATRIX_FILE}..." + +# 1. Check build_targets is not empty +if [[ "$target_count" -eq 0 ]]; then + die "matrix.yml has no build_targets" +fi + +# 2. Check required fields: postgres, postgis, variant, tags (all must be non-empty) +invalid_entries="$(jq -c ' + [ .[] | select( + .postgres == null or .postgres == "" or + .postgis == null or .postgis == "" or + .variant == null or .variant == "" or + .tags == null or .tags == "" + )] +' <<< "$build_targets")" + +invalid_count="$(jq 'length' <<< "$invalid_entries")" +if [[ "$invalid_count" -gt 0 ]]; then + log_error "Found ${invalid_count} entries with missing or empty required fields (postgres/postgis/variant/tags):" + jq '.' <<< "$invalid_entries" >&2 + exit 1 +fi + +# 3. Verify exactly one entry has 'latest' tag (prevents accidental duplicate latest) +latest_count="$(jq ' + [ .[] | select(.tags | tostring | test("(^| )latest( |$)")) ] | length +' <<< "$build_targets")" + +if [[ "$latest_count" -ne 1 ]]; then + log_error "Expected exactly 1 entry with 'latest' tag, found: $latest_count" + jq -r '.[] | select(.tags | tostring | test("(^| )latest( |$)"))' <<< "$build_targets" >&2 + exit 1 +fi + +# 4. Verify exactly one entry has 'alpine' tag (the alpine equivalent of 'latest') +alpine_count="$(jq ' + [ .[] | select(.tags | tostring | test("(^| )alpine( |$)")) ] | length +' <<< "$build_targets")" + +if [[ "$alpine_count" -ne 1 ]]; then + log_error "Expected exactly 1 entry with 'alpine' tag, found: $alpine_count" + jq -r '.[] | select(.tags | tostring | test("(^| )alpine( |$)"))' <<< "$build_targets" >&2 + exit 1 +fi + +log_info "[OK] matrix.yml valid: ${target_count} targets, all have required fields, 1 'latest' tag, 1 'alpine' tag" diff --git a/ci/prepare-dockerhub-readme.sh b/ci/prepare-dockerhub-readme.sh new file mode 100755 index 00000000..938a0e0a --- /dev/null +++ b/ci/prepare-dockerhub-readme.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# +# prepare-dockerhub-readme.sh - Prepare a README suitable for Docker Hub description +# +# Called by: .github/workflows/*.yml +# Docker Hub has a 25000 character limit; this script trims if needed. +# +set -Eeuo pipefail + +# --- Logging (CI-only, no colors) --- +log_info() { echo "[INFO] $*" >&2; } +log_warn() { echo "[WARN] $*" >&2; } +log_error() { echo "[ERROR] $*" >&2; } +die() { log_error "$1"; exit "${2:-1}"; } + +usage() { + cat <<'EOF' +Usage: ci/prepare-dockerhub-readme.sh [source-readme] [output-readme] + +Writes a Docker Hub compatible README, optionally prefixing it via: + DOCKERHUB_README_PREFIX + +Defaults: + source-readme: README.md + output-readme: _DOCKER-HUB-README.md +EOF +} + +case "${1:-}" in + -h|--help) usage; exit 0 ;; +esac + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +src_readme="${1:-README.md}" +out_readme="${2:-_DOCKER-HUB-README.md}" +prefix="${DOCKERHUB_README_PREFIX:-}" + +if [[ ! -f "$src_readme" ]]; then + die "Source README not found: $src_readme" +fi + +mkdir -p "$(dirname "$out_readme")" + +tmp_file="$(mktemp)" +if [[ -n "$prefix" ]]; then + printf '%b\n' "$prefix" > "$tmp_file" + cat "$src_readme" >> "$tmp_file" +else + cat "$src_readme" > "$tmp_file" +fi +mv "$tmp_file" "$out_readme" +chmod 644 "$out_readme" + +readme_path="$out_readme" + +# Docker Hub README limit is 25000 chars; use 24600 to leave margin for warning text +readonly DOCKERHUB_CHAR_LIMIT=24600 +size="$(wc -c < "$readme_path" | tr -d '[:space:]')" + +if [[ "$size" -ge "$DOCKERHUB_CHAR_LIMIT" ]]; then + repo="${GITHUB_REPO:-${GITHUB_REPOSITORY:-unknown/unknown}}" + warning_text=$'Note: the description for this image is longer than the Hub length limit of 25000, so has been trimmed. The full description can be found at\n"https://github.com/'"${repo}"$'/README.md"' + + start_block="${warning_text}"$'\n\n' + end_block=$'\n...\n'"${warning_text}"$'\n' + + start_len="$(printf '%s' "$start_block" | wc -c | tr -d '[:space:]')" + end_len="$(printf '%s' "$end_block" | wc -c | tr -d '[:space:]')" + avail=$(( DOCKERHUB_CHAR_LIMIT - start_len - end_len )) + + if (( avail < 0 )); then + log_error "Trimming blocks exceed limit ${DOCKERHUB_CHAR_LIMIT}" + avail=0 + fi + + # Truncate content line-by-line to fit within available space + content_tmp="$(mktemp)" + current=0 + while IFS= read -r line || [[ -n "$line" ]]; do + line_with_nl="${line}"$'\n' + line_len="$(printf '%s' "$line_with_nl" | wc -c | tr -d '[:space:]')" + if (( current + line_len > avail )); then + break + fi + printf '%s' "$line_with_nl" >> "$content_tmp" + current=$(( current + line_len )) + done < "$readme_path" + + final_tmp="$(mktemp)" + printf '%s' "$start_block" > "$final_tmp" + cat "$content_tmp" >> "$final_tmp" + printf '%s' "$end_block" >> "$final_tmp" + rm -f "$content_tmp" + mv "$final_tmp" "$readme_path" + chmod 644 "$readme_path" +fi + +log_info "[OK] Docker Hub README prepared: $out_readme" diff --git a/ci/push-manifest.sh b/ci/push-manifest.sh new file mode 100755 index 00000000..eef40d69 --- /dev/null +++ b/ci/push-manifest.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# push-manifest.sh - Create and push multi-arch Docker manifest +# +# Called by: .github/workflows/*.yml +# +set -Eeuo pipefail + +# --- Logging (CI-only, no colors) --- +log_info() { echo "[INFO] $*" >&2; } +log_warn() { echo "[WARN] $*" >&2; } +log_error() { echo "[ERROR] $*" >&2; } +die() { log_error "$1"; exit "${2:-1}"; } + +repo="${1:-}" +tags="${2:-}" +digests_dir="${3:-.}" + +if [[ -z "$repo" || -z "$tags" ]]; then + die "Usage: ci/push-manifest.sh [digests-dir]" 2 +fi + +cd "$digests_dir" + +shopt -s nullglob +digests=( * ) +shopt -u nullglob +if [[ "${#digests[@]}" -eq 0 ]]; then + die "No digest files found in $digests_dir" +fi + +tag_args="" +for tag in $tags; do + tag_args+=" -t ${repo}:${tag}" +done + +log_info "Creating multi-arch manifest with tags:${tag_args}" + +# shellcheck disable=SC2046,SC2086 +docker buildx imagetools create $tag_args \ + $(printf "${repo}@sha256:%s " "${digests[@]}") + +log_info "[OK] Manifest created and pushed" diff --git a/ci/test-image.sh b/ci/test-image.sh new file mode 100755 index 00000000..56d34e11 --- /dev/null +++ b/ci/test-image.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# +# test-image.sh - Run official-images test suite against a PostGIS image +# +# Called by: .github/workflows/*.yml, ci/local-test.sh +# +set -Eeuo pipefail + +# --- Logging (CI-only, no colors) --- +log_info() { echo "[INFO] $*" >&2; } +log_error() { echo "[ERROR] $*" >&2; } +die() { log_error "$1"; exit "${2:-1}"; } + +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +image_tag="${1:-${CI_IMAGE_TAG:-}}" +log_file="${TEST_LOG_FILE:-test.log}" +official_images_dir="official-images" +official_run="./${official_images_dir}/test/run.sh" +official_config="./${official_images_dir}/test/config.sh" + +if [[ -z "$image_tag" ]]; then + die "Usage: ci/test-image.sh " 2 +fi + +# Local development: auto-update official-images checkout (CI handles this via workflow) +if [[ "${GITHUB_ACTIONS:-}" != "true" ]]; then + if [[ -d "${official_images_dir}/.git" ]]; then + log_info "Local run: updating ./${official_images_dir}..." + git -C "$official_images_dir" fetch origin master + git -C "$official_images_dir" reset --hard origin/master + elif [[ ! -d "$official_images_dir" ]]; then + log_info "Local run: cloning docker-library/official-images..." + git clone --depth 1 https://github.com/docker-library/official-images.git "${official_images_dir}" + fi +fi + +if [[ ! -x "$official_run" ]]; then + die "${official_run} not found or not executable." +fi +if [[ ! -f "$official_config" ]]; then + die "${official_config} not found." +fi + +"$official_run" -c "$official_config" -c test/postgis-config.sh "$image_tag" | tee "$log_file" + +# These tests must pass for a valid PostGIS image +required_tests=("postgres-basics" "postgres-initdb" "postgis-basics") +for test_name in "${required_tests[@]}"; do + if ! grep -q "'${test_name}'.*passed" "$log_file"; then + die "Required test '${test_name}' did not pass!" + fi +done + +log_info "[OK] All required tests passed" diff --git a/matrix.yml b/matrix.yml new file mode 100644 index 00000000..2338038b --- /dev/null +++ b/matrix.yml @@ -0,0 +1,22 @@ +# ============================================================================= +# WARNING: AUTOMATICALLY GENERATED by ./update.sh - DO NOT EDIT MANUALLY! +# Used by: .github/workflows/test.yml +# ============================================================================= + +# --- Placeholder/skipped combinations (NOT included in build target list) --- +# - postgres: 17, postgis: 3.6, variant: default - Reason: placeholder (Debian package not available) +# - postgres: 17, postgis: master, variant: default - Reason: development branch (not a required release test target) +# - postgres: 18, postgis: master, variant: default - Reason: development branch (not a required release test target) + +build_targets: + - { postgres: '14', postgis: '3.5', variant: 'default', tags: '14-3.5 14-3.5-bullseye 14-3.5.2-bullseye' } + - { postgres: '14', postgis: '3.5', variant: 'alpine', tags: '14-3.5-alpine 14-3.5-alpine3.23 14-3.5.6-alpine3.23' } + - { postgres: '15', postgis: '3.5', variant: 'default', tags: '15-3.5 15-3.5-bullseye 15-3.5.2-bullseye' } + - { postgres: '15', postgis: '3.5', variant: 'alpine', tags: '15-3.5-alpine 15-3.5-alpine3.23 15-3.5.6-alpine3.23' } + - { postgres: '16', postgis: '3.5', variant: 'default', tags: '16-3.5 16-3.5-bullseye 16-3.5.2-bullseye' } + - { postgres: '16', postgis: '3.5', variant: 'alpine', tags: '16-3.5-alpine 16-3.5-alpine3.23 16-3.5.6-alpine3.23' } + - { postgres: '17', postgis: '3.5', variant: 'default', tags: '17-3.5 17-3.5-bullseye 17-3.5.2-bullseye latest' } + - { postgres: '17', postgis: '3.5', variant: 'alpine', tags: '17-3.5-alpine 17-3.5-alpine3.23 17-3.5.6-alpine3.23 alpine' } + - { postgres: '17', postgis: '3.6', variant: 'alpine', tags: '17-3.6-alpine 17-3.6-alpine3.23 17-3.6.3-alpine3.23' } + - { postgres: '18', postgis: '3.6', variant: 'default', tags: '18-3.6 18-3.6-trixie 18-3.6.3-trixie' } + - { postgres: '18', postgis: '3.6', variant: 'alpine', tags: '18-3.6-alpine 18-3.6-alpine3.23 18-3.6.3-alpine3.23' } diff --git a/update.sh b/update.sh index 75c14361..3e7cc305 100755 --- a/update.sh +++ b/update.sh @@ -4,8 +4,10 @@ set -Eeuo pipefail cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" +fullMatrixUpdate=false versions=( "$@" ) if [ ${#versions[@]} -eq 0 ]; then + fullMatrixUpdate=true versions=( */Dockerfile ) fi versions=( "${versions[@]%/Dockerfile}" ) @@ -27,6 +29,7 @@ IFS=$'\n'; versions=( $(echo "${versions[*]}" | sort -V) ); unset IFS defaultAlpinenSuite='3.23' defaultDebianSuite='bullseye-slim' +latestVersion="${LATEST_VERSION:-17-3.5}" declare -A debianSuite=( # https://github.com/docker-library/postgres/issues/582 [11]='bullseye-slim' @@ -51,6 +54,52 @@ declare -A postgisDebPkgNameVersionSuffixes=( ) packagesBase='http://apt.postgresql.org/pub/repos/apt/dists/' +matrixFile="matrix.yml" +matrixEntries=() +matrixSkipped=() + +function matrix_quote() { + printf "%s" "$1" | sed "s/'/''/g" +} + +function add_matrix_entry() { + local postgres="$1" + local postgis="$2" + local variant="$3" + local tags="$4" + + matrixEntries+=(" - { postgres: '$(matrix_quote "$postgres")', postgis: '$(matrix_quote "$postgis")', variant: '$(matrix_quote "$variant")', tags: '$(matrix_quote "$tags")' }") +} + +function build_matrix_tags() { + local version="$1" + local variant="$2" + local osLabel="$3" + local patchVersion="$4" + local postgresMajor + local tags=() + + postgresMajor="${version%%-*}" + if [ "$variant" = "alpine" ]; then + tags+=("${version}-alpine" "${version}-${osLabel}") + else + tags+=("${version}" "${version}-${osLabel}") + fi + + if [[ "$patchVersion" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then + tags+=("${postgresMajor}-${patchVersion}-${osLabel}") + fi + + if [ "$version" = "$latestVersion" ] && [[ "$patchVersion" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then + if [ "$variant" = "alpine" ]; then + tags+=("alpine") + else + tags+=("latest") + fi + fi + + printf '%s ' "${tags[@]}" | sed 's/[[:space:]]$//' +} cgalGitBranch='6.1.x-branch' cgalGitHash="$(git ls-remote https://github.com/CGAL/cgal.git "heads/${cgalGitBranch}" | awk '{ print $1}')" @@ -219,6 +268,7 @@ for version in "${versions[@]}"; do if [ -z "$postgisFullVersion" ] then echo "SKIP debian version"; + matrixSkipped+=("postgres: ${postgresVersion}, postgis: ${postgisVersion}, variant: default - Reason: placeholder (Debian package not available)") # debain version not found; echo " # placeholder Dockerfile" > "$version/Dockerfile" echo " # Debian version of postgis $postgisFullVersion is not detected!">> "$version/Dockerfile" @@ -249,6 +299,12 @@ for version in "${versions[@]}"; do echo "| [postgis/postgis:${version}](${dockerhublink}${version}) | [Dockerfile](${githubrepolink}/${version}/Dockerfile) | debian:${suite} | ${postgresVersion} | ${postgisDocSrc} |" >> _dockerlists_${optimized}.md ) + if [ "master" == "$postgisVersion" ]; then + matrixSkipped+=("postgres: ${postgresVersion}, postgis: ${postgisVersion}, variant: default - Reason: development branch (not a required release test target)") + else + debianTags="$(build_matrix_tags "$version" "default" "$suite" "${postgisFullVersion%%+*}")" + add_matrix_entry "$postgresVersion" "$postgisVersion" "default" "$debianTags" + fi fi if [ "master" == "$postgisVersion" ]; then @@ -297,9 +353,33 @@ for version in "${versions[@]}"; do echo "| [postgis/postgis:${version}-${variant}](${dockerhublink}${version}-${variant}) | [Dockerfile](${githubrepolink}/${version}/${variant}/Dockerfile) | alpine:${defaultAlpinenSuite} | ${postgresVersion} | ${postgisDocSrc} |" >> _dockerlists_${optimized}.md ) + alpineTags="$(build_matrix_tags "$version" "$variant" "alpine${defaultAlpinenSuite}" "$srcVersion")" + add_matrix_entry "$postgresVersion" "$postgisVersion" "$variant" "$alpineTags" done done +if [ "$fullMatrixUpdate" = true ]; then + { + echo "# =============================================================================" + echo "# WARNING: AUTOMATICALLY GENERATED by ./update.sh - DO NOT EDIT MANUALLY!" + echo "# Used by: .github/workflows/test.yml" + echo "# =============================================================================" + echo + if [ "${#matrixSkipped[@]}" -gt 0 ]; then + echo "# --- Placeholder/skipped combinations (NOT included in build target list) ---" + for skipped in "${matrixSkipped[@]}"; do + echo "# - ${skipped}" + done + echo + fi + echo "build_targets:" + printf '%s\n' "${matrixEntries[@]}" + echo + } > "$matrixFile" +else + echo "Preserving ${matrixFile}; run ./update.sh with no version arguments to regenerate the full CI matrix." +fi + echo "|-------------------------|" echo "|- Generated images -|" echo "|-------------------------|"