-
Notifications
You must be signed in to change notification settings - Fork 39
ENT-13777, ENT-13784: Added container-based CFEngine package builder #2146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
larsewi
wants to merge
9
commits into
cfengine:master
Choose a base branch
from
larsewi:container
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+736
−0
Draft
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
c995ac4
Added container-based CFEngine package builder
larsewi 554c212
Made build args conditionally required in build-in-container.py
larsewi 57bf999
Extracted argument parsing into parse_args() function
larsewi fd1ebb4
Added IMAGE_REGISTRY, IMAGE_VERSION constants and image_tag to PLATFORMS
larsewi 9b9b7cc
Added --push-image CLI argument
larsewi 038a316
Added registry_image_ref, pull_image, and push_image functions
larsewi 25a5a88
Implemented image resolution flow with registry pull/push
larsewi 1859e0c
Updated documentation for container registry support
larsewi 3a5b5f4
Added GitHub Actions workflow for building base images
larsewi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| name: Build base images | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
|
|
||
| jobs: | ||
| build-and-push: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| packages: write | ||
| strategy: | ||
| matrix: | ||
| platform: | ||
| - ubuntu-20 | ||
| - ubuntu-22 | ||
| - ubuntu-24 | ||
| - debian-11 | ||
| - debian-12 | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Log in to ghcr.io | ||
| uses: docker/login-action@v4 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Build and push image | ||
| run: ./build-in-container.py --platform ${{ matrix.platform }} --push-image |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| #!/bin/bash | ||
| set -e | ||
|
|
||
| # Configuration via environment variables: | ||
| # PROJECT, BUILD_TYPE, EXPLICIT_ROLE, BUILD_NUMBER, EXPLICIT_VERSION | ||
|
|
||
| BASEDIR=/home/builder/build | ||
| export BASEDIR | ||
| export AUTOBUILD_PATH="$BASEDIR/buildscripts" | ||
|
|
||
| mkdir -p "$BASEDIR" | ||
|
|
||
| # Bind-mounted directories may be owned by the host user's UID. | ||
| # Fix ownership so builder can write to them. | ||
| sudo chown -R "$(id -u):$(id -g)" "$HOME/.cache" /output | ||
|
|
||
| # Prevent git "dubious ownership" errors | ||
| git config --global --add safe.directory '*' | ||
|
|
||
| # === Sync source repos === | ||
| repos="buildscripts core masterfiles" | ||
| if [ "$PROJECT" = "nova" ]; then | ||
| repos="$repos enterprise nova mission-portal" | ||
| fi | ||
|
|
||
| for repo in $repos; do | ||
| src="/srv/source/$repo" | ||
| # Use rsync -aL to follow symlinks during copy. | ||
| # The source dir may use symlinks (e.g., core -> cfengine/core/). | ||
| # -L resolves them at copy time, so the destination gets real files | ||
| # regardless of the host directory layout. | ||
| # Exclude acceptance test workdirs — they contain broken symlinks left | ||
| # over from previous test runs and are not needed for building. | ||
| if [ -d "$src" ] || [ -L "$src" ]; then | ||
| echo "Syncing $repo..." | ||
| sudo rsync -aL --exclude='config.cache' --exclude='workdir' --chown="$(id -u):$(id -g)" "$src/" "$BASEDIR/$repo/" | ||
| else | ||
| echo "ERROR: Required repository $repo not found" >&2 | ||
| exit 1 | ||
| fi | ||
| done | ||
|
|
||
| install_mission_portal_deps() ( | ||
| set -e | ||
|
|
||
| if [ -f "$BASEDIR/mission-portal/public/scripts/package.json" ]; then | ||
| echo "Installing npm dependencies..." | ||
| npm ci --prefix "$BASEDIR/mission-portal/public/scripts/" | ||
| echo "Building react components..." | ||
| npm run build --prefix "$BASEDIR/mission-portal/public/scripts/" | ||
| rm -rf "$BASEDIR/mission-portal/public/scripts/node_modules" | ||
| fi | ||
|
|
||
| if [ -f "$BASEDIR/mission-portal/composer.json" ]; then | ||
| echo "Installing Mission Portal PHP dependencies..." | ||
| (cd "$BASEDIR/mission-portal" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs) | ||
| fi | ||
|
|
||
| if [ -f "$BASEDIR/nova/api/http/composer.json" ]; then | ||
| echo "Installing Nova API PHP dependencies..." | ||
| (cd "$BASEDIR/nova/api/http" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs) | ||
| fi | ||
|
|
||
| if [ -f "$BASEDIR/mission-portal/public/themes/default/bootstrap/cfengine_theme.less" ]; then | ||
| echo "Compiling Mission Portal styles..." | ||
| mkdir -p "$BASEDIR/mission-portal/public/themes/default/bootstrap/compiled/css" | ||
| (cd "$BASEDIR/mission-portal/public/themes/default/bootstrap" && | ||
| lessc --compress ./cfengine_theme.less ./compiled/css/cfengine.less.css) | ||
| fi | ||
|
|
||
| if [ -f "$BASEDIR/mission-portal/ldap/composer.json" ]; then | ||
| echo "Installing LDAP API PHP dependencies..." | ||
| (cd "$BASEDIR/mission-portal/ldap" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs) | ||
| fi | ||
| ) | ||
|
|
||
| # === Step runner with failure reporting === | ||
| # Disable set -e so we can capture exit codes and report which step failed. | ||
| set +e | ||
| run_step() { | ||
| local name="$1" | ||
| shift | ||
| echo "=== Running $name ===" | ||
| "$@" | ||
| local rc=$? | ||
| if [ $rc -ne 0 ]; then | ||
| echo "" | ||
| echo "=== FAILED: $name (exit code $rc) ===" | ||
| exit $rc | ||
| fi | ||
| } | ||
|
|
||
| # === Build steps === | ||
| run_step "01-autogen" "$BASEDIR/buildscripts/build-scripts/autogen" | ||
| run_step "02-install-dependencies" "$BASEDIR/buildscripts/build-scripts/install-dependencies" | ||
| if [ "$EXPLICIT_ROLE" = "hub" ]; then | ||
| run_step "03-mission-portal-deps" install_mission_portal_deps | ||
| fi | ||
| run_step "04-configure" "$BASEDIR/buildscripts/build-scripts/configure" | ||
| run_step "05-compile" "$BASEDIR/buildscripts/build-scripts/compile" | ||
| run_step "06-package" "$BASEDIR/buildscripts/build-scripts/package" | ||
|
|
||
| # === Copy output packages === | ||
| # Packages are created under $BASEDIR/<project>/ by dpkg-buildpackage / rpmbuild. | ||
| # Exclude deps-packaging to avoid copying dependency packages. | ||
| find "$BASEDIR" -maxdepth 4 \ | ||
| -path "$BASEDIR/buildscripts/deps-packaging" -prune -o \ | ||
| \( -name '*.deb' -o -name '*.rpm' -o -name '*.pkg.tar.gz' \) -print \ | ||
| -exec cp {} /output/ \; | ||
|
|
||
| echo "" | ||
| echo "=== Build complete ===" | ||
| ls -lh /output/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| # build-in-container | ||
|
|
||
| Build CFEngine packages inside Docker containers using build scripts. Requires | ||
| only Docker and Python 3 on the host. | ||
|
|
||
| ## Quick start | ||
|
|
||
| ```bash | ||
| # Build a community agent .deb for Ubuntu 22 | ||
| ./build-in-container.py --platform ubuntu-22 --project community --role agent --build-type DEBUG | ||
|
|
||
| # Build a nova hub release package for Debian 12 | ||
| ./build-in-container.py --platform debian-12 --project nova --role hub --build-type RELEASE | ||
| ``` | ||
|
|
||
| In the examples above, we run the script from inside `buildscripts/` (with | ||
| `buildscripts` as our current working directory). This is not required — if not | ||
| specified, defaults will: | ||
|
|
||
| - Look for sources relative to the script (parent directory of | ||
| `build-in-container.py`). | ||
| - Place cache files in the user's home directory | ||
| (`~/.cache/cfengine/buildscripts`). | ||
| - Use the current working directory for output packages (`./output/`). | ||
|
|
||
| ## Usage | ||
|
|
||
| ``` | ||
| ./build-in-container.py --platform PLATFORM --project PROJECT --role ROLE --build-type TYPE [OPTIONS] | ||
| ``` | ||
|
|
||
| ### Required arguments | ||
|
|
||
| | Option | Description | | ||
| | -------------- | ------------------------------------------------------- | | ||
| | `--platform` | Target platform (e.g. `ubuntu-22`, `debian-12`) | | ||
| | `--project` | `community` or `nova` (not required for `--push-image`) | | ||
| | `--role` | `agent` or `hub` (not required for `--push-image`) | | ||
| | `--build-type` | `DEBUG` or `RELEASE` (not required for `--push-image`) | | ||
|
|
||
| ### Optional arguments | ||
|
|
||
| | Option | Default | Description | | ||
| | ------------------ | -------------------------------- | ----------------------------------------------------------- | | ||
| | `--output-dir` | `./output` | Where to write output packages | | ||
| | `--cache-dir` | `~/.cache/cfengine/buildscripts` | Dependency cache directory | | ||
| | `--build-number` | `1` | Build number for package versioning | | ||
| | `--version` | auto | Override version string | | ||
| | `--rebuild-image` | | Force rebuild of Docker image (bypasses Docker layer cache) | | ||
| | `--push-image` | | Build image and push to registry, then exit | | ||
| | `--shell` | | Drop into a bash shell inside the container for debugging | | ||
| | `--list-platforms` | | List available platforms and exit | | ||
| | `--source-dir` | parent of `buildscripts/` | Root directory containing repos | | ||
|
|
||
| ## Supported platforms | ||
|
|
||
| | Name | Base image | | ||
| | ----------- | -------------- | | ||
| | `ubuntu-20` | `ubuntu:20.04` | | ||
| | `ubuntu-22` | `ubuntu:22.04` | | ||
| | `ubuntu-24` | `ubuntu:24.04` | | ||
| | `debian-11` | `debian:11` | | ||
| | `debian-12` | `debian:12` | | ||
|
|
||
| Adding a new Debian/Ubuntu platform requires only a new entry in the `PLATFORMS` | ||
| dict in `build-in-container.py`. Adding a non-debian based platform (e.g., | ||
| RHEL/CentOS) requires a new `container/Dockerfile.rhel` plus platform entries. | ||
|
|
||
| ## How it works | ||
|
|
||
| The system has three components: | ||
|
|
||
| 1. **`build-in-container.py`** (Python) -- the orchestrator that runs on the host. | ||
| Parses arguments, builds the Docker image, and launches the container with | ||
| the correct mounts and environment variables. | ||
|
|
||
| 2. **`build-in-container-inner.sh`** (Bash) -- runs inside the container. Copies | ||
| source repos from the read-only mount, then calls the existing build scripts | ||
| in order. | ||
|
|
||
| 3. **`container/Dockerfile.debian`** -- parameterized Dockerfile shared by all | ||
| Debian/Ubuntu platforms via a `BASE_IMAGE` build arg. | ||
|
|
||
| ### Container mounts | ||
|
|
||
| | Host path | Container path | Mode | Purpose | | ||
| | ---------------------------------------- | ----------------------------------------- | ---------- | ------------------------------------- | | ||
| | Source repos (parent of `buildscripts/`) | `/srv/source` | read-only | Protects host repos from modification | | ||
| | `~/.cache/cfengine/buildscripts/` | `/home/builder/.cache/buildscripts_cache` | read-write | Dependency cache shared across builds | | ||
| | `./output/` | `/output` | read-write | Output packages copied here | | ||
|
|
||
| ### Build steps | ||
|
|
||
| The inner script runs these steps in order: | ||
|
|
||
| 1. **autogen** -- runs `autogen.sh` in each repo | ||
| 2. **install-dependencies** -- builds and installs bundled dependencies | ||
| 3. **mission-portal-deps** -- (hub only) installs PHP/npm/LESS assets | ||
| 4. **configure** -- runs `./configure` with platform-appropriate flags | ||
| 5. **compile** -- compiles and installs to the dist tree | ||
| 6. **package** -- creates `.deb` or `.rpm` packages | ||
|
|
||
| ## Docker image management | ||
|
|
||
| By default, the script pulls a pre-built image from the container registry | ||
| (`ghcr.io/cfengine`). If the pull fails (e.g. no network, image not yet | ||
| published), it falls back to building the image locally. | ||
|
|
||
| Use `--rebuild-image` to skip the registry and force a local rebuild — useful | ||
| when iterating on the Dockerfile. The local build tracks the Dockerfile content | ||
| hash and skips rebuilding when nothing has changed. | ||
|
|
||
| ### Container registry | ||
|
|
||
| Images are hosted at `ghcr.io/cfengine` and versioned via `IMAGE_VERSION` in | ||
| `build-in-container.py`. To push a new image: | ||
|
|
||
| ```bash | ||
| # Build and push a single platform | ||
| ./build-in-container.py --platform ubuntu-22 --push-image | ||
| ``` | ||
|
|
||
| `--push-image` always builds with `--no-cache` to pick up the latest upstream | ||
| packages, then pushes to the registry. | ||
|
|
||
| ### Updating the toolchain | ||
|
|
||
| 1. Edit `container/Dockerfile.debian` as needed | ||
| 2. Test locally with `--rebuild-image` | ||
| 3. Bump `IMAGE_VERSION` in `build-in-container.py` | ||
| 4. Commit the Dockerfile change + version bump | ||
| 5. Push new images with `--push-image` (or trigger the GitHub Actions workflow) | ||
|
|
||
| ## Debugging | ||
|
|
||
| ```bash | ||
| # Drop into a shell inside the container | ||
| ./build-in-container.py --platform ubuntu-22 --project community --role agent --build-type DEBUG --shell | ||
| ``` | ||
|
|
||
| The shell session has the same mounts and environment as a build run. The | ||
| container is ephemeral (`--rm`), so any changes are lost on exit. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we should rename
build-scriptsfolder to steps / build-steps 😅.