Fix .dockerignore wildcard negation directory descent#6927
Conversation
fcba319 to
f65f4ed
Compare
|
PTAL @podman-container-tools/buildah-maintainers @podman-container-tools/buildah-reviewers |
| // Check for dockerignore-style exclusion of this item. | ||
| if rel != "." { | ||
| excluded, err := pm.Matches(filepath.ToSlash(rel)) //nolint:staticcheck | ||
| excluded, err := pm.IsMatch(filepath.ToSlash(rel)) |
There was a problem hiding this comment.
Probably out of scope, but the deprecated call could be fixed there as well? https://github.com/podman-container-tools/buildah/blob/main/copier/copier.go#L1087
| } | ||
| } | ||
| return false | ||
| return copier.IncludeDirectoryAnyway(path, pm) |
There was a problem hiding this comment.
Is there a reason to have the private function that only delegates to the public (commented) one?
| // #6615: see dockerignore-exclude-kind-of-deep-subdir-dot | ||
| withoutDocker: true, | ||
| fsSkip: []string{"(dir):subdir:mtime"}, | ||
| compatScratchConfig: types.OptionalBoolFalse, |
There was a problem hiding this comment.
Was the changed compatScratchConfig from types.OptionalBoolTrue to OptionalBoolFalse because it wasn't needed?
Also, there are removals of failOnExtraFSContent. Is that correct?
| // descended into because a negation pattern in pm might match files under it. | ||
| // It handles literal prefix matches (e.g. !cmd/main.go for dir "cmd") and | ||
| // wildcard negations (e.g. !**/*.go, !*/*.go, !src/*/config.yaml). | ||
| func IncludeDirectoryAnyway(dirPath string, pm *fileutils.PatternMatcher) bool { |
There was a problem hiding this comment.
Something like ShouldDescendExcludedDir might be a more descriptive name.
There was a problem hiding this comment.
Also, I think that the and wildcard negations (e.g. !**/*.go, !*/*.go, !src/*/config.yaml) part is not completely correct. !src/*/config.yaml is IMHO handled by the literal prefix match.
The comment might mention that the wildcard-aware check may intentionally overmatch for wildcards in the middle (it's below).
| want: true, | ||
| }, | ||
| { | ||
| name: "non-exclusion patterns are ignored", |
There was a problem hiding this comment.
TC like?:
{
name: "dir prefix match is ignored",
path: "cmds",
patterns: []string{"!cmd/main.go"},
want: false,
},|
The failed test is an unrelated flake. |
| // Matches uses filepath.FromSlash() to convert candidates before | ||
| // checking if they match the patterns it's been given, implying that | ||
| // it expects Unix-style paths. |
There was a problem hiding this comment.
Just a detail, but the comment uses the old function name Matches. It may be misleading now, and the content may no longer be relevant.
mtrmac
left a comment
There was a problem hiding this comment.
(Just a pretty brief look, I didn’t read the tests at all.)
| // if the directory is at or under that literal prefix, a file beneath this | ||
| // directory could match the negation, so keep descending. | ||
| if firstWild := strings.IndexAny(slashPattern, "*?["); firstWild >= 0 { | ||
| literalPrefix := strings.TrimRight(slashPattern[:firstWild], "/") |
There was a problem hiding this comment.
What about !cmd/image*? TrimRight will not find the /.
There was a problem hiding this comment.
@mtrmac Oh, I thought it's handled with strings.HasPrefix before, but no - only for cmd with !cmd/image*, but not cmd/images with !cmd/image* or !cmd/image*/main.go with cmd/images/main.go.
| // the literal prefix before the first wildcard and may intentionally | ||
| // overmatch (descend into directories that won't ultimately contain matches), | ||
| // which is safe because actual file-level matching happens later. | ||
| func ShouldDescendExcludedDir(dirPath string, pm *fileutils.PatternMatcher) bool { |
There was a problem hiding this comment.
This (or some variant of this) should probably be in c/storage/pkg/fileutils instead: c/storage/pkg/archive.tarWithOptionsTo` contains a similar exclusion check with the same problem.
There was a problem hiding this comment.
(If we did end up doing this Buildah-only, I think having this in something like internal/exclusions would be better, not to commit to it as a stable API — but that’s a Buildah maintainer decision.)
There was a problem hiding this comment.
Agreed, we definitely don't want to add this as an exported API that we have to keep working, even if/after how exclusion matching rules change.
There was a problem hiding this comment.
I have moved that into internal/excludes. Should I create a PR with the fix in c/storage as a follow-up?
nalind
left a comment
There was a problem hiding this comment.
I'm not very enthusiastic about updating conformance tests which test "ignores" behavior so that they don't use a baseline.
| // the literal prefix before the first wildcard and may intentionally | ||
| // overmatch (descend into directories that won't ultimately contain matches), | ||
| // which is safe because actual file-level matching happens later. | ||
| func ShouldDescendExcludedDir(dirPath string, pm *fileutils.PatternMatcher) bool { |
There was a problem hiding this comment.
Agreed, we definitely don't want to add this as an exported API that we have to keep working, even if/after how exclusion matching rules change.
| // checking if they match the patterns it's been given, implying that | ||
| // it expects Unix-style paths. | ||
| matches, err := pm.Matches(filepath.ToSlash(rel)) //nolint:staticcheck | ||
| matches, err := pm.IsMatch(filepath.ToSlash(rel)) |
There was a problem hiding this comment.
IsMatch() does more work to return the same result as Matches(), it's not worth switching which one we use.
I am also not a big fan of this, but the root cause is that go-dockerclient sends the build context through moby/go-archive's TarWithOptions, which has the same bug (moby/moby#30018, moby/moby#45608). Docker BuildKit handles it correctly, but since the framework filters client-side before the context reaches BuildKit, we can't get a valid baseline. |
| run_buildah 1 run myctr ls -l subdir/ | ||
| # !*/sub1* un-excludes subdir/sub1.txt | ||
| run_buildah run myctr ls -l subdir/ | ||
| run_buildah run myctr ls -l subdir/sub1.txt |
There was a problem hiding this comment.
This should probably be making sure that nothing else was copied to this directory.
There was a problem hiding this comment.
Something like this
run_buildah run myctr find subdir -mindepth 1 -print
assert "$output" = "subdir/sub1.txt"would make sure that nothing else was copied there.
|
PTAL @nalind |
| // Docker BuildKit handles it correctly, but we cannot compare | ||
| // through this framework. | ||
| // TODO: re-enable Docker comparison when moby/go-archive fixes | ||
| // wildcard negation handling in TarWithOptions. |
There was a problem hiding this comment.
We supply the InputStream field in the options, not the ContextDir, so we do have control over which parts of the build context we transfer.
There was a problem hiding this comment.
Ah, thanks, I didn't realize that.
Buildah skipped excluded directories when negation patterns contained wildcards (For example: !**/*.go), because the descent check only matched literal prefixes. Extract the literal prefix before the first wildcard and descend when the directory is at or under it; when the prefix is empty, always descend. Same bug as Docker's classic builder (moby/moby#30018, moby/moby#45608). Fixes: podman-container-tools#6615 Signed-off-by: Jan Rodák <hony.com@seznam.cz>
Buildah skipped excluded directories when negation patterns contained wildcards (For example: !**/*.go), because the descent check only matched literal prefixes. Extract the literal prefix before the first wildcard and descend when the directory is at or under it; when the prefix is empty, always descend.
Same bug as Docker's classic builder (moby/moby#30018, moby/moby#45608).
Fixes: #6615
What type of PR is this?
What this PR does / why we need it:
How to verify it
Which issue(s) this PR fixes:
Special notes for your reviewer:
Does this PR introduce a user-facing change?