Browse code

Refactor interaction between dispatcher.from and dispatchState

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2017/05/06 02:05:25
Showing 9 changed files
... ...
@@ -89,5 +89,5 @@ type Image interface {
89 89
 // ReleaseableLayer is an image layer that can be mounted and released
90 90
 type ReleaseableLayer interface {
91 91
 	Release() error
92
-	Mount() (string, error)
92
+	Mount(string) (string, error)
93 93
 }
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	"github.com/docker/docker/api/types/backend"
24 24
 	"github.com/docker/docker/api/types/container"
25 25
 	"github.com/docker/docker/api/types/strslice"
26
+	"github.com/docker/docker/builder/dockerfile/parser"
26 27
 	"github.com/docker/docker/pkg/signal"
27 28
 	"github.com/docker/go-connections/nat"
28 29
 	"github.com/pkg/errors"
... ...
@@ -196,24 +197,21 @@ func from(req dispatchRequest) error {
196 196
 	}
197 197
 
198 198
 	req.builder.resetImageCache()
199
-	req.state.noBaseImage = false
200
-	req.state.stageName = stageName
201 199
 	image, err := req.builder.getFromImage(req.shlex, req.args[0])
202 200
 	if err != nil {
203 201
 		return err
204 202
 	}
205
-	if image == nil {
206
-		req.state.imageID = ""
207
-		req.state.noBaseImage = true
208
-		image = newImageMount(nil, nil)
209
-	}
210 203
 	if err := req.builder.imageContexts.add(stageName, image); err != nil {
211 204
 		return err
212 205
 	}
213
-	req.state.baseImage = image
214
-
206
+	req.state.beginStage(stageName, image)
215 207
 	req.builder.buildArgs.ResetAllowed()
216
-	return req.builder.processImageFrom(req.state, image)
208
+	if image.ImageID() == "" {
209
+		// Typically this means they used "FROM scratch"
210
+		return nil
211
+	}
212
+
213
+	return processOnBuild(req)
217 214
 }
218 215
 
219 216
 func parseBuildStageName(args []string) (string, error) {
... ...
@@ -243,11 +241,7 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (*imageMount, error
243 243
 	}
244 244
 
245 245
 	if im, ok := b.imageContexts.byName[name]; ok {
246
-		if len(im.ImageID()) > 0 {
247
-			return im, nil
248
-		}
249
-		// FROM scratch does not have an ImageID
250
-		return nil, nil
246
+		return im, nil
251 247
 	}
252 248
 
253 249
 	// Windows cannot support a container with no base image.
... ...
@@ -255,7 +249,7 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (*imageMount, error
255 255
 		if runtime.GOOS == "windows" {
256 256
 			return nil, errors.New("Windows does not support FROM scratch")
257 257
 		}
258
-		return nil, nil
258
+		return newImageMount(nil, nil), nil
259 259
 	}
260 260
 	return b.getImage(name)
261 261
 }
... ...
@@ -272,6 +266,53 @@ func (b *Builder) getImage(name string) (*imageMount, error) {
272 272
 	return newImageMount(image, layer), nil
273 273
 }
274 274
 
275
+func processOnBuild(req dispatchRequest) error {
276
+	dispatchState := req.state
277
+	// Process ONBUILD triggers if they exist
278
+	if nTriggers := len(dispatchState.runConfig.OnBuild); nTriggers != 0 {
279
+		word := "trigger"
280
+		if nTriggers > 1 {
281
+			word = "triggers"
282
+		}
283
+		fmt.Fprintf(req.builder.Stderr, "# Executing %d build %s...\n", nTriggers, word)
284
+	}
285
+
286
+	// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
287
+	onBuildTriggers := dispatchState.runConfig.OnBuild
288
+	dispatchState.runConfig.OnBuild = []string{}
289
+
290
+	// Reset stdin settings as all build actions run without stdin
291
+	dispatchState.runConfig.OpenStdin = false
292
+	dispatchState.runConfig.StdinOnce = false
293
+
294
+	// parse the ONBUILD triggers by invoking the parser
295
+	for _, step := range onBuildTriggers {
296
+		dockerfile, err := parser.Parse(strings.NewReader(step))
297
+		if err != nil {
298
+			return err
299
+		}
300
+
301
+		for _, n := range dockerfile.AST.Children {
302
+			if err := checkDispatch(n); err != nil {
303
+				return err
304
+			}
305
+
306
+			upperCasedCmd := strings.ToUpper(n.Value)
307
+			switch upperCasedCmd {
308
+			case "ONBUILD":
309
+				return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
310
+			case "MAINTAINER", "FROM":
311
+				return errors.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd)
312
+			}
313
+		}
314
+
315
+		if _, err := dispatchFromDockerfile(req.builder, dockerfile, dispatchState); err != nil {
316
+			return err
317
+		}
318
+	}
319
+	return nil
320
+}
321
+
275 322
 // ONBUILD RUN echo yo
276 323
 //
277 324
 // ONBUILD triggers run when the image is used in a FROM statement.
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"github.com/docker/docker/api/types/strslice"
14 14
 	"github.com/docker/docker/builder"
15 15
 	"github.com/docker/docker/builder/dockerfile/parser"
16
+	"github.com/docker/docker/pkg/system"
16 17
 	"github.com/docker/docker/pkg/testutil"
17 18
 	"github.com/docker/go-connections/nat"
18 19
 	"github.com/stretchr/testify/assert"
... ...
@@ -192,8 +193,9 @@ func TestFromScratch(t *testing.T) {
192 192
 	}
193 193
 
194 194
 	require.NoError(t, err)
195
+	assert.True(t, req.state.hasFromImage())
195 196
 	assert.Equal(t, "", req.state.imageID)
196
-	assert.Equal(t, true, req.state.noBaseImage)
197
+	assert.Equal(t, []string{"PATH=" + system.DefaultPathEnv}, req.state.runConfig.Env)
197 198
 }
198 199
 
199 200
 func TestFromWithArg(t *testing.T) {
... ...
@@ -28,6 +28,7 @@ import (
28 28
 	"github.com/docker/docker/builder"
29 29
 	"github.com/docker/docker/builder/dockerfile/command"
30 30
 	"github.com/docker/docker/builder/dockerfile/parser"
31
+	"github.com/docker/docker/pkg/system"
31 32
 	"github.com/docker/docker/runconfig/opts"
32 33
 	"github.com/pkg/errors"
33 34
 )
... ...
@@ -184,37 +185,55 @@ type dispatchOptions struct {
184 184
 
185 185
 // dispatchState is a data object which is modified by dispatchers
186 186
 type dispatchState struct {
187
-	runConfig   *container.Config
188
-	maintainer  string
189
-	cmdSet      bool
190
-	noBaseImage bool
191
-	imageID     string
192
-	baseImage   builder.Image
193
-	stageName   string
187
+	runConfig  *container.Config
188
+	maintainer string
189
+	cmdSet     bool
190
+	imageID    string
191
+	baseImage  builder.Image
192
+	stageName  string
194 193
 }
195 194
 
196 195
 func newDispatchState() *dispatchState {
197 196
 	return &dispatchState{runConfig: &container.Config{}}
198 197
 }
199 198
 
200
-func (r *dispatchState) updateRunConfig() {
201
-	r.runConfig.Image = r.imageID
199
+func (s *dispatchState) updateRunConfig() {
200
+	s.runConfig.Image = s.imageID
202 201
 }
203 202
 
204 203
 // hasFromImage returns true if the builder has processed a `FROM <image>` line
205
-func (r *dispatchState) hasFromImage() bool {
206
-	return r.imageID != "" || r.noBaseImage
204
+func (s *dispatchState) hasFromImage() bool {
205
+	return s.imageID != "" || (s.baseImage != nil && s.baseImage.ImageID() == "")
207 206
 }
208 207
 
209
-func (r *dispatchState) runConfigEnvMapping() map[string]string {
210
-	return opts.ConvertKVStringsToMap(r.runConfig.Env)
211
-}
212
-
213
-func (r *dispatchState) isCurrentStage(target string) bool {
208
+func (s *dispatchState) isCurrentStage(target string) bool {
214 209
 	if target == "" {
215 210
 		return false
216 211
 	}
217
-	return strings.EqualFold(r.stageName, target)
212
+	return strings.EqualFold(s.stageName, target)
213
+}
214
+
215
+func (s *dispatchState) beginStage(stageName string, image builder.Image) {
216
+	s.stageName = stageName
217
+	s.imageID = image.ImageID()
218
+
219
+	if image.RunConfig() != nil {
220
+		s.runConfig = image.RunConfig()
221
+	}
222
+	s.baseImage = image
223
+	s.setDefaultPath()
224
+}
225
+
226
+// Add the default PATH to runConfig.ENV if one exists for the platform and there
227
+// is no PATH set. Note that windows won't have one as it's set by HCS
228
+func (s *dispatchState) setDefaultPath() {
229
+	if system.DefaultPathEnv == "" {
230
+		return
231
+	}
232
+	envMap := opts.ConvertKVStringsToMap(s.runConfig.Env)
233
+	if _, ok := envMap["PATH"]; !ok {
234
+		s.runConfig.Env = append(s.runConfig.Env, "PATH="+system.DefaultPathEnv)
235
+	}
218 236
 }
219 237
 
220 238
 func handleOnBuildNode(ast *parser.Node, msg *bytes.Buffer) (*parser.Node, []string, error) {
... ...
@@ -45,9 +45,9 @@ func (ic *imageContexts) update(imageID string, runConfig *container.Config) {
45 45
 func (ic *imageContexts) validate(i int) error {
46 46
 	if i < 0 || i >= len(ic.list)-1 {
47 47
 		if i == len(ic.list)-1 {
48
-			return errors.Errorf("%d refers to current build stage", i)
48
+			return errors.New("refers to current build stage")
49 49
 		}
50
-		return errors.Errorf("index out of bounds")
50
+		return errors.New("index out of bounds")
51 51
 	}
52 52
 	return nil
53 53
 }
... ...
@@ -122,7 +122,7 @@ func (im *imageMount) context() (builder.Source, error) {
122 122
 		if im.id == "" || im.layer == nil {
123 123
 			return nil, errors.Errorf("empty context")
124 124
 		}
125
-		mountPath, err := im.layer.Mount()
125
+		mountPath, err := im.layer.Mount(im.id)
126 126
 		if err != nil {
127 127
 			return nil, errors.Wrapf(err, "failed to mount %s", im.id)
128 128
 		}
... ...
@@ -136,6 +136,9 @@ func (im *imageMount) context() (builder.Source, error) {
136 136
 }
137 137
 
138 138
 func (im *imageMount) unmount() error {
139
+	if im.layer == nil {
140
+		return nil
141
+	}
139 142
 	if err := im.layer.Release(); err != nil {
140 143
 		return errors.Wrapf(err, "failed to unmount previous build image %s", im.id)
141 144
 	}
... ...
@@ -22,7 +22,6 @@ import (
22 22
 	"github.com/docker/docker/api/types/backend"
23 23
 	"github.com/docker/docker/api/types/container"
24 24
 	"github.com/docker/docker/builder"
25
-	"github.com/docker/docker/builder/dockerfile/parser"
26 25
 	"github.com/docker/docker/builder/remotecontext"
27 26
 	"github.com/docker/docker/pkg/httputils"
28 27
 	"github.com/docker/docker/pkg/ioutils"
... ...
@@ -480,74 +479,6 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
480 480
 	return copyInfos, nil
481 481
 }
482 482
 
483
-func (b *Builder) processImageFrom(dispatchState *dispatchState, img builder.Image) error {
484
-	if img != nil {
485
-		dispatchState.imageID = img.ImageID()
486
-
487
-		if img.RunConfig() != nil {
488
-			dispatchState.runConfig = img.RunConfig()
489
-		}
490
-	}
491
-
492
-	// Check to see if we have a default PATH, note that windows won't
493
-	// have one as it's set by HCS
494
-	if system.DefaultPathEnv != "" {
495
-		if _, ok := dispatchState.runConfigEnvMapping()["PATH"]; !ok {
496
-			dispatchState.runConfig.Env = append(dispatchState.runConfig.Env,
497
-				"PATH="+system.DefaultPathEnv)
498
-		}
499
-	}
500
-
501
-	if img == nil {
502
-		// Typically this means they used "FROM scratch"
503
-		return nil
504
-	}
505
-
506
-	// Process ONBUILD triggers if they exist
507
-	if nTriggers := len(dispatchState.runConfig.OnBuild); nTriggers != 0 {
508
-		word := "trigger"
509
-		if nTriggers > 1 {
510
-			word = "triggers"
511
-		}
512
-		fmt.Fprintf(b.Stderr, "# Executing %d build %s...\n", nTriggers, word)
513
-	}
514
-
515
-	// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
516
-	onBuildTriggers := dispatchState.runConfig.OnBuild
517
-	dispatchState.runConfig.OnBuild = []string{}
518
-
519
-	// Reset stdin settings as all build actions run without stdin
520
-	dispatchState.runConfig.OpenStdin = false
521
-	dispatchState.runConfig.StdinOnce = false
522
-
523
-	// parse the ONBUILD triggers by invoking the parser
524
-	for _, step := range onBuildTriggers {
525
-		dockerfile, err := parser.Parse(strings.NewReader(step))
526
-		if err != nil {
527
-			return err
528
-		}
529
-
530
-		for _, n := range dockerfile.AST.Children {
531
-			if err := checkDispatch(n); err != nil {
532
-				return err
533
-			}
534
-
535
-			upperCasedCmd := strings.ToUpper(n.Value)
536
-			switch upperCasedCmd {
537
-			case "ONBUILD":
538
-				return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
539
-			case "MAINTAINER", "FROM":
540
-				return errors.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd)
541
-			}
542
-		}
543
-
544
-		if _, err := dispatchFromDockerfile(b, dockerfile, dispatchState); err != nil {
545
-			return err
546
-		}
547
-	}
548
-	return nil
549
-}
550
-
551 483
 // probeCache checks if cache match can be found for current build instruction.
552 484
 // If an image is found, probeCache returns `(true, nil)`.
553 485
 // If no image is found, it returns `(false, nil)`.
... ...
@@ -112,6 +112,6 @@ func (l *mockLayer) Release() error {
112 112
 	return nil
113 113
 }
114 114
 
115
-func (l *mockLayer) Mount() (string, error) {
115
+func (l *mockLayer) Mount(_ string) (string, error) {
116 116
 	return "mountPath", nil
117 117
 }
... ...
@@ -17,7 +17,7 @@ import (
17 17
 type releaseableLayer struct {
18 18
 	rwLayer layer.RWLayer
19 19
 	release func(layer.RWLayer) error
20
-	mount   func() (layer.RWLayer, error)
20
+	mount   func(string) (layer.RWLayer, error)
21 21
 }
22 22
 
23 23
 func (rl *releaseableLayer) Release() error {
... ...
@@ -28,10 +28,9 @@ func (rl *releaseableLayer) Release() error {
28 28
 	return rl.release(rl.rwLayer)
29 29
 }
30 30
 
31
-func (rl *releaseableLayer) Mount() (string, error) {
31
+func (rl *releaseableLayer) Mount(imageID string) (string, error) {
32 32
 	var err error
33
-	// daemon.layerStore.CreateRWLayer(mountID, img.RootFS.ChainID(), nil)
34
-	rl.rwLayer, err = rl.mount()
33
+	rl.rwLayer, err = rl.mount(imageID)
35 34
 	if err != nil {
36 35
 		return "", errors.Wrap(err, "failed to create rwlayer")
37 36
 	}
... ...
@@ -47,8 +46,12 @@ func (rl *releaseableLayer) Mount() (string, error) {
47 47
 	return mountPath, err
48 48
 }
49 49
 
50
-func (daemon *Daemon) getReleasableLayerForImage(img *image.Image) (*releaseableLayer, error) {
51
-	mountFunc := func() (layer.RWLayer, error) {
50
+func (daemon *Daemon) getReleasableLayerForImage() *releaseableLayer {
51
+	mountFunc := func(imageID string) (layer.RWLayer, error) {
52
+		img, err := daemon.GetImage(imageID)
53
+		if err != nil {
54
+			return nil, err
55
+		}
52 56
 		mountID := stringid.GenerateRandomID()
53 57
 		return daemon.layerStore.CreateRWLayer(mountID, img.RootFS.ChainID(), nil)
54 58
 	}
... ...
@@ -59,7 +62,7 @@ func (daemon *Daemon) getReleasableLayerForImage(img *image.Image) (*releaseable
59 59
 		return err
60 60
 	}
61 61
 
62
-	return &releaseableLayer{mount: mountFunc, release: releaseFunc}, nil
62
+	return &releaseableLayer{mount: mountFunc, release: releaseFunc}
63 63
 }
64 64
 
65 65
 // TODO: could this use the regular daemon PullImage ?
... ...
@@ -94,15 +97,10 @@ func (daemon *Daemon) GetImageAndLayer(ctx context.Context, refOrID string, opts
94 94
 		image, _ := daemon.GetImage(refOrID)
95 95
 		// TODO: shouldn't we error out if error is different from "not found" ?
96 96
 		if image != nil {
97
-			layer, err := daemon.getReleasableLayerForImage(image)
98
-			return image, layer, err
97
+			return image, daemon.getReleasableLayerForImage(), nil
99 98
 		}
100 99
 	}
101 100
 
102 101
 	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output)
103
-	if err != nil {
104
-		return nil, nil, err
105
-	}
106
-	layer, err := daemon.getReleasableLayerForImage(image)
107
-	return image, layer, err
102
+	return image, daemon.getReleasableLayerForImage(), err
108 103
 }
... ...
@@ -5946,7 +5946,7 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) {
5946 5946
 			dockerfile: `
5947 5947
 		FROM busybox
5948 5948
 		COPY --from=0 foo bar`,
5949
-			expectedError: "invalid from flag value 0 refers current build block",
5949
+			expectedError: "invalid from flag value 0: refers to current build stage",
5950 5950
 		},
5951 5951
 		{
5952 5952
 			dockerfile: `