Skip to content

Commit 5692b1b

Browse files
committed
RHAIENG-2846 Prefetch: COMPONENT_DIR from Makefile, local build default, Tekton discovery
- GHA: Derive COMPONENT_DIR from Makefile; fail clearly if missing. - Makefile: Always pass LOCAL_BUILD=true for hermetic targets; require cachi2/output when prefetch-input exists; fix empty-variable warning. - prefetch-all.sh: Add find_tekton_yaml (ODH/RHDS by dockerfile param); remove --tekton-file and package-lock fallback; allow running without variant dir. - download-npm.sh: Exit 0 when Tekton file has no npm entries. - Regenerate artifacts.lock.yaml and pylock.cpu.toml; update README.
1 parent 3c7287e commit 5692b1b

File tree

8 files changed

+192
-105
lines changed

8 files changed

+192
-105
lines changed

.github/workflows/build-notebooks-TEMPLATE.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,13 @@ jobs:
280280
id: prefetch
281281
run: |
282282
set -Eeuxo pipefail
283-
COMPONENT_DIR=$(echo "${{ inputs.target }}" | sed 's|-|/|')
283+
# Derive component dir from Makefile (single source of truth). Works for all
284+
# image targets (codeserver, jupyter-*, runtime-*, rstudio-*, base-images-*).
285+
COMPONENT_DIR=$(make -n "${{ inputs.target }}" 2>&1 | sed -n 's/.*#\*# Image build directory: <\([^>]*\)>.*/\1/p' | head -n1)
286+
if [ -z "$COMPONENT_DIR" ]; then
287+
echo "Could not derive COMPONENT_DIR from Makefile for target ${{ inputs.target }}" >&2
288+
exit 1
289+
fi
284290
if [ -d "$COMPONENT_DIR/prefetch-input" ]; then
285291
echo "Hermetic build detected — prefetching dependencies for $COMPONENT_DIR"
286292
pip3 install --quiet --break-system-packages pyyaml

Makefile

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ SHELL := bash
1111
.DELETE_ON_ERROR:
1212
MAKEFLAGS += --warn-undefined-variables
1313
MAKEFLAGS += --no-builtin-rules
14+
# Used where we need an empty expansion (avoids undefined variable warning).
15+
empty :=
1416

1517
# todo: leave the default recipe prefix for now
1618
ifeq ($(origin .RECIPEPREFIX), undefined)
@@ -85,14 +87,20 @@ define build_image
8587
awk -F= '!/^#/ && NF {gsub(/^[ \t]+|[ \t]+$$/, "", $$1); gsub(/^[ \t]+|[ \t]+$$/, "", $$2); printf "--build-arg %s=%s ", $$1, $$2}' $(CONF_FILE); \
8688
fi))
8789

88-
# Hermetic local build: when cachi2/output/ exists AND this target has a
89-
# prefetch-input/ directory, mount pre-downloaded deps and set LOCAL_BUILD=true.
90-
$(eval CACHI2_VOLUME := $(if $(and $(wildcard cachi2/output),$(wildcard $(BUILD_DIR)prefetch-input)),--volume $(ROOT_DIR)cachi2/output:/cachi2/output:Z --build-arg LOCAL_BUILD=true,))
90+
# Make is only used for local and GHA builds (Konflux does not run make).
91+
# Default to local build for hermetic-capable targets: always set LOCAL_BUILD=true
92+
# when prefetch-input/ exists; mount cachi2/output only when it exists (after prefetch).
93+
$(eval LOCAL_BUILD_ARG := $(if $(wildcard $(BUILD_DIR)prefetch-input),--build-arg LOCAL_BUILD=true,))
94+
$(eval CACHI2_VOLUME := $(if $(and $(wildcard cachi2/output),$(wildcard $(BUILD_DIR)prefetch-input)),--volume $(ROOT_DIR)cachi2/output:/cachi2/output:Z,))
9195

9296
$(info # Building $(IMAGE_NAME) using $(DOCKERFILE_NAME) with $(CONF_FILE) and $(BUILD_ARGS)...)
9397

98+
@if [ -d '$(BUILD_DIR)prefetch-input' ] && [ ! -d cachi2/output ]; then \
99+
echo "Prefetch required for hermetic build. Run: scripts/lockfile-generators/prefetch-all.sh --component-dir $(patsubst %/,%,$(BUILD_DIR)) — see scripts/lockfile-generators/README.md"; \
100+
exit 1; \
101+
fi
94102
$(ROOT_DIR)/scripts/sandbox.py --dockerfile '$(2)' --platform '$(BUILD_ARCH)' -- \
95-
$(CONTAINER_ENGINE) build $(CONTAINER_BUILD_CACHE_ARGS) $(CACHI2_VOLUME) --platform=$(BUILD_ARCH) --label release=$(RELEASE) --tag $(IMAGE_NAME) --file '$(2)' $(BUILD_ARGS) {}\;
103+
$(CONTAINER_ENGINE) build $(CONTAINER_BUILD_CACHE_ARGS) $(LOCAL_BUILD_ARG) $(CACHI2_VOLUME) --platform=$(BUILD_ARCH) --label release=$(RELEASE) --tag $(IMAGE_NAME) --file '$(2)' $(BUILD_ARGS) {}\;
96104
endef
97105

98106
# Push function for the notebook image:
@@ -111,8 +119,8 @@ endef
111119
define image
112120
$(eval BUILD_DIRECTORY := $(shell echo $(2) | sed 's/\/Dockerfile.*//'))
113121
$(eval VARIANT := $(shell echo $(notdir $(2)) | cut -d. -f2))
114-
$(eval DOCKERFILE := $(BUILD_DIRECTORY)/Dockerfile$(if $(KONFLUX:no=),.konflux,$()).$(VARIANT))
115-
$(eval CONF_FILE := $(BUILD_DIRECTORY)/build-args/$(if $(KONFLUX:no=),konflux.,$())$(shell echo $(VARIANT)).conf)
122+
$(eval DOCKERFILE := $(BUILD_DIRECTORY)/Dockerfile$(if $(KONFLUX:no=),.konflux,$(empty)).$(VARIANT))
123+
$(eval CONF_FILE := $(BUILD_DIRECTORY)/build-args/$(if $(KONFLUX:no=),konflux.,$(empty))$(shell echo $(VARIANT)).conf)
116124
$(info #*# Image build Dockerfile: <$(DOCKERFILE)> #(MACHINE-PARSED LINE)#*#...)
117125
$(info #*# Image build directory: <$(BUILD_DIRECTORY)> #(MACHINE-PARSED LINE)#*#...)
118126

codeserver/ubi9-python-3.12/prefetch-input/odh/artifacts.lock.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ artifacts:
3535
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-13/ripgrep-v13.0.0-13-x86_64-unknown-linux-musl.tar.gz
3636
checksum: sha256:324cd645481db1ceda8621409c9151fc58d182b90cc5c428db80edb21fb26df3
3737
filename: ripgrep-v13.0.0-13-x86_64-unknown-linux-musl.tar.gz
38-
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-13/ripgrep-v13.0.0-13-aarch64-unknown-linux-musl.tar.gz
39-
checksum: sha256:0f308620a428f56fe871fcc5d7c668c461dfed3244f717b698f3e9e92aca037a
38+
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-13/ripgrep-v13.0.0-13-aarch64-unknown-linux-gnu.tar.gz
39+
checksum: sha256:1b0ca509f8707f2128f1b3ef245c3ea666d49a737431288536d49bd74652d143
4040
filename: ripgrep-v13.0.0-13-aarch64-unknown-linux-musl.tar.gz
4141
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-13/ripgrep-v13.0.0-13-powerpc64le-unknown-linux-gnu.tar.gz
4242
checksum: sha256:a3fdb2c6ef9d4ff927ca1cb1e56f7aed7913d1be4dd4546aec400118c26452ab
43-
filename: ripgrep-v13.0.0-13-powerpc64le-unknown-linux-gnu.tar.gz
43+
filename: ripgrep-v13.0.0-13-powerpc64le-unknown-linux-musl.tar.gz
4444
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-13/ripgrep-v13.0.0-13-s390x-unknown-linux-gnu.tar.gz
4545
checksum: sha256:555983c74789d553b107c5a2de90b883727b3e43d680f0cd289a07407efb2755
46-
filename: ripgrep-v13.0.0-13-s390x-unknown-linux-gnu.tar.gz
46+
filename: ripgrep-v13.0.0-13-s390x-unknown-linux-musl.tar.gz
4747
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-4/ripgrep-v13.0.0-4-powerpc64le-unknown-linux-gnu.tar.gz
4848
checksum: sha256:3ddd7c0797c14cefd3ee61f13f15ac219bfecee8e6f6e27fd15c102ef229653a
4949
filename: ripgrep-v13.0.0-4-powerpc64le-unknown-linux-gnu.tar.gz

codeserver/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/lockfile-generators/README.md

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ All scripts must be run from the **repository root**.
3737

3838
---
3939

40-
## Orchestrator `prefetch-all.sh`
40+
## Orchestrator `prefetch-all.sh`
4141

42-
**For most local and CI use, this is the only script you need to run.**
42+
**For most local and CI use, this is the main script you need to run.**
4343

4444
`prefetch-all.sh` orchestrates all four lockfile generators in the correct
4545
order, downloading dependencies into `cachi2/output/deps/`. After running it,
@@ -66,6 +66,7 @@ Then build with make:
6666
```bash
6767
# On macOS use gmake
6868
gmake codeserver-ubi9-python-3.12 BUILD_ARCH=linux/arm64 PUSH_IMAGES=no
69+
# OR using Local podman build (Please scroll downn to Local podman build section for more detail.)
6970
```
7071

7172
### Options
@@ -75,18 +76,30 @@ gmake codeserver-ubi9-python-3.12 BUILD_ARCH=linux/arm64 PUSH_IMAGES=no
7576
| `--component-dir DIR` | Component directory (required), e.g. `codeserver/ubi9-python-3.12` |
7677
| `--rhds` | Use downstream (RHDS) lockfiles instead of upstream (ODH, the default) |
7778
| `--flavor NAME` | Lock file flavor (default: `cpu`) |
78-
| `--tekton-file FILE` | Tekton PipelineRun YAML for npm path discovery (auto-detected from `.tekton/` if omitted) |
7979
| `--activation-key KEY` | Red Hat activation key for RHEL RPMs (optional) |
8080
| `--org ORG` | Red Hat organization ID for RHEL RPMs (optional) |
8181

8282
### What it does
8383

8484
| Step | Condition | Script called |
8585
|------|-----------|---------------|
86-
| 1. Generic artifacts | `artifacts.in.yaml` exists | `create-artifact-lockfile.py` |
87-
| 2. Pip wheels | `pyproject.toml` exists | `create-requirements-lockfile.sh --download` |
88-
| 3. NPM packages | `package-lock.json` files found | `download-npm.sh` |
89-
| 4. RPMs | `rpms.in.yaml` exists | `hermeto-fetch-rpm.sh` (if lockfile committed) or `create-rpm-lockfile.sh --download` |
86+
| 1. Generic artifacts | `prefetch-input/<variant>/artifacts.in.yaml` exists | `create-artifact-lockfile.py` |
87+
| 2. Pip wheels | `pyproject.toml` exists in component dir | `create-requirements-lockfile.sh --download` |
88+
| 3. NPM packages | Tekton PipelineRun found for component (see below) | `download-npm.sh --tekton-file` |
89+
| 4. RPMs | `prefetch-input/<variant>/rpms.in.yaml` exists | `hermeto-fetch-rpm.sh` (if lockfile committed) or `create-rpm-lockfile.sh --download` |
90+
91+
**Variant directory:** Lockfiles live under `prefetch-input/odh/` (upstream) or
92+
`prefetch-input/rhds/` (downstream). If that directory is missing, steps 1 and 4
93+
are skipped; steps 2 (pip) and 3 (npm) still run when their inputs exist
94+
(`pyproject.toml`, or a Tekton file for the component).
95+
96+
**Step 3 (NPM):** The script finds the Tekton file automatically via
97+
`find_tekton_yaml`: it looks for a `.tekton/*pull-request*.yaml` whose
98+
`dockerfile` param matches this component, RHDS first
99+
(`COMPONENT_DIR/Dockerfile.konflux.*`), then ODH (`COMPONENT_DIR/Dockerfile.*`).
100+
If no Tekton file is found, npm is skipped. If the Tekton file has no
101+
`npm`-type `prefetch-input` entries, `download-npm.sh` exits successfully
102+
(nothing to download).
90103

91104
Steps are skipped if their input files don't exist. For RPMs, if
92105
`rpms.lock.yaml` is already committed, it downloads directly (skipping
@@ -95,11 +108,13 @@ lockfile regeneration) — this avoids cross-platform issues on arm64 CI runners
95108
### GitHub Actions integration
96109

97110
The GHA workflow template (`.github/workflows/build-notebooks-TEMPLATE.yaml`)
98-
calls `prefetch-all.sh` automatically for codeserver targets before running
99-
`make`. Non-codeserver targets skip the prefetch step entirely. After the
100-
build, container tests run (e.g. `tests/containers` with pytest); image
101-
metadata is read from both Docker `Config` and `ContainerConfig` so labels
102-
work when the daemon is Podman (see
111+
derives the component directory from the **Makefile** (dry-run of the build
112+
target, parsing `#*# Image build directory: <...>`), so it works for all image
113+
targets (codeserver, jupyter-*, runtime-*, rstudio-*, base-images-*). Prefetch
114+
runs when `COMPONENT_DIR/prefetch-input` exists; otherwise the step is skipped.
115+
After the build, container tests run (e.g. `tests/containers` with pytest);
116+
image metadata is read from both Docker `Config` and `ContainerConfig` so
117+
labels work when the daemon is Podman (see
103118
[tests/containers/docs/github-vs-local-image-metadata.md](../../tests/containers/docs/github-vs-local-image-metadata.md)).
104119

105120
**uv version:** The repo root `uv.toml` specifies the `uv` version (e.g.
@@ -110,15 +125,17 @@ work when the daemon is Podman (see
110125

111126
## Individual tools
112127

113-
The four scripts below can also be run individually for debugging or partial
114-
updates. `prefetch-all.sh` calls them internally.
128+
The five options below can be used for hermetic builds. Scripts 1–4 can also be
129+
run individually for debugging or partial updates; `prefetch-all.sh` calls them
130+
internally. Option 5 (Git submodule) is a manual setup.
115131

116132
| # | Type | Main script | What it generates |
117133
|---|------|-------------|-------------------|
118134
| 1 | Generic | [create-artifact-lockfile.py](#1-generic-artifacts--create-artifact-lockfilepy) | `artifacts.lock.yaml` |
119135
| 2 | RPM | [create-rpm-lockfile.sh](#2-rpm-packages--create-rpm-lockfilesh) | `rpms.lock.yaml` |
120136
| 3 | npm | [download-npm.sh](#3-npm-packages--download-npmsh) | Downloaded tarballs in `cachi2/output/deps/npm/` |
121137
| 4 | pip (RHOAI) | [create-requirements-lockfile.sh](#4-pip-packages-rhoai--create-requirements-lockfilesh) | `pylock.<flavor>.toml` + `requirements.<flavor>.txt` |
138+
| 5 | Git submodule | (manual setup) | [Pinned repo under prefetch-input/](#5-git-submodule) |
122139

123140
### Helper scripts (used internally by the main tools)
124141

@@ -516,7 +533,8 @@ collisions. Files that already exist are skipped.
516533
- `--lock-file <path>` — process a single `package-lock.json`.
517534
- `--tekton-file <path>` — parse a Tekton PipelineRun YAML to discover all
518535
`npm`-type `prefetch-input` paths, then process every `package-lock.json`
519-
found under them.
536+
found under them. If the file has **no** `npm`-type entries, the script
537+
exits 0 (nothing to download) instead of erroring.
520538

521539
Both flags can be combined. URLs that are already local (`file:///cachi2/...`)
522540
are automatically skipped.
@@ -707,39 +725,89 @@ python3 scripts/lockfile-generators/helpers/download-pip-packages.py \
707725

708726
---
709727

728+
## 5. Git submodule
729+
730+
The notebooks repository uses external code (e.g. code-server) that is normally
731+
cloned during the Docker build. For hermetic builds, Konflux can prefetch these
732+
dependencies via **git submodules**: the external repo is added as a submodule
733+
under `prefetch-input/` and pinned to a specific commit or tag. The build then
734+
uses the checked-out tree instead of running `git clone` at build time.
735+
736+
### Setup
737+
738+
Run from the **repository root**. Replace the submodule URL and
739+
`<component>/prefetch-input/<name>` with your target path (e.g.
740+
`codeserver/ubi9-python-3.12/prefetch-input/code-server`).
741+
742+
```bash
743+
# Add the external repo as a submodule under prefetch-input
744+
git submodule add https://github.com/coder/code-server.git \
745+
codeserver/ubi9-python-3.12/prefetch-input/code-server
746+
747+
# Pin to a specific tag (or commit)
748+
cd codeserver/ubi9-python-3.12/prefetch-input/code-server
749+
git fetch --tags
750+
git checkout tags/v4.104.0
751+
git submodule update --init --recursive # pull nested submodules if any
752+
753+
# Commit the submodule and .gitmodules
754+
cd ../..
755+
git add -A .
756+
git commit -m "Added submodule code-server"
757+
```
758+
759+
Use the same tag or commit that your Dockerfile or build scripts expect, so the
760+
hermetic build uses an identical source tree.
761+
762+
---
763+
710764
## Appendix: Local podman build
711765

712766
After running `prefetch-all.sh`, the **recommended** way to build is via make:
713767

714768
```bash
715-
# Makefile auto-detects cachi2/output/ and injects --volume + LOCAL_BUILD=true
769+
# Make sets LOCAL_BUILD=true for hermetic targets; mounts cachi2/output when it exists
716770
gmake codeserver-ubi9-python-3.12 BUILD_ARCH=linux/arm64 PUSH_IMAGES=no
717771
```
718772

719-
The Makefile evaluates each target independently: `CACHI2_VOLUME` is only set
720-
when both `cachi2/output/` exists AND the target directory has a
721-
`prefetch-input/` subdirectory. Non-hermetic targets are completely unaffected.
773+
The Makefile sets `LOCAL_BUILD=true` for any target that has `prefetch-input/`;
774+
it adds the cachi2 volume only when `cachi2/output/` exists (after prefetch).
775+
Non-hermetic targets are unaffected.
722776

723777
### Alternative: manual podman build
724778

725-
For developers who want to run `podman build` directly, the key flags are:
779+
Running `podman build` directly differs from `gmake` in these ways:
780+
781+
| Aspect | `gmake codeserver-ubi9-python-3.12 BUILD_ARCH=... PUSH_IMAGES=no` | Manual `podman build ...` |
782+
|--------|-------------------------------------------------------------------|---------------------------|
783+
| **Build context** | Minimal (via `scripts/sandbox.py`: only files needed by the Dockerfile) | Full repo (`.`). |
784+
| **Volume** | `--volume $(ROOT_DIR)cachi2/output:/cachi2/output:Z` (mounts only `cachi2/output`) | Often `-v ./cachi2:/cachi2` (mounts whole dir); equivalent is `-v ./cachi2/output:/cachi2/output:z`. |
785+
| **Build args** | From `build-args/cpu.conf`: `INDEX_URL`, `BASE_IMAGE`, `PYLOCK_FLAVOR` | You must pass these (and `LOCAL_BUILD=true`) explicitly. |
786+
| **Tag** | `$(IMAGE_REGISTRY):codeserver-ubi9-python-3.12-$(RELEASE)_$(DATE)` | Whatever you pass with `-t`. |
787+
| **Label** | `--label release=$(RELEASE)` | Omitted unless you add it. |
788+
| **Cache** | Default `CONTAINER_BUILD_CACHE_ARGS ?= --no-cache` | Podman uses its default cache unless you pass `--no-cache`. |
789+
790+
To approximate the make build when running podman manually, use the same volume
791+
path as make and pass all build-args from `build-args/cpu.conf`:
726792

727-
- `-v $(realpath ./cachi2):/cachi2:z` bind-mount the prefetched dependencies
728-
so the Dockerfile can install from them offline.
729-
- `--build-arg LOCAL_BUILD=true` signals the Dockerfile that this is a local
730-
build (configures dnf to use the local cachi2 RPM repo).
793+
- Bind-mount **only** `cachi2/output` at `/cachi2/output` (same as make).
794+
- Pass `LOCAL_BUILD=true` and the same `BASE_IMAGE`, `PYLOCK_FLAVOR`, and
795+
`INDEX_URL` as in `codeserver/ubi9-python-3.12/build-args/cpu.conf`.
731796

732797
```bash
798+
# Same volume path as Makefile; build-args from build-args/cpu.conf
733799
podman build \
734800
-f codeserver/ubi9-python-3.12/Dockerfile.cpu \
735-
--platform linux/amd64 \
801+
--platform linux/arm64 \
736802
-t code-server-test \
737803
--build-arg LOCAL_BUILD=true \
738804
--build-arg BASE_IMAGE=quay.io/opendatahub/odh-base-image-cpu-py312-c9s:latest \
739805
--build-arg PYLOCK_FLAVOR=cpu \
740-
-v "$(realpath ./cachi2):/cachi2:z" \
806+
--build-arg INDEX_URL=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4-EA1/cpu-ubi9/simple/ \
807+
-v "$(realpath ./cachi2/output):/cachi2/output:z" \
741808
.
742809
```
743810

744-
To build for a different architecture, change `--platform` and `ARCH`
745-
accordingly (e.g. `linux/arm64` / `aarch64`, `linux/ppc64le` / `ppc64le`).
811+
To build for a different architecture, change `--platform` (e.g. `linux/amd64`,
812+
`linux/arm64`, `linux/ppc64le`). The manual command uses the **full repo** as
813+
context; make uses a **sandboxed** context for reproducibility.

scripts/lockfile-generators/download-npm.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ if [[ -n "$TEKTON_FILE" ]]; then
197197
npm_paths=$(extract_npm_paths_from_tekton "$TEKTON_FILE")
198198

199199
if [[ -z "$npm_paths" ]]; then
200-
error_exit "No npm-type prefetch-input entries found in $TEKTON_FILE"
200+
echo "No npm-type prefetch-input entries in $TEKTON_FILE — nothing to download"
201+
exit 0
201202
fi
202203

203204
while IFS= read -r npm_path; do

0 commit comments

Comments
 (0)