@@ -11,9 +11,11 @@ import (
11
11
"os"
12
12
"path"
13
13
"path/filepath"
14
+ "runtime"
14
15
"sort"
15
16
"strconv"
16
17
"strings"
18
+ "sync"
17
19
"time"
18
20
19
21
"github.com/containerd/containerd/platforms"
@@ -302,7 +304,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
302
304
info := argInfo {definition : metaArg , location : cmd .Location ()}
303
305
if v , ok := opt .BuildArgs [metaArg .Key ]; ! ok {
304
306
if metaArg .Value != nil {
305
- result , err := shlex .ProcessWordWithMatches (* metaArg .Value , metaArgsToMap (optMetaArgs ))
307
+ result , err := shlex .ProcessWordWithMatches (* metaArg .Value , metaArgsToEnvs (optMetaArgs ))
306
308
if err != nil {
307
309
return nil , parser .WithLocation (err , cmd .Location ())
308
310
}
@@ -329,9 +331,12 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
329
331
330
332
// set base state for every image
331
333
for i , st := range stages {
332
- nameMatch , err := shlex .ProcessWordWithMatches (st .BaseName , metaArgsToMap (optMetaArgs ))
334
+ nameMatch , err := shlex .ProcessWordWithMatches (st .BaseName , metaArgsToEnvs (optMetaArgs ))
333
335
reportUnusedFromArgs (metaArgsKeys (optMetaArgs ), nameMatch .Unmatched , st .Location , lint )
334
336
used := nameMatch .Matched
337
+ if used == nil {
338
+ used = map [string ]struct {}{}
339
+ }
335
340
336
341
if err != nil {
337
342
return nil , parser .WithLocation (err , st .Location )
@@ -353,7 +358,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
353
358
}
354
359
355
360
if v := st .Platform ; v != "" {
356
- platMatch , err := shlex .ProcessWordWithMatches (v , metaArgsToMap (optMetaArgs ))
361
+ platMatch , err := shlex .ProcessWordWithMatches (v , metaArgsToEnvs (optMetaArgs ))
357
362
reportUnusedFromArgs (metaArgsKeys (optMetaArgs ), platMatch .Unmatched , st .Location , lint )
358
363
359
364
if err != nil {
@@ -644,7 +649,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
644
649
}
645
650
646
651
// make sure that PATH is always set
647
- if _ , ok := shell .BuildEnvs (d .image .Config .Env )[ "PATH" ] ; ! ok {
652
+ if _ , ok := shell .EnvsFromSlice (d .image .Config .Env ). Get ( "PATH" ) ; ! ok {
648
653
var osName string
649
654
if d .platform != nil {
650
655
osName = d .platform .OS
@@ -770,14 +775,38 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
770
775
return target , nil
771
776
}
772
777
773
- func metaArgsToMap (metaArgs []instructions.KeyValuePairOptional ) map [string ]string {
774
- m := map [string ]string {}
778
+ func metaArgsToEnvs (metaArgs []instructions.KeyValuePairOptional ) shell.EnvGetter {
779
+ return & envsFromKeyValuePairs {in : metaArgs }
780
+ }
775
781
776
- for _ , arg := range metaArgs {
777
- m [arg .Key ] = arg .ValueString ()
782
+ type envsFromKeyValuePairs struct {
783
+ in []instructions.KeyValuePairOptional
784
+ once sync.Once
785
+ m map [string ]string
786
+ }
787
+
788
+ func (e * envsFromKeyValuePairs ) init () {
789
+ if len (e .in ) == 0 {
790
+ return
778
791
}
792
+ e .m = make (map [string ]string , len (e .in ))
793
+ for _ , kv := range e .in {
794
+ e .m [kv .Key ] = kv .ValueString ()
795
+ }
796
+ }
797
+
798
+ func (e * envsFromKeyValuePairs ) Get (key string ) (string , bool ) {
799
+ e .once .Do (e .init )
800
+ v , ok := e .m [key ] // windows: case-insensitive
801
+ return v , ok
802
+ }
779
803
780
- return m
804
+ func (e * envsFromKeyValuePairs ) Keys () []string {
805
+ keys := make ([]string , len (e .in ))
806
+ for i , kp := range e .in {
807
+ keys [i ] = kp .Key
808
+ }
809
+ return keys
781
810
}
782
811
783
812
func metaArgsKeys (metaArgs []instructions.KeyValuePairOptional ) []string {
@@ -840,17 +869,41 @@ type dispatchOpt struct {
840
869
lint * linter.Linter
841
870
}
842
871
872
+ func getEnv (state llb.State ) shell.EnvGetter {
873
+ return & envsFromState {state : & state }
874
+ }
875
+
876
+ type envsFromState struct {
877
+ state * llb.State
878
+ once sync.Once
879
+ env shell.EnvGetter
880
+ }
881
+
882
+ func (e * envsFromState ) init () {
883
+ env , err := e .state .Env (context .TODO ())
884
+ if err != nil {
885
+ return
886
+ }
887
+ e .env = shell .EnvsFromSlice (env )
888
+ }
889
+
890
+ func (e * envsFromState ) Get (key string ) (string , bool ) {
891
+ e .once .Do (e .init )
892
+ return e .env .Get (key )
893
+ }
894
+
895
+ func (e * envsFromState ) Keys () []string {
896
+ e .once .Do (e .init )
897
+ return e .env .Keys ()
898
+ }
899
+
843
900
func dispatch (d * dispatchState , cmd command , opt dispatchOpt ) error {
844
901
var err error
845
902
// ARG command value could be ignored, so defer handling the expansion error
846
903
_ , isArg := cmd .Command .(* instructions.ArgCommand )
847
904
if ex , ok := cmd .Command .(instructions.SupportsSingleWordExpansion ); ok && ! isArg {
848
905
err := ex .Expand (func (word string ) (string , error ) {
849
- env , err := d .state .Env (context .TODO ())
850
- if err != nil {
851
- return "" , err
852
- }
853
-
906
+ env := getEnv (d .state )
854
907
newword , unmatched , err := opt .shlex .ProcessWord (word , env )
855
908
reportUnmatchedVariables (cmd , d .buildArgs , env , unmatched , & opt )
856
909
return newword , err
@@ -861,12 +914,9 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
861
914
}
862
915
if ex , ok := cmd .Command .(instructions.SupportsSingleWordExpansionRaw ); ok {
863
916
err := ex .ExpandRaw (func (word string ) (string , error ) {
864
- env , err := d .state .Env (context .TODO ())
865
- if err != nil {
866
- return "" , err
867
- }
868
917
lex := shell .NewLex ('\\' )
869
918
lex .SkipProcessQuotes = true
919
+ env := getEnv (d .state )
870
920
newword , unmatched , err := lex .ProcessWord (word , env )
871
921
reportUnmatchedVariables (cmd , d .buildArgs , env , unmatched , & opt )
872
922
return newword , err
@@ -1185,10 +1235,6 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
1185
1235
args = withShell (d .image , args )
1186
1236
}
1187
1237
1188
- env , err := d .state .Env (context .TODO ())
1189
- if err != nil {
1190
- return err
1191
- }
1192
1238
opt = append (opt , llb .Args (args ), dfCmd (c ), location (dopt .sourceMap , c .Location ()))
1193
1239
if d .ignoreCache {
1194
1240
opt = append (opt , llb .IgnoreCache )
@@ -1233,6 +1279,7 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
1233
1279
if err != nil {
1234
1280
return err
1235
1281
}
1282
+ env := getEnv (d .state )
1236
1283
opt = append (opt , llb .WithCustomName (prefixCommand (d , uppercaseCmd (processCmdEnv (& shlex , customname , env )), d .prefixPlatform , pl , env )))
1237
1284
for _ , h := range dopt .extraHosts {
1238
1285
opt = append (opt , llb .AddExtraHost (h .Host , h .IP ))
@@ -1251,7 +1298,7 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
1251
1298
}
1252
1299
1253
1300
d .state = d .state .Run (opt ... ).Root ()
1254
- return commitToHistory (& d .image , "RUN " + runCommandString (args , d .buildArgs , shell . BuildEnvs ( env ) ), true , & d .state , d .epoch )
1301
+ return commitToHistory (& d .image , "RUN " + runCommandString (args , d .buildArgs , env ), true , & d .state , d .epoch )
1255
1302
}
1256
1303
1257
1304
func dispatchWorkdir (d * dispatchState , c * instructions.WorkdirCommand , commit bool , opt * dispatchOpt ) error {
@@ -1297,10 +1344,7 @@ func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bo
1297
1344
if d .platform != nil {
1298
1345
platform = * d .platform
1299
1346
}
1300
- env , err := d .state .Env (context .TODO ())
1301
- if err != nil {
1302
- return err
1303
- }
1347
+ env := getEnv (d .state )
1304
1348
d .state = d .state .File (llb .Mkdir (wd , 0755 , mkdirOpt ... ),
1305
1349
llb .WithCustomName (prefixCommand (d , uppercaseCmd (processCmdEnv (opt .shlex , c .String (), env )), d .prefixPlatform , & platform , env )),
1306
1350
location (opt .sourceMap , c .Location ()),
@@ -1375,11 +1419,7 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error {
1375
1419
platform = * d .platform
1376
1420
}
1377
1421
1378
- env , err := d .state .Env (context .TODO ())
1379
- if err != nil {
1380
- return err
1381
- }
1382
-
1422
+ env := getEnv (d .state )
1383
1423
name := uppercaseCmd (processCmdEnv (cfg .opt .shlex , cfg .cmdToPrint .String (), env ))
1384
1424
pgName := prefixCommand (d , name , d .prefixPlatform , & platform , env )
1385
1425
@@ -1636,10 +1676,7 @@ func dispatchHealthcheck(d *dispatchState, c *instructions.HealthCheckCommand, l
1636
1676
1637
1677
func dispatchExpose (d * dispatchState , c * instructions.ExposeCommand , shlex * shell.Lex ) error {
1638
1678
ports := []string {}
1639
- env , err := d .state .Env (context .TODO ())
1640
- if err != nil {
1641
- return err
1642
- }
1679
+ env := getEnv (d .state )
1643
1680
for _ , p := range c .Ports {
1644
1681
ps , err := shlex .ProcessWords (p , env )
1645
1682
if err != nil {
@@ -1720,10 +1757,7 @@ func dispatchArg(d *dispatchState, c *instructions.ArgCommand, opt *dispatchOpt)
1720
1757
v := opt .buildArgValues [arg .Key ]
1721
1758
arg .Value = & v
1722
1759
} else if hasDefault {
1723
- env , err := d .state .Env (context .TODO ())
1724
- if err != nil {
1725
- return err
1726
- }
1760
+ env := getEnv (d .state )
1727
1761
v , unmatched , err := opt .shlex .ProcessWord (* arg .Value , env )
1728
1762
reportUnmatchedVariables (c , d .buildArgs , env , unmatched , opt )
1729
1763
if err != nil {
@@ -1824,10 +1858,10 @@ func dfCmd(cmd interface{}) llb.ConstraintsOpt {
1824
1858
})
1825
1859
}
1826
1860
1827
- func runCommandString (args []string , buildArgs []instructions.KeyValuePairOptional , envMap map [ string ] string ) string {
1861
+ func runCommandString (args []string , buildArgs []instructions.KeyValuePairOptional , env shell. EnvGetter ) string {
1828
1862
var tmpBuildEnv []string
1829
1863
for _ , arg := range buildArgs {
1830
- v , ok := envMap [ arg .Key ]
1864
+ v , ok := env . Get ( arg .Key )
1831
1865
if ! ok {
1832
1866
v = arg .ValueString ()
1833
1867
}
@@ -2006,15 +2040,15 @@ func uppercaseCmd(str string) string {
2006
2040
return strings .Join (p , " " )
2007
2041
}
2008
2042
2009
- func processCmdEnv (shlex * shell.Lex , cmd string , env [] string ) string {
2043
+ func processCmdEnv (shlex * shell.Lex , cmd string , env shell. EnvGetter ) string {
2010
2044
w , _ , err := shlex .ProcessWord (cmd , env )
2011
2045
if err != nil {
2012
2046
return cmd
2013
2047
}
2014
2048
return w
2015
2049
}
2016
2050
2017
- func prefixCommand (ds * dispatchState , str string , prefixPlatform bool , platform * ocispecs.Platform , env [] string ) string {
2051
+ func prefixCommand (ds * dispatchState , str string , prefixPlatform bool , platform * ocispecs.Platform , env shell. EnvGetter ) string {
2018
2052
if ds .cmdTotal == 0 {
2019
2053
return str
2020
2054
}
@@ -2057,26 +2091,26 @@ func formatTargetPlatform(base ocispecs.Platform, target *ocispecs.Platform) str
2057
2091
}
2058
2092
2059
2093
// platformFromEnv returns defined platforms based on TARGET* environment variables
2060
- func platformFromEnv (env [] string ) * ocispecs.Platform {
2094
+ func platformFromEnv (env shell. EnvGetter ) * ocispecs.Platform {
2061
2095
var p ocispecs.Platform
2062
2096
var set bool
2063
- for _ , v := range env {
2064
- parts := strings .SplitN (v , "=" , 2 )
2065
- switch parts [0 ] {
2097
+ for _ , key := range env .Keys () {
2098
+ switch key {
2066
2099
case "TARGETPLATFORM" :
2067
- p , err := platforms .Parse (parts [1 ])
2100
+ v , _ := env .Get (key )
2101
+ p , err := platforms .Parse (v )
2068
2102
if err != nil {
2069
2103
continue
2070
2104
}
2071
2105
return & p
2072
2106
case "TARGETOS" :
2073
- p .OS = parts [ 1 ]
2107
+ p .OS , _ = env . Get ( key )
2074
2108
set = true
2075
2109
case "TARGETARCH" :
2076
- p .Architecture = parts [ 1 ]
2110
+ p .Architecture , _ = env . Get ( key )
2077
2111
set = true
2078
2112
case "TARGETVARIANT" :
2079
- p .Variant = parts [ 1 ]
2113
+ p .Variant , _ = env . Get ( key )
2080
2114
set = true
2081
2115
}
2082
2116
}
@@ -2199,7 +2233,7 @@ func validateStageNames(stages []instructions.Stage, lint *linter.Linter) {
2199
2233
}
2200
2234
}
2201
2235
2202
- func reportUnmatchedVariables (cmd instructions.Command , buildArgs []instructions.KeyValuePairOptional , env [] string , unmatched map [string ]struct {}, opt * dispatchOpt ) {
2236
+ func reportUnmatchedVariables (cmd instructions.Command , buildArgs []instructions.KeyValuePairOptional , env shell. EnvGetter , unmatched map [string ]struct {}, opt * dispatchOpt ) {
2203
2237
if len (unmatched ) == 0 {
2204
2238
return
2205
2239
}
@@ -2210,15 +2244,12 @@ func reportUnmatchedVariables(cmd instructions.Command, buildArgs []instructions
2210
2244
return
2211
2245
}
2212
2246
options := metaArgsKeys (opt .metaArgs )
2213
- for _ , envVar := range env {
2214
- key , _ := parseKeyValue (envVar )
2215
- options = append (options , key )
2216
- }
2247
+ options = append (options , env .Keys ()... )
2217
2248
for cmdVar := range unmatched {
2218
2249
if _ , nonEnvOk := nonEnvArgs [cmdVar ]; nonEnvOk {
2219
2250
continue
2220
2251
}
2221
- match , _ := suggest .Search (cmdVar , options , true )
2252
+ match , _ := suggest .Search (cmdVar , options , runtime . GOOS != "windows" )
2222
2253
msg := linter .RuleUndefinedVar .Format (cmdVar , match )
2223
2254
opt .lint .Run (& linter .RuleUndefinedVar , cmd .Location (), msg )
2224
2255
}
0 commit comments