Skip to content

fix(autodiscovery/dockercompose): skip images whose tag is not a version - #9760

Merged
olblak merged 6 commits into
updatecli:mainfrom
jouve:fix-dockercompose-unparseable-tag
Aug 2, 2026
Merged

fix(autodiscovery/dockercompose): skip images whose tag is not a version#9760
olblak merged 6 commits into
updatecli:mainfrom
jouve:fix-dockercompose-unparseable-tag

Conversation

@jouve

@jouve jouve commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

No existing issue — happy to open one if you'd prefer the usual Fix #XXX reference.

NewDockerImageSpecFromImage returns nil when the tag cannot be recognized as a version (dockerimage/spec.go:103), so sourceSpec is nil for any :latest or variant-suffixed tag. Two bugs follow, both from inlining code that the dockerfile crawler keeps in a per-image function, where return nil, nil means "skip this image".

1. Nil dereference — compose.go:164

versionFilterPattern, err = d.versionFilter.GreaterThanPattern(imageTag)
tagFilter = ""
if err != nil {
	logrus.Debugf("building version filter pattern: %s", err)
	sourceSpec.VersionFilter.Pattern = "*"   // sourceSpec is nil here
}

The two functions choke on the same input, so they always fail together: a tag that getTagFilterFromValue doesn't recognize is also a tag sv.NewVersion refuses. Whenever this branch is entered, sourceSpec is nil.

The write is also dead even when it isn't nil — nothing reads sourceSpec.VersionFilter after the block at compose.go:144-154. What reaches the template is the local versionFilterPattern, which at that point holds the empty string returned by the failed call. dockerfile.go:167 assigns that local, which is both nil-safe and the value actually rendered — this PR does the same.

Reproducer, panics on v0.119.0 and on main:

# updatecli.d/dc.yaml
autodiscovery:
  crawlers:
    dockercompose:
      versionfilter:
        kind: semver
        pattern: patch
# docker-compose.yml
services:
  a:
    image: example.test/foo/bar:pg18-latest
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x90 pc=0x16eed1e]

goroutine 1 [running]:
...dockercompose.DockerCompose.discoverDockerComposeImageManifests(...)
        pkg/plugins/autodiscovery/dockercompose/compose.go:164

addr=0x90 is consistent with a nil base plus the field offset: unsafe.Offsetof puts Spec.VersionFilter.Pattern at 136 (0x88), and a string assignment stores two words, so the fault surfaces on the second one.

2. The whole crawl is abandoned — compose.go:190

In the template selection, sourceSpec == nil with digest: false reaches return nil, nil. Inside for _, svc := range svcList that discards the manifests accumulated so far and ends discovery for every remaining compose file, rather than skipping one service.

A single :latest service therefore makes the crawler emit no pipeline at all for the repository, exit 0, and say nothing above Info level. This is how I hit it: a compose file whose images were all pinned except one stopped being watched entirely, silently.

Reproducer — no versionfilter, so the panic above is not reached:

# docker-compose.yml — discovers 0 pipelines, expected 1
services:
  aaa-pinned:
    image: cr.agentgateway.dev/agentgateway:v1.4.1
  zzz-latest:
    image: example.test/foo/bar:latest

Test

cd pkg/plugins/autodiscovery/dockercompose
go test

Scenario 3 in main_test.go covers both: the fixture lists the unparseable-tagged service after the pinned one, so the assertion on a single expected pipeline fails if the accumulated manifest is dropped. It needs its own testdata-unparseable-tag/ directory because searchDockerComposeFiles walks recursively, and a fixture under testdata/ would change the expectations of scenarios 1 and 2.

Verified that the test fails without the fix — panic inside TestDiscoverManifests/Scenario_3, at compose.go:164 — and passes with it. gofmt -l and go vet are clean on the package.

Additional Information

Checklist

  • I have updated the documentation via pull request in website repository — not applicable, no user-facing behaviour is documented differently.

Tradeoff

Keeping the versionFilterPattern = "*" fallback rather than deleting the branch is arguably belt-and-braces: in the common case the image is skipped a few lines later anyway, so the value is discarded. It still matters for degenerate tags such as 1.^\d*(\.\d*){1}$ matches it since \d* accepts the empty string, so the spec is non-nil, while sv.NewVersion("1.") fails — and it keeps the two crawlers symmetrical.

Potential improvement

matchingRule.go:39 declares ruleResults outside the for _, matchingRule := range m loop and never resets it, so a rule that follows a non-matching one can never match. Present in the dockercompose, dockerfile and other copies of that helper. Out of scope here; happy to send a separate PR if you want it fixed.

Co-authored with Claude Opus 5.

jouve and others added 2 commits July 31, 2026 14:55
NewDockerImageSpecFromImage returns nil when the tag cannot be recognized as
a version, so sourceSpec is nil for any ':latest' or variant-suffixed tag.
Two bugs follow from that, both from inlining code the dockerfile crawler
keeps in a per-image function where `return nil, nil` means "skip this image".

Writing the fallback pattern to sourceSpec.VersionFilter.Pattern dereferences
that nil whenever the crawler also carries a versionfilter, since
GreaterThanPattern fails on the very same tag. dockerfile.go assigns the local
versionFilterPattern instead, which is nil-safe and is also the variable the
template actually reads.

The `return nil, nil` in the template selection ends the whole crawl instead of
skipping one service, discarding every manifest found so far. A single
':latest' service made the crawler emit no pipeline at all for the repository,
exit 0, and say nothing above Info level.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@olblak

olblak commented Jul 31, 2026

Copy link
Copy Markdown
Member

Thanks @jouve for the fix, could I ask you to fix the two typos?

@olblak olblak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for the fix and the detailed explaination.
I confirm that your pr fix a real issue

@olblak olblak added bug Something isn't working autodiscovery All things related to the autodiscovery feature labels Aug 2, 2026
@mergify

mergify Bot commented Aug 2, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@olblak
olblak enabled auto-merge (squash) August 2, 2026 17:16
@olblak
olblak merged commit e76091b into updatecli:main Aug 2, 2026
8 checks passed
@jouve

jouve commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

thanks for quick review !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autodiscovery All things related to the autodiscovery feature bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants