feat(rpm): add chroot-based patching for RPM images missing package m… - #1473
Conversation
There was a problem hiding this comment.
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
isMissingToolsfield 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 |
| upgrade -y %s 2>&1 || true | ||
| if [ $? -eq 0 ]; then |
There was a problem hiding this comment.
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=$?.
| upgrade -y %s 2>&1 || true | |
| if [ $? -eq 0 ]; then | |
| upgrade -y %s 2>&1 | |
| dnf_exit=$? | |
| if [ "$dnf_exit" -eq 0 ]; then |
| { | ||
| 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"), | ||
| }, |
There was a problem hiding this comment.
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.
| Client: mockClient, | ||
| ImageState: llb.Scratch(), | ||
| }, | ||
| isMissingTools: true, |
There was a problem hiding this comment.
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.
| isMissingTools: true, | |
| isMissingTools: true, | |
| osType: unversioned.OsTypeAlmaLinux, |
091359f to
4afee8d
Compare
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
4afee8d to
537be4a
Compare
|
@craigbthompson thanks for the contribution! Could we add a test image to the integration tests? |
1cd6c0d to
d54475c
Compare
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. |
|
@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. |
d54475c to
488150a
Compare
|
@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: |
…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>
dd74919 to
124abc0
Compare
|
@craigbthompson thanks! this is an issue on our side, putting up a fix in #1419 |
| output=$(dnf --installroot="${COPA_CHROOT_DIR}" \ | ||
| --setopt=reposdir="${COPA_CHROOT_DIR}/etc/yum.repos.d" \ | ||
| --releasever="${COPA_RELEASE_VER}" \ | ||
| --nogpgcheck \ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Updates made per the comments
| } | ||
| } | ||
|
|
||
| func Test_dnfChrootInstallUpdates(t *testing.T) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Updates made per the comments
| dnf_exit=$? | ||
| echo "$output" | ||
| if [ $dnf_exit -ne 0 ]; then exit $dnf_exit; fi | ||
| if echo "$output" | grep -q "Nothing to do"; then |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Updates made per the comments
4f12ae3 to
0ebaec7
Compare
Signed-off-by: Craig Thompson <cthompson@hyphencare.com>
0ebaec7 to
c1628a0
Compare
|
Hi @robert-cronin , I made some updates based on your comments. |
…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>