Repository navigation
PHP images (grpc + protobuf) #40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Builds the PHP images carrying `grpc` and `protobuf`, and publishes them to GHCR. | |
| # | |
| # **Deliberately independent of `ci.yml`.** It tests nothing of the component: it builds tooling, | |
| # on its own rhythm and with its own permissions. Mixing it into CI would charge a multi-minute C++ | |
| # compilation to every pull request that touches a line of PHP. | |
| # | |
| # **Why these images exist, when CI does not need them.** Measured on this repository: | |
| # `shivammathur/setup-php` installs `grpc` in **five seconds**, on the strength of a prebuilt | |
| # binary — the "Temporal integration tests" job starts PHP as fast as the Sylius one, which does | |
| # not ask for the extension. For GitHub Actions the problem is settled, and a build chain would be | |
| # a restatement. | |
| # | |
| # What is not settled is **Docker**. A `php:8.3-alpine` image has no grpc, and | |
| # `install-php-extensions` compiles it from source on every build. Third-party images exist, | |
| # maintained by one person and outside our control. Hence these: the cost is paid once, here, and | |
| # what we consume belongs to us. | |
| name: PHP images (grpc + protobuf) | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| platforms: | |
| description: "Target architectures. arm64 goes through QEMU and lengthens a C++ compilation considerably." | |
| default: linux/amd64 | |
| type: choice | |
| options: | |
| - linux/amd64 | |
| - linux/amd64,linux/arm64 | |
| dry_run: | |
| description: "Prune: show what would be removed, without removing it." | |
| default: true | |
| type: boolean | |
| # External trigger, for whoever wants to refresh without going through the interface: | |
| # gh api repos/:owner/:repo/dispatches -f event_type=php-grpc-images | |
| repository_dispatch: | |
| types: [php-grpc-images] | |
| push: | |
| # `main` only. Without this filter, a working branch publishes its images under the | |
| # rolling tags — `8.3-cli` ends up designating an unreviewed experiment. | |
| branches: [main] | |
| paths: | |
| - 'docker/php-grpc/**' | |
| - '.github/workflows/php-grpc-images.yml' | |
| pull_request: | |
| paths: | |
| - 'docker/php-grpc/**' | |
| - '.github/workflows/php-grpc-images.yml' | |
| schedule: | |
| # Every week. Two things age here, and not at the same rate: the base's system packages, | |
| # and PHP itself — whose patch releases come out every month. A weekly cadence keeps the | |
| # gap between `php:8.4-cli` and our image counted in days. The Buildx cache makes the | |
| # rebuild cheap when nothing has moved upstream. | |
| - cron: '17 4 * * 1' | |
| concurrency: | |
| group: php-grpc-images-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build: | |
| name: PHP ${{ matrix.php }} · ${{ matrix.flavour }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| php: ['8.2', '8.3', '8.4', '8.5'] | |
| # `zts` is the variant compiled with thread support. It is not a compilation | |
| # detail: a runtime that runs several workers in a single process demands it, and | |
| # an extension loaded into a PHP whose thread-safety does not match simply refuses | |
| # to load. Both are therefore necessary, neither one replaces the other. | |
| flavour: [cli, cli-alpine, zts, zts-alpine] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: docker/setup-qemu-action@v3 | |
| if: contains(inputs.platforms, 'arm64') | |
| - uses: docker/setup-buildx-action@v3 | |
| - name: Authenticate against GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # `github.repository_owner` rather than the repository name: these images are not | |
| # Durable's, they are its author's — other projects can consume them. | |
| - name: Tags | |
| id: meta | |
| run: | | |
| base="ghcr.io/${{ github.repository_owner }}/php-grpc" | |
| echo "tags=${base}:${{ matrix.php }}-${{ matrix.flavour }},${base}:${{ matrix.php }}-${{ matrix.flavour }}-$(date -u +%Y%m%d)" >> "$GITHUB_OUTPUT" | |
| - name: Build and publish | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: docker/php-grpc | |
| platforms: ${{ inputs.platforms || 'linux/amd64' }} | |
| build-args: | | |
| PHP_VERSION=${{ matrix.php }} | |
| FLAVOUR=${{ matrix.flavour }} | |
| # On a pull request we build without publishing: the compilation is what we | |
| # want to check, the publication does not have to be. | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| cache-from: type=gha,scope=php-grpc-${{ matrix.php }}-${{ matrix.flavour }} | |
| cache-to: type=gha,mode=max,scope=php-grpc-${{ matrix.php }}-${{ matrix.flavour }} | |
| # The Dockerfile's `RUN php -m | grep` proves the extension **in** the built layer. | |
| # This one proves it in the **published** image, which is not the same assertion. | |
| - name: The published image does carry both extensions | |
| if: github.event_name != 'pull_request' && !contains(inputs.platforms, 'arm64') | |
| run: | | |
| image="ghcr.io/${{ github.repository_owner }}/php-grpc:${{ matrix.php }}-${{ matrix.flavour }}" | |
| docker run --rm "$image" php -m | grep -qx grpc | |
| docker run --rm "$image" php -m | grep -qx protobuf | |
| attendu="$(case '${{ matrix.flavour }}' in zts*) echo enabled;; *) echo disabled;; esac)" | |
| docker run --rm "$image" php -i | grep -q "Thread Safety => $attendu" | |
| docker run --rm "$image" php -v | head -n 1 | |
| # Sixteen images rebuilt every Monday, each placing a dated tag: about eight hundred tags a | |
| # year, and nothing prunes them. The version list becomes unreadable well before storage | |
| # becomes a problem. | |
| retention: | |
| name: Prune the dated tags | |
| needs: build | |
| # Never on a pull request: nothing is published there, so there is nothing to prune, and | |
| # a job that deletes published images has no place in validating a proposal. | |
| if: github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| # What decides fits in one function, and it is checked before being applied. | |
| # The case that matters: on publication Monday, `8.4-zts` and `8.4-zts-20260828` | |
| # designate the same manifest — removing "the dated tag" would remove the image the | |
| # rolling tag designates. | |
| - name: The rule checks itself before it applies | |
| run: python3 docker/php-grpc/retention.py --self-test | |
| - name: Prune | |
| env: | |
| # The package deletion API does not accept the `GITHUB_TOKEN`: it asks for a | |
| # classic personal token carrying `read:packages` and `delete:packages`. Without | |
| # the secret, the job warns and stops — a weekly failure would end up ignored, | |
| # and the real ones with it. | |
| GH_TOKEN: ${{ secrets.GHCR_RETENTION_TOKEN }} | |
| OWNER: ${{ github.repository_owner }} | |
| DRY_RUN: ${{ inputs.dry_run || 'false' }} | |
| run: | | |
| if [ -z "$GH_TOKEN" ]; then | |
| echo "::warning title=Pruning not configured::The GHCR_RETENTION_TOKEN secret is absent. Dated tags keep accumulating. Create a classic personal token with read:packages and delete:packages." | |
| exit 0 | |
| fi | |
| # Deux familles d'URL cohabitent pour les paquets : celle de l'utilisateur | |
| # authenticated user, and that of a named user. A token belonging to the | |
| # package's owner goes through the first; we try it, and fall back on the second. | |
| # Resolving here rather than at deletion time matters: the read happens every | |
| # week, the first deletion is only due in two months. | |
| base="/user/packages/container/php-grpc" | |
| if ! gh api "$base/versions?per_page=1" >/dev/null 2>&1; then | |
| base="/users/$OWNER/packages/container/php-grpc" | |
| gh api "$base/versions?per_page=1" >/dev/null | |
| fi | |
| echo "API : $base" | |
| gh api --paginate "$base/versions?per_page=100" \ | |
| -q '.[] | {id: .id, tags: (.metadata.container.tags // [])}' \ | |
| | jq -s '.' > versions.json | |
| python3 docker/php-grpc/retention.py --keep 8 < versions.json > a-retirer.txt | |
| if [ "$DRY_RUN" = "true" ]; then | |
| echo "::notice::Dry run: $(wc -l < a-retirer.txt) version(s) would have been removed." | |
| exit 0 | |
| fi | |
| while read -r id; do | |
| gh api -X DELETE "$base/versions/$id" | |
| echo "removed: $id" | |
| done < a-retirer.txt |