From 86de9ed1b6543f283c5006a87c7db3a7653e2aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Mon, 20 Apr 2026 19:45:00 +0200 Subject: [PATCH 1/5] Add devcontainer workflow for local builds and integration tests Introduce a .devcontainer image definition (moved from .gitlab/) and a thin Makefile + runner script so developers can reproduce the GitLab build + integration-test pipeline locally. The image bakes in the LLVM+musl sysroot, Rust+cbindgen for RUM, uv for Python test setup, and a prebuilt httpd 2.4. Notable details in the runner script: - UV_PROJECT_ENVIRONMENT is pointed outside the bind-mounted repo so a host-side `uv sync` on darwin can't leave mac wheels sitting in .venv/ that the container's Linux Python then tries to import. - Pytest options are passed with `--flag=VALUE` (single token). The space-separated form gets captured by pytest's early arg parser as a positional test path before conftest registers the custom option, which makes pytest use the file's parent as rootdir and silently skip conftest. The GitLab build-ci-image job's hash input and Dockerfile path are retargeted to the new .devcontainer/ location. --- {.gitlab => .devcontainer}/Dockerfile | 0 .devcontainer/devcontainer.json | 10 +++ .devcontainer/devcontainer.mk | 104 +++++++++++++++++++++++++ .devcontainer/run-integration-tests.sh | 44 +++++++++++ .gitignore | 3 + .gitlab-ci.yml | 4 +- Makefile | 18 +---- 7 files changed, 164 insertions(+), 19 deletions(-) rename {.gitlab => .devcontainer}/Dockerfile (100%) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/devcontainer.mk create mode 100755 .devcontainer/run-integration-tests.sh diff --git a/.gitlab/Dockerfile b/.devcontainer/Dockerfile similarity index 100% rename from .gitlab/Dockerfile rename to .devcontainer/Dockerfile diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..5c34a2a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,10 @@ +{ + "name": "httpd-datadog", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + "ARCH": "${localEnv:ARCH:x86_64}" + } + } +} diff --git a/.devcontainer/devcontainer.mk b/.devcontainer/devcontainer.mk new file mode 100644 index 0000000..a167eb1 --- /dev/null +++ b/.devcontainer/devcontainer.mk @@ -0,0 +1,104 @@ +DEV_CONTAINER_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +DEV_CONTAINER_REPO_ROOT := $(abspath $(DEV_CONTAINER_DIR)/..) + +# When running inside the container, tools are available directly. +# Check for /.dockerenv (Docker) or KUBERNETES_SERVICE_HOST (Kubernetes CI). +ifneq ($(or $(wildcard /.dockerenv),$(KUBERNETES_SERVICE_HOST)),) + +DEVCONTAINER_RUN := + +.PHONY: dev-image +dev-image: + @true + +else + +DEV_CONTAINER_REGISTRY ?= registry.ddbuild.io +DEV_CONTAINER_IMAGE_NAME ?= $(DEV_CONTAINER_REGISTRY)/ci/httpd-datadog + +# Match the hash computation in .gitlab-ci.yml (build-ci-image job): sha256 over +# .devcontainer/Dockerfile, deps/nginx-datadog/build_env/, and scripts/setup-httpd.py. +DEV_CONTAINER_HASH := $(shell cd $(DEV_CONTAINER_REPO_ROOT) && \ + find .devcontainer/Dockerfile deps/nginx-datadog/build_env/ scripts/setup-httpd.py -type f | sort | \ + while IFS= read -r f; do echo "--- $$f ---"; cat "$$f"; done | \ + (command -v sha256sum >/dev/null 2>&1 && sha256sum || shasum -a 256) | cut -d' ' -f1) + +DEV_CONTAINER_TAG := $(DEV_CONTAINER_HASH) +DEV_CONTAINER_IMAGE := $(DEV_CONTAINER_IMAGE_NAME):$(DEV_CONTAINER_TAG) + +# Map host arch (uname -m) to the Dockerfile's ARCH build-arg. +DEV_CONTAINER_HOST_ARCH := $(shell uname -m) +ifeq ($(DEV_CONTAINER_HOST_ARCH),arm64) +DEV_CONTAINER_ARCH := aarch64 +DEV_CONTAINER_PLATFORM := linux/arm64 +else ifeq ($(DEV_CONTAINER_HOST_ARCH),aarch64) +DEV_CONTAINER_ARCH := aarch64 +DEV_CONTAINER_PLATFORM := linux/arm64 +else +DEV_CONTAINER_ARCH := x86_64 +DEV_CONTAINER_PLATFORM := linux/amd64 +endif + +# For git worktrees, .git is a file pointing to a gitdir outside the repo root; +# bind-mount the git common-dir so `git` works inside the container. +DEV_CONTAINER_GIT_COMMON_DIR := $(shell cd $(DEV_CONTAINER_REPO_ROOT) && git rev-parse --path-format=absolute --git-common-dir 2>/dev/null) +ifneq ($(DEV_CONTAINER_GIT_COMMON_DIR),) +ifneq ($(DEV_CONTAINER_GIT_COMMON_DIR),$(DEV_CONTAINER_REPO_ROOT)/.git) +DEV_CONTAINER_GIT_MOUNT := -v $(DEV_CONTAINER_GIT_COMMON_DIR):$(DEV_CONTAINER_GIT_COMMON_DIR) +endif +endif + +DEVCONTAINER_RUN = docker run --rm \ + -v $(DEV_CONTAINER_REPO_ROOT):$(DEV_CONTAINER_REPO_ROOT) \ + $(DEV_CONTAINER_GIT_MOUNT) \ + -w $(DEV_CONTAINER_REPO_ROOT) \ + $(DEV_CONTAINER_IMAGE) + +# Ensure the dev container image is available locally. Prefer pulling from the +# registry (CI builds every tagged hash); fall back to a local build. +# Stale images with different tags are removed first. +.PHONY: dev-image +dev-image: + @if ! docker image inspect $(DEV_CONTAINER_IMAGE) >/dev/null 2>&1; then \ + stale=$$(docker images --format '{{.Repository}}:{{.Tag}}' $(DEV_CONTAINER_IMAGE_NAME) 2>/dev/null | grep -v ':$(DEV_CONTAINER_TAG)$$' || true); \ + if [ -n "$$stale" ]; then \ + echo "Removing stale dev container image(s): $$stale"; \ + docker rmi $$stale || true; \ + fi; \ + if docker pull $(DEV_CONTAINER_IMAGE) >/dev/null 2>&1; then \ + echo "Pulled $(DEV_CONTAINER_IMAGE)"; \ + else \ + echo "Building dev container image $(DEV_CONTAINER_IMAGE)..."; \ + docker buildx build \ + --platform $(DEV_CONTAINER_PLATFORM) \ + --build-arg ARCH=$(DEV_CONTAINER_ARCH) \ + --load \ + -t $(DEV_CONTAINER_IMAGE) \ + -f $(DEV_CONTAINER_REPO_ROOT)/.devcontainer/Dockerfile \ + $(DEV_CONTAINER_REPO_ROOT); \ + fi; \ + fi + +.PHONY: dev-shell +dev-shell: dev-image + docker run --rm -it \ + -v $(DEV_CONTAINER_REPO_ROOT):$(DEV_CONTAINER_REPO_ROOT) \ + -w $(DEV_CONTAINER_REPO_ROOT) \ + $(DEV_CONTAINER_IMAGE) \ + /bin/sh + +.PHONY: test-integration +test-integration: dev-image + $(DEVCONTAINER_RUN) .devcontainer/run-integration-tests.sh + +.PHONY: dev-image-clean +dev-image-clean: + @stale=$$(docker images --format '{{.Repository}}:{{.Tag}}' $(DEV_CONTAINER_IMAGE_NAME) 2>/dev/null | grep -v ':$(DEV_CONTAINER_TAG)$$' || true); \ + if [ -n "$$stale" ]; then \ + echo "Removing stale dev container image(s): $$stale"; \ + docker rmi $$stale; \ + else \ + echo "No stale dev container images found."; \ + fi + +endif diff --git a/.devcontainer/run-integration-tests.sh b/.devcontainer/run-integration-tests.sh new file mode 100755 index 0000000..1c1c10b --- /dev/null +++ b/.devcontainer/run-integration-tests.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# Build mod_datadog (with RUM) and run the full integration-test suite. +# Assumes it runs inside the devcontainer image (Alpine + musl sysroot + httpd +# at /httpd/httpd-build/ + uv). +set -eux + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +ARCH="$(uname -m)" + +# Use a container-specific build tree to avoid colliding with host builds +# (host cmake would use Unix Makefiles; the ci-release preset uses Ninja). +BUILD_DIR="$REPO_ROOT/build-container" +DIST_DIR="$REPO_ROOT/dist-container" + +# Repo root is bind-mounted from the host; cmake complains otherwise. +git config --global --add safe.directory "$REPO_ROOT" + +cmake --preset=ci-release \ + -DHTTPD_DATADOG_ENABLE_RUM=ON \ + -DCMAKE_TOOLCHAIN_FILE="/sysroot/${ARCH}-none-linux-musl/Toolchain.cmake" \ + -B "$BUILD_DIR" . +cmake --build "$BUILD_DIR" -j +cmake --install "$BUILD_DIR" --prefix "$DIST_DIR" + +cd test/integration-test + +# Keep the venv outside the bind-mounted repo so a host-side `uv sync` +# (darwin/arm64 wheels) can't shadow the container's Linux one. +export UV_PROJECT_ENVIRONMENT=/root/.venv-httpd-datadog-tests + +uv sync + +# Pytest's early arg parser runs BEFORE conftest registers `--bin-path`. +# If we pass `--bin-path VALUE` (space-separated), that parser treats the +# value as a positional test path and uses its parent as rootdir, which +# then prevents conftest from loading. The `=` syntax keeps the option and +# its value in a single token and avoids that race. +uv run pytest \ + --bin-path=/httpd/httpd-build/bin/apachectl \ + --module-path="$DIST_DIR/lib/mod_datadog.so" \ + --log-dir="$REPO_ROOT/logs" \ + -v "$@" diff --git a/.gitignore b/.gitignore index 75feb22..92a9c5e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,10 +23,13 @@ compile_commands.json __pycache__/ +build-container/ build-rum/ build/ +dist-container/ httpd-*/ httpd/ +/logs/ venv/ test/integration-test/.coverage diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4a328df..b7f8077 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -58,7 +58,7 @@ build-ci-image: TOOLCHAIN_ARCH: aarch64 script: - | - FILES=$(find .gitlab/Dockerfile deps/nginx-datadog/build_env/ scripts/setup-httpd.py -type f | sort) + FILES=$(find .devcontainer/Dockerfile deps/nginx-datadog/build_env/ scripts/setup-httpd.py -type f | sort) HASH=$(for f in $FILES; do echo "--- $f ---"; cat "$f"; done | sha256sum | cut -d ' ' -f 1) - echo "CI_IMAGE_HASH=$HASH" >> ci-image.env - IMAGE_TAG="$CI_DOCKER_IMAGE_BASE/$ARCH:$HASH" @@ -71,7 +71,7 @@ build-ci-image: --platform linux/$ARCH \ --build-arg ARCH=$TOOLCHAIN_ARCH \ --push \ - --file .gitlab/Dockerfile \ + --file .devcontainer/Dockerfile \ . echo "Image $IMAGE_TAG built for $ARCH." fi diff --git a/Makefile b/Makefile index 4360495..3b5374c 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1 @@ -# The CI image used for some GitHub jobs is built by the GitLab build-ci-image job. -# The hash is computed by this GitLab job from the files used to build the image. -# -# Whenever this image needs to be updated, one should: -# - Get the hash from a run of the GitLab build-ci-image job. -# - Copy this hash here, and in the GitHub workflow files. -# - Run: make replicate-ci-image-for-github. - -CI_DOCKER_IMAGE_HASH ?= 28219c0ef3e00f1e3d5afcab61a73a5e9bd2a9b957d7545556711cce2a6262cd -CI_IMAGE_FROM_GITLAB ?= registry.ddbuild.io/ci/httpd-datadog/amd64:$(CI_DOCKER_IMAGE_HASH) -CI_IMAGE_IN_PUBLIC_REPO_FOR_GITHUB ?= datadog/docker-library:httpd-datadog-ci-$(CI_DOCKER_IMAGE_HASH) - -.PHONY: replicate-ci-image-for-github -replicate-ci-image-for-github: - docker pull $(CI_IMAGE_FROM_GITLAB) - docker tag $(CI_IMAGE_FROM_GITLAB) $(CI_IMAGE_IN_PUBLIC_REPO_FOR_GITHUB) - docker push $(CI_IMAGE_IN_PUBLIC_REPO_FOR_GITHUB) +include .devcontainer/devcontainer.mk From 2b9dc7c7674d353406298e86ed6437c0f7927cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Mon, 20 Apr 2026 19:50:00 +0200 Subject: [PATCH 2/5] Consolidate devcontainer image build into one job Merge the three-job ci-image flow (build-ci-image + nydusify-ci-image + create-ci-manifest) into two: a single `devcontainer-image` matrix job that builds + pushes + nydusifies each arch inline, followed by `create-ci-manifest` which still stitches the per-arch nydus images into a multi-arch index. Drops the separate nydusify job entirely. Key changes: - Single job uses the NYDUS_DD_IMAGE bundle (buildx + crane + nydus-convert all in one) instead of the plain docker image, so the build-push-nydusify sequence happens in one runner. - Per-arch nydus conversion lands directly on $CI_DOCKER_IMAGE_BASE/$ARCH:$HASH before the manifest is assembled, so the final multi-arch manifest references nydus layers without a separate manifest-level conversion step. - Export DEVCONTAINER_IMAGE=$base:$HASH (full ref) via dotenv so downstream jobs can pin the image by a single variable and don't have to re-construct the ref from a base+hash pair. Also prepares run-integration-tests.sh for CI reuse: if MODULE_PATH is set in the environment, the cmake build is skipped and pytest consumes the pre-built module at that path. Default behavior (no envvar) is unchanged, so local dev still gets a full build-and-test. --- .devcontainer/run-integration-tests.sh | 22 ++++--- .gitlab-ci.yml | 85 ++++++++++++-------------- 2 files changed, 54 insertions(+), 53 deletions(-) diff --git a/.devcontainer/run-integration-tests.sh b/.devcontainer/run-integration-tests.sh index 1c1c10b..84579ac 100755 --- a/.devcontainer/run-integration-tests.sh +++ b/.devcontainer/run-integration-tests.sh @@ -2,6 +2,11 @@ # Build mod_datadog (with RUM) and run the full integration-test suite. # Assumes it runs inside the devcontainer image (Alpine + musl sysroot + httpd # at /httpd/httpd-build/ + uv). +# +# If MODULE_PATH is set in the environment, the cmake build is skipped and +# pytest is pointed at the pre-built module at that path. CI uses this to +# reuse the artifact produced by the build:$ARCH job instead of rebuilding +# from source inside the test job. set -eux REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" @@ -17,12 +22,15 @@ DIST_DIR="$REPO_ROOT/dist-container" # Repo root is bind-mounted from the host; cmake complains otherwise. git config --global --add safe.directory "$REPO_ROOT" -cmake --preset=ci-release \ - -DHTTPD_DATADOG_ENABLE_RUM=ON \ - -DCMAKE_TOOLCHAIN_FILE="/sysroot/${ARCH}-none-linux-musl/Toolchain.cmake" \ - -B "$BUILD_DIR" . -cmake --build "$BUILD_DIR" -j -cmake --install "$BUILD_DIR" --prefix "$DIST_DIR" +if [ -z "${MODULE_PATH:-}" ]; then + cmake --preset=ci-release \ + -DHTTPD_DATADOG_ENABLE_RUM=ON \ + -DCMAKE_TOOLCHAIN_FILE="/sysroot/${ARCH}-none-linux-musl/Toolchain.cmake" \ + -B "$BUILD_DIR" . + cmake --build "$BUILD_DIR" -j + cmake --install "$BUILD_DIR" --prefix "$DIST_DIR" + MODULE_PATH="$DIST_DIR/lib/mod_datadog.so" +fi cd test/integration-test @@ -39,6 +47,6 @@ uv sync # its value in a single token and avoids that race. uv run pytest \ --bin-path=/httpd/httpd-build/bin/apachectl \ - --module-path="$DIST_DIR/lib/mod_datadog.so" \ + --module-path="$MODULE_PATH" \ --log-dir="$REPO_ROOT/logs" \ -v "$@" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b7f8077..a6f0d70 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,6 @@ variables: CI_DOCKER_IMAGE_BASE: registry.ddbuild.io/ci/httpd-datadog + NYDUS_DD_IMAGE: registry.ddbuild.io/images/nydus:v98111448-6d7c7d0-v2.3.8-dd GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_DEPTH: 1 GIT_CONFIG_COUNT: 1 @@ -20,7 +21,7 @@ stages: # Template jobs (hidden, reusable) .build-template: stage: build - image: $CI_DOCKER_IMAGE_BASE:$CI_IMAGE_HASH + image: $DEVCONTAINER_IMAGE script: - git config --global --add safe.directory $PWD - > @@ -42,14 +43,20 @@ stages: .test-rum-template: stage: test - image: $CI_DOCKER_IMAGE_BASE:$CI_IMAGE_HASH + image: $DEVCONTAINER_IMAGE script: - cd $CI_PROJECT_DIR/test/integration-test && uv sync && uv run pytest scenarios --module-path $CI_PROJECT_DIR/artifacts/$ARCH/mod_datadog.so --bin-path /httpd/httpd-build/bin/apachectl --log-dir $CI_PROJECT_DIR/logs -m requires_rum -v -build-ci-image: +# Build (or reuse) the devcontainer image per arch: hash the inputs, skip if +# the registry already has the tag, otherwise buildx build + push + nydusify +# in-place. The nydusified per-arch images are then stitched into a +# multi-arch manifest by `create-ci-manifest` below. +devcontainer-image: stage: ci-image tags: ["arch:$ARCH"] - image: registry.ddbuild.io/images/docker:27.3.1 + image: $NYDUS_DD_IMAGE + variables: + DDSIGN_SKIP_SIGNING: "true" parallel: matrix: - ARCH: amd64 @@ -60,14 +67,15 @@ build-ci-image: - | FILES=$(find .devcontainer/Dockerfile deps/nginx-datadog/build_env/ scripts/setup-httpd.py -type f | sort) HASH=$(for f in $FILES; do echo "--- $f ---"; cat "$f"; done | sha256sum | cut -d ' ' -f 1) - - echo "CI_IMAGE_HASH=$HASH" >> ci-image.env - IMAGE_TAG="$CI_DOCKER_IMAGE_BASE/$ARCH:$HASH" + - echo "CI_IMAGE_HASH=$HASH" >> ci-image.env + - echo "DEVCONTAINER_IMAGE=$CI_DOCKER_IMAGE_BASE:$HASH" >> ci-image.env - | - if docker buildx imagetools inspect "$IMAGE_TAG" > /dev/null 2>&1; then + if crane manifest "$IMAGE_TAG" >/dev/null 2>&1; then echo "Image $IMAGE_TAG already exists. Skipping build." else docker buildx build \ - --tag $IMAGE_TAG \ + --tag "$IMAGE_TAG" \ --platform linux/$ARCH \ --build-arg ARCH=$TOOLCHAIN_ARCH \ --push \ @@ -75,20 +83,36 @@ build-ci-image: . echo "Image $IMAGE_TAG built for $ARCH." fi + - | + MANIFEST=$(crane manifest "$IMAGE_TAG" 2>/dev/null) || { + echo "WARNING: failed to fetch manifest for $IMAGE_TAG — skipping nydus conversion" + exit 0 + } + if echo "$MANIFEST" | jq -e '[.layers[].mediaType] | any(contains("nydus"))' >/dev/null 2>&1; then + echo "$IMAGE_TAG already has nydus layers. Skipping conversion." + else + echo "Converting $IMAGE_TAG to nydus in place..." + nydus-convert "$IMAGE_TAG" "$IMAGE_TAG" || \ + echo "WARNING: nydus conversion failed — continuing without nydus layers" + fi artifacts: reports: dotenv: ci-image.env +# Combine the per-arch (nydusified) images into a single multi-arch manifest +# at $CI_DOCKER_IMAGE_BASE:$CI_IMAGE_HASH so downstream jobs can pin by tag +# alone and let the runtime pick the right arch. create-ci-manifest: stage: ci-image needs: - - build-ci-image + - devcontainer-image tags: ["arch:amd64"] - image: registry.ddbuild.io/images/docker:27.3.1 + image: $NYDUS_DD_IMAGE script: - - MANIFEST_TAG="$CI_DOCKER_IMAGE_BASE:$CI_IMAGE_HASH" + - MANIFEST_TAG="$DEVCONTAINER_IMAGE" - | - if docker buildx imagetools inspect "$MANIFEST_TAG" > /dev/null 2>&1; then + if crane manifest "$MANIFEST_TAG" >/dev/null 2>&1 && \ + docker buildx imagetools inspect "$MANIFEST_TAG" --format '{{json .Manifest}}' 2>/dev/null | jq -e '.manifests | length >= 2' >/dev/null 2>&1; then echo "Multi-arch manifest $MANIFEST_TAG already exists. Skipping." else docker buildx imagetools create \ @@ -97,41 +121,10 @@ create-ci-manifest: "$CI_DOCKER_IMAGE_BASE/arm64:$CI_IMAGE_HASH" fi -nydusify-ci-image: - stage: ci-image - needs: - - build-ci-image - - create-ci-manifest - allow_failure: true - tags: ["arch:amd64"] - image: registry.ddbuild.io/images/nydus:v98111448-6d7c7d0-v2.3.8-dd - variables: - DDSIGN_SKIP_SIGNING: "true" - script: - - | - IMAGE="$CI_DOCKER_IMAGE_BASE:$CI_IMAGE_HASH" - REPO="${IMAGE%:*}" - - # Resolve a platform manifest digest from the multi-arch index - DIGEST=$(crane manifest "$IMAGE" 2>/dev/null | jq -r '(.manifests // [])[0].digest // empty' 2>/dev/null) - if [ -n "$DIGEST" ]; then - MANIFEST=$(crane manifest "${REPO}@${DIGEST}" 2>/dev/null) - else - MANIFEST=$(crane manifest "$IMAGE" 2>/dev/null) - fi - - # Check if the platform manifest already contains nydus layers - if echo "$MANIFEST" | jq -e '[.layers[].mediaType] | any(contains("nydus"))' > /dev/null 2>&1; then - echo "$IMAGE is already nydusified. Skipping." - else - echo "Converting $IMAGE with nydusify..." - nydus-convert "$IMAGE" "$IMAGE" - fi - build:amd64: extends: .build-template needs: - - build-ci-image + - devcontainer-image - create-ci-manifest variables: ARCH: amd64 @@ -141,7 +134,7 @@ build:amd64: build:arm64: extends: .build-template needs: - - build-ci-image + - devcontainer-image - create-ci-manifest variables: ARCH: arm64 @@ -151,7 +144,7 @@ build:arm64: test-rum:amd64: extends: .test-rum-template needs: - - build-ci-image + - devcontainer-image - job: "build:amd64" variables: ARCH: amd64 @@ -160,7 +153,7 @@ test-rum:amd64: test-rum:arm64: extends: .test-rum-template needs: - - build-ci-image + - devcontainer-image - job: "build:arm64" variables: ARCH: arm64 From d30fcd7472b6be717c8b32ad6ddbb1a157dab2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Mon, 20 Apr 2026 19:55:00 +0200 Subject: [PATCH 3/5] Extract devcontainer CI jobs to .gitlab/devcontainer.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the `devcontainer-image` build-and-nydusify job plus the `create-ci-manifest` multi-arch assembly into their own include file `.gitlab/devcontainer.yml`, and reference it from the main `.gitlab-ci.yml` via `include: - local:`. The NYDUS_DD_IMAGE variable is declared inside the include since it's only used by these jobs. Groups the devcontainer image pipeline into a single-responsibility file separate from the build/test/upload orchestration. Matches the pattern established by inject-browser-sdk's `.gitlab/devcontainer.yml` and keeps the main `.gitlab-ci.yml` readable (drops 72 lines). No behavioral change — job names, dependencies, env exports, and runtime semantics are identical; only file layout moves. --- .gitlab-ci.yml | 78 ++----------------------------------- .gitlab/devcontainer.yml | 83 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 75 deletions(-) create mode 100644 .gitlab/devcontainer.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a6f0d70..466a07b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,8 @@ +include: + - local: .gitlab/devcontainer.yml + variables: CI_DOCKER_IMAGE_BASE: registry.ddbuild.io/ci/httpd-datadog - NYDUS_DD_IMAGE: registry.ddbuild.io/images/nydus:v98111448-6d7c7d0-v2.3.8-dd GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_DEPTH: 1 GIT_CONFIG_COUNT: 1 @@ -47,80 +49,6 @@ stages: script: - cd $CI_PROJECT_DIR/test/integration-test && uv sync && uv run pytest scenarios --module-path $CI_PROJECT_DIR/artifacts/$ARCH/mod_datadog.so --bin-path /httpd/httpd-build/bin/apachectl --log-dir $CI_PROJECT_DIR/logs -m requires_rum -v -# Build (or reuse) the devcontainer image per arch: hash the inputs, skip if -# the registry already has the tag, otherwise buildx build + push + nydusify -# in-place. The nydusified per-arch images are then stitched into a -# multi-arch manifest by `create-ci-manifest` below. -devcontainer-image: - stage: ci-image - tags: ["arch:$ARCH"] - image: $NYDUS_DD_IMAGE - variables: - DDSIGN_SKIP_SIGNING: "true" - parallel: - matrix: - - ARCH: amd64 - TOOLCHAIN_ARCH: x86_64 - - ARCH: arm64 - TOOLCHAIN_ARCH: aarch64 - script: - - | - FILES=$(find .devcontainer/Dockerfile deps/nginx-datadog/build_env/ scripts/setup-httpd.py -type f | sort) - HASH=$(for f in $FILES; do echo "--- $f ---"; cat "$f"; done | sha256sum | cut -d ' ' -f 1) - - IMAGE_TAG="$CI_DOCKER_IMAGE_BASE/$ARCH:$HASH" - - echo "CI_IMAGE_HASH=$HASH" >> ci-image.env - - echo "DEVCONTAINER_IMAGE=$CI_DOCKER_IMAGE_BASE:$HASH" >> ci-image.env - - | - if crane manifest "$IMAGE_TAG" >/dev/null 2>&1; then - echo "Image $IMAGE_TAG already exists. Skipping build." - else - docker buildx build \ - --tag "$IMAGE_TAG" \ - --platform linux/$ARCH \ - --build-arg ARCH=$TOOLCHAIN_ARCH \ - --push \ - --file .devcontainer/Dockerfile \ - . - echo "Image $IMAGE_TAG built for $ARCH." - fi - - | - MANIFEST=$(crane manifest "$IMAGE_TAG" 2>/dev/null) || { - echo "WARNING: failed to fetch manifest for $IMAGE_TAG — skipping nydus conversion" - exit 0 - } - if echo "$MANIFEST" | jq -e '[.layers[].mediaType] | any(contains("nydus"))' >/dev/null 2>&1; then - echo "$IMAGE_TAG already has nydus layers. Skipping conversion." - else - echo "Converting $IMAGE_TAG to nydus in place..." - nydus-convert "$IMAGE_TAG" "$IMAGE_TAG" || \ - echo "WARNING: nydus conversion failed — continuing without nydus layers" - fi - artifacts: - reports: - dotenv: ci-image.env - -# Combine the per-arch (nydusified) images into a single multi-arch manifest -# at $CI_DOCKER_IMAGE_BASE:$CI_IMAGE_HASH so downstream jobs can pin by tag -# alone and let the runtime pick the right arch. -create-ci-manifest: - stage: ci-image - needs: - - devcontainer-image - tags: ["arch:amd64"] - image: $NYDUS_DD_IMAGE - script: - - MANIFEST_TAG="$DEVCONTAINER_IMAGE" - - | - if crane manifest "$MANIFEST_TAG" >/dev/null 2>&1 && \ - docker buildx imagetools inspect "$MANIFEST_TAG" --format '{{json .Manifest}}' 2>/dev/null | jq -e '.manifests | length >= 2' >/dev/null 2>&1; then - echo "Multi-arch manifest $MANIFEST_TAG already exists. Skipping." - else - docker buildx imagetools create \ - --tag "$MANIFEST_TAG" \ - "$CI_DOCKER_IMAGE_BASE/amd64:$CI_IMAGE_HASH" \ - "$CI_DOCKER_IMAGE_BASE/arm64:$CI_IMAGE_HASH" - fi - build:amd64: extends: .build-template needs: diff --git a/.gitlab/devcontainer.yml b/.gitlab/devcontainer.yml new file mode 100644 index 0000000..7410b09 --- /dev/null +++ b/.gitlab/devcontainer.yml @@ -0,0 +1,83 @@ +--- +# Build pipeline for the devcontainer image used by every downstream CI +# job that compiles mod_datadog or runs integration tests. The image is +# content-addressed: its tag is a sha256 over the Dockerfile plus the +# upstream toolchain files it pulls from, so the jobs here only rebuild +# when one of those inputs actually changes. + +variables: + NYDUS_DD_IMAGE: registry.ddbuild.io/images/nydus:v98111448-6d7c7d0-v2.3.8-dd + +# Build (or reuse) the devcontainer image per arch: hash the inputs, skip if +# the registry already has the tag, otherwise buildx build + push + nydusify +# in-place. The nydusified per-arch images are then stitched into a +# multi-arch manifest by `create-ci-manifest` below. +devcontainer-image: + stage: ci-image + tags: ["arch:$ARCH"] + image: $NYDUS_DD_IMAGE + variables: + DDSIGN_SKIP_SIGNING: "true" + parallel: + matrix: + - ARCH: amd64 + TOOLCHAIN_ARCH: x86_64 + - ARCH: arm64 + TOOLCHAIN_ARCH: aarch64 + script: + - | + FILES=$(find .devcontainer/Dockerfile deps/nginx-datadog/build_env/ scripts/setup-httpd.py -type f | sort) + HASH=$(for f in $FILES; do echo "--- $f ---"; cat "$f"; done | sha256sum | cut -d ' ' -f 1) + - IMAGE_TAG="$CI_DOCKER_IMAGE_BASE/$ARCH:$HASH" + - echo "CI_IMAGE_HASH=$HASH" >> ci-image.env + - echo "DEVCONTAINER_IMAGE=$CI_DOCKER_IMAGE_BASE:$HASH" >> ci-image.env + - | + if crane manifest "$IMAGE_TAG" >/dev/null 2>&1; then + echo "Image $IMAGE_TAG already exists. Skipping build." + else + docker buildx build \ + --tag "$IMAGE_TAG" \ + --platform linux/$ARCH \ + --build-arg ARCH=$TOOLCHAIN_ARCH \ + --push \ + --file .devcontainer/Dockerfile \ + . + echo "Image $IMAGE_TAG built for $ARCH." + fi + - | + MANIFEST=$(crane manifest "$IMAGE_TAG" 2>/dev/null) || { + echo "WARNING: failed to fetch manifest for $IMAGE_TAG — skipping nydus conversion" + exit 0 + } + if echo "$MANIFEST" | jq -e '[.layers[].mediaType] | any(contains("nydus"))' >/dev/null 2>&1; then + echo "$IMAGE_TAG already has nydus layers. Skipping conversion." + else + echo "Converting $IMAGE_TAG to nydus in place..." + nydus-convert "$IMAGE_TAG" "$IMAGE_TAG" || \ + echo "WARNING: nydus conversion failed — continuing without nydus layers" + fi + artifacts: + reports: + dotenv: ci-image.env + +# Combine the per-arch (nydusified) images into a single multi-arch manifest +# at $CI_DOCKER_IMAGE_BASE:$CI_IMAGE_HASH so downstream jobs can pin by tag +# alone and let the runtime pick the right arch. +create-ci-manifest: + stage: ci-image + needs: + - devcontainer-image + tags: ["arch:amd64"] + image: $NYDUS_DD_IMAGE + script: + - MANIFEST_TAG="$DEVCONTAINER_IMAGE" + - | + if crane manifest "$MANIFEST_TAG" >/dev/null 2>&1 && \ + docker buildx imagetools inspect "$MANIFEST_TAG" --format '{{json .Manifest}}' 2>/dev/null | jq -e '.manifests | length >= 2' >/dev/null 2>&1; then + echo "Multi-arch manifest $MANIFEST_TAG already exists. Skipping." + else + docker buildx imagetools create \ + --tag "$MANIFEST_TAG" \ + "$CI_DOCKER_IMAGE_BASE/amd64:$CI_IMAGE_HASH" \ + "$CI_DOCKER_IMAGE_BASE/arm64:$CI_IMAGE_HASH" + fi From b3853c0f3ba88b8c8d745aa98755c781a1aed688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Mon, 20 Apr 2026 19:57:00 +0200 Subject: [PATCH 4/5] Scope consumer image to arch-specific devcontainer build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-arch build/test jobs used to wait for both devcontainer-image matrix slots plus create-ci-manifest before they could start. That serialized the pipeline needlessly: the amd64 test doesn't need the arm64 image to exist, and neither needs the multi-arch manifest. Export DEVCONTAINER_IMAGE per arch (\$base/\$arch:\$hash) rather than multi-arch (\$base:\$hash), and scope each consumer's `needs:` to its own arch slot via `parallel: matrix:`. Drop create-ci-manifest from the needs list entirely — it's still built for external consumers but doesn't gate the internal pipeline. Effect: when a devcontainer input (Dockerfile, nginx-datadog build_env, setup-httpd.py) changes, the matching arch's image rebuilds, and that arch's build + test jobs pick it up as soon as they're ready; the other arch proceeds in parallel on its own rebuild. --- .gitlab-ci.yml | 30 ++++++++++++++++++++++++------ .gitlab/devcontainer.yml | 14 ++++++++++---- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 466a07b..498bcff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,8 +52,12 @@ stages: build:amd64: extends: .build-template needs: - - devcontainer-image - - create-ci-manifest + - job: devcontainer-image + parallel: + matrix: + - ARCH: amd64 + TOOLCHAIN_ARCH: x86_64 + artifacts: true variables: ARCH: amd64 TOOLCHAIN_ARCH: x86_64 @@ -62,8 +66,12 @@ build:amd64: build:arm64: extends: .build-template needs: - - devcontainer-image - - create-ci-manifest + - job: devcontainer-image + parallel: + matrix: + - ARCH: arm64 + TOOLCHAIN_ARCH: aarch64 + artifacts: true variables: ARCH: arm64 TOOLCHAIN_ARCH: aarch64 @@ -72,7 +80,12 @@ build:arm64: test-rum:amd64: extends: .test-rum-template needs: - - devcontainer-image + - job: devcontainer-image + parallel: + matrix: + - ARCH: amd64 + TOOLCHAIN_ARCH: x86_64 + artifacts: true - job: "build:amd64" variables: ARCH: amd64 @@ -81,7 +94,12 @@ test-rum:amd64: test-rum:arm64: extends: .test-rum-template needs: - - devcontainer-image + - job: devcontainer-image + parallel: + matrix: + - ARCH: arm64 + TOOLCHAIN_ARCH: aarch64 + artifacts: true - job: "build:arm64" variables: ARCH: arm64 diff --git a/.gitlab/devcontainer.yml b/.gitlab/devcontainer.yml index 7410b09..8f0f464 100644 --- a/.gitlab/devcontainer.yml +++ b/.gitlab/devcontainer.yml @@ -30,7 +30,11 @@ devcontainer-image: HASH=$(for f in $FILES; do echo "--- $f ---"; cat "$f"; done | sha256sum | cut -d ' ' -f 1) - IMAGE_TAG="$CI_DOCKER_IMAGE_BASE/$ARCH:$HASH" - echo "CI_IMAGE_HASH=$HASH" >> ci-image.env - - echo "DEVCONTAINER_IMAGE=$CI_DOCKER_IMAGE_BASE:$HASH" >> ci-image.env + # Export DEVCONTAINER_IMAGE as the arch-specific ref so each consumer + # runs on its own arch's image without blocking on create-ci-manifest. + # Downstream jobs scope their `needs:` to their matrix slot to pick up + # the matching value. + - echo "DEVCONTAINER_IMAGE=$IMAGE_TAG" >> ci-image.env - | if crane manifest "$IMAGE_TAG" >/dev/null 2>&1; then echo "Image $IMAGE_TAG already exists. Skipping build." @@ -61,8 +65,10 @@ devcontainer-image: dotenv: ci-image.env # Combine the per-arch (nydusified) images into a single multi-arch manifest -# at $CI_DOCKER_IMAGE_BASE:$CI_IMAGE_HASH so downstream jobs can pin by tag -# alone and let the runtime pick the right arch. +# at $CI_DOCKER_IMAGE_BASE:$CI_IMAGE_HASH so external consumers (other +# pipelines, GitHub Actions) can pin by the bare tag. Internal build/test +# jobs reference the per-arch image directly via $DEVCONTAINER_IMAGE and +# don't wait for this job. create-ci-manifest: stage: ci-image needs: @@ -70,7 +76,7 @@ create-ci-manifest: tags: ["arch:amd64"] image: $NYDUS_DD_IMAGE script: - - MANIFEST_TAG="$DEVCONTAINER_IMAGE" + - MANIFEST_TAG="$CI_DOCKER_IMAGE_BASE:$CI_IMAGE_HASH" - | if crane manifest "$MANIFEST_TAG" >/dev/null 2>&1 && \ docker buildx imagetools inspect "$MANIFEST_TAG" --format '{{json .Manifest}}' 2>/dev/null | jq -e '.manifests | length >= 2' >/dev/null 2>&1; then From 1b728235b64de6244fed17e27af8263ec4738803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Tue, 21 Apr 2026 15:39:20 +0200 Subject: [PATCH 5/5] Polish devcontainer workflow: submodule guard, image docs, pytest flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collects a few follow-ups to the devcontainer branch that were spotted while testing the flow end-to-end. devcontainer.mk: - Fail fast with actionable guidance when deps/nginx-datadog isn't initialized. Without this, the DEV_CONTAINER_HASH `find` emits warnings and silently hashes an incomplete input set, producing a tag that doesn't match what CI computed — the result was a confusing pull-then-fall-back-to-build loop. - Mount $(DEV_CONTAINER_GIT_MOUNT) into dev-shell so submodule init works from inside the container. - Move test-integration to the always-available section. $(DEVCONTAINER_RUN) is empty inside the container (script runs directly) and `docker run …` outside, so one definition works in both modes. .github/workflows/{dev,release,system-tests}.yml: - Replace the unhelpful "See in Makefile where this image comes from." comment with one that names the upstream (GitLab-built image from .devcontainer/Dockerfile, hash-computed by .gitlab/devcontainer.yml) and documents the bump procedure. .gitlab-ci.yml: - Switch pytest `--flag VALUE` to `--flag=VALUE` in .test-rum-template for the same reason as run-integration-tests.sh: pytest's early arg parser runs before conftest registers these custom options, so the space-separated form is read as a positional test path and skips conftest entirely. --- .devcontainer/devcontainer.mk | 26 ++++++++++++++++++++------ .github/workflows/dev.yml | 10 ++++++++-- .github/workflows/release.yml | 5 ++++- .github/workflows/system-tests.yml | 5 ++++- .gitlab-ci.yml | 6 +++++- 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/.devcontainer/devcontainer.mk b/.devcontainer/devcontainer.mk index a167eb1..fd9b7c1 100644 --- a/.devcontainer/devcontainer.mk +++ b/.devcontainer/devcontainer.mk @@ -13,11 +13,21 @@ dev-image: else +# Fail fast with actionable guidance if deps/nginx-datadog isn't populated — +# without this check, the DEV_CONTAINER_HASH `find` below emits warnings and +# silently hashes an incomplete input set, producing a tag that doesn't +# match what CI computed and triggering confusing pull-then-fall-back-to- +# build behavior. +ifeq ($(wildcard $(DEV_CONTAINER_REPO_ROOT)/deps/nginx-datadog/build_env/Toolchain.cmake.x86_64),) +$(error deps/nginx-datadog submodule is not initialized. Run: git submodule update --init --recursive) +endif + DEV_CONTAINER_REGISTRY ?= registry.ddbuild.io DEV_CONTAINER_IMAGE_NAME ?= $(DEV_CONTAINER_REGISTRY)/ci/httpd-datadog -# Match the hash computation in .gitlab-ci.yml (build-ci-image job): sha256 over -# .devcontainer/Dockerfile, deps/nginx-datadog/build_env/, and scripts/setup-httpd.py. +# Match the hash computation in .gitlab/devcontainer.yml (devcontainer-image +# job): sha256 over .devcontainer/Dockerfile, deps/nginx-datadog/build_env/, +# and scripts/setup-httpd.py. DEV_CONTAINER_HASH := $(shell cd $(DEV_CONTAINER_REPO_ROOT) && \ find .devcontainer/Dockerfile deps/nginx-datadog/build_env/ scripts/setup-httpd.py -type f | sort | \ while IFS= read -r f; do echo "--- $$f ---"; cat "$$f"; done | \ @@ -83,14 +93,11 @@ dev-image: dev-shell: dev-image docker run --rm -it \ -v $(DEV_CONTAINER_REPO_ROOT):$(DEV_CONTAINER_REPO_ROOT) \ + $(DEV_CONTAINER_GIT_MOUNT) \ -w $(DEV_CONTAINER_REPO_ROOT) \ $(DEV_CONTAINER_IMAGE) \ /bin/sh -.PHONY: test-integration -test-integration: dev-image - $(DEVCONTAINER_RUN) .devcontainer/run-integration-tests.sh - .PHONY: dev-image-clean dev-image-clean: @stale=$$(docker images --format '{{.Repository}}:{{.Tag}}' $(DEV_CONTAINER_IMAGE_NAME) 2>/dev/null | grep -v ':$(DEV_CONTAINER_TAG)$$' || true); \ @@ -102,3 +109,10 @@ dev-image-clean: fi endif + +# Always-available targets: $(DEVCONTAINER_RUN) is empty inside the container +# (the script runs directly) and `docker run …` outside, so one definition +# works in both modes. +.PHONY: test-integration +test-integration: dev-image + $(DEVCONTAINER_RUN) .devcontainer/run-integration-tests.sh diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 8fcd549..7c4bf54 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -19,7 +19,10 @@ jobs: needs: format runs-on: ubuntu-22.04 container: - # See in Makefile where this image comes from. + # Public mirror of the GitLab-built image defined in .devcontainer/Dockerfile + # (hash-computed by .gitlab/devcontainer.yml). Bump by copying a newer tag + # via `docker pull registry.ddbuild.io/ci/httpd-datadog: && docker tag + # ... && docker push datadog/docker-library:httpd-datadog-ci-`. image: datadog/docker-library:httpd-datadog-ci-28219c0ef3e00f1e3d5afcab61a73a5e9bd2a9b957d7545556711cce2a6262cd steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 @@ -44,7 +47,10 @@ jobs: needs: build runs-on: ubuntu-22.04 container: - # See in Makefile where this image comes from. + # Public mirror of the GitLab-built image defined in .devcontainer/Dockerfile + # (hash-computed by .gitlab/devcontainer.yml). Bump by copying a newer tag + # via `docker pull registry.ddbuild.io/ci/httpd-datadog: && docker tag + # ... && docker push datadog/docker-library:httpd-datadog-ci-`. image: datadog/docker-library:httpd-datadog-ci-28219c0ef3e00f1e3d5afcab61a73a5e9bd2a9b957d7545556711cce2a6262cd env: DD_ENV: ci diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a7539b..db5a214 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,10 @@ jobs: build: runs-on: ubuntu-22.04 container: - # See in Makefile where this image comes from. + # Public mirror of the GitLab-built image defined in .devcontainer/Dockerfile + # (hash-computed by .gitlab/devcontainer.yml). Bump by copying a newer tag + # via `docker pull registry.ddbuild.io/ci/httpd-datadog: && docker tag + # ... && docker push datadog/docker-library:httpd-datadog-ci-`. image: datadog/docker-library:httpd-datadog-ci-28219c0ef3e00f1e3d5afcab61a73a5e9bd2a9b957d7545556711cce2a6262cd steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 72414ee..3b87ccb 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -19,7 +19,10 @@ jobs: build-artifacts: runs-on: ubuntu-22.04 container: - # See in Makefile where this image comes from. + # Public mirror of the GitLab-built image defined in .devcontainer/Dockerfile + # (hash-computed by .gitlab/devcontainer.yml). Bump by copying a newer tag + # via `docker pull registry.ddbuild.io/ci/httpd-datadog: && docker tag + # ... && docker push datadog/docker-library:httpd-datadog-ci-`. image: datadog/docker-library:httpd-datadog-ci-28219c0ef3e00f1e3d5afcab61a73a5e9bd2a9b957d7545556711cce2a6262cd steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 498bcff..84d9753 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -46,8 +46,12 @@ stages: .test-rum-template: stage: test image: $DEVCONTAINER_IMAGE + # Same `--flag=VALUE` reasoning as run-integration-tests.sh: pytest's early + # arg parser runs before conftest registers these custom options, so the + # space-separated form gets read as a positional test path and skips + # conftest. script: - - cd $CI_PROJECT_DIR/test/integration-test && uv sync && uv run pytest scenarios --module-path $CI_PROJECT_DIR/artifacts/$ARCH/mod_datadog.so --bin-path /httpd/httpd-build/bin/apachectl --log-dir $CI_PROJECT_DIR/logs -m requires_rum -v + - cd $CI_PROJECT_DIR/test/integration-test && uv sync && uv run pytest scenarios --module-path=$CI_PROJECT_DIR/artifacts/$ARCH/mod_datadog.so --bin-path=/httpd/httpd-build/bin/apachectl --log-dir=$CI_PROJECT_DIR/logs -m requires_rum -v build:amd64: extends: .build-template