Skip to content

dockerfile: scope CopyIgnoredFile suppression to relevant negations - #7127

Open
fredrikblau wants to merge 1 commit into
moby:masterfrom
fredrikblau:fix/7125-copyignoredfile-negation-scope
Open

dockerfile: scope CopyIgnoredFile suppression to relevant negations#7127
fredrikblau wants to merge 1 commit into
moby:masterfrom
fredrikblau:fix/7125-copyignoredfile-negation-scope

Conversation

@fredrikblau

Copy link
Copy Markdown

Fixes #7125

CopyIgnoredFile stops firing for the entire build as soon as .dockerignore contains any ! line — including a negation for an unrelated directory, or one naming a path that does not exist.

The cause is in validateCopySourcePath:

	// Do not validate copy source paths if there is no dockerignore file
	// or if the dockerignore file contains exclusions.
	//
	// Exclusions are too difficult to statically determine if they're proper
	// because it's ok for a directory to be excluded and a file inside the directory
	// to be negated.
	if cfg.ignoreMatcher == nil || cfg.ignoreMatcher.Exclusions() {
		return nil
	}

PatternMatcher.Exclusions() is a single global boolean — true when the pattern list contains a negation anywhere, with no relation to the path being copied. One ! line therefore disables the rule for every COPY and ADD in the Dockerfile. The failure mode is quiet in an unpleasant way: the documented way to silence a genuine CopyIgnoredFile warning is to add a ! line, and doing so switches the check off for everything after it while docker build --check keeps reporting no warnings.

Keeping the original concern

The comment describes a real false positive and this change preserves it. With sub and !sub/keep.txt, COPY sub /dst still copies sub/keep.txt, so warning that sub is excluded would be wrong — and MatchesOrParentMatches("sub") does return true.

But that ambiguity only arises when a negation re-includes something at or below the path being copied. Everywhere else MatchesOrParentMatches already resolves negations correctly: with that same .dockerignore, COPY sub/a.txt correctly reports excluded and COPY sub/keep.txt correctly reports not excluded.

So rather than the global flag, the source path is now compared against the individual negated patterns, and the check is skipped only when some negation could re-include a path at or below that source path. Patterns containing *, ? or [ cannot be resolved statically, so they are treated as matching anything at or below the longest path prefix preceding the first wildcard, in both directions. That deliberately errs toward suppression — a noisy linter is worse than a quiet one. . and / context roots keep their existing handling.

Known imprecision

A wildcard negation whose literal prefix is empty (!*.txt, !**/x) still suppresses the rule for all source paths, even though patternmatcher's * does not cross / and such a pattern can only re-include top-level entries. That is no worse than current behaviour and can be tightened separately if you'd like.

Tests

TestValidateCopySourcePath is a table covering every row of the issue's reproduction — unrelated negation, negation for a nonexistent path, negation placed before the exclude, and a wildcard negation with a disjoint prefix — plus the cases that must stay silent: sub + !sub/keep.txt with COPY sub, nested and wildcard negations inside the copied directory, a wildcard negation that could reach into it (!*/keep.txt), parent-directory re-inclusion (** + !sub with COPY sub/a.txt), and the three context-root cases.

Reverting validations.go alone fails 5 of these subtests and passes the rest; with the change all 19 pass.

testCopyIgnoredFileNegation adds the same two groups to the lint integration suite against a real build context. I could not run the integration suite locally — it needs a buildkitd worker — so those are compile-checked only; the unit table above is what I actually ran.

The CopyIgnoredFile rule was skipped for the whole build as soon as the
.dockerignore contained any negated pattern, because PatternMatcher's
Exclusions() is a single global flag with no relation to the path being
copied. A negation for an unrelated directory, or for a path that does
not exist, silently disabled the rule for every COPY and ADD in the
Dockerfile.

The suppression exists because it is valid for a directory to be excluded
and a file inside it to be negated, in which case copying the directory
still copies content and the warning would be wrong. That case only
arises when a negation re-includes a path at or below the copied path,
so only check for such negations instead of any negation at all.
MatchesOrParentMatches already resolves negations correctly for a given
path in every other case.

Patterns containing wildcards cannot be statically resolved, so they are
treated as matching anything at or below the longest path prefix that
precedes the first wildcard, keeping the suppression conservative.

Signed-off-by: amirahrari <ahrariamir@proton.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CopyIgnoredFile goes silent for all files when the .dockerignore contains any negated pattern

1 participant