diff --git a/.github/workflows/7.3.yml b/.github/workflows/7.3.yml index 3ea4f68..6b177e5 100644 --- a/.github/workflows/7.3.yml +++ b/.github/workflows/7.3.yml @@ -54,6 +54,7 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + if: true - name: GHCR Login uses: docker/login-action@v1 with: @@ -67,6 +68,7 @@ jobs: set: | *.platform=linux/amd64,linux/arm,linux/arm64 push: ${{ github.event_name == 'push' }} + if: true - name: GHCR build and push uses: docker/bake-action@v2 env: diff --git a/.github/workflows/7.4.yml b/.github/workflows/7.4.yml index ed42b53..7c0824e 100644 --- a/.github/workflows/7.4.yml +++ b/.github/workflows/7.4.yml @@ -54,6 +54,7 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + if: true - name: GHCR Login uses: docker/login-action@v1 with: @@ -67,6 +68,7 @@ jobs: set: | *.platform=linux/amd64,linux/arm,linux/arm64 push: ${{ github.event_name == 'push' }} + if: true - name: GHCR build and push uses: docker/bake-action@v2 env: diff --git a/.github/workflows/8.0.yml b/.github/workflows/8.0.yml index 42f8fb3..4d556f6 100644 --- a/.github/workflows/8.0.yml +++ b/.github/workflows/8.0.yml @@ -54,6 +54,7 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + if: true - name: GHCR Login uses: docker/login-action@v1 with: @@ -67,6 +68,7 @@ jobs: set: | *.platform=linux/amd64,linux/arm,linux/arm64 push: ${{ github.event_name == 'push' }} + if: true - name: GHCR build and push uses: docker/bake-action@v2 env: diff --git a/.github/workflows/8.1.yml b/.github/workflows/8.1.yml index 2fcffc0..db08f3d 100644 --- a/.github/workflows/8.1.yml +++ b/.github/workflows/8.1.yml @@ -14,6 +14,9 @@ defaults: run: shell: 'bash -Eeuo pipefail -x {0}' +permissions: + contents: read + jobs: verify-templating: name: Verify templating @@ -31,14 +34,14 @@ jobs: runs-on: ubuntu-latest needs: - verify-templating + permissions: + contents: read + packages: write strategy: matrix: target: [ - "8_1_9-bullseye", - "8_1_9-buster", - "8_1_9-alpine3_16", - "8_1_9-alpine3_15", + "8_1_34-alpine3_22", ] steps: - name: Checkout @@ -54,6 +57,7 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + if: false - name: GHCR Login uses: docker/login-action@v1 with: @@ -65,8 +69,9 @@ jobs: with: targets: ${{ matrix.target }} set: | - *.platform=linux/amd64,linux/arm,linux/arm64 + *.platform=linux/amd64 push: ${{ github.event_name == 'push' }} + if: false - name: GHCR build and push uses: docker/bake-action@v2 env: @@ -75,5 +80,5 @@ jobs: with: targets: ${{ matrix.target }} set: | - *.platform=linux/amd64,linux/arm,linux/arm64 + *.platform=linux/amd64 push: ${{ github.event_name == 'push' }} diff --git a/.github/workflows/8.2.yml b/.github/workflows/8.2.yml index 72859b2..2b3d464 100644 --- a/.github/workflows/8.2.yml +++ b/.github/workflows/8.2.yml @@ -54,6 +54,7 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + if: true - name: GHCR Login uses: docker/login-action@v1 with: @@ -67,6 +68,7 @@ jobs: set: | *.platform=linux/amd64,linux/arm,linux/arm64 push: ${{ github.event_name == 'push' }} + if: true - name: GHCR build and push uses: docker/bake-action@v2 env: diff --git a/8.1/alpine3.16/Dockerfile b/8.1/alpine3.16/Dockerfile deleted file mode 100644 index 8c4730a..0000000 --- a/8.1/alpine3.16/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -# NOTE: This file was generated via generate.sh. Don't edit it directly - -FROM sunasteriskrnd/php:8.1.9-nginx-alpine3.16 - -ENV COMPOSER_VERSION=2.4.1 - -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --version="$COMPOSER_VERSION" - -COPY rootfs / -RUN apk add --no-cache git - -RUN apk add --update linux-headers --no-cache --virtual .ext-deps autoconf gcc musl-dev make \ - && pecl install xdebug pcov \ - && docker-php-ext-enable xdebug pcov \ - && apk del .ext-deps diff --git a/8.1/alpine3.16/rootfs/usr/local/bin/docker-php-ext-disable b/8.1/alpine3.16/rootfs/usr/local/bin/docker-php-ext-disable deleted file mode 100755 index 3622ebc..0000000 --- a/8.1/alpine3.16/rootfs/usr/local/bin/docker-php-ext-disable +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -# remove config file created by docker-php-ext-enable -rm /usr/local/etc/php/conf.d/docker-php-ext-$1.ini diff --git a/8.1/alpine3.15/Dockerfile b/8.1/alpine3.22/Dockerfile similarity index 77% rename from 8.1/alpine3.15/Dockerfile rename to 8.1/alpine3.22/Dockerfile index c251d1e..6b65032 100644 --- a/8.1/alpine3.15/Dockerfile +++ b/8.1/alpine3.22/Dockerfile @@ -1,6 +1,6 @@ # NOTE: This file was generated via generate.sh. Don't edit it directly -FROM sunasteriskrnd/php:8.1.9-nginx-alpine3.15 +FROM ghcr.io/sun-asterisk-research/php:8.1.34-nginx-alpine3.22@sha256:52414238eb01bf163d0df29bb7c10adb04559a89cdd9242c8794c35defe7e998 ENV COMPOSER_VERSION=2.4.1 diff --git a/8.1/alpine3.15/rootfs/usr/local/bin/docker-php-ext-disable b/8.1/alpine3.22/rootfs/usr/local/bin/docker-php-ext-disable similarity index 100% rename from 8.1/alpine3.15/rootfs/usr/local/bin/docker-php-ext-disable rename to 8.1/alpine3.22/rootfs/usr/local/bin/docker-php-ext-disable diff --git a/8.1/bullseye/Dockerfile b/8.1/bullseye/Dockerfile deleted file mode 100644 index 9476b5e..0000000 --- a/8.1/bullseye/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -# NOTE: This file was generated via generate.sh. Don't edit it directly - -FROM sunasteriskrnd/php:8.1.9-nginx-bullseye - -ENV COMPOSER_VERSION=2.4.1 - -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --version="$COMPOSER_VERSION" - -COPY rootfs / -RUN apt-get update \ - && apt-get install -y git vim \ - && rm -rf /var/lib/apt/lists/* - -RUN pecl install xdebug pcov \ - && docker-php-ext-enable xdebug pcov diff --git a/8.1/bullseye/rootfs/usr/local/bin/docker-php-ext-disable b/8.1/bullseye/rootfs/usr/local/bin/docker-php-ext-disable deleted file mode 100755 index 3622ebc..0000000 --- a/8.1/bullseye/rootfs/usr/local/bin/docker-php-ext-disable +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -# remove config file created by docker-php-ext-enable -rm /usr/local/etc/php/conf.d/docker-php-ext-$1.ini diff --git a/8.1/buster/Dockerfile b/8.1/buster/Dockerfile deleted file mode 100644 index 50b8238..0000000 --- a/8.1/buster/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -# NOTE: This file was generated via generate.sh. Don't edit it directly - -FROM sunasteriskrnd/php:8.1.9-nginx-buster - -ENV COMPOSER_VERSION=2.4.1 - -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --version="$COMPOSER_VERSION" - -COPY rootfs / -RUN apt-get update \ - && apt-get install -y git vim \ - && rm -rf /var/lib/apt/lists/* - -RUN pecl install xdebug pcov \ - && docker-php-ext-enable xdebug pcov diff --git a/8.1/buster/rootfs/usr/local/bin/docker-php-ext-disable b/8.1/buster/rootfs/usr/local/bin/docker-php-ext-disable deleted file mode 100755 index 3622ebc..0000000 --- a/8.1/buster/rootfs/usr/local/bin/docker-php-ext-disable +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -# remove config file created by docker-php-ext-enable -rm /usr/local/etc/php/conf.d/docker-php-ext-$1.ini diff --git a/Dockerfile-00.template b/Dockerfile-00.template index ebb1164..af830c2 100644 --- a/Dockerfile-00.template +++ b/Dockerfile-00.template @@ -1,4 +1,4 @@ -FROM sunasteriskrnd/php:{{ version }}-nginx-{{ distro_release }} +FROM {{ base_image }}:{{ version }}-nginx-{{ distro_release }}{{ base_image_digest }} ENV COMPOSER_VERSION={{ composer_version }} diff --git a/Makefile b/Makefile index ec6a6b7..7347ff3 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,19 @@ generate: @./generate.sh @for versions in $$(ls versions_*.yml); do \ - APPEND=true ./generate.sh $$versions; \ + APPEND=true ./generate.sh $$versions || exit 1; \ done clean: @./generate.sh clean - @for versions in $$(versions_*.yml); do \ - APPEND=true ./generate.sh clean $$versions; \ + @for versions in $$(ls versions_*.yml); do \ + APPEND=true ./generate.sh clean $$versions || exit 1; \ + done + +# Validate every versions file first, so a bad one stops make all before anything is removed +check: + @for versions in versions.yml $$(ls versions_*.yml); do \ + ./generate.sh check $$versions || exit 1; \ done -all: clean generate +all: check clean generate diff --git a/ci.yml.template b/ci.yml.template index 16f35d2..0561f13 100644 --- a/ci.yml.template +++ b/ci.yml.template @@ -12,7 +12,7 @@ defaults: run: shell: 'bash -Eeuo pipefail -x {0}' -jobs: +{{ workflow_permissions }}jobs: verify-templating: name: Verify templating runs-on: ubuntu-latest @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest needs: - verify-templating - strategy: +{{ docker_permissions }} strategy: matrix: target: {{ targets }} @@ -47,6 +47,7 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + if: {{ dockerhub_push }} - name: GHCR Login uses: docker/login-action@v1 with: @@ -60,6 +61,7 @@ jobs: set: | *.platform={{ platforms }} push: ${{ github.event_name == 'push' }} + if: {{ dockerhub_push }} - name: GHCR build and push uses: docker/bake-action@v2 env: diff --git a/docker-bake.hcl b/docker-bake.hcl index 6fb92a2..38eb229 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -142,82 +142,6 @@ target "8_0-alpine3_15" { inherits = ["8_0_22-alpine3_15"] } -target "8_1_9-bullseye" { - context = "./8.1/bullseye" - cache-from = [ - "${REGISTRY}/${REPO}:8.1-bullseye" - ] - tags = [ - "${REGISTRY}/${REPO}:8-bullseye", - "${REGISTRY}/${REPO}:8", - "${REGISTRY}/${REPO}:8.1-bullseye", - "${REGISTRY}/${REPO}:8.1", - "${REGISTRY}/${REPO}:8.1.9-bullseye", - "${REGISTRY}/${REPO}:8.1.9", - "${REGISTRY}/${REPO}:bullseye", - "${REGISTRY}/${REPO}:latest", - ] -} - -target "8_1-bullseye" { - inherits = ["8_1_9-bullseye"] -} - -target "8_1_9-buster" { - context = "./8.1/buster" - cache-from = [ - "${REGISTRY}/${REPO}:8.1-buster" - ] - tags = [ - "${REGISTRY}/${REPO}:8-buster", - "${REGISTRY}/${REPO}:8.1-buster", - "${REGISTRY}/${REPO}:8.1.9-buster", - "${REGISTRY}/${REPO}:buster", - ] -} - -target "8_1-buster" { - inherits = ["8_1_9-buster"] -} - -target "8_1_9-alpine3_16" { - context = "./8.1/alpine3.16" - cache-from = [ - "${REGISTRY}/${REPO}:8.1-alpine3.16" - ] - tags = [ - "${REGISTRY}/${REPO}:8-alpine3.16", - "${REGISTRY}/${REPO}:8-alpine", - "${REGISTRY}/${REPO}:8.1-alpine3.16", - "${REGISTRY}/${REPO}:8.1-alpine", - "${REGISTRY}/${REPO}:8.1.9-alpine3.16", - "${REGISTRY}/${REPO}:8.1.9-alpine", - "${REGISTRY}/${REPO}:alpine3.16", - "${REGISTRY}/${REPO}:alpine", - ] -} - -target "8_1-alpine3_16" { - inherits = ["8_1_9-alpine3_16"] -} - -target "8_1_9-alpine3_15" { - context = "./8.1/alpine3.15" - cache-from = [ - "${REGISTRY}/${REPO}:8.1-alpine3.15" - ] - tags = [ - "${REGISTRY}/${REPO}:8-alpine3.15", - "${REGISTRY}/${REPO}:8.1-alpine3.15", - "${REGISTRY}/${REPO}:8.1.9-alpine3.15", - "${REGISTRY}/${REPO}:alpine3.15", - ] -} - -target "8_1-alpine3_15" { - inherits = ["8_1_9-alpine3_15"] -} - target "7_3_33-bullseye" { context = "./7.3/bullseye" cache-from = [ @@ -282,6 +206,20 @@ target "7_3-alpine3_14" { inherits = ["7_3_33-alpine3_14"] } +target "8_1_34-alpine3_22" { + context = "./8.1/alpine3.22" + cache-from = [ + "${REGISTRY}/${REPO}:8.1-alpine3.22" + ] + tags = [ + "${REGISTRY}/${REPO}:8.1.34-alpine3.22", + ] +} + +target "8_1-alpine3_22" { + inherits = ["8_1_34-alpine3_22"] +} + target "8_2_1-bullseye" { context = "./8.2/bullseye" cache-from = [ diff --git a/generate.sh b/generate.sh index b65cd32..417f0a9 100755 --- a/generate.sh +++ b/generate.sh @@ -4,6 +4,67 @@ set -e source generate_utils.sh +# Print the value of an optional boolean key from versions*.yml, or the default when unset. +# Fails when the value is neither true nor false. +# +# Usage: bool_key +# e.g. bool_key exact_tags_only false +# +bool_key() { + local value="${!1:-$2}" + + if [ "$value" != true ] && [ "$value" != false ]; then + echo "$1 must be true or false, got '$value'" >&2 + return 1 + fi + + echo "$value" +} + +# Print "@sha256:" for an optional digest key from versions*.yml, or nothing when unset. +# The key holds the bare 64-char hex: parse_versions can't carry a ":" inside a value. +# +# Usage: digest_suffix +# e.g. digest_suffix base_image_sha256 +# +digest_suffix() { + local value="${!1}" + + if [ -z "$value" ]; then + return 0 + fi + + if ! [[ "$value" =~ ^[0-9a-f]{64}$ ]]; then + echo "$1 must be a 64-char hex sha256 without the 'sha256:' prefix, got '$value'" >&2 + return 1 + fi + + echo "@sha256:$value" +} + +# Validate the keys of the loaded versions*.yml before anything is written or removed. +# base_image is required; base_image_sha256 is required when exact_tags_only is true, +# so an exact (immutable) tag is never built from an unpinned base. +# +# Usage: validate_config +# +validate_config() { + if ! [[ "$base_image" =~ ^[a-z0-9][a-z0-9./_-]*$ ]]; then + echo "$1: base_image must be an image repository like ghcr.io/org/php, got '$base_image'" >&2 + return 1 + fi + + local exact_only + exact_only=$(bool_key exact_tags_only false) || return 1 + bool_key dockerhub_push true > /dev/null || return 1 + digest_suffix base_image_sha256 > /dev/null || return 1 + + if [ "$exact_only" = true ] && [ -z "$base_image_sha256" ]; then + echo "$1: base_image_sha256 is required when exact_tags_only is true" >&2 + return 1 + fi +} + # Generate a script that can be used with `eval` to set meta variables from a tag # # Usage: meta_from_full_tag @@ -41,6 +102,10 @@ generate_dockerfile() { local dir="$php_minor/$distro_release" local dockerfile="$dir/Dockerfile" + # Base php image digest pin (key base_image_sha256); validate_config already checked the keys + local base_image_digest + base_image_digest=$(digest_suffix base_image_sha256) + mkdir -p "$dir" write_warn_edit $dockerfile @@ -50,6 +115,8 @@ generate_dockerfile() { version=php_version \ composer_version \ distro_release \ + base_image \ + base_image_digest \ >> $dockerfile done @@ -151,6 +218,15 @@ generate_bake_file_target() { distro_tags="$distro_tags,default" fi + # Opt-in: publish only the immutable full tag, without minor/major versions or aliases + local exact_only + exact_only=$(bool_key exact_tags_only false) + + if [ "$exact_only" = true ]; then + version_tags="$php_version" + distro_tags="$distro_release" + fi + tags=$( IFS=',' generate_tags "$version_tags" "$distro_tags" \ | sed -E 's/(^|[[:space:]])/\1${REGISTRY}\/${REPO}:/g' \ @@ -207,13 +283,45 @@ generate_workflow() { local platforms=$(echo $platforms | sed 's/ /,/g') - tpl ci.yml.template php_minor targets platforms >> $workflow_file + # Docker Hub steps also need the secrets; dockerhub_push: false turns them off for this file + local dockerhub_push_enabled + dockerhub_push_enabled=$(bool_key dockerhub_push true) + + # Exact-tag builds publish to ghcr with GITHUB_TOKEN, so they ask for packages: write + # themselves rather than depend on the org default token permissions + local workflow_permissions="" + local docker_permissions="" + + if [ "$(bool_key exact_tags_only false)" = true ]; then + workflow_permissions=$'permissions:\n contents: read\n\n' + docker_permissions=$' permissions:\n contents: read\n packages: write\n' + fi + + tpl ci.yml.template php_minor targets platforms dockerhub_push=dockerhub_push_enabled \ + workflow_permissions docker_permissions >> $workflow_file } -if [ "$1" = "clean" ]; then - eval $(generate_config ${2:-versions.yml}) - clean_all -else - eval $(generate_config ${1:-versions.yml}) - generate_all +# Usage: generate.sh [clean|check] [versions file] +case "$1" in + clean|check) mode="$1"; versions_file="${2:-versions.yml}" ;; + *) mode=generate; versions_file="${1:-versions.yml}" ;; +esac + +if [ ! -f "$versions_file" ]; then + echo "$versions_file: no such versions file" >&2 + exit 1 fi + +# generate_config cannot parse a value containing ':', so catch a prefixed digest before eval +if grep -qE "^base_image_sha256:[[:space:]]*[\"']?sha256:" "$versions_file"; then + echo "$versions_file: base_image_sha256 must be a 64-char hex sha256 without the 'sha256:' prefix" >&2 + exit 1 +fi + +eval $(generate_config "$versions_file") +validate_config "$versions_file" + +case "$mode" in + clean) clean_all ;; + generate) generate_all ;; +esac diff --git a/versions.yml b/versions.yml index 4dee411..e17e813 100644 --- a/versions.yml +++ b/versions.yml @@ -1,9 +1,9 @@ php_versions: - 7.4.30 - 8.0.22 -- 8.1.9 composer_version: 2.4.1 +base_image: sunasteriskrnd/php alpine_releases: - alpine3.16 diff --git a/versions_7.3.yml b/versions_7.3.yml index 3e823c5..cb26ee1 100644 --- a/versions_7.3.yml +++ b/versions_7.3.yml @@ -2,6 +2,7 @@ php_versions: - 7.3.33 composer_version: 2.4.1 +base_image: sunasteriskrnd/php default_php_7_version: 7.4 default_distro: debian diff --git a/versions_8.1.yml b/versions_8.1.yml new file mode 100644 index 0000000..4a1b20c --- /dev/null +++ b/versions_8.1.yml @@ -0,0 +1,19 @@ +php_versions: +- 8.1.34 + +composer_version: 2.4.1 +base_image: ghcr.io/sun-asterisk-research/php +base_image_sha256: 52414238eb01bf163d0df29bb7c10adb04559a89cdd9242c8794c35defe7e998 + +default_php_8_version: 8.1 +default_distro: alpine +default_alpine_release: alpine3.22 + +alpine_releases: +- alpine3.22 + +platforms: +- linux/amd64 + +exact_tags_only: true +dockerhub_push: false diff --git a/versions_8.2.yml b/versions_8.2.yml index 10407f2..04bcf71 100644 --- a/versions_8.2.yml +++ b/versions_8.2.yml @@ -2,6 +2,7 @@ php_versions: - 8.2.1 composer_version: 2.4.1 +base_image: sunasteriskrnd/php default_php_8_version: 8.2 default_distro: debian