Skip to content

feat(rpm): add chroot-based patching for RPM images missing package m… - #1473

Merged
robert-cronin merged 8 commits into
project-copacetic:mainfrom
craigbthompson:quarkus-fix
Mar 26, 2026
Merged

feat(rpm): add chroot-based patching for RPM images missing package m…#1473
robert-cronin merged 8 commits into
project-copacetic:mainfrom
craigbthompson:quarkus-fix

Conversation

@craigbthompson

Copy link
Copy Markdown
Contributor

…anagers

Support patching RPM-based container images (e.g., Quarkus UBI micro) that have a valid RPM database but lack package management tools like dnf, yum, or microdnf. When such images are detected, copa now uses a tooling image with dnf and --installroot to apply updates via a chroot approach, similar to the existing SUSE/Zypper chroot path.

Closes #602

Describe the changes in this pull request using active verbs such as Add, Remove, Replace ...

Closes #<issue_ID>

Copilot AI 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.

Pull request overview

This PR implements chroot-based patching for RPM images that have a valid RPM database but lack package management tools (dnf, yum, microdnf, rpm). This addresses issue #602 and enables Copa to patch minimal container images like Quarkus UBI micro that are stripped of package managers for security and size optimization.

Changes:

  • Added isMissingTools field to rpmManager to track images missing package management tools
  • Implemented getChrootToolImage() to select appropriate tooling images for different RPM distributions
  • Implemented dnfChrootInstallUpdates() using dnf with --installroot for chroot-based patching, similar to existing zypper chroot approach
  • Modified probeRPMStatus() to detect missing tools and fallback to chroot mode instead of failing
  • Added comprehensive unit tests for the new functionality

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
pkg/pkgmgr/rpm.go Core implementation: added isMissingTools field, getChrootToolImage() function, dnfChrootInstallUpdates() function, and updated probeRPMStatus() to detect and handle missing tools
pkg/pkgmgr/rpm_test.go Test coverage: added tests for GetPackageType() with missing tools, TestGetChrootToolImage() for various RPM distributions, and Test_dnfChrootInstallUpdates() for the chroot patching flow

Comment thread pkg/pkgmgr/rpm.go Outdated
Comment on lines +1068 to +1069
upgrade -y %s 2>&1 || true
if [ $? -eq 0 ]; then

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

The exit code check with $? after the || true operator will always return 0, not the dnf command's exit code. The || true forces the previous command's exit status to be 0, making the subsequent check ineffective. This means the updates marker file will never be created in the ignoreErrors path when dnf succeeds.

The correct logic should capture the dnf exit code before applying || true, similar to the non-ignoreErrors path which uses dnf_exit=$?.

Suggested change
upgrade -y %s 2>&1 || true
if [ $? -eq 0 ]; then
upgrade -y %s 2>&1
dnf_exit=$?
if [ "$dnf_exit" -eq 0 ]; then

Copilot uses AI. Check for mistakes.
Comment thread pkg/pkgmgr/rpm_test.go
Comment on lines +994 to +1006
{
name: "dnf chroot - Ignore errors during update",
mockSetup: func(mr *mocks.MockReference) {
mr.On("ReadFile", mock.Anything, mock.Anything).Return([]byte("package1\t1.0.1\tx86_64\n"), nil)
},
updates: unversioned.UpdatePackages{
{Name: "package1", FixedVersion: "2.0.0"},
},
toolImage: "almalinux:9",
ignoreErrors: true,
expectedError: false,
expectedResult: []byte("package1\t1.0.1\tx86_64\n"),
},

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

The test case "dnf chroot - Ignore errors during update" doesn't adequately test the error handling path. It mocks ReadFile to return an outdated package version (1.0.1 instead of the expected 2.0.0), but this only tests that the function completes without error when ignoreErrors is true. It doesn't verify that the actual dnf upgrade command's error handling works correctly, nor does it test the marker file logic.

Consider adding a test case that verifies the behavior when the dnf command itself fails (e.g., with a mock that returns an error from Solve or ReadFile for the updates marker), to ensure the ignoreErrors flag properly suppresses failures during the actual patching operation.

Copilot uses AI. Check for mistakes.
Comment thread pkg/pkgmgr/rpm_test.go
Client: mockClient,
ImageState: llb.Scratch(),
},
isMissingTools: true,

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

The test creates an rpmManager without setting the osType field, but the dnfChrootInstallUpdates function calls getChrootToolImage(rm.osType, rm.osVersion) at line 309 of rpm.go. When osType is empty (""), getChrootToolImage will fall through to the default case and return "almalinux:9", which happens to work for this test since it uses "almalinux:9" as the toolImage anyway.

However, this masks a potential issue where the test doesn't properly reflect the real-world initialization. Consider setting osType in the test setup to ensure the test accurately simulates production behavior and catches any issues with osType handling.

Suggested change
isMissingTools: true,
isMissingTools: true,
osType: unversioned.OsTypeAlmaLinux,

Copilot uses AI. Check for mistakes.
@craigbthompson
craigbthompson force-pushed the quarkus-fix branch 2 times, most recently from 091359f to 4afee8d Compare February 18, 2026 13:21
@codecov

codecov Bot commented Feb 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.24590% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.67%. Comparing base (776845f) to head (e6c923e).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pkg/pkgmgr/rpm.go 85.24% 16 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1473      +/-   ##
==========================================
+ Coverage   38.01%   38.67%   +0.65%     
==========================================
  Files          57       57              
  Lines        9477     9590     +113     
==========================================
+ Hits         3603     3709     +106     
- Misses       5605     5611       +6     
- Partials      269      270       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ashnamehrotra

Copy link
Copy Markdown
Contributor

@craigbthompson thanks for the contribution! Could we add a test image to the integration tests?

@craigbthompson
craigbthompson force-pushed the quarkus-fix branch 2 times, most recently from 1cd6c0d to d54475c Compare February 18, 2026 19:45
@craigbthompson

Copy link
Copy Markdown
Contributor Author

@craigbthompson thanks for the contribution! Could we add a test image to the integration tests?

Thanks @ashnamehrotra, I wasn't exactly sure how do add a test image, but I think I got it. All tests seem to be passing now.

@ashnamehrotra

Copy link
Copy Markdown
Contributor

@craigbthompson thanks! Could you address the lint error on the CI?

@craigbthompson

Copy link
Copy Markdown
Contributor Author

@craigbthompson thanks! Could you address the lint error on the CI?

@ashnamehrotra , I don't think the lint errors are in the files that I made changes too. Please let me know if I'm wrong.

@craigbthompson

Copy link
Copy Markdown
Contributor Author

@ashnamehrotra , I was able to fix the linting errors by rebasing. But now the tests are failing. Any advice?

[Blocking] Build fails at Install required tools in .github/workflows/scripts/download-tooling.sh with exit code 22 (curl), e.g. in:
https://github.com/project-copacetic/copacetic/actions/runs/22577979274/job/65402152124?pr=1473

Craig Thompson and others added 2 commits March 2, 2026 10:19
…anagers

Support patching RPM-based container images (e.g., Quarkus UBI micro) that
have a valid RPM database but lack package management tools like dnf, yum,
or microdnf. When such images are detected, copa now uses a tooling image
with dnf and --installroot to apply updates via a chroot approach, similar
to the existing SUSE/Zypper chroot path.

Closes project-copacetic#602

Signed-off-by: Craig Thompson <cthompson@hyphencare.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Craig Thompson <cthompson@hyphencare.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Craig Thompson <cthompson@hyphencare.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Craig Thompson <cthompson@hyphencare.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Craig Thompson <cthompson@hyphencare.com>
Signed-off-by: Craig Thompson <cthompson@hyphencare.com>
@ashnamehrotra

Copy link
Copy Markdown
Contributor

@craigbthompson thanks! this is an issue on our side, putting up a fix in #1419

Comment thread pkg/pkgmgr/rpm.go
output=$(dnf --installroot="${COPA_CHROOT_DIR}" \
--setopt=reposdir="${COPA_CHROOT_DIR}/etc/yum.repos.d" \
--releasever="${COPA_RELEASE_VER}" \
--nogpgcheck \

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.

nit: might be worth adding a comment explaining why --nogpgcheck is needed here. I'm guessing GPG keys from the target image's repos aren't available in the tooling image's trust store. The zypper chroot path doesn't explicitly skip GPG checks so it'd be good to note the difference. Not a blocker.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updates made per the comments

Comment thread pkg/pkgmgr/rpm_test.go
}
}

func Test_dnfChrootInstallUpdates(t *testing.T) {

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.

nit: might be worth adding a test case for the re-patching path where rm.config.PatchedConfigData != nil, that would exercise the diff/merge logic for previously patched images. Not a blocker though

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updates made per the comments

Comment thread pkg/pkgmgr/rpm.go Outdated
dnf_exit=$?
echo "$output"
if [ $dnf_exit -ne 0 ]; then exit $dnf_exit; fi
if echo "$output" | grep -q "Nothing to do"; then

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.

nit: the ignoreErrors=true branch (line 1070) and both zypper branches (lines 938, 954) use if ! echo "$output" | grep -q "Nothing to do" while this branch inverts the logic with an if/else. They're logically equivalent but it'd be nice to use the same idiom for consistency.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updates made per the comments

Signed-off-by: Craig Thompson <cthompson@hyphencare.com>
@craigbthompson

Copy link
Copy Markdown
Contributor Author

Hi @robert-cronin , I made some updates based on your comments.

@robert-cronin robert-cronin 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.

LGTM

@robert-cronin
robert-cronin merged commit 95ecf3d into project-copacetic:main Mar 26, 2026
77 checks passed
@github-project-automation github-project-automation Bot moved this from 🆕 New to ✅ Done in Copacetic Workboard Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

[REQ] Support RPM-based images with valid rpm status but missing tools

4 participants