Skip to content

Commit 36f076d

Browse files
authored
Merge pull request moby#5213 from daghack/experimental-rule-checks
Proposal: Experimental Rulechecks
2 parents 836fe2b + a62980e commit 36f076d

File tree

9 files changed

+146
-69
lines changed

9 files changed

+146
-69
lines changed

frontend/dockerfile/dockerfile2llb/convert.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
596596
ctxPaths := map[string]struct{}{}
597597

598598
var dockerIgnoreMatcher *patternmatcher.PatternMatcher
599-
if opt.Client != nil && opt.Client.CopyIgnoredCheckEnabled {
599+
if opt.Client != nil {
600600
dockerIgnorePatterns, err := opt.Client.DockerIgnorePatterns(ctx)
601601
if err != nil {
602602
return nil, err

frontend/dockerfile/dockerfile_lint_test.go

+38-4
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,46 @@ ADD Dockerfile /windy
6464
StreamBuildErrRegexp: regexp.MustCompile(`failed to solve: failed to compute cache key: failed to calculate checksum of ref [^\s]+ "/Dockerfile": not found`),
6565
})
6666

67+
dockerfile = []byte(`# check=experimental=CopyIgnoredFile
68+
FROM scratch
69+
COPY Dockerfile .
70+
ADD Dockerfile /windy
71+
`)
72+
6773
checkLinterWarnings(t, sb, &lintTestParams{
68-
Dockerfile: dockerfile,
69-
DockerIgnore: dockerignore,
70-
FrontendAttrs: map[string]string{
71-
"build-arg:BUILDKIT_DOCKERFILE_CHECK_COPYIGNORED_EXPERIMENT": "true",
74+
Dockerfile: dockerfile,
75+
DockerIgnore: dockerignore,
76+
BuildErrLocation: 3,
77+
StreamBuildErrRegexp: regexp.MustCompile(`failed to solve: failed to compute cache key: failed to calculate checksum of ref [^\s]+ "/Dockerfile": not found`),
78+
Warnings: []expectedLintWarning{
79+
{
80+
RuleName: "CopyIgnoredFile",
81+
Description: "Attempting to Copy file that is excluded by .dockerignore",
82+
Detail: `Attempting to Copy file "Dockerfile" that is excluded by .dockerignore`,
83+
URL: "https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/",
84+
Level: 1,
85+
Line: 3,
86+
},
87+
{
88+
RuleName: "CopyIgnoredFile",
89+
Description: "Attempting to Copy file that is excluded by .dockerignore",
90+
Detail: `Attempting to Add file "Dockerfile" that is excluded by .dockerignore`,
91+
URL: "https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/",
92+
Level: 1,
93+
Line: 4,
94+
},
7295
},
96+
})
97+
98+
dockerfile = []byte(`# check=skip=all;experimental=CopyIgnoredFile
99+
FROM scratch
100+
COPY Dockerfile .
101+
ADD Dockerfile /windy
102+
`)
103+
104+
checkLinterWarnings(t, sb, &lintTestParams{
105+
Dockerfile: dockerfile,
106+
DockerIgnore: dockerignore,
73107
BuildErrLocation: 3,
74108
StreamBuildErrRegexp: regexp.MustCompile(`failed to solve: failed to compute cache key: failed to calculate checksum of ref [^\s]+ "/Dockerfile": not found`),
75109
Warnings: []expectedLintWarning{

frontend/dockerfile/docs/rules/_index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ $ docker build --check .
9797
<td>FROM --platform flag should not use a constant value</td>
9898
</tr>
9999
<tr>
100-
<td><a href="./copy-ignored-file/">CopyIgnoredFile</a></td>
100+
<td><a href="./copy-ignored-file/">CopyIgnoredFile (experimental)</a></td>
101101
<td>Attempting to Copy file that is excluded by .dockerignore</td>
102102
</tr>
103103
</tbody>

frontend/dockerfile/docs/rules/copy-ignored-file.md

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ aliases:
55
- /go/dockerfile/rule/copy-ignored-file/
66
---
77

8+
> **Note**
9+
>
10+
> This check is experimental and is not enabled by default. To enable it, see
11+
> [Experimental checks](https://docs.docker.com/go/build-checks-experimental/).
12+
813
## Output
914

1015
```text

frontend/dockerfile/linter/docs/_index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ $ docker build --check .
3030
<tbody>
3131
{{- range .Rules }}
3232
<tr>
33-
<td><a href="./{{ .PageName }}/">{{ .Name }}</a></td>
33+
<td><a href="./{{ .PageName }}/">{{ .Name }}{{- if .Experimental }} (experimental){{- end}}</a></td>
3434
<td>{{ .Description }}</td>
3535
</tr>
3636
{{- end }}

frontend/dockerfile/linter/generate.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import (
2020
)
2121

2222
type Rule struct {
23-
Name string
24-
Description string
25-
URL *url.URL
26-
PageName string
27-
URLAlias string
23+
Name string
24+
Description string
25+
URL *url.URL
26+
PageName string
27+
URLAlias string
28+
Experimental bool
2829
}
2930

3031
const tmplStr = `---
@@ -35,6 +36,13 @@ aliases:
3536
- {{ .Rule.URLAlias }}
3637
{{- end }}
3738
---
39+
{{- if .Rule.Experimental }}
40+
41+
> **Note**
42+
>
43+
> This check is experimental and is not enabled by default. To enable it, see
44+
> [Experimental checks](https://docs.docker.com/go/build-checks-experimental/).
45+
{{- end }}
3846
3947
{{ .Content }}
4048
`
@@ -160,6 +168,10 @@ func listRules() ([]Rule, error) {
160168
}
161169
rule.URL = u
162170
}
171+
case "Experimental":
172+
if basicLit, ok := kv.Value.(*ast.Ident); ok {
173+
rule.Experimental = basicLit.Name == "true"
174+
}
163175
}
164176
}
165177
}

frontend/dockerfile/linter/linter.go

+63-26
Original file line numberDiff line numberDiff line change
@@ -10,42 +10,61 @@ import (
1010
)
1111

1212
type Config struct {
13-
Warn LintWarnFunc
14-
SkipRules []string
15-
SkipAll bool
16-
ReturnAsError bool
13+
ExperimentalAll bool
14+
ExperimentalRules []string
15+
ReturnAsError bool
16+
SkipAll bool
17+
SkipRules []string
18+
Warn LintWarnFunc
1719
}
1820

1921
type Linter struct {
20-
SkippedRules map[string]struct{}
21-
CalledRules []string
22-
SkipAll bool
23-
ReturnAsError bool
24-
Warn LintWarnFunc
22+
CalledRules []string
23+
ExperimentalAll bool
24+
ExperimentalRules map[string]struct{}
25+
ReturnAsError bool
26+
SkipAll bool
27+
SkippedRules map[string]struct{}
28+
Warn LintWarnFunc
2529
}
2630

2731
func New(config *Config) *Linter {
2832
toret := &Linter{
29-
SkippedRules: map[string]struct{}{},
30-
CalledRules: []string{},
31-
Warn: config.Warn,
33+
SkippedRules: map[string]struct{}{},
34+
ExperimentalRules: map[string]struct{}{},
35+
CalledRules: []string{},
36+
Warn: config.Warn,
3237
}
3338
toret.SkipAll = config.SkipAll
39+
toret.ExperimentalAll = config.ExperimentalAll
3440
toret.ReturnAsError = config.ReturnAsError
3541
for _, rule := range config.SkipRules {
3642
toret.SkippedRules[rule] = struct{}{}
3743
}
44+
for _, rule := range config.ExperimentalRules {
45+
toret.ExperimentalRules[rule] = struct{}{}
46+
}
3847
return toret
3948
}
4049

4150
func (lc *Linter) Run(rule LinterRuleI, location []parser.Range, txt ...string) {
42-
if lc == nil || lc.Warn == nil || lc.SkipAll || rule.IsDeprecated() {
51+
if lc == nil || lc.Warn == nil || rule.IsDeprecated() {
4352
return
4453
}
54+
4555
rulename := rule.RuleName()
46-
if _, ok := lc.SkippedRules[rulename]; ok {
47-
return
56+
if rule.IsExperimental() {
57+
_, experimentalOk := lc.ExperimentalRules[rulename]
58+
if !(lc.ExperimentalAll || experimentalOk) {
59+
return
60+
}
61+
} else {
62+
_, skipOk := lc.SkippedRules[rulename]
63+
if lc.SkipAll || skipOk {
64+
return
65+
}
4866
}
67+
4968
lc.CalledRules = append(lc.CalledRules, rulename)
5069
rule.Run(lc.Warn, location, txt...)
5170
}
@@ -72,14 +91,16 @@ type LinterRuleI interface {
7291
RuleName() string
7392
Run(warn LintWarnFunc, location []parser.Range, txt ...string)
7493
IsDeprecated() bool
94+
IsExperimental() bool
7595
}
7696

7797
type LinterRule[F any] struct {
78-
Name string
79-
Description string
80-
Deprecated bool
81-
URL string
82-
Format F
98+
Name string
99+
Description string
100+
Deprecated bool
101+
Experimental bool
102+
URL string
103+
Format F
83104
}
84105

85106
func (rule *LinterRule[F]) RuleName() string {
@@ -98,6 +119,10 @@ func (rule *LinterRule[F]) IsDeprecated() bool {
98119
return rule.Deprecated
99120
}
100121

122+
func (rule *LinterRule[F]) IsExperimental() bool {
123+
return rule.Experimental
124+
}
125+
101126
func LintFormatShort(rulename, msg string, line int) string {
102127
msg = fmt.Sprintf("%s: %s", rulename, msg)
103128
if line > 0 {
@@ -114,9 +139,9 @@ func ParseLintOptions(checkStr string) (*Config, error) {
114139
return &Config{}, nil
115140
}
116141

117-
parts := strings.SplitN(checkStr, ";", 2)
118-
var skipSet []string
119-
var errorOnWarn, skipAll bool
142+
parts := strings.SplitN(checkStr, ";", 3)
143+
var skipSet, experimentalSet []string
144+
var errorOnWarn, skipAll, experimentalAll bool
120145
for _, p := range parts {
121146
k, v, ok := strings.Cut(p, "=")
122147
if !ok {
@@ -134,6 +159,16 @@ func ParseLintOptions(checkStr string) (*Config, error) {
134159
skipSet[i] = strings.TrimSpace(rule)
135160
}
136161
}
162+
case "experimental":
163+
v = strings.TrimSpace(v)
164+
if v == "all" {
165+
experimentalAll = true
166+
} else {
167+
experimentalSet = strings.Split(v, ",")
168+
for i, rule := range experimentalSet {
169+
experimentalSet[i] = strings.TrimSpace(rule)
170+
}
171+
}
137172
case "error":
138173
v, err := strconv.ParseBool(strings.TrimSpace(v))
139174
if err != nil {
@@ -145,8 +180,10 @@ func ParseLintOptions(checkStr string) (*Config, error) {
145180
}
146181
}
147182
return &Config{
148-
SkipRules: skipSet,
149-
SkipAll: skipAll,
150-
ReturnAsError: errorOnWarn,
183+
ExperimentalAll: experimentalAll,
184+
ExperimentalRules: experimentalSet,
185+
SkipRules: skipSet,
186+
SkipAll: skipAll,
187+
ReturnAsError: errorOnWarn,
151188
}, nil
152189
}

frontend/dockerfile/linter/ruleset.go

+1
Original file line numberDiff line numberDiff line change
@@ -163,5 +163,6 @@ var (
163163
Format: func(cmd, file string) string {
164164
return fmt.Sprintf("Attempting to %s file %q that is excluded by .dockerignore", cmd, file)
165165
},
166+
Experimental: true,
166167
}
167168
)

frontend/dockerui/config.go

+19-31
Original file line numberDiff line numberDiff line change
@@ -46,30 +46,28 @@ const (
4646

4747
// Don't forget to update frontend documentation if you add
4848
// a new build-arg: frontend/dockerfile/docs/reference.md
49-
keyCacheNSArg = "build-arg:BUILDKIT_CACHE_MOUNT_NS"
50-
keyMultiPlatformArg = "build-arg:BUILDKIT_MULTI_PLATFORM"
51-
keyHostnameArg = "build-arg:BUILDKIT_SANDBOX_HOSTNAME"
52-
keyDockerfileLintArg = "build-arg:BUILDKIT_DOCKERFILE_CHECK"
53-
keyContextKeepGitDirArg = "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR"
54-
keySourceDateEpoch = "build-arg:SOURCE_DATE_EPOCH"
55-
keyCopyIgnoredCheckEnabled = "build-arg:BUILDKIT_DOCKERFILE_CHECK_COPYIGNORED_EXPERIMENT"
49+
keyCacheNSArg = "build-arg:BUILDKIT_CACHE_MOUNT_NS"
50+
keyMultiPlatformArg = "build-arg:BUILDKIT_MULTI_PLATFORM"
51+
keyHostnameArg = "build-arg:BUILDKIT_SANDBOX_HOSTNAME"
52+
keyDockerfileLintArg = "build-arg:BUILDKIT_DOCKERFILE_CHECK"
53+
keyContextKeepGitDirArg = "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR"
54+
keySourceDateEpoch = "build-arg:SOURCE_DATE_EPOCH"
5655
)
5756

5857
type Config struct {
59-
BuildArgs map[string]string
60-
CacheIDNamespace string
61-
CgroupParent string
62-
Epoch *time.Time
63-
ExtraHosts []llb.HostIP
64-
Hostname string
65-
ImageResolveMode llb.ResolveMode
66-
Labels map[string]string
67-
NetworkMode pb.NetMode
68-
ShmSize int64
69-
Target string
70-
Ulimits []pb.Ulimit
71-
LinterConfig *linter.Config
72-
CopyIgnoredCheckEnabled bool
58+
BuildArgs map[string]string
59+
CacheIDNamespace string
60+
CgroupParent string
61+
Epoch *time.Time
62+
ExtraHosts []llb.HostIP
63+
Hostname string
64+
ImageResolveMode llb.ResolveMode
65+
Labels map[string]string
66+
NetworkMode pb.NetMode
67+
ShmSize int64
68+
Target string
69+
Ulimits []pb.Ulimit
70+
LinterConfig *linter.Config
7371

7472
CacheImports []client.CacheOptionsEntry
7573
TargetPlatforms []ocispecs.Platform // nil means default
@@ -291,16 +289,6 @@ func (bc *Client) init() error {
291289
}
292290
}
293291

294-
// CopyIgnoredCheckEnabled is an experimental feature to check if COPY is ignored by .dockerignore,
295-
// and it is disabled by default. It is expected that this feature will be enabled by default in a future
296-
// release, and this build-arg will be removed.
297-
if v, ok := opts[keyCopyIgnoredCheckEnabled]; ok {
298-
bc.CopyIgnoredCheckEnabled, err = strconv.ParseBool(v)
299-
if err != nil {
300-
return errors.Wrapf(err, "failed to parse %s", keyCopyIgnoredCheckEnabled)
301-
}
302-
}
303-
304292
bc.localsSessionIDs = parseLocalSessionIDs(opts)
305293

306294
return nil

0 commit comments

Comments
 (0)