Skip to content

Commit e83d79a

Browse files
authored
Merge pull request #5135 from daghack/copy-ignored-file-check
Rule Check: CopyIgnoredFiles
2 parents 62ba6fe + 07fe324 commit e83d79a

File tree

7 files changed

+364
-103
lines changed

7 files changed

+364
-103
lines changed

frontend/dockerfile/dockerfile2llb/convert.go

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/moby/buildkit/util/suggest"
4141
"github.com/moby/buildkit/util/system"
4242
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
43+
"github.com/moby/patternmatcher"
4344
"github.com/moby/sys/signal"
4445
digest "github.com/opencontainers/go-digest"
4546
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
@@ -594,6 +595,20 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
594595
buildContext := &mutableOutput{}
595596
ctxPaths := map[string]struct{}{}
596597

598+
var dockerIgnoreMatcher *patternmatcher.PatternMatcher
599+
if opt.Client != nil && opt.Client.CopyIgnoredCheckEnabled {
600+
dockerIgnorePatterns, err := opt.Client.DockerIgnorePatterns(ctx)
601+
if err != nil {
602+
return nil, err
603+
}
604+
if len(dockerIgnorePatterns) > 0 {
605+
dockerIgnoreMatcher, err = patternmatcher.New(dockerIgnorePatterns)
606+
if err != nil {
607+
return nil, err
608+
}
609+
}
610+
}
611+
597612
for _, d := range allDispatchStates.states {
598613
if !opt.AllStages {
599614
if _, ok := allReachable[d]; !ok || d.noinit {
@@ -634,24 +649,27 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
634649
return nil, parser.WithLocation(err, d.stage.Location)
635650
}
636651
}
652+
637653
d.state = d.state.Network(opt.NetworkMode)
654+
638655
opt := dispatchOpt{
639-
allDispatchStates: allDispatchStates,
640-
metaArgs: optMetaArgs,
641-
buildArgValues: opt.BuildArgs,
642-
shlex: shlex,
643-
buildContext: llb.NewState(buildContext),
644-
proxyEnv: proxyEnv,
645-
cacheIDNamespace: opt.CacheIDNamespace,
646-
buildPlatforms: platformOpt.buildPlatforms,
647-
targetPlatform: platformOpt.targetPlatform,
648-
extraHosts: opt.ExtraHosts,
649-
shmSize: opt.ShmSize,
650-
ulimit: opt.Ulimits,
651-
cgroupParent: opt.CgroupParent,
652-
llbCaps: opt.LLBCaps,
653-
sourceMap: opt.SourceMap,
654-
lint: lint,
656+
allDispatchStates: allDispatchStates,
657+
metaArgs: optMetaArgs,
658+
buildArgValues: opt.BuildArgs,
659+
shlex: shlex,
660+
buildContext: llb.NewState(buildContext),
661+
proxyEnv: proxyEnv,
662+
cacheIDNamespace: opt.CacheIDNamespace,
663+
buildPlatforms: platformOpt.buildPlatforms,
664+
targetPlatform: platformOpt.targetPlatform,
665+
extraHosts: opt.ExtraHosts,
666+
shmSize: opt.ShmSize,
667+
ulimit: opt.Ulimits,
668+
cgroupParent: opt.CgroupParent,
669+
llbCaps: opt.LLBCaps,
670+
sourceMap: opt.SourceMap,
671+
lint: lint,
672+
dockerIgnoreMatcher: dockerIgnoreMatcher,
655673
}
656674

657675
if err = dispatchOnBuildTriggers(d, d.image.Config.OnBuild, opt); err != nil {
@@ -810,22 +828,23 @@ func toCommand(ic instructions.Command, allDispatchStates *dispatchStates) (comm
810828
}
811829

812830
type dispatchOpt struct {
813-
allDispatchStates *dispatchStates
814-
metaArgs []instructions.KeyValuePairOptional
815-
buildArgValues map[string]string
816-
shlex *shell.Lex
817-
buildContext llb.State
818-
proxyEnv *llb.ProxyEnv
819-
cacheIDNamespace string
820-
targetPlatform ocispecs.Platform
821-
buildPlatforms []ocispecs.Platform
822-
extraHosts []llb.HostIP
823-
shmSize int64
824-
ulimit []pb.Ulimit
825-
cgroupParent string
826-
llbCaps *apicaps.CapSet
827-
sourceMap *llb.SourceMap
828-
lint *linter.Linter
831+
allDispatchStates *dispatchStates
832+
metaArgs []instructions.KeyValuePairOptional
833+
buildArgValues map[string]string
834+
shlex *shell.Lex
835+
buildContext llb.State
836+
proxyEnv *llb.ProxyEnv
837+
cacheIDNamespace string
838+
targetPlatform ocispecs.Platform
839+
buildPlatforms []ocispecs.Platform
840+
extraHosts []llb.HostIP
841+
shmSize int64
842+
ulimit []pb.Ulimit
843+
cgroupParent string
844+
llbCaps *apicaps.CapSet
845+
sourceMap *llb.SourceMap
846+
lint *linter.Linter
847+
dockerIgnoreMatcher *patternmatcher.PatternMatcher
829848
}
830849

831850
func getEnv(state llb.State) shell.EnvGetter {
@@ -912,6 +931,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
912931
keepGitDir: c.KeepGitDir,
913932
checksum: checksum,
914933
location: c.Location(),
934+
ignoreMatcher: opt.dockerIgnoreMatcher,
915935
opt: opt,
916936
})
917937
}
@@ -946,12 +966,15 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
946966
err = dispatchArg(d, c, &opt)
947967
case *instructions.CopyCommand:
948968
l := opt.buildContext
969+
var ignoreMatcher *patternmatcher.PatternMatcher
949970
if len(cmd.sources) != 0 {
950971
src := cmd.sources[0]
951972
if !src.noinit {
952973
return errors.Errorf("cannot copy from stage %q, it needs to be defined before current stage %q", c.From, d.stageName)
953974
}
954975
l = src.state
976+
} else {
977+
ignoreMatcher = opt.dockerIgnoreMatcher
955978
}
956979
err = dispatchCopy(d, copyConfig{
957980
params: c.SourcesAndDest,
@@ -964,6 +987,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
964987
link: c.Link,
965988
parents: c.Parents,
966989
location: c.Location(),
990+
ignoreMatcher: ignoreMatcher,
967991
opt: opt,
968992
})
969993
if err == nil {
@@ -1442,6 +1466,7 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error {
14421466
a = a.Copy(st, f, dest, opts...)
14431467
}
14441468
} else {
1469+
validateCopySourcePath(src, &cfg)
14451470
var patterns []string
14461471
if cfg.parents {
14471472
// detect optional pivot point
@@ -1558,6 +1583,7 @@ type copyConfig struct {
15581583
checksum digest.Digest
15591584
parents bool
15601585
location []parser.Range
1586+
ignoreMatcher *patternmatcher.PatternMatcher
15611587
opt dispatchOpt
15621588
}
15631589

@@ -1871,6 +1897,27 @@ func addReachableStages(s *dispatchState, stages map[*dispatchState]struct{}) {
18711897
}
18721898
}
18731899

1900+
func validateCopySourcePath(src string, cfg *copyConfig) error {
1901+
if cfg.ignoreMatcher == nil {
1902+
return nil
1903+
}
1904+
cmd := "Copy"
1905+
if cfg.isAddCommand {
1906+
cmd = "Add"
1907+
}
1908+
1909+
ok, err := cfg.ignoreMatcher.MatchesOrParentMatches(src)
1910+
if err != nil {
1911+
return err
1912+
}
1913+
if ok {
1914+
msg := linter.RuleCopyIgnoredFile.Format(cmd, src)
1915+
cfg.opt.lint.Run(&linter.RuleCopyIgnoredFile, cfg.location, msg)
1916+
}
1917+
1918+
return nil
1919+
}
1920+
18741921
func validateCircularDependency(states []*dispatchState) error {
18751922
var visit func(*dispatchState, []instructions.Command) []instructions.Command
18761923
if states == nil {

frontend/dockerfile/dockerfile_lint_test.go

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"maps"
88
"os"
9+
"regexp"
910
"sort"
1011
"testing"
1112
"time"
@@ -44,8 +45,70 @@ var lintTests = integration.TestFuncs(
4445
testSecretsUsedInArgOrEnv,
4546
testInvalidDefaultArgInFrom,
4647
testFromPlatformFlagConstDisallowed,
48+
testCopyIgnoredFiles,
4749
)
4850

51+
func testCopyIgnoredFiles(t *testing.T, sb integration.Sandbox) {
52+
dockerignore := []byte(`
53+
Dockerfile
54+
`)
55+
dockerfile := []byte(`
56+
FROM scratch
57+
COPY Dockerfile .
58+
ADD Dockerfile /windy
59+
`)
60+
checkLinterWarnings(t, sb, &lintTestParams{
61+
Dockerfile: dockerfile,
62+
DockerIgnore: dockerignore,
63+
BuildErrLocation: 3,
64+
StreamBuildErrRegexp: regexp.MustCompile(`failed to solve: failed to compute cache key: failed to calculate checksum of ref [^\s]+ "/Dockerfile": not found`),
65+
})
66+
67+
checkLinterWarnings(t, sb, &lintTestParams{
68+
Dockerfile: dockerfile,
69+
DockerIgnore: dockerignore,
70+
FrontendAttrs: map[string]string{
71+
"build-arg:BUILDKIT_DOCKERFILE_CHECK_COPYIGNORED_EXPERIMENT": "true",
72+
},
73+
BuildErrLocation: 3,
74+
StreamBuildErrRegexp: regexp.MustCompile(`failed to solve: failed to compute cache key: failed to calculate checksum of ref [^\s]+ "/Dockerfile": not found`),
75+
Warnings: []expectedLintWarning{
76+
{
77+
RuleName: "CopyIgnoredFile",
78+
Description: "Attempting to Copy file that is excluded by .dockerignore",
79+
Detail: `Attempting to Copy file "Dockerfile" that is excluded by .dockerignore`,
80+
URL: "https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/",
81+
Level: 1,
82+
Line: 3,
83+
},
84+
{
85+
RuleName: "CopyIgnoredFile",
86+
Description: "Attempting to Copy file that is excluded by .dockerignore",
87+
Detail: `Attempting to Add file "Dockerfile" that is excluded by .dockerignore`,
88+
URL: "https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/",
89+
Level: 1,
90+
Line: 4,
91+
},
92+
},
93+
})
94+
95+
dockerignore = []byte(`
96+
foobar
97+
`)
98+
dockerfile = []byte(`
99+
FROM scratch AS base
100+
COPY Dockerfile /foobar
101+
ADD Dockerfile /windy
102+
103+
FROM base
104+
COPY --from=base /foobar /Dockerfile
105+
`)
106+
checkLinterWarnings(t, sb, &lintTestParams{
107+
Dockerfile: dockerfile,
108+
DockerIgnore: dockerignore,
109+
})
110+
}
111+
49112
func testSecretsUsedInArgOrEnv(t *testing.T, sb integration.Sandbox) {
50113
dockerfile := []byte(`
51114
FROM scratch
@@ -1206,11 +1269,19 @@ func checkUnmarshal(t *testing.T, sb integration.Sandbox, lintTest *lintTestPara
12061269
lintResults, err := unmarshalLintResults(res)
12071270
require.NoError(t, err)
12081271

1209-
if lintResults.Error != nil {
1210-
require.Equal(t, lintTest.UnmarshalBuildErr, lintResults.Error.Message)
1272+
if lintTest.UnmarshalBuildErr == "" && lintTest.UnmarshalBuildErrRegexp == nil {
1273+
require.Nil(t, lintResults.Error)
1274+
} else {
1275+
require.NotNil(t, lintResults.Error)
1276+
if lintTest.UnmarshalBuildErr != "" {
1277+
require.Equal(t, lintTest.UnmarshalBuildErr, lintResults.Error.Message)
1278+
} else if !lintTest.UnmarshalBuildErrRegexp.MatchString(lintResults.Error.Message) {
1279+
t.Fatalf("error %q does not match %q", lintResults.Error.Message, lintTest.UnmarshalBuildErrRegexp.String())
1280+
}
12111281
require.Greater(t, lintResults.Error.Location.SourceIndex, int32(-1))
12121282
require.Less(t, lintResults.Error.Location.SourceIndex, int32(len(lintResults.Sources)))
12131283
}
1284+
12141285
require.Equal(t, len(warnings), len(lintResults.Warnings))
12151286

12161287
sort.Slice(lintResults.Warnings, func(i, j int) bool {
@@ -1230,6 +1301,7 @@ func checkUnmarshal(t *testing.T, sb integration.Sandbox, lintTest *lintTestPara
12301301
_, err = lintTest.Client.Build(sb.Context(), client.SolveOpt{
12311302
LocalMounts: map[string]fsutil.FS{
12321303
dockerui.DefaultLocalNameDockerfile: lintTest.TmpDir,
1304+
dockerui.DefaultLocalNameContext: lintTest.TmpDir,
12331305
},
12341306
}, "", frontend, nil)
12351307
require.NoError(t, err)
@@ -1276,10 +1348,14 @@ func checkProgressStream(t *testing.T, sb integration.Sandbox, lintTest *lintTes
12761348
dockerui.DefaultLocalNameContext: lintTest.TmpDir,
12771349
},
12781350
}, status)
1279-
if lintTest.StreamBuildErr == "" {
1351+
if lintTest.StreamBuildErr == "" && lintTest.StreamBuildErrRegexp == nil {
12801352
require.NoError(t, err)
12811353
} else {
1282-
require.EqualError(t, err, lintTest.StreamBuildErr)
1354+
if lintTest.StreamBuildErr != "" {
1355+
require.EqualError(t, err, lintTest.StreamBuildErr)
1356+
} else if !lintTest.StreamBuildErrRegexp.MatchString(err.Error()) {
1357+
t.Fatalf("error %q does not match %q", err.Error(), lintTest.StreamBuildErrRegexp.String())
1358+
}
12831359
}
12841360

12851361
select {
@@ -1313,9 +1389,15 @@ func checkLinterWarnings(t *testing.T, sb integration.Sandbox, lintTest *lintTes
13131389
integration.SkipOnPlatform(t, "windows")
13141390

13151391
if lintTest.TmpDir == nil {
1392+
testfiles := []fstest.Applier{
1393+
fstest.CreateFile("Dockerfile", lintTest.Dockerfile, 0600),
1394+
}
1395+
if lintTest.DockerIgnore != nil {
1396+
testfiles = append(testfiles, fstest.CreateFile(".dockerignore", lintTest.DockerIgnore, 0600))
1397+
}
13161398
lintTest.TmpDir = integration.Tmpdir(
13171399
t,
1318-
fstest.CreateFile("Dockerfile", lintTest.Dockerfile, 0600),
1400+
testfiles...,
13191401
)
13201402
}
13211403

@@ -1374,13 +1456,16 @@ type expectedLintWarning struct {
13741456
}
13751457

13761458
type lintTestParams struct {
1377-
Client *client.Client
1378-
TmpDir *integration.TmpDirWithName
1379-
Dockerfile []byte
1380-
Warnings []expectedLintWarning
1381-
UnmarshalWarnings []expectedLintWarning
1382-
StreamBuildErr string
1383-
UnmarshalBuildErr string
1384-
BuildErrLocation int32
1385-
FrontendAttrs map[string]string
1459+
Client *client.Client
1460+
TmpDir *integration.TmpDirWithName
1461+
Dockerfile []byte
1462+
DockerIgnore []byte
1463+
Warnings []expectedLintWarning
1464+
UnmarshalWarnings []expectedLintWarning
1465+
StreamBuildErr string
1466+
StreamBuildErrRegexp *regexp.Regexp
1467+
UnmarshalBuildErr string
1468+
UnmarshalBuildErrRegexp *regexp.Regexp
1469+
BuildErrLocation int32
1470+
FrontendAttrs map[string]string
13861471
}

frontend/dockerfile/docs/rules/_index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,9 @@ $ docker build --check .
9696
<td><a href="./from-platform-flag-const-disallowed/">FromPlatformFlagConstDisallowed</a></td>
9797
<td>FROM --platform flag should not use a constant value</td>
9898
</tr>
99+
<tr>
100+
<td><a href="./copy-ignored-file/">CopyIgnoredFile</a></td>
101+
<td>Attempting to Copy file that is excluded by .dockerignore</td>
102+
</tr>
99103
</tbody>
100104
</table>

0 commit comments

Comments
 (0)