diff --git a/skills/ci/rhdh-base-images/SKILL.md b/skills/ci/rhdh-base-images/SKILL.md index d446ba0b..1deb5d92 100644 --- a/skills/ci/rhdh-base-images/SKILL.md +++ b/skills/ci/rhdh-base-images/SKILL.md @@ -72,6 +72,13 @@ until that path is migrated. that same latest released 4.y (for example `/rhocp/4.21/` → `/rhocp/4.22/`). Leave `ocp-tools` URLs unchanged. Do not rewrite when the life cycle fetch fails. +- When the RPM Containerfile uses `dnf`/`microdnf install --installroot` + (operator empty rootfs), regenerate the lockfile with + `rpm-lockfile-prototype --bare` so the full closure (glibc, coreutils, bash) + is locked. Do not pass `-f`; that loads the UBI rpmdb and omits those + packages. Layered installs onto the base image still use `-f`. If the solve + fails (`nothing provides`, `conflicting requests`), restore + `rpms.lock.yaml` / `rpms.in.yaml` and do not push. - On RHDH, update Node headers when the builder image changes Node. Then, when those checkouts are in scope, pin plugin-catalog `build/containerfiles/builder.Containerfile` FROM to the same UBI Node diff --git a/skills/ci/rhdh-base-images/references/repos.md b/skills/ci/rhdh-base-images/references/repos.md index b530b51a..55ba87c6 100644 --- a/skills/ci/rhdh-base-images/references/repos.md +++ b/skills/ci/rhdh-base-images/references/repos.md @@ -22,7 +22,7 @@ - Clone: `https://github.com/redhat-developer/rhdh-operator.git` - Branches: `main`, `release-1.9`, `release-1.10`, … - Base images: `.rhdh/docker/Dockerfile`, root `Dockerfile` -- RPM lock: `.rhdh/docker/Dockerfile` + `rpms.in.yaml` → `rpms.lock.yaml` +- RPM lock: root `Dockerfile` or `.rhdh/docker/Dockerfile` + `rpms.in.yaml` → `rpms.lock.yaml`. The `rpm-builder` stage uses `--installroot`; regenerate with `rpm-lockfile-prototype --bare` (not `-f`). - Workflow reference: `.github/workflows/update-rpm-lockfile.yaml` ## GitLab rhidp/rhdh-plugin-catalog diff --git a/skills/ci/rhdh-base-images/scripts/base-images-and-rpms.sh b/skills/ci/rhdh-base-images/scripts/base-images-and-rpms.sh index bfebaa2f..542f9456 100755 --- a/skills/ci/rhdh-base-images/scripts/base-images-and-rpms.sh +++ b/skills/ci/rhdh-base-images/scripts/base-images-and-rpms.sh @@ -540,21 +540,45 @@ bump_catalog_builder_rhocp() { bump_rhocp_version_in_file "${catalog_cf}" "${new_ver}" } +# Empty --installroot (operator rpm-builder) needs --bare so glibc/coreutils +# land in the lockfile. Layered installs onto the base image stay on -f. +containerfile_uses_installroot() { + local file="$1" + [[ -f "${file}" ]] || return 1 + grep -qE -- '--installroot' "${file}" +} + +# $1: file to inspect for --installroot +# $2: Containerfile path passed to -f (relative after cd into the repo) +# stdout: arguments after the rpm-lockfile-prototype binary. +rpm_lockfile_prototype_args() { + local inspect="$1" + local relative="$2" + if containerfile_uses_installroot "${inspect}"; then + printf '%s\n' "--bare rpms.in.yaml" + else + printf '%s\n' "-f ${relative} rpms.in.yaml" + fi +} + update_rpm_lockfile() { local repo_dir="$1" local kind="$2" local rpm_tool="$3" local branch="$4" - local containerfile rpm_err rpm_rc + local containerfile rpm_err rpm_rc proto_args + local -a proto_cmd=() containerfile=$(rpm_containerfile_for "${repo_dir}" "${kind}" "${branch}") [[ -f "${repo_dir}/${containerfile}" ]] || die "${repo_dir}: missing ${containerfile}" [[ -f "${repo_dir}/rpms.in.yaml" ]] || die "${repo_dir}: missing rpms.in.yaml" - log "RPM lockfile: $(basename "${repo_dir}") using ${containerfile}" + proto_args=$(rpm_lockfile_prototype_args "${repo_dir}/${containerfile}" "${containerfile}") + + log "RPM lockfile: $(basename "${repo_dir}") using ${containerfile} (${proto_args})" if [[ ${DRY_RUN} -eq 1 ]]; then echo " dry-run: bump OpenShift N-el9-beta URLs in ${repo_dir}/rpms.in.yaml when present" - echo " dry-run: (cd ${repo_dir} && ${rpm_tool} -f ${containerfile} rpms.in.yaml)" + echo " dry-run: (cd ${repo_dir} && ${rpm_tool} ${proto_args})" echo " dry-run: commit and push rpms.in.yaml and rpms.lock.yaml to open base-images PR branch (or chore/automated-update-rpm-lockfile/${branch})" return 0 fi @@ -564,11 +588,15 @@ update_rpm_lockfile() { pushd "${repo_dir}" >/dev/null rpm_err=$(mktemp) rpm_rc=0 - "${rpm_tool}" -f "${containerfile}" rpms.in.yaml 2>"${rpm_err}" || rpm_rc=$? + read -r -a proto_cmd <<< "${proto_args}" + "${rpm_tool}" "${proto_cmd[@]}" 2>"${rpm_err}" || rpm_rc=$? filter_rpm_lockfile_source_warnings <"${rpm_err}" >&2 rm -f "${rpm_err}" if [[ ${rpm_rc} -ne 0 ]]; then warn "rpm-lockfile-prototype failed for ${repo_dir}; keeping existing rpms.lock.yaml" + git checkout -- rpms.lock.yaml rpms.in.yaml 2>/dev/null || true + popd >/dev/null + return 0 fi commit_push_rpm_lockfile "${branch}" popd >/dev/null diff --git a/skills/ci/rhdh-base-images/workflows/update-base-images.md b/skills/ci/rhdh-base-images/workflows/update-base-images.md index 32a3421c..15ee8db8 100644 --- a/skills/ci/rhdh-base-images/workflows/update-base-images.md +++ b/skills/ci/rhdh-base-images/workflows/update-base-images.md @@ -152,7 +152,10 @@ Matches each repo's GitHub Action, then commits and pushes to the automation PR: # capped at the latest released 4.y (4.21-el9-beta 404s once 4.22-el9-beta # is the newest released stream still on the mirror). Do not follow # unreleased directories such as 4.23-el9-beta or 5.1-el9-beta. +# Layered install onto the base image: rpm-lockfile-prototype -f CONTAINERFILE rpms.in.yaml +# Empty --installroot (operator rpm-builder): do not pass -f +rpm-lockfile-prototype --bare rpms.in.yaml git add rpms.in.yaml rpms.lock.yaml git commit -s -m "chore: update rpms.lock.yaml [skip-build]" git push origin # same PR as base images @@ -203,6 +206,7 @@ Do not downgrade. If `go.mod` already pins a newer toolchain (for example `go1.2 - Omitting `registry.redhat.io` login before base image updates. - Committing `rpms.lock.yaml` without checking the base image minor (e.g. UBI `9.8`) still matches `rpms.in.yaml` repo URLs. - Regenerating `rpms.lock.yaml` while `rpms.in.yaml` still points at a vanished OpenShift `N-el9-beta` directory (hermeto FetchError on `openshift-clients`). +- Solving an `--installroot` Containerfile with `-f` (image rpmdb). openssl then lands without glibc/coreutils; hermetic `dnf install --installroot` fails with `nothing provides`. Use `--bare`. - Bumping `rpms.in.yaml` or catalog `builder.Containerfile` `/rhocp/4.N/` to an unreleased OpenShift stream (`4.23`, `5.1`). Those RPMs are signed with a key UBI 9 microdnf cannot verify. Stay on the latest released 4.y. - Treating `rpm-lockfile-prototype` `No sources found for` / "no matching sources" warnings as a failure or remaining risk. The source RPM is often unpublished; the lockfile is still valid. - Lowering `go.mod` `toolchain` (or `go`) to match an older UBI Go toolset image. Keep the newer pin. diff --git a/tests/unit/test_base_images_and_rpms.py b/tests/unit/test_base_images_and_rpms.py index 2603b890..9e5db452 100644 --- a/tests/unit/test_base_images_and_rpms.py +++ b/tests/unit/test_base_images_and_rpms.py @@ -473,6 +473,60 @@ def test_bump_rhocp_version_in_file_rewrites_rhocp_only(self, tmp_path: Path) -> assert "/rhocp/4.21/" not in text assert "/ocp-tools/4.22/os/" in text + def test_rpm_lockfile_prototype_args_bare_for_installroot(self, tmp_path: Path) -> None: + helpers = ( + _extract_bash_function(MAIN_SCRIPT, "containerfile_uses_installroot") + + "\n" + + _extract_bash_function(MAIN_SCRIPT, "rpm_lockfile_prototype_args") + ) + dockerfile = tmp_path / "Dockerfile" + dockerfile.write_text( + "RUN dnf install --installroot /mnt/rootfs openssl --releasever 10 -y\n", + encoding="utf-8", + ) + result = subprocess.run( + [ + "bash", + "-c", + f'{helpers}\nrpm_lockfile_prototype_args "$1" "$2"', + "bash", + str(dockerfile), + "Dockerfile", + ], + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, result.stderr + assert result.stdout.strip() == "--bare rpms.in.yaml" + + def test_rpm_lockfile_prototype_args_layered_uses_containerfile(self, tmp_path: Path) -> None: + helpers = ( + _extract_bash_function(MAIN_SCRIPT, "containerfile_uses_installroot") + + "\n" + + _extract_bash_function(MAIN_SCRIPT, "rpm_lockfile_prototype_args") + ) + containerfile = tmp_path / "Containerfile" + containerfile.write_text( + "RUN microdnf install -y tar gzip jq\n", + encoding="utf-8", + ) + result = subprocess.run( + [ + "bash", + "-c", + f'{helpers}\nrpm_lockfile_prototype_args "$1" "$2"', + "bash", + str(containerfile), + "Containerfile", + ], + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, result.stderr + assert result.stdout.strip() == "-f Containerfile rpms.in.yaml" + def test_skill_covers_openshift_el9_beta_rpm_bump(self) -> None: skill = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8") assert "N-el9-beta" in skill @@ -481,6 +535,10 @@ def test_skill_covers_openshift_el9_beta_rpm_bump(self) -> None: script = MAIN_SCRIPT.read_text(encoding="utf-8") assert "bump_openshift_rpm_repos" in script assert "bump_catalog_builder_rhocp" in script + assert "rpm_lockfile_prototype_args" in script + assert "--bare rpms.in.yaml" in script + assert "keeping existing rpms.lock.yaml" in script + assert "rpm-lockfile-prototype --bare" in skill unresolved = script.split( 'warn "plugin-catalog: could not resolve a Node headers version', 1 )[1][:500]