Skip to content

Commit b3dcf4c

Browse files
committed
docs: update documentation for architecture filtering and cachi2 namespaces
1 parent 4a2c1d2 commit b3dcf4c

3 files changed

Lines changed: 178 additions & 7 deletions

File tree

docs/hermetic-guide.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Benefits:
4040
┌──────────────────────────────────────────────────────────────────┐
4141
│ Prefetch (before podman build) │
4242
│ │
43-
│ Local/GHA: prefetch-all.sh → cachi2/output/deps/
43+
│ Local/GHA: prefetch-all.sh → cachi2/output/<hash>/deps/ │
4444
│ Konflux: prefetch-dependencies Tekton task │
4545
└──────────────────┬───────────────────────────────────────────────┘
4646
@@ -216,9 +216,12 @@ detailed usage.
216216
The `build_image` macro was updated to auto-detect hermetic builds:
217217

218218
```makefile
219-
$(eval CACHI2_VOLUME := $(if $(and $(wildcard cachi2/output),$(wildcard $(BUILD_DIR)prefetch-input)),\
220-
--volume $(ROOT_DIR)cachi2/output:/cachi2/output:Z \
221-
--volume $(ROOT_DIR)cachi2/output/deps/rpm/$(RPM_ARCH)/repos.d/:/etc/yum.repos.d/:Z,))
219+
$(eval COMPONENT_DIR_STR := $(patsubst %/,%,$(BUILD_DIR)))
220+
$(eval CACHI2_HASH := $(shell python3 -c "import hashlib; print(hashlib.md5('$(COMPONENT_DIR_STR)'.encode()).hexdigest())"))
221+
$(eval CACHI2_DIR := cachi2/output/$(CACHI2_HASH))
222+
$(eval CACHI2_VOLUME := $(if $(and $(wildcard $(CACHI2_DIR)),$(wildcard $(BUILD_DIR)prefetch-input)),\
223+
--volume $(ROOT_DIR)$(CACHI2_DIR):/cachi2/output:Z \
224+
--volume $(ROOT_DIR)$(CACHI2_DIR)/deps/rpm/$(RPM_ARCH)/repos.d/:/etc/yum.repos.d/:Z,))
222225
```
223226

224227
This evaluates per-target: only targets with both `cachi2/output/` and a
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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.

scripts/lockfile-generators/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ All scripts must be run from the **repository root**.
4444
**For most local and CI use, this is the main script you need to run.**
4545

4646
`prefetch-all.sh` orchestrates all five lockfile generators in the correct
47-
order, downloading dependencies into `cachi2/output/deps/`. After running it,
48-
the Makefile auto-detects `cachi2/output/` and passes `--volume` to
47+
order, downloading dependencies into `cachi2/output/<hash>/deps/` (where `<hash>` is the MD5 hash of the component directory name to allow concurrent local builds). After running it,
48+
the Makefile auto-detects the component's `cachi2/output/<hash>` directory and passes `--volume` to
4949
`podman build`.
5050

5151
```bash
5252
# Upstream ODH (default variant, CentOS Stream base, no subscription):
5353
scripts/lockfile-generators/prefetch-all.sh \
54-
--component-dir codeserver/ubi9-python-3.12
54+
--component-dir codeserver/ubi9-python-3.12 --arch aarch64
5555

5656
# Downstream RHDS (with RHEL subscription for cdn.redhat.com RPMs):
5757
scripts/lockfile-generators/prefetch-all.sh \
@@ -78,6 +78,7 @@ gmake codeserver-ubi9-python-3.12 BUILD_ARCH=linux/arm64 PUSH_IMAGES=no
7878
| `--component-dir DIR` | Component directory (required), e.g. `codeserver/ubi9-python-3.12` |
7979
| `--rhds` | Use downstream (RHDS) lockfiles instead of upstream (ODH, the default) |
8080
| `--flavor NAME` | Lock file flavor (default: `cpu`) |
81+
| `--arch ARCH` | Target architecture to filter downloads (default: host architecture) |
8182
| `--activation-key KEY` | Red Hat activation key for RHEL RPMs (optional) |
8283
| `--org ORG` | Red Hat organization ID for RHEL RPMs (optional) |
8384

0 commit comments

Comments
 (0)