From d099d2de1abf6addeb0f4d59a916bb9924aed217 Mon Sep 17 00:00:00 2001 From: Talon Bowler Date: Mon, 8 Jul 2024 16:21:53 -0700 Subject: [PATCH] Adds a rule check for copying files which match the .dockerignore patterns Signed-off-by: Talon Bowler --- frontend/dockerfile/builder/build.go | 5 + frontend/dockerfile/dockerfile2llb/convert.go | 117 +++++++++++------- frontend/dockerfile/linter/ruleset.go | 8 ++ frontend/dockerui/config.go | 92 ++++++++------ 4 files changed, 140 insertions(+), 82 deletions(-) diff --git a/frontend/dockerfile/builder/build.go b/frontend/dockerfile/builder/build.go index a6a4e7b73c26d..5d9051992c1ec 100644 --- a/frontend/dockerfile/builder/build.go +++ b/frontend/dockerfile/builder/build.go @@ -71,6 +71,10 @@ func Build(ctx context.Context, c client.Client) (_ *client.Result, err error) { return nil, capsError } + dockerIgnorePatterns, err := bc.DockerIgnorePatterns(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to parse ignore patterns") + } convertOpt := dockerfile2llb.ConvertOpt{ Config: bc.Config, Client: bc, @@ -84,6 +88,7 @@ func Build(ctx context.Context, c client.Client) (_ *client.Result, err error) { msg = linter.LintFormatShort(rulename, msg, startLine) src.Warn(ctx, msg, warnOpts(location, [][]byte{[]byte(description)}, url)) }, + DockerIgnorePatterns: dockerIgnorePatterns, } if res, ok, err := bc.HandleSubrequest(ctx, dockerui.RequestHandler{ diff --git a/frontend/dockerfile/dockerfile2llb/convert.go b/frontend/dockerfile/dockerfile2llb/convert.go index 84716ec3ee91a..1d856cfc1c182 100644 --- a/frontend/dockerfile/dockerfile2llb/convert.go +++ b/frontend/dockerfile/dockerfile2llb/convert.go @@ -40,6 +40,7 @@ import ( "github.com/moby/buildkit/util/suggest" "github.com/moby/buildkit/util/system" dockerspec "github.com/moby/docker-image-spec/specs-go/v1" + "github.com/moby/patternmatcher" "github.com/moby/sys/signal" digest "github.com/opencontainers/go-digest" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" @@ -67,14 +68,15 @@ var nonEnvArgs = map[string]struct{}{ type ConvertOpt struct { dockerui.Config - Client *dockerui.Client - MainContext *llb.State - SourceMap *llb.SourceMap - TargetPlatform *ocispecs.Platform - MetaResolver llb.ImageMetaResolver - LLBCaps *apicaps.CapSet - Warn linter.LintWarnFunc - AllStages bool + Client *dockerui.Client + MainContext *llb.State + SourceMap *llb.SourceMap + TargetPlatform *ocispecs.Platform + MetaResolver llb.ImageMetaResolver + LLBCaps *apicaps.CapSet + Warn linter.LintWarnFunc + AllStages bool + DockerIgnorePatterns []string } type SBOMTargets struct { @@ -630,24 +632,31 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS return nil, parser.WithLocation(err, d.stage.Location) } } + d.state = d.state.Network(opt.NetworkMode) + dockerIgnorePatterns, err := opt.Client.DockerIgnorePatterns(ctx) + if err != nil { + return nil, err + } + opt := dispatchOpt{ - allDispatchStates: allDispatchStates, - metaArgs: optMetaArgs, - buildArgValues: opt.BuildArgs, - shlex: shlex, - buildContext: llb.NewState(buildContext), - proxyEnv: proxyEnv, - cacheIDNamespace: opt.CacheIDNamespace, - buildPlatforms: platformOpt.buildPlatforms, - targetPlatform: platformOpt.targetPlatform, - extraHosts: opt.ExtraHosts, - shmSize: opt.ShmSize, - ulimit: opt.Ulimits, - cgroupParent: opt.CgroupParent, - llbCaps: opt.LLBCaps, - sourceMap: opt.SourceMap, - lint: lint, + allDispatchStates: allDispatchStates, + metaArgs: optMetaArgs, + buildArgValues: opt.BuildArgs, + shlex: shlex, + buildContext: llb.NewState(buildContext), + proxyEnv: proxyEnv, + cacheIDNamespace: opt.CacheIDNamespace, + buildPlatforms: platformOpt.buildPlatforms, + targetPlatform: platformOpt.targetPlatform, + extraHosts: opt.ExtraHosts, + shmSize: opt.ShmSize, + ulimit: opt.Ulimits, + cgroupParent: opt.CgroupParent, + llbCaps: opt.LLBCaps, + sourceMap: opt.SourceMap, + lint: lint, + dockerIgnorePatterns: dockerIgnorePatterns, } if err = dispatchOnBuildTriggers(d, d.image.Config.OnBuild, opt); err != nil { @@ -806,22 +815,23 @@ func toCommand(ic instructions.Command, allDispatchStates *dispatchStates) (comm } type dispatchOpt struct { - allDispatchStates *dispatchStates - metaArgs []instructions.KeyValuePairOptional - buildArgValues map[string]string - shlex *shell.Lex - buildContext llb.State - proxyEnv *llb.ProxyEnv - cacheIDNamespace string - targetPlatform ocispecs.Platform - buildPlatforms []ocispecs.Platform - extraHosts []llb.HostIP - shmSize int64 - ulimit []pb.Ulimit - cgroupParent string - llbCaps *apicaps.CapSet - sourceMap *llb.SourceMap - lint *linter.Linter + allDispatchStates *dispatchStates + metaArgs []instructions.KeyValuePairOptional + buildArgValues map[string]string + shlex *shell.Lex + buildContext llb.State + proxyEnv *llb.ProxyEnv + cacheIDNamespace string + targetPlatform ocispecs.Platform + buildPlatforms []ocispecs.Platform + extraHosts []llb.HostIP + shmSize int64 + ulimit []pb.Ulimit + cgroupParent string + llbCaps *apicaps.CapSet + sourceMap *llb.SourceMap + lint *linter.Linter + dockerIgnorePatterns []string } func getEnv(state llb.State) shell.EnvGetter { @@ -909,7 +919,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error { checksum: checksum, location: c.Location(), opt: opt, - }) + }, opt.lint) } if err == nil { for _, src := range c.SourcePaths { @@ -961,7 +971,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error { parents: c.Parents, location: c.Location(), opt: opt, - }) + }, opt.lint) if err == nil { if len(cmd.sources) == 0 { for _, src := range c.SourcePaths { @@ -1313,7 +1323,7 @@ func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bo return nil } -func dispatchCopy(d *dispatchState, cfg copyConfig) error { +func dispatchCopy(d *dispatchState, cfg copyConfig, lint *linter.Linter) error { dest, err := pathRelativeToWorkingDir(d.state, cfg.params.DestPath, *d.platform) if err != nil { return err @@ -1381,6 +1391,8 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error { var a *llb.FileAction + validateCopySourcePath(cfg.opt.dockerIgnorePatterns, cfg.params.SourcePaths, cfg.location, lint) + for _, src := range cfg.params.SourcePaths { commitMessage.WriteString(" " + src) gitRef, gitRefErr := gitutil.ParseGitRef(src) @@ -1867,6 +1879,25 @@ func addReachableStages(s *dispatchState, stages map[*dispatchState]struct{}) { } } +func validateCopySourcePath(excludePatterns, paths []string, location []parser.Range, lint *linter.Linter) error { + matcher, err := patternmatcher.New(excludePatterns) + if err != nil { + return err + } + for _, src := range paths { + + ok, err := matcher.MatchesOrParentMatches(src) + if err != nil { + return err + } + if ok { + msg := linter.RuleCopyIgnoredFile.Format(src) + lint.Run(&linter.RuleCopyIgnoredFile, location, msg) + } + } + return nil +} + func validateCircularDependency(states []*dispatchState) error { var visit func(*dispatchState, []instructions.Command) []instructions.Command if states == nil { diff --git a/frontend/dockerfile/linter/ruleset.go b/frontend/dockerfile/linter/ruleset.go index b333c4c7c2258..99a972772c109 100644 --- a/frontend/dockerfile/linter/ruleset.go +++ b/frontend/dockerfile/linter/ruleset.go @@ -148,4 +148,12 @@ var ( return fmt.Sprintf("Default value for ARG %v results in empty or invalid base image name", baseName) }, } + RuleCopyIgnoredFile = LinterRule[func(string) string]{ + Name: "CopyIgnoredFile", + Description: "File is ignored by .dockerignore", + URL: "", + Format: func(file string) string { + return fmt.Sprintf("File %q is ignored by .dockerignore", file) + }, + } ) diff --git a/frontend/dockerui/config.go b/frontend/dockerui/config.go index ea87c4a56cb38..1298d9c18948b 100644 --- a/frontend/dockerui/config.go +++ b/frontend/dockerui/config.go @@ -410,45 +410,7 @@ func (bc *Client) MainContext(ctx context.Context, opts ...llb.LocalOption) (*ll return bctx.context, nil } - if bc.dockerignore == nil { - st := llb.Local(bctx.contextLocalName, - llb.SessionID(bc.bopts.SessionID), - llb.FollowPaths([]string{DefaultDockerignoreName}), - llb.SharedKeyHint(bctx.contextLocalName+"-"+DefaultDockerignoreName), - WithInternalName("load "+DefaultDockerignoreName), - llb.Differ(llb.DiffNone, false), - ) - def, err := st.Marshal(ctx, bc.marshalOpts()...) - if err != nil { - return nil, err - } - res, err := bc.client.Solve(ctx, client.SolveRequest{ - Definition: def.ToPB(), - }) - if err != nil { - return nil, err - } - ref, err := res.SingleRef() - if err != nil { - return nil, err - } - dt, _ := ref.ReadFile(ctx, client.ReadRequest{ // ignore error - Filename: DefaultDockerignoreName, - }) - if dt == nil { - dt = []byte{} - } - bc.dockerignore = dt - bc.dockerignoreName = DefaultDockerignoreName - } - - var excludes []string - if len(bc.dockerignore) != 0 { - excludes, err = ignorefile.ReadAll(bytes.NewBuffer(bc.dockerignore)) - if err != nil { - return nil, errors.Wrapf(err, "failed parsing %s", bc.dockerignoreName) - } - } + excludes, err := bc.dockerIgnoreExcludes(ctx, bctx) opts = append([]llb.LocalOption{ llb.SessionID(bc.bopts.SessionID), @@ -493,6 +455,15 @@ func (bc *Client) IsNoCache(name string) bool { return false } +func (bc *Client) DockerIgnorePatterns(ctx context.Context) ([]string, error) { + bctx, err := bc.buildContext(ctx) + if err != nil { + return nil, err + } + + return bc.dockerIgnoreExcludes(ctx, bctx) +} + func DefaultMainContext(opts ...llb.LocalOption) *llb.State { opts = append([]llb.LocalOption{ llb.SharedKeyHint(DefaultLocalNameContext), @@ -505,3 +476,46 @@ func DefaultMainContext(opts ...llb.LocalOption) *llb.State { func WithInternalName(name string) llb.ConstraintsOpt { return llb.WithCustomName("[internal] " + name) } + +func (bc *Client) dockerIgnoreExcludes(ctx context.Context, bctx *buildContext) ([]string, error) { + if bc.dockerignore == nil { + st := llb.Local(bctx.contextLocalName, + llb.SessionID(bc.bopts.SessionID), + llb.FollowPaths([]string{DefaultDockerignoreName}), + llb.SharedKeyHint(bctx.contextLocalName+"-"+DefaultDockerignoreName), + WithInternalName("load "+DefaultDockerignoreName), + llb.Differ(llb.DiffNone, false), + ) + def, err := st.Marshal(ctx, bc.marshalOpts()...) + if err != nil { + return nil, err + } + res, err := bc.client.Solve(ctx, client.SolveRequest{ + Definition: def.ToPB(), + }) + if err != nil { + return nil, err + } + ref, err := res.SingleRef() + if err != nil { + return nil, err + } + dt, _ := ref.ReadFile(ctx, client.ReadRequest{ // ignore error + Filename: DefaultDockerignoreName, + }) + if dt == nil { + dt = []byte{} + } + bc.dockerignore = dt + bc.dockerignoreName = DefaultDockerignoreName + } + var err error + var excludes []string + if len(bc.dockerignore) != 0 { + excludes, err = ignorefile.ReadAll(bytes.NewBuffer(bc.dockerignore)) + if err != nil { + return nil, errors.Wrapf(err, "failed parsing %s", bc.dockerignoreName) + } + } + return excludes, nil +}