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
63 changes: 58 additions & 5 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,64 @@ variable "tagPrefix" {
}

variable "phpMatrix" {
default = [ "8.2.34", "8.3.35", "8.4.26", "8.5.11" ]
default = [ "8.2.35", "8.3.36", "8.4.27", "8.5.12" ]
}

variable "frankenphpMatrix" {
default = [ "8.2.34", "8.3.35", "8.4.26", "8.5.11" ]
default = [ "8.2.35", "8.3.36", "8.4.27", "8.5.12" ]
}

variable "phpLibraryRef" {
default = "e3eaff845f7a9f7259c31da54dce0320b47a3bbd"
}

variable "alpineVersion" {
default = "3.24"
}

variable "frankenphpRef" {
default = "v1.13.1"
}

variable "goVersion" {
default = "1.27"
}

# PHP built from source with the upstream docker-library/php Dockerfiles

target "php-fpm-alpine" {
name = "php-fpm-alpine-${replace(substr(php, 0, 3), ".", "-")}"
context = "https://github.com/docker-library/php.git#${phpLibraryRef}:${substr(php, 0, 3)}/alpine${alpineVersion}/fpm"
matrix = {
"php" = phpMatrix
}
platforms = [ "linux/amd64", "linux/arm64" ]
}

target "php-zts-trixie" {
name = "php-zts-trixie-${replace(substr(php, 0, 3), ".", "-")}"
context = "https://github.com/docker-library/php.git#${phpLibraryRef}:${substr(php, 0, 3)}/trixie/zts"
matrix = {
"php" = frankenphpMatrix
}
platforms = [ "linux/amd64", "linux/arm64" ]
}

target "frankenphp-upstream" {
name = "frankenphp-upstream-${replace(substr(php, 0, 3), ".", "-")}"
context = "https://github.com/php/frankenphp.git#${frankenphpRef}"
target = "runner"
matrix = {
"php" = frankenphpMatrix
}
contexts = {
php-base = "target:php-zts-trixie-${replace(substr(php, 0, 3), ".", "-")}"
golang-base = "docker-image://docker.io/library/golang:${goVersion}-trixie"
}
args = {
"FRANKENPHP_VERSION" = frankenphpRef
}
platforms = [ "linux/amd64", "linux/arm64" ]
}

# Frankenphp
Expand All @@ -22,8 +75,8 @@ target "frankenphp" {
matrix = {
"php" = frankenphpMatrix
}
args = {
"PHP_VERSION" = php
contexts = {
base = "target:frankenphp-upstream-${replace(substr(php, 0, 3), ".", "-")}"
}
platforms = [ "linux/amd64", "linux/arm64" ]
tags = imageSuffix != "" ? [
Expand Down Expand Up @@ -69,7 +122,7 @@ target "fpm" {
"php" = phpMatrix
}
contexts = {
base = "docker-image://docker.io/library/php:${php}-fpm-alpine"
base = "target:php-fpm-alpine-${replace(substr(php, 0, 3), ".", "-")}"
}
platforms = [ "linux/amd64", "linux/arm64" ]
tags = imageSuffix != "" ? [
Expand Down
3 changes: 1 addition & 2 deletions frankenphp/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#syntax=docker/dockerfile:1.4
#check=skip=SecretsUsedInArgOrEnv

ARG PHP_VERSION=8.4
FROM dunglas/frankenphp:php${PHP_VERSION}
FROM base

RUN <<EOF
set -e
Expand Down
143 changes: 45 additions & 98 deletions update-php-matrix.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,132 +5,79 @@ import fs from 'node:fs/promises';
// Supported PHP major.minor versions
const supportedVersions = ['8.2', '8.3', '8.4', '8.5'];

// Function to fetch PHP tags from Docker Hub API
async function fetchPhpTags(version) {
const url = `https://hub.docker.com/v2/repositories/library/php/tags/?page_size=50&page=1&name=${version}.`;
const phpLibraryRepository = 'docker-library/php';

async function fetchJson(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch PHP tags for version ${version}: ${response.status} ${response.statusText}`);
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
}

const jsonData = await response.json();
return jsonData;
return response.json();
}

// Function to fetch FrankenPHP tags from Docker Hub API
async function fetchFrankenPhpTags(version) {
const url = `https://hub.docker.com/v2/repositories/dunglas/frankenphp/tags/?page_size=50&page=1&name=php${version}.`;

const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch FrankenPHP tags for version ${version}: ${response.status} ${response.statusText}`);
}
async function fetchPhpLibraryRef() {
const commit = await fetchJson(`https://api.github.com/repos/${phpLibraryRepository}/commits/master`);
return commit.sha;
}

const jsonData = await response.json();
return jsonData;
async function fetchVersions(ref) {
return fetchJson(`https://raw.githubusercontent.com/${phpLibraryRepository}/${ref}/versions.json`);
}

// Function to extract the latest patch version from the API response
function getLatestPatchVersion(apiResponse) {
if (!apiResponse || !Array.isArray(apiResponse.results)) {
throw new Error('Invalid API response');
// The official php:*-fpm-alpine images follow the newest Alpine release
function getLatestAlpineVersion(versions) {
const alpineVersions = supportedVersions.flatMap(version => versions[version].variants ?? [])
.map(variant => variant.match(/^alpine(\d+\.\d+)\/fpm$/)?.[1])
.filter(Boolean)
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));

if (alpineVersions.length === 0) {
throw new Error('No Alpine variant found');
}

for (const entry of apiResponse.results) {
// Skip RC versions
if (entry.name.includes('RC')) {
continue;
}
return alpineVersions.at(-1);
}

// Extract version number (e.g., 8.1.33)
const versionMatch = entry.name.match(/^(\d+\.\d+\.\d+)/);
if (versionMatch) {
return versionMatch[1];
function getPhpVersions(versions) {
return supportedVersions.map(version => {
const entry = versions[version];
if (!entry || !/^\d+\.\d+\.\d+$/.test(entry.version)) {
throw new Error(`No valid version found for PHP ${version}`);
}
}

return null;
return entry.version;
});
}

// Function to extract the latest PHP patch version from FrankenPHP API response
function getLatestFrankenphpPatchVersion(apiResponse) {
if (!apiResponse || !Array.isArray(apiResponse.results)) {
throw new Error('Invalid API response');
}

for (const entry of apiResponse.results) {
// Skip RC versions
if (entry.name.includes('RC')) {
continue;
}
function replaceVariable(hclContent, name, value) {
const regex = new RegExp(`(variable "${name}" \\{\\s*default = )(\\[[^\\]]*\\]|"[^"]*")(\\s*\\})`, 's');

// Extract PHP version number from FrankenPHP tag (e.g., 1.9.1-php8.2.29 -> 8.2.29)
const versionMatch = entry.name.match(/-php(\d+\.\d+\.\d+)$/);
if (versionMatch) {
return versionMatch[1];
}
if (!regex.test(hclContent)) {
throw new Error(`${name} variable not found in docker-bake.hcl`);
}

return null;
return hclContent.replace(regex, `$1${value}$3`);
}

// Function to update the phpMatrix and frankenphpMatrix in docker-bake.hcl
async function updatePhpMatrixInHcl(phpVersions, frankenphpVersions) {
async function updateBakeFile(phpVersions, phpLibraryRef, alpineVersion) {
const hclPath = 'docker-bake.hcl';
let hclContent = await fs.readFile(hclPath, 'utf8');

// Find the phpMatrix variable and replace its value
const phpMatrixRegex = /(variable "phpMatrix" \{\s*default = )(\[[^\]]*\])(\s*\})/s;
const newPhpMatrix = `[ ${phpVersions.map(v => `"${v}"`).join(', ')} ]`;

if (!phpMatrixRegex.test(hclContent)) {
throw new Error('phpMatrix variable not found in docker-bake.hcl');
}

hclContent = hclContent.replace(phpMatrixRegex, `$1${newPhpMatrix}$3`);

// Find the frankenphpMatrix variable and replace its value
const frankenphpMatrixRegex = /(variable "frankenphpMatrix" \{\s*default = )(\[[^\]]*\])(\s*\})/s;
const newFrankenphpMatrix = `[ ${frankenphpVersions.map(v => `"${v}"`).join(', ')} ]`;

if (!frankenphpMatrixRegex.test(hclContent)) {
throw new Error('frankenphpMatrix variable not found in docker-bake.hcl');
}

hclContent = hclContent.replace(frankenphpMatrixRegex, `$1${newFrankenphpMatrix}$3`);
const matrix = `[ ${phpVersions.map(v => `"${v}"`).join(', ')} ]`;
hclContent = replaceVariable(hclContent, 'phpMatrix', matrix);
hclContent = replaceVariable(hclContent, 'frankenphpMatrix', matrix);
hclContent = replaceVariable(hclContent, 'phpLibraryRef', `"${phpLibraryRef}"`);
hclContent = replaceVariable(hclContent, 'alpineVersion', `"${alpineVersion}"`);

await fs.writeFile(hclPath, hclContent);
console.log('Successfully updated phpMatrix and frankenphpMatrix in docker-bake.hcl');
console.log('Successfully updated docker-bake.hcl');
console.log('PHP versions:', phpVersions);
console.log('FrankenPHP versions:', frankenphpVersions);
console.log('docker-library/php ref:', phpLibraryRef);
console.log('Alpine version:', alpineVersion);
}

const phpVersions = [];
const frankenphpVersions = [];

for (const version of supportedVersions) {
console.log(`Fetching latest patch version for PHP ${version}...`);
const apiResponse = await fetchPhpTags(version);
const latestVersion = getLatestPatchVersion(apiResponse);

if (latestVersion) {
phpVersions.push(latestVersion);
console.log(`Found latest version for PHP ${version}: ${latestVersion}`);
} else {
throw new Error(`No valid version found for PHP ${version}`);
}

console.log(`Fetching FrankenPHP version for PHP ${version}...`);
const frankenphpApiResponse = await fetchFrankenPhpTags(version);
const frankenphpLatestVersion = getLatestFrankenphpPatchVersion(frankenphpApiResponse);

if (frankenphpLatestVersion) {
frankenphpVersions.push(frankenphpLatestVersion);
console.log(`Found FrankenPHP version for PHP ${version}: ${frankenphpLatestVersion}`);
} else {
console.log(`No FrankenPHP version found for PHP ${version}, skipping...`);
}
}
const phpLibraryRef = await fetchPhpLibraryRef();
const versions = await fetchVersions(phpLibraryRef);

await updatePhpMatrixInHcl(phpVersions, frankenphpVersions);
await updateBakeFile(getPhpVersions(versions), phpLibraryRef, getLatestAlpineVersion(versions));