Skip to content

refactor: replace install-common.sh with declarative artifact system - #470

Closed
eoinfennessy wants to merge 6 commits into
opendatahub-io:mainfrom
eoinfennessy:unify-install-scripts
Closed

refactor: replace install-common.sh with declarative artifact system#470
eoinfennessy wants to merge 6 commits into
opendatahub-io:mainfrom
eoinfennessy:unify-install-scripts

Conversation

@eoinfennessy

@eoinfennessy eoinfennessy commented Jun 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Replaces install-common.sh (which used runtime tools like hf download, docling-tools, tiktoken) with a shared declarative artifact system consistent with how downstream Konflux hermetic builds fetch artifacts
  • Adds artifacts.lock.yaml declaring all ML model artifacts with download URLs and SHA256 checksums, pinned to specific revisions
  • Adds fetch_artifacts.py (async downloader with checksum verification) and copy-artifacts.sh (copies artifacts into correct cache layouts for both standard and Konflux builds)

Test plan

  • Build container image locally with podman build -t ogx-core . and verify all artifacts are fetched and placed correctly
  • Verify docling models load at runtime (file processing endpoints)
  • Verify tiktoken encoding works (vector store chunking)
  • Verify granite embedding model loads (embedding inference)
  • Run smoke tests against the built image

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a bundled artifact prefetch and copy workflow so required ML/model assets are staged during image build.
    • Added a versioned artifact manifest and configured cache locations for document processing and Hugging Face downloads.
  • Bug Fixes
    • Switched to headless OpenCV to avoid missing system library issues in the container.
    • Added checksum verification and filename/path safety checks for downloading artifacts.

Make the upstream build's file download mechanism consistent with what is
done for downstream Konflux hermetic builds. Replaces install-common.sh
(which used runtime tools like hf download, docling-tools, and tiktoken)
with a shared declarative artifact system:

- artifacts.lock.yaml: declares all ML model artifacts with download URLs
  and SHA256 checksums, pinned to specific revisions for reproducibility
- fetch_artifacts.py: async Python downloader with checksum verification
- copy-artifacts.sh: copies fetched artifacts into correct cache layouts,
  used by both standard and Konflux builds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 51fc1d01-440b-4b6e-810a-82a7c5fde953

📥 Commits

Reviewing files that changed from the base of the PR and between fc46665 and e57ab0f.

📒 Files selected for processing (2)
  • Containerfile
  • Containerfile.in
🚧 Files skipped from review as they are similar to previous changes (2)
  • Containerfile
  • Containerfile.in

📝 Walkthrough

Walkthrough

Container build logic now swaps opencv-python for opencv-python-headless, stages a new artifact manifest and helper scripts into the image, downloads locked artifacts into /tmp/artifacts, copies them into cache locations, and removes the staging directory. The new Python downloader validates artifact paths and SHA-256 checksums. DOCLING_ARTIFACTS_PATH and HF_HOME are set for runtime cache placement.

Estimated code review effort: 4 (Complex) | ~45 minutes

Changes

File(s) Change Summary
distribution/artifacts.lock.yaml Adds a versioned artifact manifest with download_url, sha256, and filename entries.
distribution/fetch_artifacts.py Adds an async downloader with checksum verification, path validation, concurrency limiting, and CLI handling.
distribution/copy-artifacts.sh Adds a cache-copy script for tiktoken, Granite, Docling, RapidOCR, and MiniLM artifacts, plus ownership/permission changes.
Containerfile, Containerfile.in Replace install-common.sh with the staged fetch/copy flow, swap OpenCV to the headless package, and set cache environment variables.
distribution/install-common.sh Deletes the prior artifact installation script.

Security notes: CWE-22 path traversal checks are added in fetch_artifacts.py; CWE-494 integrity depends on the lock file provenance; CWE-59 and CWE-362 remain relevant around resolved paths and temp-file rename behavior; CWE-732 applies to recursive chown/chmod in copy-artifacts.sh; supply-chain exposure remains for external artifact URLs and dependency pins.

🚥 Pre-merge checks | ✅ 8 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Contribution Quality And Spam Detection ⚠️ Warning PR body is a rigid Summary/Test plan template with a Claude footer; the new fetch_artifacts path checks ship with no test additions. Rewrite the description in repo-specific terms and add tests for artifact path validation plus download/copy behavior.
No Hardcoded Secrets ⚠️ Warning Containerfile embeds a 13,036-char base64 LABEL payload in source config; the check flags long base64 literals here (CWE-200 exposure risk). Move that blob out of the Dockerfile/Containerfile into a generated file or external artifact reference; keep only a non-encoded reference in source.
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main refactor: replacing install-common.sh with a declarative artifact workflow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No Weak Cryptography ✅ Passed No CWE-327/CWE-208 issue: SHA1 is only used for a tiktoken cache key; no banned primitives, custom crypto, or secret comparisons found.
No Injection Vectors ✅ Passed No CWE-89/78/94/502/79 sink found; fetch_artifacts.py uses yaml.safe_load + path checks, and copy-artifacts.sh uses quoted, hardcoded paths.
No Privileged Containers ✅ Passed No changed Dockerfile/manifests set privileged flags, host namespaces, SYS_ADMIN, or runAsUser: 0; vllm/Containerfile uses USER 1001.
No Sensitive Data In Logs ✅ Passed No CWE-532/CWE-200 issue: added prints only emit artifact counts/names and usage; no passwords, tokens, PII, bearer tokens, or raw bodies.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mergify

mergify Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

⚠️ Heads up! This PR modifies Containerfile.
A corresponding change may be needed in the Konflux Dockerfile: https://github.com/red-hat-data-services/ogx-distribution/blob/main/Dockerfile.konflux
Please verify if the changes need to be synchronized.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
distribution/copy-artifacts.sh (1)

23-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not hardcode the tiktoken cache key in a second place.

9b5ad71b2ce5302211f9c61530b329a4922fc6a4 is derived from the URL in distribution/artifacts.lock.yaml. If that URL changes, the build still succeeds but runtime lookup misses the file. Put the cache key in the lockfile or derive it from the same URL during copy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@distribution/copy-artifacts.sh` around lines 23 - 25, The tiktoken cache key
is hardcoded in copy-artifacts logic and can drift from the source URL in the
lockfile. Update the artifact copy step in distribution/copy-artifacts.sh so it
does not duplicate the key manually; instead, read the cache key from
distribution/artifacts.lock.yaml or derive it from the same download URL used
there, keeping the tiktoken copy in sync with the lockfile.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@distribution/copy-artifacts.sh`:
- Around line 30-41: The Hugging Face cache location in copy-artifacts.sh is
tied to ${HOME}/.cache/huggingface/hub, so the prefetched snapshot can be missed
when HOME changes between build and runtime. Update the cache root handling in
copy-artifacts.sh to place the HF cache under ${APP_ROOT} and make the image use
the same location by exporting HF_HOME/TRANSFORMERS_CACHE alongside the existing
env vars in the Containerfile, keeping the snapshot_dir and refs/main setup
aligned with that shared path.

In `@distribution/fetch_artifacts.py`:
- Around line 54-64: The artifact download flow in fetch_artifacts.py is
vulnerable to path traversal because download_artifact is called with output_dir
/ a["filename"] without validating the lock entry. Add validation before
building the destination path so each filename is strictly relative, reject
absolute paths and any path containing .. segments, and verify the resolved
target stays under output_dir. Apply this check in the artifact iteration that
prepares the tasks list, using the existing download_artifact and output_dir
handling as the integration points.
- Around line 1-6: The script header in fetch_artifacts.py leaves httpx and
pyyaml unpinned, so lock those dependencies in the script metadata or move the
script to the repo’s locked dependency path. Also update the artifact-writing
logic in fetch_artifacts.py where output_dir is joined with a["filename"] to
normalize the manifest-provided path and reject any absolute path or traversal
attempt (for example, any path that escapes the staging directory) before
writing files.

---

Nitpick comments:
In `@distribution/copy-artifacts.sh`:
- Around line 23-25: The tiktoken cache key is hardcoded in copy-artifacts logic
and can drift from the source URL in the lockfile. Update the artifact copy step
in distribution/copy-artifacts.sh so it does not duplicate the key manually;
instead, read the cache key from distribution/artifacts.lock.yaml or derive it
from the same download URL used there, keeping the tiktoken copy in sync with
the lockfile.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: c60d2cab-a723-4885-9213-e7467bf050a2

📥 Commits

Reviewing files that changed from the base of the PR and between a063894 and 39599b6.

📒 Files selected for processing (7)
  • Containerfile
  • Containerfile.in
  • distribution/artifacts.lock.yaml
  • distribution/constraints.txt
  • distribution/copy-artifacts.sh
  • distribution/fetch_artifacts.py
  • distribution/install-common.sh
💤 Files with no reviewable changes (1)
  • distribution/install-common.sh

Comment thread distribution/copy-artifacts.sh Outdated
Comment thread distribution/fetch_artifacts.py
Comment thread distribution/fetch_artifacts.py
eoinfennessy and others added 4 commits June 30, 2026 14:26
The HuggingFace hub cache was placed under ${HOME} during build, but the
container runs as user 1001 with a different HOME, so SentenceTransformer
could not find the prefetched embedding model at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Validate artifact filenames before downloading to reject absolute paths
and directory traversal attempts. Pin httpx and pyyaml version bounds in
the PEP 723 script metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Compute the SHA-1 hash at copy time so the cache filename stays in sync
with the download URL rather than being a separately maintained constant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The opencv-python<0 constraint in constraints.txt silently caused the
resolver to drop rapidocr and downgrade docling. Replace it with an
explicit uninstall/install step in the Containerfile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Containerfile (1)

19-23: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

ENV var used by copy-artifacts.sh is set after the step that consumes it.

DOCLING_ARTIFACTS_PATH is only defined via ENV on Line 22, but copy-artifacts.sh is invoked on Line 20, before that ENV instruction takes effect. Per the upstream script logic (distribution/copy-artifacts.sh:8-57), DOCLING_ARTIFACTS_PATH="${DOCLING_ARTIFACTS_PATH:-${APP_ROOT}/.cache/docling/models}" — since the env var is unset at execution time, the script silently falls back to its hardcoded default. That default happens to match the value on Line 22 today, but this is fragile: this is a latent misconfiguration bug (CWE-665, incorrect initialization) — any future change to the ENV value or the script default will silently diverge with no build failure to flag it. Move the ENV DOCLING_ARTIFACTS_PATH declaration above Line 19 so the value is authoritative and actually consumed by the script, rather than relying on coincidental default parity.

🔒 Proposed fix
+ENV DOCLING_ARTIFACTS_PATH="${APP_ROOT}/.cache/docling/models"
 RUN uv run ${APP_ROOT}/fetch_artifacts.py ${APP_ROOT}/artifacts.lock.yaml /tmp/artifacts \
     && ${APP_ROOT}/copy-artifacts.sh /tmp/artifacts \
     && rm -rf /tmp/artifacts
-ENV DOCLING_ARTIFACTS_PATH="${APP_ROOT}/.cache/docling/models"
 ENV HF_HOME="${APP_ROOT}/.cache/huggingface"

The line-range-change-details confirm ENV DOCLING_ARTIFACTS_PATH is introduced after the fetch/copy RUN step

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Containerfile` around lines 19 - 23, The `DOCLING_ARTIFACTS_PATH` environment
variable is defined too late for `copy-artifacts.sh` to use it during the `RUN`
step. Move the `ENV DOCLING_ARTIFACTS_PATH` declaration before the `RUN`
instruction so `copy-artifacts.sh` sees the intended value instead of falling
back to its internal default; keep `HF_HOME` where appropriate, and verify the
build step still uses `fetch_artifacts.py` plus `copy-artifacts.sh` with the
updated initialization order.
🧹 Nitpick comments (1)
Containerfile (1)

19-21: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

No layer cache cleanup for uv after artifact fetch.

uv run will populate uv's package/download cache during execution; nothing here scrubs it (unlike the explicit rm -rf /tmp/artifacts). Combined with any transient pip/uv cache from Line 12-14, this can quietly bloat the final image layer. Consider UV_NO_CACHE=1 or an explicit cache purge in the same RUN.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Containerfile` around lines 19 - 21, The artifact fetch RUN step leaves uv’s
cache behind, which can bloat the final image layer. Update the Containerfile’s
artifact-fetch command to disable uv caching during `uv run` (for example via
UV_NO_CACHE) or explicitly remove uv’s cache in the same RUN block, alongside
the existing cleanup after `copy-artifacts.sh`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Containerfile`:
- Around line 12-14: The OpenCV replacement step in the Containerfile is
bypassing the existing constraint file and leaving opencv-python-headless
unpinned, which breaks build reproducibility. Update the RUN block that installs
opencv-python-headless so it uses the same ${APP_ROOT}/constraints.txt
constraint as the other uv pip installs and specify an explicit version pin for
opencv-python-headless. Keep the change localized to the dependency swap
sequence around the uv pip install/uninstall commands.

---

Outside diff comments:
In `@Containerfile`:
- Around line 19-23: The `DOCLING_ARTIFACTS_PATH` environment variable is
defined too late for `copy-artifacts.sh` to use it during the `RUN` step. Move
the `ENV DOCLING_ARTIFACTS_PATH` declaration before the `RUN` instruction so
`copy-artifacts.sh` sees the intended value instead of falling back to its
internal default; keep `HF_HOME` where appropriate, and verify the build step
still uses `fetch_artifacts.py` plus `copy-artifacts.sh` with the updated
initialization order.

---

Nitpick comments:
In `@Containerfile`:
- Around line 19-21: The artifact fetch RUN step leaves uv’s cache behind, which
can bloat the final image layer. Update the Containerfile’s artifact-fetch
command to disable uv caching during `uv run` (for example via UV_NO_CACHE) or
explicitly remove uv’s cache in the same RUN block, alongside the existing
cleanup after `copy-artifacts.sh`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: bd22bf96-7881-4cec-a04b-8d996a4c4de7

📥 Commits

Reviewing files that changed from the base of the PR and between f6ed9d2 and fc46665.

📒 Files selected for processing (2)
  • Containerfile
  • Containerfile.in
🚧 Files skipped from review as they are similar to previous changes (1)
  • Containerfile.in

Comment thread Containerfile Outdated
Capture the exact version from uv pip show before uninstalling, so the
headless variant matches what the resolver chose for rapidocr.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@eoinfennessy

Copy link
Copy Markdown
Member Author

Superseded by #473 (pushed to upstream branch for CI).

mergify Bot added a commit that referenced this pull request Jul 7, 2026
…473)

## Summary
- Replaces `install-common.sh` (which used runtime tools like `hf download`, `docling-tools`, `tiktoken`) with a shared declarative artifact system consistent with how downstream Konflux hermetic builds fetch artifacts
- Adds `artifacts.lock.yaml` declaring all ML model artifacts with download URLs and SHA256 checksums, pinned to specific revisions
- Adds `fetch_artifacts.py` (async downloader with checksum verification) and `copy-artifacts.sh` (copies artifacts into correct cache layouts for both standard and Konflux builds)
- Fixes opencv-python swap: the previous `opencv-python<0` constraint silently dropped rapidocr and downgraded docling; replaced with a post-install swap pinned to the resolved version
- Fixes HF cache location to use `APP_ROOT` instead of `HOME` (build-time vs runtime user mismatch)
- Adds path traversal validation to `fetch_artifacts.py`
- Derives tiktoken cache key from URL instead of hardcoding

Supersedes #470 (pushed to upstream branch for CI).

## Test plan
- [x] Build container image locally and verify all 27 artifacts are fetched and checksums verified
- [x] Verify tiktoken loads from cache and encodes correctly (`cl100k_base`)
- [x] Verify granite embedding model loads from HF cache and produces meaningful 768-dim embeddings (semantic similarity check)
- [x] Verify docling `DocumentConverter` initializes and processes a PDF end-to-end (layout analysis, RapidOCR, text extraction all using prefetched models)
- [x] Verify `opencv-python` is absent and `opencv-python-headless` is installed at the same version (`4.13.0.92`)
- [x] Run smoke tests against the built image (requires vLLM + PostgreSQL — covered by CI)

🤖 Generated with [Claude Code](https://claude.com/claude-code)



## Summary by CodeRabbit

* **New Features**
  * Container builds now fetch and cache required model and tokenizer artifacts from a pinned lockfile, improving consistency across environments.
  * Added support for a Hugging Face cache location and artifact path configuration during image builds.

* **Bug Fixes**
  * Replaced the standard OpenCV package with the headless variant to prevent runtime failures caused by missing system graphics libraries.
  * Artifact downloads are now verified with checksums and copied into the image safely.




Approved-by: derekhiggins

Approved-by: Artemon-line
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant