mirror of
https://github.com/chialab/docker-php.git
synced 2026-11-12 15:13:33 -04:00
chore: replace compat workflows
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
name: Track deb.sury.org patches
|
||||
description: >
|
||||
For each EOL version in eol/, compares the Salsa commit pinned in its Dockerfiles' DEB_PATCHES
|
||||
with the one deb.sury.org last built from, and opens or updates an issue when they differ.
|
||||
|
||||
inputs:
|
||||
token:
|
||||
description: Token used to read and write the repository's issues.
|
||||
default: ${{ github.token }}
|
||||
distribution:
|
||||
description: Debian release whose deb.sury.org source index is read.
|
||||
default: trixie
|
||||
|
||||
runs:
|
||||
using: node24
|
||||
main: index.mjs
|
||||
@@ -0,0 +1,171 @@
|
||||
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { gunzipSync } from 'node:zlib';
|
||||
|
||||
const SALSA = 'https://salsa.debian.org/php-team/php';
|
||||
const SALSA_API = 'https://salsa.debian.org/api/v4/projects/php-team%2Fphp';
|
||||
|
||||
const token = process.env['INPUT_TOKEN'];
|
||||
const distribution = process.env['INPUT_DISTRIBUTION'] || 'trixie';
|
||||
const repository = process.env.GITHUB_REPOSITORY;
|
||||
const githubApi = process.env.GITHUB_API_URL || 'https://api.github.com';
|
||||
|
||||
// Salsa sometimes answers with a 502, so server and network errors are retried.
|
||||
async function download(url, attempts = 5) {
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
const response = await fetch(url).catch((error) => ({ ok: false, status: 0, error }));
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
const clientError = response.status >= 400 && response.status < 500;
|
||||
if (clientError || attempt === attempts) {
|
||||
throw new Error(`${url}: ${response.error?.message ?? `HTTP ${response.status}`}`);
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, attempt * 3000));
|
||||
}
|
||||
}
|
||||
|
||||
async function github(method, path, body) {
|
||||
const response = await fetch(`${githubApi}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
accept: 'application/vnd.github+json',
|
||||
authorization: `Bearer ${token}`,
|
||||
'content-type': 'application/json',
|
||||
'x-github-api-version': '2022-11-28',
|
||||
},
|
||||
body: body && JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`GitHub API ${method} ${path}: HTTP ${response.status} ${await response.text()}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/** The EOL versions: every eol/<X.Y> with a cli Dockerfile. */
|
||||
function eolVersions() {
|
||||
return readdirSync('eol', { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && existsSync(join('eol', entry.name, 'cli', 'Dockerfile')))
|
||||
.map((entry) => entry.name)
|
||||
.sort();
|
||||
}
|
||||
|
||||
/** The Salsa commit pinned in DEB_PATCHES, which every flavour of the version must agree on. */
|
||||
function pinnedCommit(version) {
|
||||
const commits = new Set();
|
||||
for (const flavor of readdirSync(join('eol', version))) {
|
||||
const dockerfile = join('eol', version, flavor, 'Dockerfile');
|
||||
const match = readFileSync(dockerfile, 'utf8').match(/DEB_PATCHES=\S*\/archive\/([0-9a-f]{40})\//);
|
||||
if (!match) {
|
||||
throw new Error(`${dockerfile} has no DEB_PATCHES commit`);
|
||||
}
|
||||
commits.add(match[1]);
|
||||
}
|
||||
if (commits.size !== 1) {
|
||||
throw new Error(`the Dockerfiles pin different commits: ${[...commits].join(', ')}`);
|
||||
}
|
||||
return [...commits][0];
|
||||
}
|
||||
|
||||
/** Package name → version, from deb.sury.org's index of source packages. */
|
||||
async function latestBuilds() {
|
||||
const response = await download(`https://packages.sury.org/php/dists/${distribution}/main/source/Sources.gz`);
|
||||
const index = gunzipSync(Buffer.from(await response.arrayBuffer())).toString('utf8');
|
||||
const builds = new Map();
|
||||
for (const stanza of index.split(/\n\s*\n/)) {
|
||||
const name = stanza.match(/^Package: (\S+)$/m)?.[1];
|
||||
const version = stanza.match(/^Version: (\S+)$/m)?.[1];
|
||||
if (name && version && !builds.has(name)) {
|
||||
builds.set(name, version);
|
||||
}
|
||||
}
|
||||
return builds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a build version such as "1:7.4.33-31+0~20261021.121+debian13~1.gbpe9c428" into its
|
||||
* revision without epoch ("7.4.33-31") and the start of the Salsa commit it was built from
|
||||
* ("e9c428").
|
||||
*/
|
||||
function parseBuild(version) {
|
||||
const match = version.match(/^(?:\d+:)?([^+]+)\+.*\.gbp([0-9a-f]+)$/);
|
||||
return match && { revision: match[1], commitPrefix: match[2] };
|
||||
}
|
||||
|
||||
async function fullCommit(prefix) {
|
||||
const response = await download(`${SALSA_API}/repository/commits/${prefix}`);
|
||||
return (await response.json()).id;
|
||||
}
|
||||
|
||||
async function seriesPatches(commit) {
|
||||
const response = await download(`${SALSA}/-/raw/${commit}/debian/patches/series`);
|
||||
return (await response.text())
|
||||
.split('\n')
|
||||
.map((line) => line.trim().split(/\s+/)[0])
|
||||
.filter((name) => name && !name.startsWith('#'));
|
||||
}
|
||||
|
||||
async function openIssues() {
|
||||
const issues = [];
|
||||
for (let page = 1; ; page++) {
|
||||
const batch = await github('GET', `/repos/${repository}/issues?state=open&per_page=100&page=${page}`);
|
||||
issues.push(...batch.filter((issue) => !issue.pull_request));
|
||||
if (batch.length < 100) {
|
||||
return issues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function check(version, builds, issues) {
|
||||
const pinned = pinnedCommit(version);
|
||||
const build = builds.get(`php${version}`);
|
||||
const parsed = build && parseBuild(build);
|
||||
if (!parsed) {
|
||||
throw new Error(`no php${version} build with a commit in the deb.sury.org index (got ${build ?? 'nothing'})`);
|
||||
}
|
||||
if (pinned.startsWith(parsed.commitPrefix)) {
|
||||
console.log(`PHP ${version}: up to date (${build})`);
|
||||
return;
|
||||
}
|
||||
|
||||
const latest = await fullCommit(parsed.commitPrefix);
|
||||
const [before, after] = await Promise.all([seriesPatches(pinned), seriesPatches(latest)]);
|
||||
const added = after.filter((name) => !before.includes(name));
|
||||
|
||||
const title = `PHP ${version} patches: new deb.sury.org build ${parsed.revision}`;
|
||||
const body = [
|
||||
`deb.sury.org published \`${build}\`, built from Salsa commit [\`${latest.slice(0, 12)}\`](${SALSA}/-/commit/${latest}). Our Dockerfiles pin [\`${pinned.slice(0, 12)}\`](${SALSA}/-/commit/${pinned}).`,
|
||||
'',
|
||||
`Changes: ${SALSA}/-/compare/${pinned}...${latest}`,
|
||||
'',
|
||||
'Patches added to its series:',
|
||||
...(added.length ? added.map((name) => `- \`${name}\``) : ['None.']),
|
||||
'',
|
||||
`To update, set the new commit in \`DEB_PATCHES\` and the revision in \`PHP_PATCHES\` in every \`eol/${version}/*/Dockerfile\`, and review each new patch into \`eol/patches/series.${version}\`, applied or commented out.`,
|
||||
].join('\n');
|
||||
|
||||
const existing = issues.find((issue) => issue.title.startsWith(`PHP ${version} patches:`));
|
||||
if (existing) {
|
||||
await github('PATCH', `/repos/${repository}/issues/${existing.number}`, { title, body });
|
||||
console.log(`PHP ${version}: updated issue #${existing.number} (${build})`);
|
||||
} else {
|
||||
const issue = await github('POST', `/repos/${repository}/issues`, { title, body });
|
||||
console.log(`PHP ${version}: opened issue #${issue.number} (${build})`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const builds = await latestBuilds();
|
||||
const issues = await openIssues();
|
||||
for (const version of eolVersions()) {
|
||||
try {
|
||||
await check(version, builds, issues);
|
||||
} catch (error) {
|
||||
console.log(`::error::PHP ${version}: ${error.message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`::error::${error.message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
@@ -70,6 +70,7 @@ jobs:
|
||||
[[ "$VERSION" == '5.6' || "$VERSION" == '7.0' ]] && pcov=false || pcov=true
|
||||
[[ "$ARCH" == 'amd64' ]] && xhprof=true || xhprof=false
|
||||
[[ "$VERSION" == 8.* || "$VERSION" == 'latest' || "$VERSION" == 'alpine' ]] && otel=true || otel=false
|
||||
[[ "$VERSION" == '5.6' ]] && compat=true || compat=false
|
||||
{
|
||||
echo "tag=$tag"
|
||||
echo "eol=$eol"
|
||||
@@ -79,6 +80,7 @@ jobs:
|
||||
echo "pcov=$pcov"
|
||||
echo "xhprof=$xhprof"
|
||||
echo "otel=$otel"
|
||||
echo "compat=$compat"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
@@ -184,6 +186,20 @@ jobs:
|
||||
no-cache: ${{ env.NO_CACHE }}
|
||||
test: otel
|
||||
|
||||
- name: Build compat image
|
||||
if: ${{ steps.plan.outputs.compat == 'true' }}
|
||||
uses: ./.github/actions/build-image
|
||||
with:
|
||||
image: php-compat
|
||||
tag: ${{ steps.plan.outputs.tag }}
|
||||
arch: ${{ matrix.arch }}
|
||||
context: ./compat
|
||||
build-args: BASE_IMAGE=${{ steps.php.outputs.ref }}
|
||||
push: ${{ env.PUSH }}
|
||||
cache-write: ${{ env.CACHE_WRITE }}
|
||||
no-cache: ${{ env.NO_CACHE }}
|
||||
test: compat
|
||||
|
||||
- name: Upload digests
|
||||
if: ${{ env.PUSH == 'ghcr' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
name: Publish compatibility images
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Test compatibility images"]
|
||||
branches: [main]
|
||||
types: [completed]
|
||||
|
||||
# Every completed test run starts this workflow; runs that won't publish get their own group, so
|
||||
# they can't cancel a publish in progress.
|
||||
concurrency:
|
||||
group: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'pull_request' && github.event.workflow_run.head_sha == github.sha && github.workflow || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
publish-compat:
|
||||
name: Build and publish compatibility images
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'pull_request' && github.event.workflow_run.head_sha == github.sha }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: [ '5.6' ]
|
||||
flavor: [ '', '-apache', '-fpm' ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Login to DockerHub (pull)
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_PULL_USER_4 }}
|
||||
password: ${{ secrets.DOCKER_PULL_PASS_4 }}
|
||||
|
||||
- name: Pull base image
|
||||
run: docker pull debian:stretch-slim
|
||||
|
||||
- name: Login to DockerHub (push)
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
# cache-from: type=registry,ref=ghcr.io/chialab/php-compat:${{ matrix.version }}${{ matrix.flavor }}
|
||||
cache-to: type=inline
|
||||
context: ./compat
|
||||
build-contexts: patches=eol/patches
|
||||
file: ${{ format('compat/{0}.{1}{2}', 'Dockerfile', matrix.version, matrix.flavor) }}
|
||||
tags: |
|
||||
chialab/php-compat:${{ matrix.version }}${{ matrix.flavor }}
|
||||
ghcr.io/chialab/php-compat:${{ matrix.version }}${{ matrix.flavor }}
|
||||
push: true
|
||||
@@ -1,55 +0,0 @@
|
||||
name: Test compatibility images
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# chosen by fair dice roll
|
||||
- cron: '42 18 * * 2'
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test-compat:
|
||||
name: Build and test compatibility images
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: [ '5.6' ]
|
||||
flavor: [ '', '-apache', '-fpm' ]
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
ports:
|
||||
- 5000:5000
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_PULL_USER_4 }}
|
||||
password: ${{ secrets.DOCKER_PULL_PASS_4 }}
|
||||
|
||||
- name: Build image for testing
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
# cache-from: type=registry,ref=ghcr.io/chialab/php-compat:${{ matrix.version }}${{ matrix.flavor }}
|
||||
cache-to: type=inline
|
||||
context: ./compat
|
||||
build-contexts: patches=eol/patches
|
||||
file: ${{ format('compat/{0}.{1}{2}', 'Dockerfile', matrix.version, matrix.flavor) }}
|
||||
tags: localhost:5000/chialab/php-compat:${{ matrix.version }}${{ matrix.flavor }}
|
||||
push: true
|
||||
|
||||
- name: Test image
|
||||
env:
|
||||
REGISTRY: localhost:5000/
|
||||
VERSION: ${{ matrix.version }}${{ matrix.flavor }}
|
||||
run: 'docker pull localhost:5000/chialab/php-compat:${{ matrix.version }}${{ matrix.flavor }} && make -C compat test'
|
||||
@@ -0,0 +1,23 @@
|
||||
name: Track deb.sury.org patches
|
||||
|
||||
# Opens an issue when deb.sury.org publishes a build of an EOL version from a newer Salsa commit
|
||||
# than the one pinned in its Dockerfiles' DEB_PATCHES.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 6 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
track:
|
||||
name: Compare pinned commits with deb.sury.org builds
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Check each EOL version
|
||||
uses: ./.github/actions/track-patches
|
||||
Reference in New Issue
Block a user