Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions base-images/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Tag bumps for `quay.io/centos/centos` are blocked by `allowedVersions` in
| `cpu/` | CPU-only base image |
| `cuda/` | NVIDIA CUDA (12.9, 13.0) |
| `rocm/` | AMD ROCm (6.4, 7.1) |
| `build-args/` | `.conf` files with `INDEX_URL` per variant |
| `build-args/` | `.conf` files with `INDEX_URL` per variant (managed by `versions_config.yml` → `make sync-build-args-from-versions`) |
| `utils/` | Shared scripts: `aipcc.sh`, `dnf-helper.sh`, `fix-permissions`, `pip.conf.in`, `uv.toml.in` |
| `copr/` | Tool to rebuild Fedora SRPMs for EL9 ([README](copr/README.md)) |

Expand Down Expand Up @@ -89,5 +89,7 @@ Steps:
1. Copy an existing version directory (e.g. `cuda/12.9/` -> `cuda/13.1/`)
2. Update the Dockerfile stages from the upstream vendor Dockerfiles for the new SDK version
3. Update `cuda-repos/` repo files if needed (GPG keys, baseurls)
4. Create `build-args/<variant>.conf` with the correct `INDEX_URL`
4. Set `release.aipcc_wheel_index` in `versions_config.yml` and run
`make sync-build-args-from-versions` so `build-args/<variant>.conf` gets the
correct `INDEX_URL` (do not hand-edit those confs)
5. Add Tekton pipeline YAMLs in `.tekton/`
42 changes: 40 additions & 2 deletions ci/versions_config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "versions_config.schema.json",
"$defs": {
"AipccWheelIndex": {
"additionalProperties": false,
"description": "AIPCC wheel index stream baked into ODH base-images INDEX_URL build-args.",
"properties": {
"stream": {
"description": "AIPCC public-rhai rhoai path segment for base-images INDEX_URL (for example \"3.5-EA2\" or \"3.5\"). Independent of release.full_version.",
"examples": [
"3.5-EA2",
"3.6-EA1",
"3.5"
],
"pattern": "^[0-9]+\\.[0-9]+(-EA[0-9]+)?$",
"title": "AIPCC wheel index stream",
"type": "string"
},
"use_test": {
"description": "When true, use *-ubi9-test simple indexes; when false, use prod *-ubi9 indexes.",
"title": "Use test index",
"type": "boolean"
}
},
"required": [
"stream",
"use_test"
],
"title": "AipccWheelIndex",
"type": "object"
},
"Artifacts": {
"additionalProperties": false,
"description": "Artifact groups managed by the versions sync flow.",
Expand Down Expand Up @@ -213,12 +241,18 @@
"pattern": "^[0-9]+\\.[0-9]+$",
"title": "Python version",
"type": "string"
},
"aipcc_wheel_index": {
"$ref": "#/$defs/AipccWheelIndex",
"description": "Operator input for base-images/build-args/*.conf INDEX_URL. Synced by make sync-build-args-from-versions.",
"title": "AIPCC wheel index"
}
},
"required": [
"full_version",
"rhds_os_base",
"python_version"
"python_version",
"aipcc_wheel_index"
],
"title": "Release",
"type": "object"
Expand Down Expand Up @@ -338,7 +372,11 @@
"release": {
"full_version": "3.5.0",
"rhds_os_base": "el9.6",
"python_version": "3.12"
"python_version": "3.12",
"aipcc_wheel_index": {
"stream": "3.5-EA2",
"use_test": true
}
},
"artifacts": {
"base_image": {
Expand Down
30 changes: 30 additions & 0 deletions ci/versions_config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@
examples=["13.0", "7.14"],
),
]
AIPCC_WHEEL_INDEX_STREAM_PATTERN = r"^[0-9]+\.[0-9]+(-EA[0-9]+)?$"
AipccWheelIndexStream = Annotated[
str,
StringConstraints(pattern=AIPCC_WHEEL_INDEX_STREAM_PATTERN),
Field(
title="AIPCC wheel index stream",
description=(
"AIPCC public-rhai rhoai path segment for base-images INDEX_URL "
'(for example "3.5-EA2" or "3.5"). Independent of release.full_version.'
),
examples=["3.5-EA2", "3.6-EA1", "3.5"],
),
]
OdhOrigin = Literal["in-house", "midstream"]
SchemaVersion = Literal[1]

Expand All @@ -82,6 +95,16 @@ class StrictModel(BaseModel):
model_config = STRICT_CONFIG


class AipccWheelIndex(StrictModel):
"""AIPCC wheel index stream baked into ODH base-images INDEX_URL build-args."""

stream: AipccWheelIndexStream
use_test: bool = Field(
title="Use test index",
description="When true, use *-ubi9-test simple indexes; when false, use prod *-ubi9 indexes.",
)


class Release(StrictModel):
"""Release metadata shared across RHDS and ODH base-image resolution."""

Expand All @@ -92,6 +115,12 @@ class Release(StrictModel):
)
rhds_os_base: RhdsOsBase
python_version: PythonVersion
aipcc_wheel_index: AipccWheelIndex = Field(
title="AIPCC wheel index",
description=(
"Operator input for base-images/build-args/*.conf INDEX_URL. Synced by make sync-build-args-from-versions."
),
)


class RhdsFastCpuPolicy(StrictModel):
Expand Down Expand Up @@ -226,6 +255,7 @@ def build_json_schema() -> dict[str, Any]:
"full_version": "3.5.0",
"rhds_os_base": "el9.6",
"python_version": "3.12",
"aipcc_wheel_index": {"stream": "3.5-EA2", "use_test": True},
},
"artifacts": {
"base_image": {
Expand Down
24 changes: 21 additions & 3 deletions docs/base_image_versions_update_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,27 @@ The sync flow manages:
- `RELEASE` in managed `build-args/*.conf` files when that key is already present
- root `Makefile` `RELEASE`
- root `Makefile` `RELEASE_PYTHON_VERSION`
- `INDEX_URL` in `base-images/build-args/{cpu,cuda12.9,cuda13.0,rocm7.14}.conf`
from `release.aipcc_wheel_index`

It does not update unrelated files or regenerate lock files. If the image update
also requires Python lock refreshes, run `make refresh-lock-files` separately.

The script only manages known build-args filenames:
The script only manages known build-args filenames under notebook trees:

- `cpu.conf`, `cuda.conf`, `rocm.conf`
- `konflux.cpu.conf`, `konflux.cuda.conf`, `konflux.rocm.conf`

If an unexpected `build-args` filename appears in a managed tree, the sync fails
early instead of guessing.
For ODH base images it manages exactly these `INDEX_URL` conf files:

- `base-images/build-args/cpu.conf`
- `base-images/build-args/cuda12.9.conf`
- `base-images/build-args/cuda13.0.conf`
- `base-images/build-args/rocm7.14.conf`

If an unexpected `build-args` filename appears in a managed tree (or an unexpected
or missing file under `base-images/build-args/`), the sync fails early instead of
guessing.

## Prerequisites

Expand Down Expand Up @@ -98,6 +108,9 @@ release:
full_version: "3.5.0"
rhds_os_base: "el9.6"
python_version: "3.12"
aipcc_wheel_index:
stream: "3.5-EA2"
use_test: true

artifacts:
base_image:
Expand Down Expand Up @@ -126,6 +139,11 @@ artifacts:
`el9.6`
- `release.python_version` selects the managed `RELEASE_PYTHON_VERSION` value and
drives CPU ODH repository naming
- `release.aipcc_wheel_index.stream` is the AIPCC `public-rhai/rhoai/<stream>/…`
path segment for ODH base-image `INDEX_URL` values (independent of
`full_version`; for example `3.5-EA2` or `3.6-EA1`)
- `release.aipcc_wheel_index.use_test` selects `*-ubi9-test` indexes when true,
otherwise prod `*-ubi9` indexes

### Policy Keys

Expand Down
96 changes: 91 additions & 5 deletions scripts/update_build_args_from_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

This flow validates the root config, scans managed image-tree ``build-args``
files, rewrites managed ``BASE_IMAGE`` and ``RELEASE`` assignments plus the
root ``Makefile`` release defaults, resolves newer RHDS ``channel: fast``
releases to the highest already-published phase per target repository, uses
``skopeo`` to select the latest build in the chosen release-and-phase family,
and pins each resolved ``BASE_IMAGE`` to an immutable
``repository:tag@sha256:…`` reference.
root ``Makefile`` release defaults, rewrites ``INDEX_URL`` in
``base-images/build-args/*.conf`` from ``release.aipcc_wheel_index``, resolves
newer RHDS ``channel: fast`` releases to the highest already-published phase
per target repository, uses ``skopeo`` to select the latest build in the
chosen release-and-phase family, and pins each resolved ``BASE_IMAGE`` to an
immutable ``repository:tag@sha256:…`` reference.
"""

from __future__ import annotations
Expand All @@ -25,9 +26,20 @@

import yaml

from scripts.index_url_resolver import build_rhoai_index_url, build_rhoai_test_index_url

ROOT_DIR = Path(__file__).resolve().parents[1]
DEFAULT_CONFIG_PATH = ROOT_DIR / "versions_config.yml"
MANAGED_ROOTS = ("jupyter", "runtimes", "codeserver")
BASE_IMAGES_BUILD_ARGS_DIR = Path("base-images") / "build-args"
# Tekton --build-arg-file inputs for ODH base images (INDEX_URL only).
BASE_IMAGES_INDEX_CONFS: dict[str, str] = {
"cpu.conf": "cpu",
"cuda12.9.conf": "cuda12.9",
"cuda13.0.conf": "cuda13.0",
"rocm7.14.conf": "rocm7.14",
}
AIPCC_WHEEL_INDEX_STREAM_RE = re.compile(r"^[0-9]+\.[0-9]+(-EA[0-9]+)?$")
POLICY_SCHEMA = object()
GPU_FLAVORS = {
"cuda": ("minimal", "pytorch", "pytorch-llmcompressor", "tensorflow"),
Expand Down Expand Up @@ -84,6 +96,10 @@
"full_version": None,
"rhds_os_base": None,
"python_version": None,
"aipcc_wheel_index": {
"stream": None,
"use_test": None,
},
},
"artifacts": {
"base_image": BASE_IMAGE_SCHEMA,
Expand All @@ -109,11 +125,18 @@
_STABLE_ACC_VERSION_INSPECT_FAILED = object()


@dataclass(frozen=True)
class AipccWheelIndexConfig:
stream: str
use_test: bool


@dataclass(frozen=True)
class ReleaseConfig:
full_version: str
rhds_os_base: str
python_version: str
aipcc_wheel_index: AipccWheelIndexConfig


@dataclass(frozen=True)
Expand Down Expand Up @@ -549,10 +572,12 @@ def load_versions_config(path: Path) -> VersionsConfig:
raise ValueError(f"Unsupported schema_version in {path}: {data['schema_version']!r}")

release_data = data["release"]
aipcc_wheel_index = parse_aipcc_wheel_index(release_data["aipcc_wheel_index"])
release = ReleaseConfig(
full_version=scalar_to_string(release_data["full_version"]),
rhds_os_base=scalar_to_string(release_data["rhds_os_base"]),
python_version=scalar_to_string(release_data["python_version"]),
aipcc_wheel_index=aipcc_wheel_index,
)
parse_release_version(release.full_version)
if not release.rhds_os_base:
Expand All @@ -563,6 +588,65 @@ def load_versions_config(path: Path) -> VersionsConfig:
return VersionsConfig(release=release, base_image=base_image, gpu_acc_versions=gpu_acc_versions)


def parse_aipcc_wheel_index(raw: object) -> AipccWheelIndexConfig:
if not isinstance(raw, dict):
raise ValueError("Expected mapping at release.aipcc_wheel_index")
stream = scalar_to_string(raw["stream"])
if not AIPCC_WHEEL_INDEX_STREAM_RE.fullmatch(stream):
raise ValueError(
"release.aipcc_wheel_index.stream must look like '3.5' or '3.5-EA2', " f"got {stream!r}"
)
use_test = raw["use_test"]
if not isinstance(use_test, bool):
raise ValueError(
"release.aipcc_wheel_index.use_test must be a boolean, " f"got {type(use_test).__name__}"
)
return AipccWheelIndexConfig(stream=stream, use_test=use_test)


def build_base_images_index_url(*, stream: str, accelerator: str, use_test: bool) -> str:
if use_test:
return build_rhoai_test_index_url(release=stream, accelerator=accelerator)
return build_rhoai_index_url(release=stream, accelerator=accelerator)


def plan_base_images_index_updates(root_dir: Path, config: VersionsConfig) -> list[PlannedUpdate]:
build_args_dir = root_dir / BASE_IMAGES_BUILD_ARGS_DIR
if not build_args_dir.is_dir():
# Unit-test fixtures often omit base-images/; real checkouts always have it.
return []

found = {path.name: path for path in sorted(build_args_dir.glob("*.conf"))}
unexpected = sorted(set(found) - set(BASE_IMAGES_INDEX_CONFS))
missing = sorted(set(BASE_IMAGES_INDEX_CONFS) - set(found))
if unexpected:
raise ValueError(
"Unexpected base-images/build-args conf file(s): "
f"{', '.join(unexpected)}; expected only {', '.join(sorted(BASE_IMAGES_INDEX_CONFS))}"
)
if missing:
raise ValueError(f"Missing base-images/build-args conf file(s): {', '.join(missing)}")

wheel_index = config.release.aipcc_wheel_index
updates: list[PlannedUpdate] = []
for conf_name, accelerator in BASE_IMAGES_INDEX_CONFS.items():
path = found[conf_name]
original_text = path.read_text(encoding="utf-8")
index_url = build_base_images_index_url(
stream=wheel_index.stream,
accelerator=accelerator,
use_test=wheel_index.use_test,
)
updates.append(
PlannedUpdate(
path=path,
original_text=original_text,
updated_text=rewrite_conf_text(original_text, {"INDEX_URL": index_url}),
)
)
return updates


def classify_conf_name(name: str) -> tuple[str, str] | None:
mapping = {
"cpu.conf": ("cpu", "odh"),
Expand Down Expand Up @@ -1612,6 +1696,8 @@ def plan_updates(
)
)

updates.extend(plan_base_images_index_updates(root_dir, config))

return updates


Expand Down
1 change: 1 addition & 0 deletions tests/test_versions_config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def _minimal_valid_config() -> dict:
"full_version": "3.5.0",
"rhds_os_base": "el9.6",
"python_version": "3.12",
"aipcc_wheel_index": {"stream": "3.5-EA2", "use_test": True},

@atheo89 atheo89 Aug 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to specify the index here? The index is automatically fetched from the corresponding image label and locks the file. I thing we should find other way to do this. I’m concerned that if we add it here, we’ll have to update it every time we move from EA1 → EA2 → GA. This would introduce unnecessary maintenance overhead.

},
"artifacts": {
"base_image": {
Expand Down
Loading
Loading