Skip to content

Commit 7bdabae

Browse files
committed
Added container-based CFEngine package builder
Introduced build-in-container, a Python/Docker-based build system that builds CFEngine packages inside containers using the existing build scripts. Ticket: ENT-13777 Signed-off-by: Lars Erik Wik <lars.erik.wik@northern.tech>
1 parent fe841b0 commit 7bdabae

File tree

4 files changed

+590
-0
lines changed

4 files changed

+590
-0
lines changed

build-in-container-inner.sh

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Configuration via environment variables:
5+
# PROJECT, BUILD_TYPE, EXPLICIT_ROLE, BUILD_NUMBER, EXPLICIT_VERSION
6+
7+
BASEDIR=/home/builder/build
8+
export BASEDIR
9+
export AUTOBUILD_PATH="$BASEDIR/buildscripts"
10+
11+
mkdir -p "$BASEDIR"
12+
13+
# Bind-mounted directories may be owned by the host user's UID.
14+
# Fix ownership so builder can write to them.
15+
sudo chown -R "$(id -u):$(id -g)" "$HOME/.cache" /output
16+
17+
# Prevent git "dubious ownership" errors
18+
git config --global --add safe.directory '*'
19+
20+
# === Sync source repos ===
21+
repos="buildscripts core masterfiles"
22+
if [ "$PROJECT" = "nova" ]; then
23+
repos="$repos enterprise nova mission-portal"
24+
fi
25+
26+
for repo in $repos; do
27+
src="/srv/source/$repo"
28+
# Use rsync -aL to follow symlinks during copy.
29+
# The source dir may use symlinks (e.g., core -> cfengine/core/).
30+
# -L resolves them at copy time, so the destination gets real files
31+
# regardless of the host directory layout.
32+
# Exclude acceptance test workdirs — they contain broken symlinks left
33+
# over from previous test runs and are not needed for building.
34+
if [ -d "$src" ] || [ -L "$src" ]; then
35+
echo "Syncing $repo..."
36+
sudo rsync -aL --exclude='config.cache' --exclude='workdir' --chown="$(id -u):$(id -g)" "$src/" "$BASEDIR/$repo/"
37+
else
38+
echo "ERROR: Required repository $repo not found" >&2
39+
exit 1
40+
fi
41+
done
42+
43+
install_mission_portal_deps() (
44+
set -e
45+
46+
if [ -f "$BASEDIR/mission-portal/public/scripts/package.json" ]; then
47+
echo "Installing npm dependencies..."
48+
npm ci --prefix "$BASEDIR/mission-portal/public/scripts/"
49+
echo "Building react components..."
50+
npm run build --prefix "$BASEDIR/mission-portal/public/scripts/"
51+
rm -rf "$BASEDIR/mission-portal/public/scripts/node_modules"
52+
fi
53+
54+
if [ -f "$BASEDIR/mission-portal/composer.json" ]; then
55+
echo "Installing Mission Portal PHP dependencies..."
56+
(cd "$BASEDIR/mission-portal" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs)
57+
fi
58+
59+
if [ -f "$BASEDIR/nova/api/http/composer.json" ]; then
60+
echo "Installing Nova API PHP dependencies..."
61+
(cd "$BASEDIR/nova/api/http" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs)
62+
fi
63+
64+
if [ -f "$BASEDIR/mission-portal/public/themes/default/bootstrap/cfengine_theme.less" ]; then
65+
echo "Compiling Mission Portal styles..."
66+
mkdir -p "$BASEDIR/mission-portal/public/themes/default/bootstrap/compiled/css"
67+
(cd "$BASEDIR/mission-portal/public/themes/default/bootstrap" &&
68+
lessc --compress ./cfengine_theme.less ./compiled/css/cfengine.less.css)
69+
fi
70+
71+
if [ -f "$BASEDIR/mission-portal/ldap/composer.json" ]; then
72+
echo "Installing LDAP API PHP dependencies..."
73+
(cd "$BASEDIR/mission-portal/ldap" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs)
74+
fi
75+
)
76+
77+
# === Step runner with failure reporting ===
78+
# Disable set -e so we can capture exit codes and report which step failed.
79+
set +e
80+
run_step() {
81+
local name="$1"
82+
shift
83+
echo "=== Running $name ==="
84+
"$@"
85+
local rc=$?
86+
if [ $rc -ne 0 ]; then
87+
echo ""
88+
echo "=== FAILED: $name (exit code $rc) ==="
89+
exit $rc
90+
fi
91+
}
92+
93+
# === Build steps ===
94+
run_step "01-autogen" "$BASEDIR/buildscripts/build-scripts/autogen"
95+
run_step "02-install-dependencies" "$BASEDIR/buildscripts/build-scripts/install-dependencies"
96+
if [ "$EXPLICIT_ROLE" = "hub" ]; then
97+
run_step "03-mission-portal-deps" install_mission_portal_deps
98+
fi
99+
run_step "04-configure" "$BASEDIR/buildscripts/build-scripts/configure"
100+
run_step "05-compile" "$BASEDIR/buildscripts/build-scripts/compile"
101+
run_step "06-package" "$BASEDIR/buildscripts/build-scripts/package"
102+
103+
# === Copy output packages ===
104+
# Packages are created under $BASEDIR/<project>/ by dpkg-buildpackage / rpmbuild.
105+
# Exclude deps-packaging to avoid copying dependency packages.
106+
find "$BASEDIR" -maxdepth 4 \
107+
-path "$BASEDIR/buildscripts/deps-packaging" -prune -o \
108+
\( -name '*.deb' -o -name '*.rpm' -o -name '*.pkg.tar.gz' \) -print \
109+
-exec cp {} /output/ \;
110+
111+
echo ""
112+
echo "=== Build complete ==="
113+
ls -lh /output/

build-in-container.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# build-in-container
2+
3+
Build CFEngine packages inside Docker containers using build scripts. Requires
4+
only Docker and Python 3 on the host.
5+
6+
## Quick start
7+
8+
```bash
9+
# Build a community agent .deb for Ubuntu 22
10+
./build-in-container.py --platform ubuntu-22 --project community --role agent --build-type DEBUG
11+
12+
# Build a nova hub release package for Debian 12
13+
./build-in-container.py --platform debian-12 --project nova --role hub --build-type RELEASE
14+
```
15+
16+
In the examples above, we run the script from inside `buildscripts/` (with
17+
`buildscripts` as our current working directory). This is not required — if not
18+
specified, defaults will:
19+
20+
- Look for sources relative to the script (parent directory of
21+
`build-in-container.py`).
22+
- Place cache files in the user's home directory
23+
(`~/.cache/cfengine/buildscripts`).
24+
- Use the current working directory for output packages (`./output/`).
25+
26+
## Usage
27+
28+
```
29+
./build-in-container.py --platform PLATFORM --project PROJECT --role ROLE --build-type TYPE [OPTIONS]
30+
```
31+
32+
### Required arguments
33+
34+
| Option | Description |
35+
|--------------------|-------------------------------------------------|
36+
| `--platform` | Target platform (e.g. `ubuntu-22`, `debian-12`) |
37+
| `--project` | `community` or `nova` |
38+
| `--role` | `agent` or `hub` |
39+
| `--build-type` | `DEBUG` or `RELEASE` |
40+
41+
### Optional arguments
42+
43+
| Option | Default | Description |
44+
|--------------------|----------------------------------|-------------------------------------------------------------|
45+
| `--output-dir` | `./output` | Where to write output packages |
46+
| `--cache-dir` | `~/.cache/cfengine/buildscripts` | Dependency cache directory |
47+
| `--build-number` | `1` | Build number for package versioning |
48+
| `--version` | auto | Override version string |
49+
| `--rebuild-image` | | Force rebuild of Docker image (bypasses Docker layer cache) |
50+
| `--shell` | | Drop into a bash shell inside the container for debugging |
51+
| `--list-platforms` | | List available platforms and exit |
52+
| `--source-dir` | parent of `buildscripts/` | Root directory containing repos |
53+
54+
## Supported platforms
55+
56+
| Name | Base image |
57+
|-------------|----------------|
58+
| `ubuntu-20` | `ubuntu:20.04` |
59+
| `ubuntu-22` | `ubuntu:22.04` |
60+
| `ubuntu-24` | `ubuntu:24.04` |
61+
| `debian-11` | `debian:11` |
62+
| `debian-12` | `debian:12` |
63+
64+
Adding a new Debian/Ubuntu platform requires only a new entry in the `PLATFORMS`
65+
dict in `build-in-container.py`. Adding a non-debian based platform (e.g.,
66+
RHEL/CentOS) requires a new `container/Dockerfile.rhel` plus platform entries.
67+
68+
## How it works
69+
70+
The system has three components:
71+
72+
1. **`build-in-container.py`** (Python) -- the orchestrator that runs on the host.
73+
Parses arguments, builds the Docker image, and launches the container with
74+
the correct mounts and environment variables.
75+
76+
2. **`build-in-container-inner.sh`** (Bash) -- runs inside the container. Copies
77+
source repos from the read-only mount, then calls the existing build scripts
78+
in order.
79+
80+
3. **`container/Dockerfile.debian`** -- parameterized Dockerfile shared by all
81+
Debian/Ubuntu platforms via a `BASE_IMAGE` build arg.
82+
83+
### Container mounts
84+
85+
| Host path | Container path | Mode | Purpose |
86+
|------------------------------------------|-------------------------------------------|------------|---------------------------------------|
87+
| Source repos (parent of `buildscripts/`) | `/srv/source` | read-only | Protects host repos from modification |
88+
| `~/.cache/cfengine/buildscripts/` | `/home/builder/.cache/buildscripts_cache` | read-write | Dependency cache shared across builds |
89+
| `./output/` | `/output` | read-write | Output packages copied here |
90+
91+
### Build steps
92+
93+
The inner script runs these steps in order:
94+
95+
1. **autogen** -- runs `autogen.sh` in each repo
96+
2. **install-dependencies** -- builds and installs bundled dependencies
97+
3. **mission-portal-deps** -- (hub only) installs PHP/npm/LESS assets
98+
4. **configure** -- runs `./configure` with platform-appropriate flags
99+
5. **compile** -- compiles and installs to the dist tree
100+
6. **package** -- creates `.deb` or `.rpm` packages
101+
102+
## Docker image management
103+
104+
The Docker image is tagged `cfengine-builder-{platform}` and rebuilt
105+
automatically when the Dockerfile changes (tracked via a content hash stored as
106+
an image label). Use `--rebuild-image` to force a full rebuild bypassing the
107+
Docker layer cache (useful when upstream packages change).
108+
109+
## Debugging
110+
111+
```bash
112+
# Drop into a shell inside the container
113+
./build-in-container.py --platform ubuntu-22 --project community --role agent --build-type DEBUG --shell
114+
```
115+
116+
The shell session has the same mounts and environment as a build run. The
117+
container is ephemeral (`--rm`), so any changes are lost on exit.

0 commit comments

Comments
 (0)