@@ -162,6 +162,64 @@ func ListTargets(ctx context.Context, dt []byte) (*targets.List, error) {
162
162
return l , nil
163
163
}
164
164
165
+ func parseLintOptions (checkStr string ) (* linter.Config , error ) {
166
+ checkStr = strings .TrimSpace (checkStr )
167
+ if checkStr == "" {
168
+ return & linter.Config {}, nil
169
+ }
170
+
171
+ parts := strings .SplitN (checkStr , ";" , 2 )
172
+ var skipSet []string
173
+ var errorOnWarn , skipAll bool
174
+ for _ , p := range parts {
175
+ k , v , ok := strings .Cut (p , "=" )
176
+ if ! ok {
177
+ return nil , errors .Errorf ("invalid check option %q" , p )
178
+ }
179
+ k = strings .TrimSpace (k )
180
+ switch k {
181
+ case "skip" :
182
+ v = strings .TrimSpace (v )
183
+ if v == "all" {
184
+ skipAll = true
185
+ } else {
186
+ skipSet = strings .Split (v , "," )
187
+ for i , rule := range skipSet {
188
+ skipSet [i ] = strings .TrimSpace (rule )
189
+ }
190
+ }
191
+ case "error" :
192
+ v , err := strconv .ParseBool (strings .TrimSpace (v ))
193
+ if err != nil {
194
+ return nil , errors .Wrapf (err , "failed to parse check option %q" , p )
195
+ }
196
+ errorOnWarn = v
197
+ default :
198
+ return nil , errors .Errorf ("invalid check option %q" , k )
199
+ }
200
+ }
201
+ return & linter.Config {
202
+ SkipRules : skipSet ,
203
+ SkipAll : skipAll ,
204
+ ReturnAsError : errorOnWarn ,
205
+ }, nil
206
+ }
207
+
208
+ func newRuleLinter (dt []byte , opt * ConvertOpt ) (* linter.Linter , error ) {
209
+ var lintOptionStr string
210
+ if opt .Client != nil && opt .Client .LinterConfig != nil {
211
+ lintOptionStr = * opt .Client .LinterConfig
212
+ } else {
213
+ lintOptionStr , _ , _ , _ = parser .ParseDirective ("check" , dt )
214
+ }
215
+ lintConfig , err := parseLintOptions (lintOptionStr )
216
+ if err != nil {
217
+ return nil , errors .Wrapf (err , "failed to parse check options" )
218
+ }
219
+ lintConfig .Warn = opt .Warn
220
+ return linter .New (lintConfig ), nil
221
+ }
222
+
165
223
func toDispatchState (ctx context.Context , dt []byte , opt ConvertOpt ) (* dispatchState , error ) {
166
224
if len (dt ) == 0 {
167
225
return nil , errors .Errorf ("the Dockerfile cannot be empty" )
@@ -184,8 +242,9 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
184
242
return nil , nil , nil
185
243
}
186
244
187
- if opt .Warn == nil {
188
- opt .Warn = func (string , string , string , string , []parser.Range ) {}
245
+ lint , err := newRuleLinter (dt , & opt )
246
+ if err != nil {
247
+ return nil , err
189
248
}
190
249
191
250
if opt .Client != nil && opt .LLBCaps == nil {
@@ -214,19 +273,19 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
214
273
if warning .URL == linter .RuleNoEmptyContinuations .URL {
215
274
location := []parser.Range {* warning .Location }
216
275
msg := linter .RuleNoEmptyContinuations .Format ()
217
- linter . RuleNoEmptyContinuations . Run (opt . Warn , location , msg )
276
+ lint . Run (& linter . RuleNoEmptyContinuations , location , msg )
218
277
}
219
278
}
220
279
221
- validateCommandCasing (dockerfile , opt . Warn )
280
+ validateCommandCasing (dockerfile , lint )
222
281
223
282
proxyEnv := proxyEnvFromBuildArgs (opt .BuildArgs )
224
283
225
- stages , metaArgs , err := instructions .Parse (dockerfile .AST , opt . Warn )
284
+ stages , metaArgs , err := instructions .Parse (dockerfile .AST , lint )
226
285
if err != nil {
227
286
return nil , err
228
287
}
229
- validateStageNames (stages , opt . Warn )
288
+ validateStageNames (stages , lint )
230
289
231
290
shlex := shell .NewLex (dockerfile .EscapeToken )
232
291
outline := newOutlineCapture ()
@@ -264,7 +323,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
264
323
// set base state for every image
265
324
for i , st := range stages {
266
325
nameMatch , err := shlex .ProcessWordWithMatches (st .BaseName , metaArgsToMap (optMetaArgs ))
267
- reportUnusedFromArgs (metaArgsKeys (optMetaArgs ), nameMatch .Unmatched , st .Location , opt . Warn )
326
+ reportUnusedFromArgs (metaArgsKeys (optMetaArgs ), nameMatch .Unmatched , st .Location , lint )
268
327
used := nameMatch .Matched
269
328
270
329
if err != nil {
@@ -288,7 +347,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
288
347
289
348
if v := st .Platform ; v != "" {
290
349
platMatch , err := shlex .ProcessWordWithMatches (v , metaArgsToMap (optMetaArgs ))
291
- reportUnusedFromArgs (metaArgsKeys (optMetaArgs ), platMatch .Unmatched , st .Location , opt . Warn )
350
+ reportUnusedFromArgs (metaArgsKeys (optMetaArgs ), platMatch .Unmatched , st .Location , lint )
292
351
293
352
if err != nil {
294
353
return nil , parser .WithLocation (errors .Wrapf (err , "failed to process arguments for platform %s" , platMatch .Result ), st .Location )
@@ -542,7 +601,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
542
601
llb .WithCustomName (prefixCommand (d , "FROM " + d .stage .BaseName , opt .MultiPlatformRequested , platform , nil )),
543
602
location (opt .SourceMap , d .stage .Location ),
544
603
)
545
- validateBaseImagePlatform (origName , * platform , d .image .Platform , d .stage .Location , opt . Warn )
604
+ validateBaseImagePlatform (origName , * platform , d .image .Platform , d .stage .Location , lint )
546
605
}
547
606
d .platform = platform
548
607
return nil
@@ -614,7 +673,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
614
673
cgroupParent : opt .CgroupParent ,
615
674
llbCaps : opt .LLBCaps ,
616
675
sourceMap : opt .SourceMap ,
617
- lintWarn : opt . Warn ,
676
+ lint : lint ,
618
677
}
619
678
620
679
if err = dispatchOnBuildTriggers (d , d .image .Config .OnBuild , opt ); err != nil {
@@ -658,6 +717,14 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
658
717
target .image .Config .Labels [k ] = v
659
718
}
660
719
720
+ // If lint.Error() returns an error, it means that
721
+ // there were warnings, and that our linter has been
722
+ // configured to return an error on warnings,
723
+ // so we appropriately return that error here.
724
+ if err := lint .Error (); err != nil {
725
+ return nil , err
726
+ }
727
+
661
728
opts := filterPaths (ctxPaths )
662
729
bctx := opt .MainContext
663
730
if opt .Client != nil {
@@ -758,7 +825,7 @@ type dispatchOpt struct {
758
825
cgroupParent string
759
826
llbCaps * apicaps.CapSet
760
827
sourceMap * llb.SourceMap
761
- lintWarn linter.LintWarnFunc
828
+ lint * linter.Linter
762
829
}
763
830
764
831
func dispatch (d * dispatchState , cmd command , opt dispatchOpt ) error {
@@ -839,11 +906,11 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
839
906
case * instructions.OnbuildCommand :
840
907
err = dispatchOnbuild (d , c )
841
908
case * instructions.CmdCommand :
842
- err = dispatchCmd (d , c , opt .lintWarn )
909
+ err = dispatchCmd (d , c , opt .lint )
843
910
case * instructions.EntrypointCommand :
844
- err = dispatchEntrypoint (d , c , opt .lintWarn )
911
+ err = dispatchEntrypoint (d , c , opt .lint )
845
912
case * instructions.HealthCheckCommand :
846
- err = dispatchHealthcheck (d , c , opt .lintWarn )
913
+ err = dispatchHealthcheck (d , c , opt .lint )
847
914
case * instructions.ExposeCommand :
848
915
err = dispatchExpose (d , c , opt .shlex )
849
916
case * instructions.UserCommand :
@@ -1183,7 +1250,7 @@ func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bo
1183
1250
// by fixing the first one.
1184
1251
if ! d .workdirSet && ! system .IsAbs (c .Path , d .platform .OS ) {
1185
1252
msg := linter .RuleWorkdirRelativePath .Format (c .Path )
1186
- linter . RuleWorkdirRelativePath .Run (opt . lintWarn , c .Location (), msg )
1253
+ opt . lint .Run (& linter . RuleWorkdirRelativePath , c .Location (), msg )
1187
1254
}
1188
1255
d .workdirSet = true
1189
1256
}
@@ -1499,14 +1566,14 @@ func dispatchOnbuild(d *dispatchState, c *instructions.OnbuildCommand) error {
1499
1566
return nil
1500
1567
}
1501
1568
1502
- func dispatchCmd (d * dispatchState , c * instructions.CmdCommand , warn linter.LintWarnFunc ) error {
1503
- validateUsedOnce (c , & d .cmd , warn )
1569
+ func dispatchCmd (d * dispatchState , c * instructions.CmdCommand , lint * linter.Linter ) error {
1570
+ validateUsedOnce (c , & d .cmd , lint )
1504
1571
1505
1572
var args []string = c .CmdLine
1506
1573
if c .PrependShell {
1507
1574
if len (d .image .Config .Shell ) == 0 {
1508
1575
msg := linter .RuleJSONArgsRecommended .Format (c .Name ())
1509
- linter . RuleJSONArgsRecommended . Run (warn , c .Location (), msg )
1576
+ lint . Run (& linter . RuleJSONArgsRecommended , c .Location (), msg )
1510
1577
}
1511
1578
args = withShell (d .image , args )
1512
1579
}
@@ -1515,14 +1582,14 @@ func dispatchCmd(d *dispatchState, c *instructions.CmdCommand, warn linter.LintW
1515
1582
return commitToHistory (& d .image , fmt .Sprintf ("CMD %q" , args ), false , nil , d .epoch )
1516
1583
}
1517
1584
1518
- func dispatchEntrypoint (d * dispatchState , c * instructions.EntrypointCommand , warn linter.LintWarnFunc ) error {
1519
- validateUsedOnce (c , & d .entrypoint , warn )
1585
+ func dispatchEntrypoint (d * dispatchState , c * instructions.EntrypointCommand , lint * linter.Linter ) error {
1586
+ validateUsedOnce (c , & d .entrypoint , lint )
1520
1587
1521
1588
var args []string = c .CmdLine
1522
1589
if c .PrependShell {
1523
1590
if len (d .image .Config .Shell ) == 0 {
1524
1591
msg := linter .RuleJSONArgsRecommended .Format (c .Name ())
1525
- linter . RuleJSONArgsRecommended . Run (warn , c .Location (), msg )
1592
+ lint . Run (& linter . RuleJSONArgsRecommended , c .Location (), msg )
1526
1593
}
1527
1594
args = withShell (d .image , args )
1528
1595
}
@@ -1533,8 +1600,8 @@ func dispatchEntrypoint(d *dispatchState, c *instructions.EntrypointCommand, war
1533
1600
return commitToHistory (& d .image , fmt .Sprintf ("ENTRYPOINT %q" , args ), false , nil , d .epoch )
1534
1601
}
1535
1602
1536
- func dispatchHealthcheck (d * dispatchState , c * instructions.HealthCheckCommand , warn linter.LintWarnFunc ) error {
1537
- validateUsedOnce (c , & d .healthcheck , warn )
1603
+ func dispatchHealthcheck (d * dispatchState , c * instructions.HealthCheckCommand , lint * linter.Linter ) error {
1604
+ validateUsedOnce (c , & d .healthcheck , lint )
1538
1605
d .image .Config .Healthcheck = & dockerspec.HealthcheckConfig {
1539
1606
Test : c .Health .Test ,
1540
1607
Interval : c .Health .Interval ,
@@ -2058,7 +2125,7 @@ func isSelfConsistentCasing(s string) bool {
2058
2125
return s == strings .ToLower (s ) || s == strings .ToUpper (s )
2059
2126
}
2060
2127
2061
- func validateCommandCasing (dockerfile * parser.Result , warn linter.LintWarnFunc ) {
2128
+ func validateCommandCasing (dockerfile * parser.Result , lint * linter.Linter ) {
2062
2129
var lowerCount , upperCount int
2063
2130
for _ , node := range dockerfile .AST .Children {
2064
2131
if isSelfConsistentCasing (node .Value ) {
@@ -2077,7 +2144,7 @@ func validateCommandCasing(dockerfile *parser.Result, warn linter.LintWarnFunc)
2077
2144
// command to the casing of the majority of commands.
2078
2145
if ! isSelfConsistentCasing (node .Value ) {
2079
2146
msg := linter .RuleSelfConsistentCommandCasing .Format (node .Value )
2080
- linter . RuleSelfConsistentCommandCasing . Run (warn , node .Location (), msg )
2147
+ lint . Run (& linter . RuleSelfConsistentCommandCasing , node .Location (), msg )
2081
2148
} else {
2082
2149
var msg string
2083
2150
var needsLintWarn bool
@@ -2089,7 +2156,7 @@ func validateCommandCasing(dockerfile *parser.Result, warn linter.LintWarnFunc)
2089
2156
needsLintWarn = true
2090
2157
}
2091
2158
if needsLintWarn {
2092
- linter . RuleFileConsistentCommandCasing . Run (warn , node .Location (), msg )
2159
+ lint . Run (& linter . RuleFileConsistentCommandCasing , node .Location (), msg )
2093
2160
}
2094
2161
}
2095
2162
}
@@ -2100,25 +2167,28 @@ var reservedStageNames = map[string]struct{}{
2100
2167
"scratch" : {},
2101
2168
}
2102
2169
2103
- func validateStageNames (stages []instructions.Stage , warn linter.LintWarnFunc ) {
2170
+ func validateStageNames (stages []instructions.Stage , lint * linter.Linter ) {
2104
2171
stageNames := make (map [string ]struct {})
2105
2172
for _ , stage := range stages {
2106
2173
if stage .Name != "" {
2107
2174
if _ , ok := reservedStageNames [stage .Name ]; ok {
2108
2175
msg := linter .RuleReservedStageName .Format (stage .Name )
2109
- linter . RuleReservedStageName . Run (warn , stage .Location , msg )
2176
+ lint . Run (& linter . RuleReservedStageName , stage .Location , msg )
2110
2177
}
2111
2178
2112
2179
if _ , ok := stageNames [stage .Name ]; ok {
2113
2180
msg := linter .RuleDuplicateStageName .Format (stage .Name )
2114
- linter . RuleDuplicateStageName . Run (warn , stage .Location , msg )
2181
+ lint . Run (& linter . RuleDuplicateStageName , stage .Location , msg )
2115
2182
}
2116
2183
stageNames [stage .Name ] = struct {}{}
2117
2184
}
2118
2185
}
2119
2186
}
2120
2187
2121
2188
func reportUnmatchedVariables (cmd instructions.Command , buildArgs []instructions.KeyValuePairOptional , env []string , unmatched map [string ]struct {}, opt * dispatchOpt ) {
2189
+ if len (unmatched ) == 0 {
2190
+ return
2191
+ }
2122
2192
for _ , buildArg := range buildArgs {
2123
2193
delete (unmatched , buildArg .Key )
2124
2194
}
@@ -2136,7 +2206,7 @@ func reportUnmatchedVariables(cmd instructions.Command, buildArgs []instructions
2136
2206
}
2137
2207
match , _ := suggest .Search (cmdVar , options , true )
2138
2208
msg := linter .RuleUndefinedVar .Format (cmdVar , match )
2139
- linter . RuleUndefinedVar .Run (opt . lintWarn , cmd .Location (), msg )
2209
+ opt . lint .Run (& linter . RuleUndefinedVar , cmd .Location (), msg )
2140
2210
}
2141
2211
}
2142
2212
@@ -2190,11 +2260,11 @@ func toPBLocation(sourceIndex int, location []parser.Range) pb.Location {
2190
2260
}
2191
2261
}
2192
2262
2193
- func reportUnusedFromArgs (values []string , unmatched map [string ]struct {}, location []parser.Range , warn linter.LintWarnFunc ) {
2263
+ func reportUnusedFromArgs (values []string , unmatched map [string ]struct {}, location []parser.Range , lint * linter.Linter ) {
2194
2264
for arg := range unmatched {
2195
2265
suggest , _ := suggest .Search (arg , values , true )
2196
2266
msg := linter .RuleUndeclaredArgInFrom .Format (arg , suggest )
2197
- linter . RuleUndeclaredArgInFrom . Run (warn , location , msg )
2267
+ lint . Run (& linter . RuleUndeclaredArgInFrom , location , msg )
2198
2268
}
2199
2269
}
2200
2270
@@ -2208,12 +2278,12 @@ func (v *instructionTracker) MarkUsed(loc []parser.Range) {
2208
2278
v .IsSet = true
2209
2279
}
2210
2280
2211
- func validateUsedOnce (c instructions.Command , loc * instructionTracker , warn linter.LintWarnFunc ) {
2281
+ func validateUsedOnce (c instructions.Command , loc * instructionTracker , lint * linter.Linter ) {
2212
2282
if loc .IsSet {
2213
2283
msg := linter .RuleMultipleInstructionsDisallowed .Format (c .Name ())
2214
2284
// Report the location of the previous invocation because it is the one
2215
2285
// that will be ignored.
2216
- linter . RuleMultipleInstructionsDisallowed . Run (warn , loc .Loc , msg )
2286
+ lint . Run (& linter . RuleMultipleInstructionsDisallowed , loc .Loc , msg )
2217
2287
}
2218
2288
loc .MarkUsed (c .Location ())
2219
2289
}
@@ -2229,11 +2299,11 @@ func wrapSuggestAny(err error, keys map[string]struct{}, options []string) error
2229
2299
return err
2230
2300
}
2231
2301
2232
- func validateBaseImagePlatform (name string , expected , actual ocispecs.Platform , location []parser.Range , warn linter.LintWarnFunc ) {
2302
+ func validateBaseImagePlatform (name string , expected , actual ocispecs.Platform , location []parser.Range , lint * linter.Linter ) {
2233
2303
if expected .OS != actual .OS || expected .Architecture != actual .Architecture {
2234
2304
expectedStr := platforms .Format (platforms .Normalize (expected ))
2235
2305
actualStr := platforms .Format (platforms .Normalize (actual ))
2236
2306
msg := linter .RuleInvalidBaseImagePlatform .Format (name , expectedStr , actualStr )
2237
- linter . RuleInvalidBaseImagePlatform . Run (warn , location , msg )
2307
+ lint . Run (& linter . RuleInvalidBaseImagePlatform , location , msg )
2238
2308
}
2239
2309
}
0 commit comments