fix: look up ContainerProfile by with-container slug, not workload slug - #396
Conversation
ContainerProfile objects are named using the with-container slug (InstanceID.GetSlug(false)), but HandleImageScanningScopedRequest and handlePodWatcher looked them up using the no-container slug (GetSlug(true), the ApplicationProfile convention), so the lookup always missed and every scheduled scan silently fell back to a plain image scan instead of a relevancy scan. Both call sites also deduped container-profile scans per workload instead of per container, which would have dropped every container after the first in multi-container workloads once the slug itself was corrected. GetContainerProfileScanCommand additionally built its scan command with CommandName: apis.TypeScanApplicationProfile, which has no case in runCommand's dispatch switch (it only recognizes utils.CommandScanContainerProfile) and would have been silently dropped even once a profile was correctly found. Fixes #395 Docs-exempt: pure bug fix restoring intended behavior, no new feature or API surface described in docs/; no existing doc (cel-admission-rules.md, node-agent-autoscaler.md) covers ContainerProfile scan dispatch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
📝 WalkthroughWalkthroughContainer-profile lookups now use container-specific slugs in scheduled and pod-watcher scans. Deduplication remains per container, generated commands identify container-profile scans, and regression tests cover single- and multi-container workloads. ChangesContainer profile scanning
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
mainhandler/handlerequests_test.go (1)
182-182: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace deprecated
k8sfake.NewSimpleClientsetin new test code.Static analysis flags both new call sites as using a deprecated constructor (
NewClientsetis the replacement). These are newly-added lines, so unlike other pre-existing usages elsewhere in the repo, this could newly trip a lint gate for this PR.♻️ Proposed fix
- k8sClient := k8sfake.NewSimpleClientset(pod) + k8sClient := k8sfake.NewClientset(pod)Apply the same substitution at line 210 (
k8sfake.NewSimpleClientset(pod)).Also applies to: 210-210
🤖 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 `@mainhandler/handlerequests_test.go` at line 182, Replace both newly added k8sfake.NewSimpleClientset calls in the test with the non-deprecated k8sfake.NewClientset constructor, preserving the existing pod argument and test behavior.Source: Linters/SAST tools
🤖 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.
Nitpick comments:
In `@mainhandler/handlerequests_test.go`:
- Line 182: Replace both newly added k8sfake.NewSimpleClientset calls in the
test with the non-deprecated k8sfake.NewClientset constructor, preserving the
existing pod argument and test behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3035a1c5-f33c-490e-b292-0022d2dd3cf8
📒 Files selected for processing (5)
mainhandler/handlerequests.gomainhandler/handlerequests_test.goutils/containerprofile.gowatcher/podwatcher.gowatcher/podwatcher_test.go
|
Summary:
|
matthyx
left a comment
There was a problem hiding this comment.
The production fix is correct
All three changes verified:
- Slug fix —
mapSlugToInstanceID(podwatcher.go:230) and theslugsToImagesmap both key onGetSlug(false), so passingslug/sis right, and it matches the actualContainerProfilenaming. CommandNamefix — real bug, and the more severe of the two. Confirmed by printing the constants:apis.TypeScanApplicationProfile == "scanApplicationProfile"whileutils.CommandScanContainerProfile == "scanContainerProfile", andrunCommand's switch only cases the latter — so the command fell through todefault:and was dropped. Note thatscanContainerProfilestill hardcodesapis.TypeScanApplicationProfileatvulnscan.go:303for scanner-URL selection; that is correct (it is the kubevuln endpoint path) and rightly left untouched.- Dedup removal — safe. The
SlugToImageID.Set/WlidAndImageID.Addcalls that lived inside the deleted skip-branches are duplicated in the fall-through tail, so no cache state is lost.
I checked the fix direction by mutation rather than by reading: reverting the slug fix makes both new mainhandler tests fail, and reverting the CommandName fix makes the new podwatcher test fail.
Two test-quality problems, both proven by mutation
1. TestHandleImageScanningScopedRequest_MultiContainerDoesNotDedupeAcrossContainers does not test what it claims
I restored the per-workload dedup exactly as it was before this PR (re-adding the noContainerSlug lookup/continue and slugs[noContainerSlug] = true) while keeping the corrected slug in the profile lookup — and the test still passed.
Cause: newMainHandlerForTest disables Kubevuln, so HandleSingleRequest returns "kubevuln is not enabled" and the continue at handlerequests.go:444 fires before slugs[...] = true at :448. The dedup marker is therefore never set, and the dedup can never trigger inside this test. The test log confirms all three containers take the error path:
[info] triggering container profile scan. name: pod-my-pod-nginx-d00d-dd12
[error] failed to complete action. error: kubevuln is not enabled; ...
[info] triggering container profile scan. name: pod-my-pod-sidecar-7ab1-e32f
[error] failed to complete action. error: kubevuln is not enabled; ...
[info] triggering container profile scan. name: pod-my-pod-init-66cb-5b76
[error] failed to complete action. error: kubevuln is not enabled; ...
As written it is effectively a duplicate of TestHandleImageScanningScopedRequest_LooksUpWithContainerSlug — it covers the slug, not the dedup.
2. Neither mainhandler test covers fix #3
Reverting only the utils/containerprofile.go change (CommandScanContainerProfile → apis.TypeScanApplicationProfile), leaving both slug fixes in place, leaves go test ./mainhandler/... green. Only the podwatcher test catches it — which it does, correctly.
So the scoped-request path, which is the one #395 is actually about, has no assertion that it emits a CommandScanContainerProfile.
Suggested fix for both
Both findings stem from the same choice: the mainhandler tests assert on recorded fake-client get containerprofiles actions rather than on the emitted command. Enabling Kubevuln in the test config and pointing KubevulnURL at an httptest.NewServer addresses both at once — HandleSingleRequest then succeeds, so the dedup marker actually gets set (making finding 1 detectable) and dispatch has to survive runCommand's switch (making finding 2 detectable). The new podwatcher tests already assert on emitted commands and are the better model here.
Minor
The description states that go test -count=1 ./... passes across the full repo. TestRegistryCommandWatch fails under ./watcher/.... I checked out main and it fails there too, so it is pre-existing and not caused by this PR — but the claim should be corrected so the failure is not mistaken for a regression from these changes.
Address review feedback on PR #396: - CodeRabbit nitpick: replace the deprecated k8sfake.NewSimpleClientset with k8sfake.NewClientset in the two newly-added test call sites. - matthyx found that both mainhandler tests were weaker than intended: with Kubevuln disabled, HandleSingleRequest returns an error before slugs[s] = true is ever reached, so the multi-container test's dedup assertion could never fail even with the old per-workload dedup bug reintroduced -- it was effectively a duplicate of the single-container test. Neither test covered the CommandName fix either, since a wrong CommandName is silently dropped by runCommand's default case, which also returns no error. Fix: enable Kubevuln and point KubevulnURL at an httptest.Server stub, so a correctly-dispatched scan actually completes end-to-end through actionHandler.scanContainerProfile. Assert on the stub's received request count in addition to the existing storage-client Get assertions. Verified by mutation: reverting the CommandName fix now fails both tests (0 requests reach the stub instead of 1/3), and reintroducing the old per-workload dedup now fails the multi-container test (1 request instead of 3). Docs-exempt: test-only change, no behavioral or doc-relevant code touched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
|
Thanks both for the review — pushed 70f45ca addressing this. CodeRabbit nitpick: replaced the deprecated @matthyx — both test-quality findings were real, fixed by the same change: Verified by mutation, the same way you found it:
Also: you're right that the PR description's " |
matthyx
left a comment
There was a problem hiding this comment.
Re-reviewed at 70f45ca. Production code is unchanged from the previous round; this commit is test-only. Both earlier findings are addressed, and I verified the mutation claims independently rather than taking the commit message's word for them:
- Revert the
CommandNamefix →TestHandleImageScanningScopedRequest_LooksUpWithContainerSlugfails (expected: 1, actual: 0) and..._MultiContainerDoesNotDedupeAcrossContainersfails (expected: 3, actual: 0). Previously both stayed green. - Reintroduce the per-workload dedup → the multi-container test fails (
expected: 3, actual: 1). Previously it stayed green.
Also confirmed: go vet clean on the touched packages, ./mainhandler/... and ./utils/... pass, and the two new tests pass under -race (the stub's counter is mutex-guarded and the handler goroutines are the only writers). TestRegistryCommandWatch still fails under ./watcher/..., unchanged and pre-existing on main.
Two minor points, neither blocking:
1. VulnScanHttpClient is mutated globally without restore
newMainHandlerForTest assigns the package-level VulnScanHttpClient (handlerequests.go:62) and never restores it, while the httptest.Server it points at is closed by t.Cleanup. Nothing else in the package currently reaches that global, so this is harmless today — but it is a latent trap. A future mainhandler test that reaches sendWorkloadWithCredentials after these two run would POST to a closed port and hit the connection-refused retry loop at vulnscan.go:448: 5 retries × a 5s sleep, i.e. a ~25s hang that would present as a mysteriously slow test rather than a clear failure.
Cheap guard:
prev := VulnScanHttpClient
t.Cleanup(func() { VulnScanHttpClient = prev })
VulnScanHttpClient = utils.InitHttpClient(clusterConfig.KubevulnURL)(which requires threading t into the helper).
2. The scanner stub counts requests but does not discriminate by path
The stub accepts every path, so the request count proves a scan dispatched, not that it was a container-profile scan. Verified by mutation: reverting only the slug fix leaves both count assertions satisfied (1 and 3), because the fallback image-scan path POSTs to the same stub — only the getContainerProfileActionNames assertion catches that regression.
The two assertions together are adequate, so coverage is not actually missing. But asserting the path in the stub handler (/v1/scanApplicationProfile for the container-profile route, per getAPScanURL, vs. the image-scan route from getVulnScanURL) would make the count assertion mean what its comment claims — "the container-profile scan actually dispatched" — instead of "some scan dispatched".
LGTM otherwise.
|
Summary:
|
Overview
This PR fixes #395
Signed Commits
ContainerProfileobjects are named using the with-container slug (InstanceID.GetSlug(false)), butHandleImageScanningScopedRequestandhandlePodWatcherlooked them up using the no-container slug (GetSlug(true), theApplicationProfilenaming convention). The lookup therefore always missed, and every scheduled scan (thekubevuln-schedulerCronJob) silently fell back to a plain image scan instead of a relevancy (container-profile) scan.Three fixes, all in this PR:
mainhandler/handlerequests.go—HandleImageScanningScopedRequestnow passes the with-container slug (already computed ass) intoGetContainerProfileForRelevancyScan, and dedups onslugs[s]instead of a separate no-container-slug map.watcher/podwatcher.go—handlePodWatcherhad the identical bug pattern at two call sites; same fix, using the outer loop'sslug(already with-container).utils/containerprofile.go— a second, previously-unreported bug found while verifying this fix:GetContainerProfileScanCommandbuilt its command withCommandName: apis.TypeScanApplicationProfile, which has no case inrunCommand's dispatch switch (it only recognizesutils.CommandScanContainerProfile) — so even a correctly-found profile's scan command would have been silently dropped. Fixed to use the correct constant.Both call sites also previously deduped container-profile scans per workload instead of per container (via a no-container-slug map), which — once the slug itself was fixed naively — would have dropped every container after the first in multi-container workloads. That dedup logic has been removed; the existing per-container slug dedup (
slugs[s]/ the outer per-container loop key) already provides correct dedup.How to Test
New regression tests were added and verified by mutation (fail against the pre-fix code, pass against the fix — confirmed by temporarily reverting each production change and rerunning):
watcher/podwatcher_test.go:Test_handlePodWatcher_ContainerProfile— single-container hit case, and a multi-container case asserting one scan command per container (regression test for the removed per-workload dedup).mainhandler/handlerequests_test.go:TestHandleImageScanningScopedRequest_LooksUpWithContainerSlugandTestHandleImageScanningScopedRequest_MultiContainerDoesNotDedupeAcrossContainers. These enable Kubevuln against anhttptest.Serverstub so a correctly-dispatched scan completes end-to-end throughactionHandler.scanContainerProfile, and assert on the stub's received request count — this is what actually distinguishes a correct fix from one that finds the profile but silently drops the resulting command (see fix Trigger ks #3) or dedups across containers (see the removed per-workload dedup).go build ./...,go vet ./..., andgo test -count=1 ./mainhandler/... ./watcher/... ./utils/...(and the rest of the repo's packages) pass. Note:watcher/registryhandler_test.go'sTestRegistryCommandWatchis a testcontainers-based integration test that is independently flaky and not exercised by this PR's branch (it's untracked locally); it is unrelated to this change and fails the same way onmain.Additional Information
Out of scope for this PR: the issue also floats a larger redesign (replace per-container
Get()calls with a singleListmatched on thekubescape.io/instance-idannotation, closing a remaining slug-truncation edge case) as a "worth considering instead" follow-up, not a requirement for this fix.Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
AI-skills: oh-my-claudecode:team | cmds: /oh-my-claudecode:ralplan
Summary by CodeRabbit