Skip to content

Commit f0563a5

Browse files
committed
dockerfile: update env replacement efficiency
Signed-off-by: Tonis Tiigi <[email protected]>
1 parent 3f369b1 commit f0563a5

File tree

8 files changed

+227
-170
lines changed

8 files changed

+227
-170
lines changed

frontend/dockerfile/dockerfile2llb/convert.go

Lines changed: 89 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import (
1111
"os"
1212
"path"
1313
"path/filepath"
14+
"runtime"
1415
"sort"
1516
"strconv"
1617
"strings"
18+
"sync"
1719
"time"
1820

1921
"github.com/containerd/containerd/platforms"
@@ -302,7 +304,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
302304
info := argInfo{definition: metaArg, location: cmd.Location()}
303305
if v, ok := opt.BuildArgs[metaArg.Key]; !ok {
304306
if metaArg.Value != nil {
305-
result, err := shlex.ProcessWordWithMatches(*metaArg.Value, metaArgsToMap(optMetaArgs))
307+
result, err := shlex.ProcessWordWithMatches(*metaArg.Value, metaArgsToEnvs(optMetaArgs))
306308
if err != nil {
307309
return nil, parser.WithLocation(err, cmd.Location())
308310
}
@@ -329,9 +331,12 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
329331

330332
// set base state for every image
331333
for i, st := range stages {
332-
nameMatch, err := shlex.ProcessWordWithMatches(st.BaseName, metaArgsToMap(optMetaArgs))
334+
nameMatch, err := shlex.ProcessWordWithMatches(st.BaseName, metaArgsToEnvs(optMetaArgs))
333335
reportUnusedFromArgs(metaArgsKeys(optMetaArgs), nameMatch.Unmatched, st.Location, lint)
334336
used := nameMatch.Matched
337+
if used == nil {
338+
used = map[string]struct{}{}
339+
}
335340

336341
if err != nil {
337342
return nil, parser.WithLocation(err, st.Location)
@@ -353,7 +358,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
353358
}
354359

355360
if v := st.Platform; v != "" {
356-
platMatch, err := shlex.ProcessWordWithMatches(v, metaArgsToMap(optMetaArgs))
361+
platMatch, err := shlex.ProcessWordWithMatches(v, metaArgsToEnvs(optMetaArgs))
357362
reportUnusedFromArgs(metaArgsKeys(optMetaArgs), platMatch.Unmatched, st.Location, lint)
358363

359364
if err != nil {
@@ -644,7 +649,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
644649
}
645650

646651
// 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 {
648653
var osName string
649654
if d.platform != nil {
650655
osName = d.platform.OS
@@ -770,14 +775,38 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
770775
return target, nil
771776
}
772777

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+
}
775781

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
778791
}
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+
}
779803

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
781810
}
782811

783812
func metaArgsKeys(metaArgs []instructions.KeyValuePairOptional) []string {
@@ -840,17 +869,41 @@ type dispatchOpt struct {
840869
lint *linter.Linter
841870
}
842871

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+
843900
func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
844901
var err error
845902
// ARG command value could be ignored, so defer handling the expansion error
846903
_, isArg := cmd.Command.(*instructions.ArgCommand)
847904
if ex, ok := cmd.Command.(instructions.SupportsSingleWordExpansion); ok && !isArg {
848905
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)
854907
newword, unmatched, err := opt.shlex.ProcessWord(word, env)
855908
reportUnmatchedVariables(cmd, d.buildArgs, env, unmatched, &opt)
856909
return newword, err
@@ -861,12 +914,9 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
861914
}
862915
if ex, ok := cmd.Command.(instructions.SupportsSingleWordExpansionRaw); ok {
863916
err := ex.ExpandRaw(func(word string) (string, error) {
864-
env, err := d.state.Env(context.TODO())
865-
if err != nil {
866-
return "", err
867-
}
868917
lex := shell.NewLex('\\')
869918
lex.SkipProcessQuotes = true
919+
env := getEnv(d.state)
870920
newword, unmatched, err := lex.ProcessWord(word, env)
871921
reportUnmatchedVariables(cmd, d.buildArgs, env, unmatched, &opt)
872922
return newword, err
@@ -1185,10 +1235,6 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
11851235
args = withShell(d.image, args)
11861236
}
11871237

1188-
env, err := d.state.Env(context.TODO())
1189-
if err != nil {
1190-
return err
1191-
}
11921238
opt = append(opt, llb.Args(args), dfCmd(c), location(dopt.sourceMap, c.Location()))
11931239
if d.ignoreCache {
11941240
opt = append(opt, llb.IgnoreCache)
@@ -1233,6 +1279,7 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
12331279
if err != nil {
12341280
return err
12351281
}
1282+
env := getEnv(d.state)
12361283
opt = append(opt, llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(&shlex, customname, env)), d.prefixPlatform, pl, env)))
12371284
for _, h := range dopt.extraHosts {
12381285
opt = append(opt, llb.AddExtraHost(h.Host, h.IP))
@@ -1251,7 +1298,7 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
12511298
}
12521299

12531300
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)
12551302
}
12561303

12571304
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
12971344
if d.platform != nil {
12981345
platform = *d.platform
12991346
}
1300-
env, err := d.state.Env(context.TODO())
1301-
if err != nil {
1302-
return err
1303-
}
1347+
env := getEnv(d.state)
13041348
d.state = d.state.File(llb.Mkdir(wd, 0755, mkdirOpt...),
13051349
llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(opt.shlex, c.String(), env)), d.prefixPlatform, &platform, env)),
13061350
location(opt.sourceMap, c.Location()),
@@ -1375,11 +1419,7 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error {
13751419
platform = *d.platform
13761420
}
13771421

1378-
env, err := d.state.Env(context.TODO())
1379-
if err != nil {
1380-
return err
1381-
}
1382-
1422+
env := getEnv(d.state)
13831423
name := uppercaseCmd(processCmdEnv(cfg.opt.shlex, cfg.cmdToPrint.String(), env))
13841424
pgName := prefixCommand(d, name, d.prefixPlatform, &platform, env)
13851425

@@ -1636,10 +1676,7 @@ func dispatchHealthcheck(d *dispatchState, c *instructions.HealthCheckCommand, l
16361676

16371677
func dispatchExpose(d *dispatchState, c *instructions.ExposeCommand, shlex *shell.Lex) error {
16381678
ports := []string{}
1639-
env, err := d.state.Env(context.TODO())
1640-
if err != nil {
1641-
return err
1642-
}
1679+
env := getEnv(d.state)
16431680
for _, p := range c.Ports {
16441681
ps, err := shlex.ProcessWords(p, env)
16451682
if err != nil {
@@ -1720,10 +1757,7 @@ func dispatchArg(d *dispatchState, c *instructions.ArgCommand, opt *dispatchOpt)
17201757
v := opt.buildArgValues[arg.Key]
17211758
arg.Value = &v
17221759
} else if hasDefault {
1723-
env, err := d.state.Env(context.TODO())
1724-
if err != nil {
1725-
return err
1726-
}
1760+
env := getEnv(d.state)
17271761
v, unmatched, err := opt.shlex.ProcessWord(*arg.Value, env)
17281762
reportUnmatchedVariables(c, d.buildArgs, env, unmatched, opt)
17291763
if err != nil {
@@ -1824,10 +1858,10 @@ func dfCmd(cmd interface{}) llb.ConstraintsOpt {
18241858
})
18251859
}
18261860

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 {
18281862
var tmpBuildEnv []string
18291863
for _, arg := range buildArgs {
1830-
v, ok := envMap[arg.Key]
1864+
v, ok := env.Get(arg.Key)
18311865
if !ok {
18321866
v = arg.ValueString()
18331867
}
@@ -2006,15 +2040,15 @@ func uppercaseCmd(str string) string {
20062040
return strings.Join(p, " ")
20072041
}
20082042

2009-
func processCmdEnv(shlex *shell.Lex, cmd string, env []string) string {
2043+
func processCmdEnv(shlex *shell.Lex, cmd string, env shell.EnvGetter) string {
20102044
w, _, err := shlex.ProcessWord(cmd, env)
20112045
if err != nil {
20122046
return cmd
20132047
}
20142048
return w
20152049
}
20162050

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 {
20182052
if ds.cmdTotal == 0 {
20192053
return str
20202054
}
@@ -2057,26 +2091,26 @@ func formatTargetPlatform(base ocispecs.Platform, target *ocispecs.Platform) str
20572091
}
20582092

20592093
// platformFromEnv returns defined platforms based on TARGET* environment variables
2060-
func platformFromEnv(env []string) *ocispecs.Platform {
2094+
func platformFromEnv(env shell.EnvGetter) *ocispecs.Platform {
20612095
var p ocispecs.Platform
20622096
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 {
20662099
case "TARGETPLATFORM":
2067-
p, err := platforms.Parse(parts[1])
2100+
v, _ := env.Get(key)
2101+
p, err := platforms.Parse(v)
20682102
if err != nil {
20692103
continue
20702104
}
20712105
return &p
20722106
case "TARGETOS":
2073-
p.OS = parts[1]
2107+
p.OS, _ = env.Get(key)
20742108
set = true
20752109
case "TARGETARCH":
2076-
p.Architecture = parts[1]
2110+
p.Architecture, _ = env.Get(key)
20772111
set = true
20782112
case "TARGETVARIANT":
2079-
p.Variant = parts[1]
2113+
p.Variant, _ = env.Get(key)
20802114
set = true
20812115
}
20822116
}
@@ -2199,7 +2233,7 @@ func validateStageNames(stages []instructions.Stage, lint *linter.Linter) {
21992233
}
22002234
}
22012235

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) {
22032237
if len(unmatched) == 0 {
22042238
return
22052239
}
@@ -2210,15 +2244,12 @@ func reportUnmatchedVariables(cmd instructions.Command, buildArgs []instructions
22102244
return
22112245
}
22122246
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()...)
22172248
for cmdVar := range unmatched {
22182249
if _, nonEnvOk := nonEnvArgs[cmdVar]; nonEnvOk {
22192250
continue
22202251
}
2221-
match, _ := suggest.Search(cmdVar, options, true)
2252+
match, _ := suggest.Search(cmdVar, options, runtime.GOOS != "windows")
22222253
msg := linter.RuleUndefinedVar.Format(cmdVar, match)
22232254
opt.lint.Run(&linter.RuleUndefinedVar, cmd.Location(), msg)
22242255
}

0 commit comments

Comments
 (0)