Browse code

Merge pull request #32532 from dnephin/refactor-some-builder-parts

[builder] cleanup env dispatcher and Builder.build()

Tõnis Tiigi authored on 2017/04/13 03:11:06
Showing 11 changed files
... ...
@@ -100,8 +100,7 @@ type ContainerCommitConfig struct {
100 100
 	Changes []string
101 101
 }
102 102
 
103
-// ProgressWriter is an interface
104
-// to transport progress streams.
103
+// ProgressWriter is a data object to transport progress streams to the client
105 104
 type ProgressWriter struct {
106 105
 	Output             io.Writer
107 106
 	StdoutFormatter    *streamformatter.StdoutFormatter
... ...
@@ -2,7 +2,6 @@ package dockerfile
2 2
 
3 3
 import (
4 4
 	"bytes"
5
-	"errors"
6 5
 	"fmt"
7 6
 	"io"
8 7
 	"io/ioutil"
... ...
@@ -20,7 +19,7 @@ import (
20 20
 	"github.com/docker/docker/builder/dockerfile/parser"
21 21
 	"github.com/docker/docker/image"
22 22
 	"github.com/docker/docker/pkg/stringid"
23
-	perrors "github.com/pkg/errors"
23
+	"github.com/pkg/errors"
24 24
 	"golang.org/x/net/context"
25 25
 )
26 26
 
... ...
@@ -51,7 +50,6 @@ type Builder struct {
51 51
 	docker    builder.Backend
52 52
 	context   builder.Context
53 53
 	clientCtx context.Context
54
-	cancel    context.CancelFunc
55 54
 
56 55
 	runConfig     *container.Config // runconfig for cmd, run, entrypoint etc.
57 56
 	flags         *BFlags
... ...
@@ -66,9 +64,6 @@ type Builder struct {
66 66
 	buildArgs     *buildArgs
67 67
 	directive     parser.Directive
68 68
 
69
-	// TODO: remove once docker.Commit can receive a tag
70
-	id string
71
-
72 69
 	imageCache builder.ImageCache
73 70
 	from       builder.Image
74 71
 }
... ...
@@ -117,10 +112,8 @@ func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, back
117 117
 	if config == nil {
118 118
 		config = new(types.ImageBuildOptions)
119 119
 	}
120
-	ctx, cancel := context.WithCancel(clientCtx)
121 120
 	b = &Builder{
122
-		clientCtx:     ctx,
123
-		cancel:        cancel,
121
+		clientCtx:     clientCtx,
124 122
 		options:       config,
125 123
 		Stdout:        os.Stdout,
126 124
 		Stderr:        os.Stderr,
... ...
@@ -128,7 +121,6 @@ func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, back
128 128
 		context:       buildContext,
129 129
 		runConfig:     new(container.Config),
130 130
 		tmpContainers: map[string]struct{}{},
131
-		id:            stringid.GenerateNonCryptoID(),
132 131
 		buildArgs:     newBuildArgs(config.BuildArgs),
133 132
 		directive: parser.Directive{
134 133
 			EscapeSeen:           false,
... ...
@@ -186,17 +178,6 @@ func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
186 186
 
187 187
 // build runs the Dockerfile builder from a context and a docker object that allows to make calls
188 188
 // to Docker.
189
-//
190
-// This will (barring errors):
191
-//
192
-// * read the dockerfile from context
193
-// * parse the dockerfile if not already parsed
194
-// * walk the AST and execute it by dispatching to handlers. If Remove
195
-//   or ForceRemove is set, additional cleanup around containers happens after
196
-//   processing.
197
-// * Tag image, if applicable.
198
-// * Print a happy message and return the image ID.
199
-//
200 189
 func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (string, error) {
201 190
 	defer b.imageContexts.unmount()
202 191
 
... ...
@@ -204,7 +185,7 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
204 204
 	b.Stderr = stderr
205 205
 	b.Output = out
206 206
 
207
-	dockerfile, err := b.readDockerfile()
207
+	dockerfile, err := b.readAndParseDockerfile()
208 208
 	if err != nil {
209 209
 		return "", err
210 210
 	}
... ...
@@ -216,14 +197,37 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
216 216
 
217 217
 	addNodesForLabelOption(dockerfile, b.options.Labels)
218 218
 
219
-	var shortImgID string
220
-	total := len(dockerfile.Children)
221
-	for _, n := range dockerfile.Children {
222
-		if err := b.checkDispatch(n, false); err != nil {
223
-			return "", perrors.Wrapf(err, "Dockerfile parse error line %d", n.StartLine)
219
+	if err := checkDispatchDockerfile(dockerfile); err != nil {
220
+		return "", err
221
+	}
222
+
223
+	shortImageID, err := b.dispatchDockerfileWithCancellation(dockerfile)
224
+	if err != nil {
225
+		return "", err
226
+	}
227
+
228
+	b.warnOnUnusedBuildArgs()
229
+
230
+	if b.image == "" {
231
+		return "", errors.New("No image was generated. Is your Dockerfile empty?")
232
+	}
233
+
234
+	if b.options.Squash {
235
+		if err := b.squashBuild(); err != nil {
236
+			return "", err
224 237
 		}
225 238
 	}
226 239
 
240
+	fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImageID)
241
+	if err := b.tagImages(repoAndTags); err != nil {
242
+		return "", err
243
+	}
244
+	return b.image, nil
245
+}
246
+
247
+func (b *Builder) dispatchDockerfileWithCancellation(dockerfile *parser.Node) (string, error) {
248
+	total := len(dockerfile.Children)
249
+	var shortImgID string
227 250
 	for i, n := range dockerfile.Children {
228 251
 		select {
229 252
 		case <-b.clientCtx.Done():
... ...
@@ -253,37 +257,23 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
253 253
 	}
254 254
 
255 255
 	if b.options.Target != "" && !b.imageContexts.isCurrentTarget(b.options.Target) {
256
-		return "", perrors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
256
+		return "", errors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
257 257
 	}
258 258
 
259
-	b.warnOnUnusedBuildArgs()
260
-
261
-	if b.image == "" {
262
-		return "", errors.New("No image was generated. Is your Dockerfile empty?")
263
-	}
259
+	return shortImgID, nil
260
+}
264 261
 
265
-	if b.options.Squash {
266
-		var fromID string
267
-		if b.from != nil {
268
-			fromID = b.from.ImageID()
269
-		}
270
-		b.image, err = b.docker.SquashImage(b.image, fromID)
271
-		if err != nil {
272
-			return "", perrors.Wrap(err, "error squashing image")
273
-		}
262
+func (b *Builder) squashBuild() error {
263
+	var fromID string
264
+	var err error
265
+	if b.from != nil {
266
+		fromID = b.from.ImageID()
274 267
 	}
275
-
276
-	fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
277
-
278
-	imageID := image.ID(b.image)
279
-	for _, rt := range repoAndTags {
280
-		if err := b.docker.TagImageWithReference(imageID, rt); err != nil {
281
-			return "", err
282
-		}
283
-		fmt.Fprintf(b.Stdout, "Successfully tagged %s\n", reference.FamiliarString(rt))
268
+	b.image, err = b.docker.SquashImage(b.image, fromID)
269
+	if err != nil {
270
+		return errors.Wrap(err, "error squashing image")
284 271
 	}
285
-
286
-	return b.image, nil
272
+	return nil
287 273
 }
288 274
 
289 275
 func addNodesForLabelOption(dockerfile *parser.Node, labels map[string]string) {
... ...
@@ -304,16 +294,22 @@ func (b *Builder) warnOnUnusedBuildArgs() {
304 304
 	}
305 305
 }
306 306
 
307
+func (b *Builder) tagImages(repoAndTags []reference.Named) error {
308
+	imageID := image.ID(b.image)
309
+	for _, rt := range repoAndTags {
310
+		if err := b.docker.TagImageWithReference(imageID, rt); err != nil {
311
+			return err
312
+		}
313
+		fmt.Fprintf(b.Stdout, "Successfully tagged %s\n", reference.FamiliarString(rt))
314
+	}
315
+	return nil
316
+}
317
+
307 318
 // hasFromImage returns true if the builder has processed a `FROM <image>` line
308 319
 func (b *Builder) hasFromImage() bool {
309 320
 	return b.image != "" || b.noBaseImage
310 321
 }
311 322
 
312
-// Cancel cancels an ongoing Dockerfile build.
313
-func (b *Builder) Cancel() {
314
-	b.cancel()
315
-}
316
-
317 323
 // BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile
318 324
 // It will:
319 325
 // - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries.
... ...
@@ -346,18 +342,31 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
346 346
 	b.Stderr = ioutil.Discard
347 347
 	b.disableCommit = true
348 348
 
349
-	total := len(ast.Children)
350
-	for _, n := range ast.Children {
351
-		if err := b.checkDispatch(n, false); err != nil {
352
-			return nil, err
349
+	if err := checkDispatchDockerfile(ast); err != nil {
350
+		return nil, err
351
+	}
352
+
353
+	if err := dispatchFromDockerfile(b, ast); err != nil {
354
+		return nil, err
355
+	}
356
+	return b.runConfig, nil
357
+}
358
+
359
+func checkDispatchDockerfile(dockerfile *parser.Node) error {
360
+	for _, n := range dockerfile.Children {
361
+		if err := checkDispatch(n); err != nil {
362
+			return errors.Wrapf(err, "Dockerfile parse error line %d", n.StartLine)
353 363
 		}
354 364
 	}
365
+	return nil
366
+}
355 367
 
368
+func dispatchFromDockerfile(b *Builder, ast *parser.Node) error {
369
+	total := len(ast.Children)
356 370
 	for i, n := range ast.Children {
357 371
 		if err := b.dispatch(i, total, n); err != nil {
358
-			return nil, err
372
+			return err
359 373
 		}
360 374
 	}
361
-
362
-	return b.runConfig, nil
375
+	return nil
363 376
 }
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"strings"
17 17
 	"time"
18 18
 
19
+	"bytes"
19 20
 	"github.com/Sirupsen/logrus"
20 21
 	"github.com/docker/docker/api"
21 22
 	"github.com/docker/docker/api/types"
... ...
@@ -46,45 +47,22 @@ func env(b *Builder, args []string, attributes map[string]bool, original string)
46 46
 		return err
47 47
 	}
48 48
 
49
-	// TODO/FIXME/NOT USED
50
-	// Just here to show how to use the builder flags stuff within the
51
-	// context of a builder command. Will remove once we actually add
52
-	// a builder command to something!
53
-	/*
54
-		flBool1 := b.flags.AddBool("bool1", false)
55
-		flStr1 := b.flags.AddString("str1", "HI")
56
-
57
-		if err := b.flags.Parse(); err != nil {
58
-			return err
59
-		}
60
-
61
-		fmt.Printf("Bool1:%v\n", flBool1)
62
-		fmt.Printf("Str1:%v\n", flStr1)
63
-	*/
64
-
65
-	commitStr := "ENV"
66
-
67
-	for j := 0; j < len(args); j++ {
68
-		// name  ==> args[j]
69
-		// value ==> args[j+1]
49
+	commitMessage := bytes.NewBufferString("ENV")
70 50
 
51
+	for j := 0; j < len(args); j += 2 {
71 52
 		if len(args[j]) == 0 {
72 53
 			return errBlankCommandNames("ENV")
73 54
 		}
74
-		newVar := args[j] + "=" + args[j+1] + ""
75
-		commitStr += " " + newVar
55
+		name := args[j]
56
+		value := args[j+1]
57
+		newVar := name + "=" + value
58
+		commitMessage.WriteString(" " + newVar)
76 59
 
77 60
 		gotOne := false
78 61
 		for i, envVar := range b.runConfig.Env {
79 62
 			envParts := strings.SplitN(envVar, "=", 2)
80 63
 			compareFrom := envParts[0]
81
-			compareTo := args[j]
82
-			if runtime.GOOS == "windows" {
83
-				// Case insensitive environment variables on Windows
84
-				compareFrom = strings.ToUpper(compareFrom)
85
-				compareTo = strings.ToUpper(compareTo)
86
-			}
87
-			if compareFrom == compareTo {
64
+			if equalEnvKeys(compareFrom, name) {
88 65
 				b.runConfig.Env[i] = newVar
89 66
 				gotOne = true
90 67
 				break
... ...
@@ -93,10 +71,9 @@ func env(b *Builder, args []string, attributes map[string]bool, original string)
93 93
 		if !gotOne {
94 94
 			b.runConfig.Env = append(b.runConfig.Env, newVar)
95 95
 		}
96
-		j++
97 96
 	}
98 97
 
99
-	return b.commit("", b.runConfig.Cmd, commitStr)
98
+	return b.commit("", b.runConfig.Cmd, commitMessage.String())
100 99
 }
101 100
 
102 101
 // MAINTAINER some text <maybe@an.email.address>
... ...
@@ -440,6 +417,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
440 440
 		return err
441 441
 	}
442 442
 
443
+	// FIXME: this is duplicated with the defer above in this function (i think?)
443 444
 	// revert to original config environment and set the command string to
444 445
 	// have the build-time env vars in it (if any) so that future cache look-ups
445 446
 	// properly match it.
... ...
@@ -132,27 +132,34 @@ func TestCommandseBlankNames(t *testing.T) {
132 132
 }
133 133
 
134 134
 func TestEnv2Variables(t *testing.T) {
135
-	variables := []string{"var1", "val1", "var2", "val2"}
136
-
137
-	bflags := &BFlags{}
138
-	config := &container.Config{}
135
+	b := newBuilderWithMockBackend()
136
+	b.disableCommit = true
139 137
 
140
-	b := &Builder{flags: bflags, runConfig: config, disableCommit: true}
138
+	args := []string{"var1", "val1", "var2", "val2"}
139
+	err := env(b, args, nil, "")
140
+	assert.NilError(t, err)
141 141
 
142
-	if err := env(b, variables, nil, ""); err != nil {
143
-		t.Fatalf("Error when executing env: %s", err.Error())
142
+	expected := []string{
143
+		fmt.Sprintf("%s=%s", args[0], args[1]),
144
+		fmt.Sprintf("%s=%s", args[2], args[3]),
144 145
 	}
146
+	assert.DeepEqual(t, b.runConfig.Env, expected)
147
+}
145 148
 
146
-	expectedVar1 := fmt.Sprintf("%s=%s", variables[0], variables[1])
147
-	expectedVar2 := fmt.Sprintf("%s=%s", variables[2], variables[3])
149
+func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
150
+	b := newBuilderWithMockBackend()
151
+	b.disableCommit = true
152
+	b.runConfig.Env = []string{"var1=old", "var2=fromenv"}
148 153
 
149
-	if b.runConfig.Env[0] != expectedVar1 {
150
-		t.Fatalf("Wrong env output for first variable. Got: %s. Should be: %s", b.runConfig.Env[0], expectedVar1)
151
-	}
154
+	args := []string{"var1", "val1"}
155
+	err := env(b, args, nil, "")
156
+	assert.NilError(t, err)
152 157
 
153
-	if b.runConfig.Env[1] != expectedVar2 {
154
-		t.Fatalf("Wrong env output for second variable. Got: %s, Should be: %s", b.runConfig.Env[1], expectedVar2)
158
+	expected := []string{
159
+		fmt.Sprintf("%s=%s", args[0], args[1]),
160
+		"var2=fromenv",
155 161
 	}
162
+	assert.DeepEqual(t, b.runConfig.Env, expected)
156 163
 }
157 164
 
158 165
 func TestMaintainer(t *testing.T) {
... ...
@@ -26,3 +26,9 @@ func normaliseWorkdir(current string, requested string) (string, error) {
26 26
 func errNotJSON(command, _ string) error {
27 27
 	return fmt.Errorf("%s requires the arguments to be in JSON form", command)
28 28
 }
29
+
30
+// equalEnvKeys compare two strings and returns true if they are equal. On
31
+// Windows this comparison is case insensitive.
32
+func equalEnvKeys(from, to string) bool {
33
+	return from == to
34
+}
... ...
@@ -85,3 +85,9 @@ func errNotJSON(command, original string) error {
85 85
 	}
86 86
 	return fmt.Errorf("%s requires the arguments to be in JSON form%s", command, extra)
87 87
 }
88
+
89
+// equalEnvKeys compare two strings and returns true if they are equal. On
90
+// Windows this comparison is case insensitive.
91
+func equalEnvKeys(from, to string) bool {
92
+	return strings.ToUpper(from) == strings.ToUpper(to)
93
+}
... ...
@@ -20,13 +20,13 @@
20 20
 package dockerfile
21 21
 
22 22
 import (
23
-	"errors"
24 23
 	"fmt"
25 24
 	"strings"
26 25
 
27 26
 	"github.com/docker/docker/builder/dockerfile/command"
28 27
 	"github.com/docker/docker/builder/dockerfile/parser"
29
-	runconfigopts "github.com/docker/docker/runconfig/opts"
28
+	"github.com/docker/docker/runconfig/opts"
29
+	"github.com/pkg/errors"
30 30
 )
31 31
 
32 32
 // Environment variable interpolation will happen on these statements only.
... ...
@@ -187,7 +187,7 @@ func (b *Builder) evaluateEnv(cmd string, str string, envs []string) ([]string,
187 187
 // args that are not overriden by runConfig environment variables.
188 188
 func (b *Builder) buildArgsWithoutConfigEnv() []string {
189 189
 	envs := []string{}
190
-	configEnv := runconfigopts.ConvertKVStringsToMap(b.runConfig.Env)
190
+	configEnv := b.runConfigEnvMapping()
191 191
 
192 192
 	for key, val := range b.buildArgs.GetAllAllowed() {
193 193
 		if _, ok := configEnv[key]; !ok {
... ...
@@ -197,13 +197,16 @@ func (b *Builder) buildArgsWithoutConfigEnv() []string {
197 197
 	return envs
198 198
 }
199 199
 
200
+func (b *Builder) runConfigEnvMapping() map[string]string {
201
+	return opts.ConvertKVStringsToMap(b.runConfig.Env)
202
+}
203
+
200 204
 // checkDispatch does a simple check for syntax errors of the Dockerfile.
201 205
 // Because some of the instructions can only be validated through runtime,
202 206
 // arg, env, etc., this syntax check will not be complete and could not replace
203 207
 // the runtime check. Instead, this function is only a helper that allows
204 208
 // user to find out the obvious error in Dockerfile earlier on.
205
-// onbuild bool: indicate if instruction XXX is part of `ONBUILD XXX` trigger
206
-func (b *Builder) checkDispatch(ast *parser.Node, onbuild bool) error {
209
+func checkDispatch(ast *parser.Node) error {
207 210
 	cmd := ast.Value
208 211
 	upperCasedCmd := strings.ToUpper(cmd)
209 212
 
... ...
@@ -221,19 +224,9 @@ func (b *Builder) checkDispatch(ast *parser.Node, onbuild bool) error {
221 221
 		}
222 222
 	}
223 223
 
224
-	// The instruction is part of ONBUILD trigger (not the instruction itself)
225
-	if onbuild {
226
-		switch upperCasedCmd {
227
-		case "ONBUILD":
228
-			return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
229
-		case "MAINTAINER", "FROM":
230
-			return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd)
231
-		}
232
-	}
233
-
234 224
 	if _, ok := evaluateTable[cmd]; ok {
235 225
 		return nil
236 226
 	}
237 227
 
238
-	return fmt.Errorf("Unknown instruction: %s", upperCasedCmd)
228
+	return errors.Errorf("unknown instruction: %s", upperCasedCmd)
239 229
 }
... ...
@@ -35,7 +35,6 @@ import (
35 35
 	"github.com/docker/docker/pkg/system"
36 36
 	"github.com/docker/docker/pkg/tarsum"
37 37
 	"github.com/docker/docker/pkg/urlutil"
38
-	"github.com/docker/docker/runconfig/opts"
39 38
 	"github.com/pkg/errors"
40 39
 )
41 40
 
... ...
@@ -433,11 +432,7 @@ func (b *Builder) processImageFrom(img builder.Image) error {
433 433
 	// Check to see if we have a default PATH, note that windows won't
434 434
 	// have one as it's set by HCS
435 435
 	if system.DefaultPathEnv != "" {
436
-		// Convert the slice of strings that represent the current list
437
-		// of env vars into a map so we can see if PATH is already set.
438
-		// If it's not set then go ahead and give it our default value
439
-		configEnv := opts.ConvertKVStringsToMap(b.runConfig.Env)
440
-		if _, ok := configEnv["PATH"]; !ok {
436
+		if _, ok := b.runConfigEnvMapping()["PATH"]; !ok {
441 437
 			b.runConfig.Env = append(b.runConfig.Env,
442 438
 				"PATH="+system.DefaultPathEnv)
443 439
 		}
... ...
@@ -472,19 +467,24 @@ func (b *Builder) processImageFrom(img builder.Image) error {
472 472
 			return err
473 473
 		}
474 474
 
475
-		total := len(ast.Children)
476 475
 		for _, n := range ast.Children {
477
-			if err := b.checkDispatch(n, true); err != nil {
476
+			if err := checkDispatch(n); err != nil {
478 477
 				return err
479 478
 			}
480
-		}
481
-		for i, n := range ast.Children {
482
-			if err := b.dispatch(i, total, n); err != nil {
483
-				return err
479
+
480
+			upperCasedCmd := strings.ToUpper(n.Value)
481
+			switch upperCasedCmd {
482
+			case "ONBUILD":
483
+				return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
484
+			case "MAINTAINER", "FROM":
485
+				return errors.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd)
484 486
 			}
485 487
 		}
486
-	}
487 488
 
489
+		if err := dispatchFromDockerfile(b, ast); err != nil {
490
+			return err
491
+		}
492
+	}
488 493
 	return nil
489 494
 }
490 495
 
... ...
@@ -649,8 +649,8 @@ func (b *Builder) clearTmp() {
649 649
 	}
650 650
 }
651 651
 
652
-// readDockerfile reads a Dockerfile from the current context.
653
-func (b *Builder) readDockerfile() (*parser.Node, error) {
652
+// readAndParseDockerfile reads a Dockerfile from the current context.
653
+func (b *Builder) readAndParseDockerfile() (*parser.Node, error) {
654 654
 	// If no -f was specified then look for 'Dockerfile'. If we can't find
655 655
 	// that then look for 'dockerfile'.  If neither are found then default
656 656
 	// back to 'Dockerfile' and use that in the error message.
... ...
@@ -77,6 +77,6 @@ func readAndCheckDockerfile(t *testing.T, testName, contextDir, dockerfilePath,
77 77
 
78 78
 	b := &Builder{options: options, context: context}
79 79
 
80
-	_, err = b.readDockerfile()
80
+	_, err = b.readAndParseDockerfile()
81 81
 	assert.Error(t, err, expectedError)
82 82
 }
... ...
@@ -8,7 +8,6 @@ package dockerfile
8 8
 
9 9
 import (
10 10
 	"fmt"
11
-	"runtime"
12 11
 	"strings"
13 12
 	"text/scanner"
14 13
 	"unicode"
... ...
@@ -296,17 +295,10 @@ func (sw *shellWord) processName() string {
296 296
 }
297 297
 
298 298
 func (sw *shellWord) getEnv(name string) string {
299
-	if runtime.GOOS == "windows" {
300
-		// Case-insensitive environment variables on Windows
301
-		name = strings.ToUpper(name)
302
-	}
303 299
 	for _, env := range sw.envs {
304 300
 		i := strings.Index(env, "=")
305 301
 		if i < 0 {
306
-			if runtime.GOOS == "windows" {
307
-				env = strings.ToUpper(env)
308
-			}
309
-			if name == env {
302
+			if equalEnvKeys(name, env) {
310 303
 				// Should probably never get here, but just in case treat
311 304
 				// it like "var" and "var=" are the same
312 305
 				return ""
... ...
@@ -314,10 +306,7 @@ func (sw *shellWord) getEnv(name string) string {
314 314
 			continue
315 315
 		}
316 316
 		compareName := env[:i]
317
-		if runtime.GOOS == "windows" {
318
-			compareName = strings.ToUpper(compareName)
319
-		}
320
-		if name != compareName {
317
+		if !equalEnvKeys(name, compareName) {
321 318
 			continue
322 319
 		}
323 320
 		return env[i+1:]
... ...
@@ -6133,7 +6133,7 @@ func (s *DockerSuite) TestBuildCopyFromPreviousFromWindows(c *check.C) {
6133 6133
 func (s *DockerSuite) TestBuildCopyFromForbidWindowsSystemPaths(c *check.C) {
6134 6134
 	testRequires(c, DaemonIsWindows)
6135 6135
 	dockerfile := `
6136
-		FROM ` + testEnv.MinimalBaseImage() + `		
6136
+		FROM ` + testEnv.MinimalBaseImage() + `
6137 6137
 		FROM ` + testEnv.MinimalBaseImage() + `
6138 6138
 		COPY --from=0 %s c:\\oscopy
6139 6139
 		`
... ...
@@ -6150,7 +6150,7 @@ func (s *DockerSuite) TestBuildCopyFromForbidWindowsSystemPaths(c *check.C) {
6150 6150
 func (s *DockerSuite) TestBuildCopyFromForbidWindowsRelativePaths(c *check.C) {
6151 6151
 	testRequires(c, DaemonIsWindows)
6152 6152
 	dockerfile := `
6153
-		FROM ` + testEnv.MinimalBaseImage() + `		
6153
+		FROM ` + testEnv.MinimalBaseImage() + `
6154 6154
 		FROM ` + testEnv.MinimalBaseImage() + `
6155 6155
 		COPY --from=0 %s c:\\oscopy
6156 6156
 		`
... ...
@@ -6169,7 +6169,7 @@ func (s *DockerSuite) TestBuildCopyFromWindowsIsCaseInsensitive(c *check.C) {
6169 6169
 	testRequires(c, DaemonIsWindows)
6170 6170
 	dockerfile := `
6171 6171
 		FROM ` + testEnv.MinimalBaseImage() + `
6172
-		COPY foo /	
6172
+		COPY foo /
6173 6173
 		FROM ` + testEnv.MinimalBaseImage() + `
6174 6174
 		COPY --from=0 c:\\fOo c:\\copied
6175 6175
 		RUN type c:\\copied
... ...
@@ -6322,23 +6322,23 @@ func (s *DockerSuite) TestBuildLineErrorOnBuild(c *check.C) {
6322 6322
 }
6323 6323
 
6324 6324
 // FIXME(vdemeester) should be a unit test
6325
-func (s *DockerSuite) TestBuildLineErrorUknownInstruction(c *check.C) {
6325
+func (s *DockerSuite) TestBuildLineErrorUnknownInstruction(c *check.C) {
6326 6326
 	name := "test_build_line_error_unknown_instruction"
6327
-	buildImage(name, build.WithDockerfile(`FROM busybox
6327
+	cli.Docker(cli.Build(name), build.WithDockerfile(`FROM busybox
6328 6328
   RUN echo hello world
6329 6329
   NOINSTRUCTION echo ba
6330 6330
   RUN echo hello
6331 6331
   ERROR
6332 6332
   `)).Assert(c, icmd.Expected{
6333 6333
 		ExitCode: 1,
6334
-		Err:      "Dockerfile parse error line 3: Unknown instruction: NOINSTRUCTION",
6334
+		Err:      "Dockerfile parse error line 3: unknown instruction: NOINSTRUCTION",
6335 6335
 	})
6336 6336
 }
6337 6337
 
6338 6338
 // FIXME(vdemeester) should be a unit test
6339 6339
 func (s *DockerSuite) TestBuildLineErrorWithEmptyLines(c *check.C) {
6340 6340
 	name := "test_build_line_error_with_empty_lines"
6341
-	buildImage(name, build.WithDockerfile(`
6341
+	cli.Docker(cli.Build(name), build.WithDockerfile(`
6342 6342
   FROM busybox
6343 6343
 
6344 6344
   RUN echo hello world
... ...
@@ -6348,21 +6348,21 @@ func (s *DockerSuite) TestBuildLineErrorWithEmptyLines(c *check.C) {
6348 6348
   CMD ["/bin/init"]
6349 6349
   `)).Assert(c, icmd.Expected{
6350 6350
 		ExitCode: 1,
6351
-		Err:      "Dockerfile parse error line 6: Unknown instruction: NOINSTRUCTION",
6351
+		Err:      "Dockerfile parse error line 6: unknown instruction: NOINSTRUCTION",
6352 6352
 	})
6353 6353
 }
6354 6354
 
6355 6355
 // FIXME(vdemeester) should be a unit test
6356 6356
 func (s *DockerSuite) TestBuildLineErrorWithComments(c *check.C) {
6357 6357
 	name := "test_build_line_error_with_comments"
6358
-	buildImage(name, build.WithDockerfile(`FROM busybox
6358
+	cli.Docker(cli.Build(name), build.WithDockerfile(`FROM busybox
6359 6359
   # This will print hello world
6360 6360
   # and then ba
6361 6361
   RUN echo hello world
6362 6362
   NOINSTRUCTION echo ba
6363 6363
   `)).Assert(c, icmd.Expected{
6364 6364
 		ExitCode: 1,
6365
-		Err:      "Dockerfile parse error line 5: Unknown instruction: NOINSTRUCTION",
6365
+		Err:      "Dockerfile parse error line 5: unknown instruction: NOINSTRUCTION",
6366 6366
 	})
6367 6367
 }
6368 6368