Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
File renamed without changes.
21 changes: 21 additions & 0 deletions .devcontainer/context.files
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Build-context allowlist for the devcontainer image. One rsync-relative
# path per line; '#' comments and blank lines are stripped before rsync
# sees them. Anything the Dockerfile COPYs has to live under one of these.
#
# Only files whose contents affect the image go in this list — anything
# that's only invoked at runtime via volume mount (e.g.
# run-integration-tests.sh) is intentionally excluded so editing it
# doesn't invalidate the cached image tag.

# image recipe
.devcontainer/Dockerfile

# musl/LLVM toolchain inputs (CHECKSUMS, locale.h.diff, alltypes.h.diff,
# Toolchain.cmake.<arch>, glibc_compat.c) — sourced from the
# nginx-datadog submodule's build_env so the cross-compile sysroot here
# matches what nginx-datadog uses.
deps/nginx-datadog/build_env/

# Downloads + builds httpd 2.4 inside the image so test jobs have an
# Apache to load mod_datadog into without rebuilding it per pipeline.
scripts/setup-httpd.py
11 changes: 11 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "httpd-datadog",
"initializeCommand": "make -f .devcontainer/devcontainer.mk .devcontainer-stage-context",
"build": {
"dockerfile": ".staged/.devcontainer/Dockerfile",
"context": ".staged",
"args": {
"ARCH": "${localEnv:ARCH:x86_64}"
}
}
}
214 changes: 214 additions & 0 deletions .devcontainer/devcontainer.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# DO NOT EDIT. Synced from DataDog/dd-repo-tools by the
# dd-repo-tools devcontainer-bundle campaigner — edits here will be
# overwritten on the next run.
# Source of truth: https://github.com/DataDog/dd-repo-tools/blob/main/shared/devcontainer/devcontainer.mk
#
# Inputs (set before `include`):
# DEV_CONTAINER_REPO_ROOT Absolute path to the repo root.
# DEV_CONTAINER_IMAGE_NAME Override image name. Defaults to
# registry.ddbuild.io/ci/<repo>/devcontainer.
# DEV_CONTAINER_REQUIRED_PATHS Whitespace-separated paths that must exist
# (e.g. submodule checkouts). Missing ->
# $(error).
# DOCKER_BUILDX_FLAGS Extra flags appended to the local build.
#
# This file is the single source of truth for staging + hashing. The
# GitLab template (devcontainer.yml) calls into `make` so both paths
# share the same code.

DEV_CONTAINER_REPO_ROOT ?= $(CURDIR)
_DEV_CONTAINER_REPO_NAME := $(notdir $(patsubst %/,%,$(DEV_CONTAINER_REPO_ROOT)))
DEV_CONTAINER_IMAGE_NAME ?= registry.ddbuild.io/ci/$(_DEV_CONTAINER_REPO_NAME)/devcontainer

_DEV_CONTAINER_CONTEXT_FILES := $(DEV_CONTAINER_REPO_ROOT)/.devcontainer/context.files
_DEV_CONTAINER_CONTEXT_FILTER := $(DEV_CONTAINER_REPO_ROOT)/.devcontainer/context.filter
_DEV_CONTAINER_DOCKERFILE := $(DEV_CONTAINER_REPO_ROOT)/.devcontainer/Dockerfile
_DEV_CONTAINER_STAGED := $(DEV_CONTAINER_REPO_ROOT)/.devcontainer/.staged

# uname -m -> docker --platform / conventional ARCH build-arg.
_DEV_CONTAINER_UNAME_M := $(shell uname -m)
ifneq ($(filter $(_DEV_CONTAINER_UNAME_M),arm64 aarch64),)
_DEV_CONTAINER_PLATFORM := linux/arm64
_DEV_CONTAINER_ARCH := aarch64
else
Comment thread
pawelchcki marked this conversation as resolved.
_DEV_CONTAINER_PLATFORM := linux/amd64
_DEV_CONTAINER_ARCH := x86_64
endif

# Pass --build-arg ARCH only if the Dockerfile actually declares it.
_DEV_CONTAINER_ARCH_ARG := $(shell grep -q '^ARG ARCH' $(_DEV_CONTAINER_DOCKERFILE) 2>/dev/null && echo --build-arg ARCH=$(_DEV_CONTAINER_ARCH))

# Skip everything inside the container (no nested docker, no pull).
_DEV_CONTAINER_INSIDE := $(shell [ -f /.dockerenv ] || [ -n "$$KUBERNETES_SERVICE_HOST" ] && echo 1)

# Stage .devcontainer/context.files (+ optional context.filter) into $$ctx.
# Caller sets $$ctx (and arranges cleanup if needed). Verifies Dockerfile
# was staged.
define _dev_container_stage_into_ctx
if [ ! -f "$(_DEV_CONTAINER_CONTEXT_FILES)" ]; then \
echo "ERROR: $(_DEV_CONTAINER_CONTEXT_FILES) not found" >&2; exit 1; \
fi; \
filter_arg=; \
[ -f "$(_DEV_CONTAINER_CONTEXT_FILTER)" ] && filter_arg="--filter=merge $(_DEV_CONTAINER_CONTEXT_FILTER)"; \
grep -Ev '^[[:space:]]*(#|$$)' "$(_DEV_CONTAINER_CONTEXT_FILES)" | \
rsync -aR --files-from=- $$filter_arg "$(DEV_CONTAINER_REPO_ROOT)/" "$$ctx/"; \
if [ ! -f "$$ctx/.devcontainer/Dockerfile" ]; then \
echo "ERROR: .devcontainer/Dockerfile not staged; check .devcontainer/context.files" >&2; \
exit 1; \
fi
endef

# Stage into a fresh mktemp dir, cleaned up on shell exit. Sets $$ctx.
define _dev_container_stage_context
ctx=$$(mktemp -d); \
trap 'rm -rf "$$ctx"' EXIT; \
$(_dev_container_stage_into_ctx)
endef

# Compute the 12-char tag hash from the staged tree at $$ctx.
define _dev_container_compute_tag
tag=$$(cd "$$ctx" && find . -type f | LC_ALL=C sort | \
while IFS= read -r f; do printf -- '--- %s ---\n' "$$f"; cat "$$f"; done | \
sha256sum | cut -c1-12)
endef

# Git-worktree support: when .git is a gitdir pointer, make the common dir
# visible inside the container so `git` calls work.
_DEV_CONTAINER_GIT_COMMON_DIR := $(shell cd $(DEV_CONTAINER_REPO_ROOT) && git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
_DEV_CONTAINER_DEFAULT_GIT_DIR := $(DEV_CONTAINER_REPO_ROOT)/.git
ifneq ($(_DEV_CONTAINER_GIT_COMMON_DIR),)
ifneq ($(_DEV_CONTAINER_GIT_COMMON_DIR),$(_DEV_CONTAINER_DEFAULT_GIT_DIR))
_DEV_CONTAINER_WORKTREE_MOUNT := -v $(_DEV_CONTAINER_GIT_COMMON_DIR):$(_DEV_CONTAINER_GIT_COMMON_DIR)
endif
endif

# Precondition check for caller-declared required paths.
ifneq ($(strip $(DEV_CONTAINER_REQUIRED_PATHS)),)
_DEV_CONTAINER_MISSING_PATHS := $(foreach p,$(DEV_CONTAINER_REQUIRED_PATHS),$(if $(wildcard $(DEV_CONTAINER_REPO_ROOT)/$(p)),,$(p)))
ifneq ($(strip $(_DEV_CONTAINER_MISSING_PATHS)),)
$(error Missing required paths (did you init submodules?): $(_DEV_CONTAINER_MISSING_PATHS))
endif
endif

.PHONY: dev-image dev-image-x86_64 dev-shell dev-image-tag \
.devcontainer-stage-context .devcontainer-image-hash

# Stage the build context into a stable path under .devcontainer/.staged/.
# Invoked from VS Code's initializeCommand (so build.context = `.staged`
# gets the same narrow tree CI and `make dev-image` use) and from
# devcontainer.yml's CI job. Hash-computing local targets keep using a
# mktemp dir for parallel safety; this one writes to a stable path
# because VS Code (and CI's downstream buildx step) need one.
.devcontainer-stage-context:
@rm -rf "$(_DEV_CONTAINER_STAGED)"
@mkdir -p "$(_DEV_CONTAINER_STAGED)"
@ctx="$(_DEV_CONTAINER_STAGED)"; $(_dev_container_stage_into_ctx)

# Print the 12-char hash of the already-staged .devcontainer/.staged/ tree.
# Caller must have run `make .devcontainer-stage-context` first. Used by
# devcontainer.yml so the tag is computed by the same code that the local
# Makefile uses.
.devcontainer-image-hash:
@ctx="$(_DEV_CONTAINER_STAGED)"; \
test -d "$$ctx" || { echo "ERROR: $$ctx not staged; run 'make .devcontainer-stage-context' first" >&2; exit 1; }; \
$(_dev_container_compute_tag); \
echo "$$tag"

# Print the full image:tag that would be used. Useful for debugging.
# Works inside or outside a container (no docker required).
dev-image-tag:
@$(_dev_container_stage_context); \
$(_dev_container_compute_tag); \
echo "$(DEV_CONTAINER_IMAGE_NAME):$$tag"

# Shared mounts for any in-container invocation: the repo root, plus the
# git common dir when running inside a worktree (so `git` works).
_DEV_CONTAINER_MOUNTS := \
-v $(DEV_CONTAINER_REPO_ROOT):$(DEV_CONTAINER_REPO_ROOT) \
$(_DEV_CONTAINER_WORKTREE_MOUNT) \
-w $(DEV_CONTAINER_REPO_ROOT)

# Public: prefix for running a scripted command in the devcontainer image.
# Includes the resolved image ref and `-i` (so stdin pipes work) but no
# `-t` (so it works under `make` / CI without a TTY). Usage:
# foo: dev-image
# $(IN_DEVCONTAINER) cmd args
# When `make` is invoked from inside a container, IN_DEVCONTAINER is empty,
# so the command runs in-place — same recipe works inside or outside.
IN_DEVCONTAINER ?= docker run --rm -i $(_DEV_CONTAINER_MOUNTS) \
$$(cat $(DEV_CONTAINER_REPO_ROOT)/.devcontainer/.image-ref)

# linux/amd64 variant: same shape as IN_DEVCONTAINER but pinned to amd64,
# for running cross-compiled x86_64 binaries (e.g. Windows test exes under
# wine64) on any host. On Apple Silicon, Docker emulates via Rosetta. On
# native amd64 the platform pin is a no-op and we reuse the regular image.
_DEV_CONTAINER_X86_64_IMAGE_REF_FILE := $(DEV_CONTAINER_REPO_ROOT)/.devcontainer/.image-ref-x86_64
IN_DEVCONTAINER_X86_64 ?= docker run --rm -i --platform=linux/amd64 $(_DEV_CONTAINER_MOUNTS) \
$$(cat $(_DEV_CONTAINER_X86_64_IMAGE_REF_FILE))

ifeq ($(_DEV_CONTAINER_INSIDE),1)
IN_DEVCONTAINER :=
IN_DEVCONTAINER_X86_64 :=
endif

# Targets whose behaviour differs inside vs outside a container.
# Inside: dev-image is a no-op (already running on it); dev-shell fails
# loudly because there's no docker daemon.
ifeq ($(_DEV_CONTAINER_INSIDE),1)

dev-image dev-image-x86_64:
@:

dev-shell:
@echo "make dev-shell: already inside the devcontainer; run /bin/sh directly" >&2; exit 1

else # _DEV_CONTAINER_INSIDE

# Pull the image if available; otherwise build locally from the staged context.
dev-image:
@$(_dev_container_stage_context); \
$(_dev_container_compute_tag); \
ref="$(DEV_CONTAINER_IMAGE_NAME):$$tag"; \
if docker pull "$$ref" >/dev/null 2>&1; then \
echo "Pulled $$ref"; \
else \
echo "Pull miss; building $$ref locally"; \
docker buildx build \
--platform $(_DEV_CONTAINER_PLATFORM) \
--file "$$ctx/.devcontainer/Dockerfile" \
--build-context repo="$$ctx" \
$(_DEV_CONTAINER_ARCH_ARG) \
$(DOCKER_BUILDX_FLAGS) \
--load -t "$$ref" \
"$$ctx"; \
fi; \
echo "$$ref" > $(DEV_CONTAINER_REPO_ROOT)/.devcontainer/.image-ref

# Build the linux/amd64 variant. On amd64 hosts this reuses dev-image; on
# arm64 hosts it produces a separate `:<tag>-x86_64` image so it doesn't
# clobber the native one — built locally only (CI doesn't publish the
# `-x86_64` ref), so no docker pull attempt. Buildx caches per-platform
# so repeats are cheap.
ifeq ($(_DEV_CONTAINER_PLATFORM),linux/amd64)
dev-image-x86_64: dev-image
@cp $(DEV_CONTAINER_REPO_ROOT)/.devcontainer/.image-ref $(_DEV_CONTAINER_X86_64_IMAGE_REF_FILE)
else
dev-image-x86_64:
@$(_dev_container_stage_context); \
$(_dev_container_compute_tag); \
ref="$(DEV_CONTAINER_IMAGE_NAME):$$tag-x86_64"; \
docker buildx build --platform=linux/amd64 \
--file "$$ctx/.devcontainer/Dockerfile" \
--build-context repo="$$ctx" \
--build-arg ARCH=x86_64 \
$(DOCKER_BUILDX_FLAGS) \
--load -t "$$ref" \
"$$ctx"; \
echo "$$ref" > $(_DEV_CONTAINER_X86_64_IMAGE_REF_FILE)
endif

dev-shell: dev-image
@ref=$$(cat $(DEV_CONTAINER_REPO_ROOT)/.devcontainer/.image-ref); \
docker run --rm -it $(_DEV_CONTAINER_MOUNTS) "$$ref" /bin/sh

endif # _DEV_CONTAINER_INSIDE
55 changes: 55 additions & 0 deletions .devcontainer/run-integration-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/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).
#
# 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)"
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"

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
# Wipe DIST_DIR so a removed/renamed file in the build doesn't
# linger from a previous install and silently get used by tests.
rm -rf "$DIST_DIR"
cmake --install "$BUILD_DIR" --prefix "$DIST_DIR"
Comment thread
pawelchcki marked this conversation as resolved.
MODULE_PATH="$DIST_DIR/lib/mod_datadog.so"
fi

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="$MODULE_PATH" \
--log-dir="$REPO_ROOT/logs" \
-v "$@"
44 changes: 44 additions & 0 deletions .github/workflows/CI_IMAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# CI image used by GitHub Actions

The workflows in this directory pin `image:` to
`datadog/docker-library:httpd-datadog-ci-<hash>` — a public Docker Hub
mirror of the internal devcontainer image built by GitLab CI from
[`.devcontainer/Dockerfile`](../../.devcontainer/Dockerfile). The build
+ caching pipeline lives in [`.gitlab/devcontainer.yml`](../../.gitlab/devcontainer.yml);
the image-tag hash is the sha256 of the staged build context defined
by [`.devcontainer/context.files`](../../.devcontainer/context.files).

GitHub-hosted runners can't pull from `registry.ddbuild.io`, so we
periodically copy the latest amd64 variant to Docker Hub and bump the
`image:` reference in each workflow.

## Bumping the image

Once GitLab CI has published a new tag (any change to a path listed in
`.devcontainer/context.files` invalidates the cache and triggers a
rebuild), mirror it from the repo root:

```sh
make mirror-public-image
```

The target stages the build context, computes the same 12-char hash
GitLab uses, pulls `registry.ddbuild.io/ci/httpd-datadog/devcontainer:amd64-<hash>`,
retags it as `datadog/docker-library:httpd-datadog-ci-<hash>`, and
pushes. It then prints the exact `image:` value to copy into:

- `dev.yml` (build + test jobs)
- `release.yml` (build job)
- `system-tests.yml` (build-artifacts job)

## Why two image namespaces

| Consumer | Pull from | Why |
|-----------------|----------------------------------------------------------------------------|-----|
| GitLab CI | `registry.ddbuild.io/ci/httpd-datadog/devcontainer:<arch>-<hash>` | private, fast, nydus-optimized |
| GitHub Actions | `datadog/docker-library:httpd-datadog-ci-<hash>` (mirrored from the above) | public, reachable from GitHub-hosted runners |
| Local `make` | Either of the above (pull `registry.ddbuild.io/...` if you have access; build locally otherwise) | dev convenience |

The two mirrors stay in sync only as far as the most recent
`make mirror-public-image` run — bump the workflows promptly after
running it.
14 changes: 3 additions & 11 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,12 @@ jobs:
needs: format
runs-on: ubuntu-22.04
container:
# See in Makefile where this image comes from.
# See .github/workflows/CI_IMAGE.md — bump via `make mirror-public-image`.
image: datadog/docker-library:httpd-datadog-ci-28219c0ef3e00f1e3d5afcab61a73a5e9bd2a9b957d7545556711cce2a6262cd
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Add cloned repo as safe
run: sh -c "git config --global --add safe.directory $PWD"
- name: Init required submodules
run: git submodule update --init --depth=1 deps/dd-trace-cpp deps/nginx-datadog
- name: Configure
run: cmake --preset=ci-dev -B build .
- name: Build
run: |
cmake --build build -j --verbose
cmake --install build --prefix dist
run: make ci-build PRESET=ci-dev
- name: Export library
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
Expand All @@ -44,7 +36,7 @@ jobs:
needs: build
runs-on: ubuntu-22.04
container:
# See in Makefile where this image comes from.
# See .github/workflows/CI_IMAGE.md — bump via `make mirror-public-image`.
image: datadog/docker-library:httpd-datadog-ci-28219c0ef3e00f1e3d5afcab61a73a5e9bd2a9b957d7545556711cce2a6262cd
env:
DD_ENV: ci
Expand Down
Loading
Loading