Skip to content

Commit 8d5b7d5

Browse files
ci-images: generate per-version buildx-bake build + mirror pipeline
The image list (PHP versions and tags) is derived from the docker-compose.yml + .env files in each dockerfiles/ci/<os>/ dir (single source of truth). .gitlab/generate-ci-images.php renders .gitlab/ci-images.yml.tpl, emitting per Linux OS: - <OS> build : one matrix job over PHP version; 'docker buildx bake --no-cache --pull --push' builds both arches (x-bake platforms from compose) on the amd64 runner's managed ci builder and pushes a multi-arch manifest to registry.ddbuild.io - <OS> publish:<v>: manual mirror to Docker Hub via DataDog/public-images, dependency-free (just syncs whatever is in the internal registry) Static preamble + Windows jobs live in .gitlab/ci-images.static.yml (Windows is single-arch). The generator runs in generate-templates and is triggered as a child pipeline via the manual 'ci-images' job; the old .gitlab/ci-images.yml local include is removed.
1 parent b904efc commit 8d5b7d5

5 files changed

Lines changed: 204 additions & 177 deletions

File tree

.gitlab-ci.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ include:
1818
- project: DataDog/apm-reliability/libdatadog-build
1919
ref: 5826819695d93286569e70ed087ae6bf906ce2c3
2020
file: templates/ci_authenticated_job.yml
21-
- local: .gitlab/ci-images.yml
2221

2322
generate-templates:
2423
stage: build
@@ -57,6 +56,7 @@ generate-templates:
5756
- php ./.gitlab/generate-appsec.php | tee .gitlab/appsec-gen.yml
5857
- php ./.gitlab/generate-profiler.php | tee .gitlab/profiler-gen.yml
5958
- php ./.gitlab/generate-shared.php | tee .gitlab/shared-gen.yml
59+
- php ./.gitlab/generate-ci-images.php | tee .gitlab/ci-images-gen.yml
6060
variables:
6161
GIT_SUBMODULE_STRATEGY: none
6262
artifacts:
@@ -90,6 +90,22 @@ appsec-trigger:
9090
PARENT_PIPELINE_ID: $CI_PIPELINE_ID
9191
GIT_SUBMODULE_PATHS: libdatadog appsec/third_party/cpp-base64 appsec/third_party/libddwaf appsec/third_party/libddwaf-rust appsec/third_party/msgpack-c
9292

93+
# Manual maintenance pipeline that (re)builds the CI Docker images. Generated
94+
# from dockerfiles/ci/*/docker-compose.yml + .env so versions live in one place.
95+
# No strategy: depend — the parent must not wait on these manual jobs.
96+
ci-images:
97+
stage: ci-build
98+
rules:
99+
- when: manual
100+
allow_failure: true
101+
needs:
102+
- job: generate-templates
103+
artifacts: true
104+
trigger:
105+
include:
106+
- artifact: .gitlab/ci-images-gen.yml
107+
job: generate-templates
108+
93109
profiler-trigger:
94110
stage: tests
95111
needs: [ "generate-templates" ]
Lines changed: 29 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,48 @@
1-
variables:
2-
CI_REGISTRY_IMAGE:
3-
value: "registry.ddbuild.io/ci/dd-trace-php/dd-trace-ci"
1+
# DO NOT EDIT THE GENERATED LINUX JOBS — they are produced by
2+
# .gitlab/generate-ci-images.php from the docker-compose.yml + .env files.
3+
# This file holds the STATIC preamble (stages, templates) and the Windows jobs,
4+
# which have no multi-arch manifest and stay hand-maintained.
45

5-
CentOS:
6-
stage: ci-build
7-
rules:
8-
- when: manual
9-
allow_failure: true
10-
needs: []
11-
tags: ["arch:amd64"]
12-
timeout: 4h
13-
image: 486234852809.dkr.ecr.us-east-1.amazonaws.com/docker:29.4.0-noble
14-
variables:
15-
DDCI_CONFIGURE_OTEL_EXPORTER: "true"
16-
parallel:
17-
matrix:
18-
- PHP_VERSION:
19-
- base
20-
- php-8.5
21-
- php-8.4
22-
- php-8.3
23-
- php-8.2
24-
- php-8.1
25-
- php-8.0
26-
- php-7.4
27-
- php-7.3
28-
- php-7.2
29-
- php-7.1
30-
- php-7.0
31-
script:
32-
- cd dockerfiles/ci/centos/7
33-
- docker buildx bake --no-cache --pull --push $PHP_VERSION
6+
stages:
7+
- ci-build
8+
- ci-publish
349

35-
Alpine:
10+
variables:
11+
CI_REGISTRY_IMAGE: "registry.ddbuild.io/ci/dd-trace-php/dd-trace-ci"
12+
13+
.linux_image_build:
3614
stage: ci-build
3715
rules:
3816
- when: manual
3917
allow_failure: true
4018
needs: []
41-
tags: ["arch:amd64"]
4219
timeout: 4h
4320
image: 486234852809.dkr.ecr.us-east-1.amazonaws.com/docker:29.4.0-noble
4421
variables:
4522
DDCI_CONFIGURE_OTEL_EXPORTER: "true"
46-
parallel:
47-
matrix:
48-
- PHP_VERSION:
49-
- base-alpine
50-
- 8.5-alpine
51-
- 8.4-alpine
52-
- 8.3-alpine
53-
- 8.2-alpine
54-
- 8.1-alpine
55-
- 8.0-alpine
56-
- 7.4-alpine
57-
- 7.3-alpine
58-
- 7.2-alpine
59-
- 7.1-alpine
60-
- 7.0-alpine
61-
script:
62-
- cd dockerfiles/ci/alpine_compile_extension
63-
- docker buildx bake --no-cache --pull --push $PHP_VERSION
23+
KUBERNETES_CPU_REQUEST: "8"
24+
KUBERNETES_CPU_LIMIT: "8"
25+
KUBERNETES_MEMORY_REQUEST: "16Gi"
26+
KUBERNETES_MEMORY_LIMIT: "16Gi"
27+
MAKE_JOBS: "$KUBERNETES_CPU_LIMIT"
6428

65-
Bookworm:
66-
stage: ci-build
29+
.linux_publish:
30+
stage: ci-publish
6731
rules:
6832
- when: manual
6933
allow_failure: true
34+
# No deps: a publish just mirrors whatever already exists in
35+
# registry.ddbuild.io to Docker Hub, so it can run without (re)building.
7036
needs: []
71-
tags: ["arch:amd64"]
72-
timeout: 4h
73-
image: 486234852809.dkr.ecr.us-east-1.amazonaws.com/docker:29.4.0-noble
37+
trigger:
38+
project: DataDog/public-images
39+
branch: main
40+
# $TAG is supplied per matrix entry by the generated publish jobs.
7441
variables:
75-
DDCI_CONFIGURE_OTEL_EXPORTER: "true"
76-
MAKE_JOBS: "2"
77-
parallel:
78-
matrix:
79-
- PHP_VERSION:
80-
- base
81-
- php-8.5
82-
- php-8.4
83-
- php-8.3
84-
- php-8.2
85-
- php-8.1
86-
- php-8.0
87-
- php-8.0-shared-ext
88-
- php-7.4
89-
- php-7.4-shared-ext
90-
- php-7.3
91-
- php-7.2
92-
- php-7.1
93-
- php-7.0
94-
script:
95-
- cd dockerfiles/ci/bookworm
96-
- docker buildx bake --no-cache --pull --push $PHP_VERSION
42+
IMG_REGISTRIES: "dockerhub"
43+
IMG_SIGNING: false
44+
IMG_SOURCES: "registry.ddbuild.io/ci/dd-trace-php/dd-trace-ci:${TAG}"
45+
IMG_DESTINATIONS: "dd-trace-ci:${TAG}"
9746

9847
.windows_image_build:
9948
stage: ci-build
@@ -196,98 +145,6 @@ Bookworm:
196145
- "php-7.3"
197146
- "php-7.2"
198147

199-
Publish CentOS:
200-
stage: ci-publish
201-
rules:
202-
- when: manual
203-
allow_failure: true
204-
needs:
205-
- job: CentOS
206-
trigger:
207-
project: DataDog/public-images
208-
branch: main
209-
parallel:
210-
matrix:
211-
- TAG_NAME:
212-
- "centos-7"
213-
- "php-8.5_centos-7"
214-
- "php-8.4_centos-7"
215-
- "php-8.3_centos-7"
216-
- "php-8.2_centos-7"
217-
- "php-8.1_centos-7"
218-
- "php-8.0_centos-7"
219-
- "php-7.4_centos-7"
220-
- "php-7.3_centos-7"
221-
- "php-7.2_centos-7"
222-
- "php-7.1_centos-7"
223-
- "php-7.0_centos-7"
224-
variables:
225-
IMG_SOURCES: "registry.ddbuild.io/ci/dd-trace-php/dd-trace-ci:${TAG_NAME}"
226-
IMG_DESTINATIONS: "dd-trace-ci:${TAG_NAME}"
227-
IMG_REGISTRIES: "dockerhub"
228-
229-
Publish Bookworm:
230-
stage: ci-publish
231-
rules:
232-
- when: manual
233-
allow_failure: true
234-
needs:
235-
- job: Bookworm
236-
trigger:
237-
project: DataDog/public-images
238-
branch: main
239-
parallel:
240-
matrix:
241-
- TAG_NAME:
242-
- "bookworm-9"
243-
- "php-8.5_bookworm-9"
244-
- "php-8.4_bookworm-9"
245-
- "php-8.3_bookworm-9"
246-
- "php-8.2_bookworm-9"
247-
- "php-8.1_bookworm-9"
248-
- "php-8.0_bookworm-9"
249-
- "php-8.0-shared-ext-9"
250-
- "php-7.4_bookworm-9"
251-
- "php-7.4-shared-ext-9"
252-
- "php-7.3_bookworm-9"
253-
- "php-7.2_bookworm-9"
254-
- "php-7.1_bookworm-9"
255-
- "php-7.0_bookworm-9"
256-
variables:
257-
IMG_SOURCES: "registry.ddbuild.io/ci/dd-trace-php/dd-trace-ci:${TAG_NAME}"
258-
IMG_DESTINATIONS: "dd-trace-ci:${TAG_NAME}"
259-
IMG_REGISTRIES: "dockerhub"
260-
261-
Publish Alpine:
262-
stage: ci-publish
263-
rules:
264-
- when: manual
265-
allow_failure: true
266-
needs:
267-
- job: Alpine
268-
trigger:
269-
project: DataDog/public-images
270-
branch: main
271-
parallel:
272-
matrix:
273-
- TAG_NAME:
274-
- "php-compile-extension-alpine"
275-
- "php-compile-extension-alpine-8.5"
276-
- "php-compile-extension-alpine-8.4"
277-
- "php-compile-extension-alpine-8.3"
278-
- "php-compile-extension-alpine-8.2"
279-
- "php-compile-extension-alpine-8.1"
280-
- "php-compile-extension-alpine-8.0"
281-
- "php-compile-extension-alpine-7.4"
282-
- "php-compile-extension-alpine-7.3"
283-
- "php-compile-extension-alpine-7.2"
284-
- "php-compile-extension-alpine-7.1"
285-
- "php-compile-extension-alpine-7.0"
286-
variables:
287-
IMG_SOURCES: "registry.ddbuild.io/ci/dd-trace-php/dd-trace-ci:${TAG_NAME}"
288-
IMG_DESTINATIONS: "dd-trace-ci:${TAG_NAME}"
289-
IMG_REGISTRIES: "dockerhub"
290-
291148
Publish Windows:
292149
stage: ci-publish
293150
rules:
@@ -321,3 +178,4 @@ Publish Windows:
321178
IMG_SOURCES: "registry.ddbuild.io/ci/dd-trace-php/dd-trace-ci:${TAG_NAME}"
322179
IMG_DESTINATIONS: "dd-trace-ci:${TAG_NAME}"
323180
IMG_REGISTRIES: "dockerhub"
181+
IMG_SIGNING: false

.gitlab/ci-images.yml.tpl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php echo file_get_contents(__DIR__ . '/ci-images.static.yml'); ?>
2+
<?php foreach ($osList as ['name' => $os, 'dir' => $dir, 'services' => $services]): ?>
3+
<?php /*
4+
One build job per PHP version. buildx bake reads the per-service x-bake
5+
platforms from docker-compose.yml and builds BOTH arches on the amd64 runner's
6+
managed "ci" builder, pushing a multi-arch manifest straight to the tag in the
7+
compose `image:` field. No per-arch split, no manifest fuse job.
8+
*/ ?>
9+
10+
<?= $os ?> build:
11+
extends: .linux_image_build
12+
tags: ["arch:amd64"]
13+
parallel:
14+
matrix:
15+
- PHP_VERSION:
16+
<?php foreach (array_keys($services) as $svc): ?>
17+
- <?= $svc, "\n" ?>
18+
<?php endforeach; ?>
19+
script:
20+
- cd <?= $dir, "\n" ?>
21+
- docker buildx bake --no-cache --pull --push "${PHP_VERSION}"
22+
<?php /*
23+
Mirror to Docker Hub, one matrix job per OS (grouped in the UI like the
24+
builds). Independent (needs: [] via .linux_publish): just syncs whatever is
25+
already in registry.ddbuild.io, so it can run without rebuilding. IMG_SOURCES
26+
/ IMG_DESTINATIONS are built from $TAG in .linux_publish.
27+
*/ ?>
28+
29+
<?= $os ?> publish:
30+
extends: .linux_publish
31+
parallel:
32+
matrix:
33+
- TAG:
34+
<?php foreach ($services as $tag): ?>
35+
- "<?= $tag ?>"
36+
<?php endforeach; ?>
37+
<?php endforeach; ?>

.gitlab/generate-ci-images.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
/**
4+
* Generates the CI image build/manifest/publish pipeline from ci-images.yml.tpl.
5+
*
6+
* Source of truth (NO duplication):
7+
* - dockerfiles/ci/<os>/docker-compose.yml : service name -> image:TAG
8+
* - dockerfiles/ci/bookworm/.env : $BOOKWORM_NEXT_VERSION etc.
9+
*
10+
* The compose service name is the `docker buildx bake` target and the build
11+
* matrix value; the `image:` tag (with env vars resolved) is the published tag.
12+
* Per Linux image the template emits one build matrix job over PHP versions
13+
* (bake builds the multi-arch image and pushes it) plus a manual mirror/publish
14+
* job per service. The static preamble (templates) and Windows jobs live in
15+
* ci-images.static.yml (Windows is single-arch).
16+
*/
17+
18+
$root = dirname(__DIR__);
19+
20+
// Resolve $VAR / ${VAR} from a key=value .env file.
21+
function parse_env(string $path): array
22+
{
23+
$env = [];
24+
foreach (@file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) {
25+
if (preg_match('/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/', $line, $m)) {
26+
$env[$m[1]] = $m[2];
27+
}
28+
}
29+
return $env;
30+
}
31+
32+
function substitute(string $s, array $env): string
33+
{
34+
return preg_replace_callback('/\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?/', function ($m) use ($env) {
35+
return $env[$m[1]] ?? $m[0];
36+
}, $s);
37+
}
38+
39+
// Parse a docker-compose.yml into [service => tag], preserving file order.
40+
function parse_compose(string $path, array $env): array
41+
{
42+
$services = [];
43+
$cur = null;
44+
$inServices = false;
45+
foreach (file($path, FILE_IGNORE_NEW_LINES) as $line) {
46+
if (preg_match('/^services:\s*$/', $line)) {
47+
$inServices = true;
48+
continue;
49+
}
50+
if (!$inServices) {
51+
continue;
52+
}
53+
if (preg_match('/^\S/', $line)) { // back to a top-level key
54+
$inServices = false;
55+
continue;
56+
}
57+
if (preg_match('/^ ([A-Za-z0-9][A-Za-z0-9._-]*):\s*$/', $line, $m)) {
58+
$cur = $m[1];
59+
$services[$cur] = null;
60+
continue;
61+
}
62+
// image: ${CI_REGISTRY_IMAGE:-...}:TAG (first image: wins per service)
63+
if ($cur !== null && $services[$cur] === null
64+
&& preg_match('/^ image:\s*[\'"]?\$\{[^}]+\}:([^\s\'"]+)/', $line, $m)) {
65+
$services[$cur] = substitute($m[1], $env);
66+
}
67+
}
68+
return array_filter($services, fn($v) => $v !== null);
69+
}
70+
71+
$dirs = [
72+
"Bookworm" => "dockerfiles/ci/bookworm",
73+
"CentOS" => "dockerfiles/ci/centos/7",
74+
"Alpine" => "dockerfiles/ci/alpine_compile_extension",
75+
];
76+
77+
$osList = [];
78+
foreach ($dirs as $os => $dir) {
79+
$services = parse_compose("$root/$dir/docker-compose.yml", parse_env("$root/$dir/.env"));
80+
if (!$services) {
81+
fwrite(STDERR, "WARNING: no services parsed for $os ($dir)\n");
82+
continue;
83+
}
84+
$osList[] = ["name" => $os, "dir" => $dir, "services" => $services];
85+
}
86+
87+
require __DIR__ . "/ci-images.yml.tpl";

0 commit comments

Comments
 (0)