|
| 1 | +# Hermetic Build Architecture for Codeserver |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The codeserver workbench (`codeserver/ubi9-python-3.12`) uses a fully hermetic build |
| 6 | +where all dependencies (RPMs, npm packages, Python wheels, generic tarballs) are |
| 7 | +prefetched before the Docker build runs. The build operates without network access. |
| 8 | + |
| 9 | +## Build Chain |
| 10 | + |
| 11 | +```text |
| 12 | +prefetch-all.sh → populates cachi2/output/<hash>/deps/ |
| 13 | +Makefile → detects cachi2/output/ → injects --volume into podman build |
| 14 | +sandbox.py → creates minimal build context from Dockerfile COPY/ADD directives |
| 15 | +podman build → runs Dockerfile with /cachi2/output/ mounted |
| 16 | +``` |
| 17 | + |
| 18 | +### prefetch-all.sh |
| 19 | + |
| 20 | +Orchestrates five lockfile generators in sequence: |
| 21 | + |
| 22 | +| Step | Generator | Input | Output | |
| 23 | +|------|-----------|-------|--------| |
| 24 | +| 1 | `create-artifact-lockfile.py` | `artifacts.in.yaml` | `cachi2/output/deps/generic/` (GPG keys, nfpm, node headers, oc client, VS Code extensions) | |
| 25 | +| 2 | `create-requirements-lockfile.sh` | `pyproject.toml` | `cachi2/output/deps/pip/` (Python wheels) | |
| 26 | +| 3 | `download-npm.sh` | `package-lock.json` files | `cachi2/output/deps/npm/` (npm tarballs) | |
| 27 | +| 4 | `hermeto-fetch-rpm.sh` | `rpms.lock.yaml` | `cachi2/output/deps/rpm/{arch}/` (RPMs + repo metadata) | |
| 28 | +| 5 | `create-go-lockfile.sh` | `go.mod` (via git submodule) | `cachi2/output/deps/gomod/` (Go modules) | |
| 29 | + |
| 30 | +Variants are selected via `--rhds` flag: |
| 31 | +- Default (`odh`): uses CentOS Stream + UBI repos (no subscription needed) |
| 32 | +- `--rhds`: uses RHEL subscription repos (needs `--activation-key` and `--org`) |
| 33 | + |
| 34 | +### Makefile auto-detection |
| 35 | + |
| 36 | +```makefile |
| 37 | +$(eval CACHI2_VOLUME := $(if $(and $(wildcard cachi2/output),$(wildcard $(BUILD_DIR)prefetch-input)),\ |
| 38 | + --volume $(ROOT_DIR)cachi2/output:/cachi2/output:Z \ |
| 39 | + --volume $(ROOT_DIR)cachi2/output/deps/rpm/$(RPM_ARCH)/repos.d/:/etc/yum.repos.d/:Z,)) |
| 40 | +``` |
| 41 | + |
| 42 | +When both `cachi2/output/` and `<target>/prefetch-input/` exist, the Makefile |
| 43 | +automatically mounts the prefetched dependencies into the build. The second |
| 44 | +mount overlays `/etc/yum.repos.d/` with hermeto-generated repos, making local |
| 45 | +builds behave like Konflux (repos are already in place when the Dockerfile runs). |
| 46 | + |
| 47 | +### sandbox.py |
| 48 | + |
| 49 | +Wraps `podman build` by creating a minimal build context: |
| 50 | +1. Parses the Dockerfile using `bin/buildinputs` (Go tool, Dockerfile → LLB → JSON) |
| 51 | +2. Identifies all files referenced in COPY/ADD directives |
| 52 | +3. Creates a temporary directory with only those files |
| 53 | +4. Passes `{}` placeholder to podman which gets replaced with the tmpdir path |
| 54 | + |
| 55 | +sandbox.py does NOT modify volumes, build args, or repos — it only manages the |
| 56 | +build context. |
| 57 | + |
| 58 | +## cachi2/output Directory Structure |
| 59 | + |
| 60 | +After prefetching, the directory looks like: |
| 61 | + |
| 62 | +```text |
| 63 | +cachi2/output/ |
| 64 | +├── deps/ |
| 65 | +│ ├── rpm/ |
| 66 | +│ │ ├── x86_64/ |
| 67 | +│ │ │ ├── <repo-name>/ # RPM files + repodata/ |
| 68 | +│ │ │ └── repos.d/ # Generated .repo files with file:// URLs |
| 69 | +│ │ ├── aarch64/ |
| 70 | +│ │ ├── ppc64le/ |
| 71 | +│ │ └── s390x/ |
| 72 | +│ ├── npm/ # npm tarballs |
| 73 | +│ ├── pip/ # Python wheels |
| 74 | +│ └── generic/ # GPG keys, tarballs, etc. |
| 75 | +├── bom.json |
| 76 | +└── .build-config.json |
| 77 | +``` |
| 78 | + |
| 79 | +Key detail: when `rpms.in.yaml` declares `moduleEnable: [nodejs:22]`, hermeto |
| 80 | +downloads module metadata (`modules.yaml`) alongside the RPMs and includes it |
| 81 | +in the generated repodata. This allows `dnf module enable nodejs:22` to work |
| 82 | +with the hermeto repos. Both our `hermeto-fetch-rpm.sh` wrapper and Konflux's |
| 83 | +`prefetch-dependencies-oci-ta` task produce repos with this metadata. |
| 84 | + |
| 85 | +## Three Build Environments |
| 86 | + |
| 87 | +### Local development |
| 88 | + |
| 89 | +```bash |
| 90 | +scripts/lockfile-generators/prefetch-all.sh --component-dir codeserver/ubi9-python-3.12 |
| 91 | +make codeserver-ubi9-python-3.12 |
| 92 | +``` |
| 93 | + |
| 94 | +Makefile detects `cachi2/output/` and auto-injects the volume mount. |
| 95 | + |
| 96 | +### GitHub Actions |
| 97 | + |
| 98 | +The TEMPLATE workflow (`build-notebooks-TEMPLATE.yaml`) handles it transparently: |
| 99 | + |
| 100 | +1. **Prefetch step**: runs `prefetch-all.sh`, outputs `EXTRA_BUILD_ARGS` with |
| 101 | + volume mount |
| 102 | +2. **Build step**: runs `make` with `CONTAINER_BUILD_CACHE_ARGS` containing |
| 103 | + the volume mount |
| 104 | +3. For subscription builds (AIPCC), passes `--rhds --activation-key ... --org ...` |
| 105 | + to use the RHDS variant lockfiles |
| 106 | + |
| 107 | +### Konflux (Tekton) |
| 108 | + |
| 109 | +1. PipelineRun YAML declares `prefetch-input` entries pointing to lockfiles |
| 110 | +2. cachi2's `prefetch-dependencies` task downloads everything using hermeto |
| 111 | +3. Build task mounts `/cachi2/output/` automatically |
| 112 | +4. Network isolation enforced at the pipeline level |
| 113 | + |
| 114 | +All three environments produce the same `/cachi2/output/deps/` structure because |
| 115 | +they all use hermeto under the hood for RPM prefetching. |
| 116 | + |
| 117 | +## Variant Directories (ODH vs RHDS) |
| 118 | + |
| 119 | +Lockfiles are organized into two variant directories under `prefetch-input/`: |
| 120 | + |
| 121 | +```text |
| 122 | +prefetch-input/ |
| 123 | +├── odh/ # upstream (CentOS Stream + UBI repos) |
| 124 | +│ ├── rpms.in.yaml |
| 125 | +│ ├── rpms.lock.yaml |
| 126 | +│ ├── artifacts.in.yaml |
| 127 | +│ └── artifacts.lock.yaml |
| 128 | +├── rhds/ # downstream (RHEL subscription repos) |
| 129 | +│ ├── rpms.in.yaml |
| 130 | +│ ├── rpms.lock.yaml |
| 131 | +│ ├── artifacts.in.yaml |
| 132 | +│ └── artifacts.lock.yaml |
| 133 | +├── repos/ # shared DNF repo definitions |
| 134 | +├── code-server/ # git submodule (vendored source) |
| 135 | +└── patches/ # build patches for offline operation |
| 136 | +``` |
| 137 | + |
| 138 | +ODH uses CentOS Stream packages; RHDS uses RHEL packages. The choice matters |
| 139 | +because base images differ: ODH uses a c9s base, AIPCC uses a RHEL base. |
| 140 | +Mixing variants causes RPM conflicts (see openssl-fips-provider-conflict.md). |
| 141 | + |
| 142 | +## Dockerfile Structure |
| 143 | + |
| 144 | +The Dockerfile is multi-stage with 5 stages: |
| 145 | + |
| 146 | +| Stage | Purpose | |
| 147 | +|-------|---------| |
| 148 | +| `rpm-base` | Builds code-server from source into an RPM | |
| 149 | +| `whl-cache` | Installs Python wheels, exports compiled C-extension wheels for ppc64le/s390x | |
| 150 | +| `cpu-base` | Installs OS packages + tools (oc client, micropipenv, uv) | |
| 151 | +| `codeserver` | Final image (code-server + nginx + Python packages) | |
| 152 | +| `tests` | Smoke test stage | |
| 153 | + |
| 154 | +Each stage that runs `dnf install` needs repos configured. Repos are injected |
| 155 | +by the infrastructure, not by the Dockerfile: |
| 156 | + |
| 157 | +- **Local/GHA**: The Makefile volume-mounts `repos.d/` at `/etc/yum.repos.d/`, |
| 158 | + overlaying the base image's default repos. |
| 159 | +- **Konflux**: The `buildah-oci-ta` task volume-mounts `YUM_REPOS_D_FETCHED` |
| 160 | + at `/etc/yum.repos.d/` in the same way. |
| 161 | + |
| 162 | +Both environments replace the base image's default repos. For targets that |
| 163 | +need nodejs (codeserver), `rpms.in.yaml` declares `moduleEnable: [nodejs:22]`, |
| 164 | +which makes hermeto include module metadata in the repodata. The Dockerfile |
| 165 | +runs `dnf module enable nodejs:22 -y` to activate the module stream. |
| 166 | + |
| 167 | +No `LOCAL_BUILD` build arg, no if/else branching, no `rm -f` or `cp` of repos. |
0 commit comments