Browse code

builder: add an option for specifying build target

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2017/04/11 07:27:42
Showing 7 changed files
... ...
@@ -56,6 +56,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
56 56
 	options.ExtraHosts = r.Form["extrahosts"]
57 57
 	options.SecurityOpt = r.Form["securityopt"]
58 58
 	options.Squash = httputils.BoolValue(r, "squash")
59
+	options.Target = r.FormValue("target")
59 60
 
60 61
 	if r.Form.Get("shmsize") != "" {
61 62
 		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
... ...
@@ -176,6 +176,7 @@ type ImageBuildOptions struct {
176 176
 	CacheFrom   []string
177 177
 	SecurityOpt []string
178 178
 	ExtraHosts  []string // List of extra hosts
179
+	Target      string
179 180
 }
180 181
 
181 182
 // ImageBuildResponse holds information
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"github.com/docker/docker/api/types/backend"
17 17
 	"github.com/docker/docker/api/types/container"
18 18
 	"github.com/docker/docker/builder"
19
+	"github.com/docker/docker/builder/dockerfile/command"
19 20
 	"github.com/docker/docker/builder/dockerfile/parser"
20 21
 	"github.com/docker/docker/image"
21 22
 	"github.com/docker/docker/pkg/stringid"
... ...
@@ -253,6 +254,10 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
253 253
 			// Not cancelled yet, keep going...
254 254
 		}
255 255
 
256
+		if command.From == n.Value && b.imageContexts.isCurrentTarget(b.options.Target) {
257
+			break
258
+		}
259
+
256 260
 		if err := b.dispatch(i, total, n); err != nil {
257 261
 			if b.options.ForceRemove {
258 262
 				b.clearTmp()
... ...
@@ -267,6 +272,10 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
267 267
 		}
268 268
 	}
269 269
 
270
+	if b.options.Target != "" && !b.imageContexts.isCurrentTarget(b.options.Target) {
271
+		return "", perrors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
272
+	}
273
+
270 274
 	b.warnOnUnusedBuildArgs()
271 275
 
272 276
 	if b.image == "" {
... ...
@@ -15,10 +15,11 @@ import (
15 15
 // imageContexts is a helper for stacking up built image rootfs and reusing
16 16
 // them as contexts
17 17
 type imageContexts struct {
18
-	b      *Builder
19
-	list   []*imageMount
20
-	byName map[string]*imageMount
21
-	cache  *pathCache
18
+	b           *Builder
19
+	list        []*imageMount
20
+	byName      map[string]*imageMount
21
+	cache       *pathCache
22
+	currentName string
22 23
 }
23 24
 
24 25
 func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) {
... ...
@@ -35,6 +36,7 @@ func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) {
35 35
 	if increment {
36 36
 		ic.list = append(ic.list, im)
37 37
 	}
38
+	ic.currentName = name
38 39
 	return im, nil
39 40
 }
40 41
 
... ...
@@ -88,6 +90,13 @@ func (ic *imageContexts) unmount() (retErr error) {
88 88
 	return
89 89
 }
90 90
 
91
+func (ic *imageContexts) isCurrentTarget(target string) bool {
92
+	if target == "" {
93
+		return false
94
+	}
95
+	return strings.EqualFold(ic.currentName, target)
96
+}
97
+
91 98
 func (ic *imageContexts) getCache(id, path string) (interface{}, bool) {
92 99
 	if ic.cache != nil {
93 100
 		if id == "" {
... ...
@@ -64,6 +64,7 @@ type buildOptions struct {
64 64
 	securityOpt    []string
65 65
 	networkMode    string
66 66
 	squash         bool
67
+	target         string
67 68
 }
68 69
 
69 70
 // NewBuildCommand creates a new `docker build` command
... ...
@@ -115,6 +116,7 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
115 115
 	flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
116 116
 	flags.SetAnnotation("network", "version", []string{"1.25"})
117 117
 	flags.Var(&options.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
118
+	flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
118 119
 
119 120
 	command.AddTrustVerificationFlags(flags)
120 121
 
... ...
@@ -302,6 +304,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
302 302
 		NetworkMode:    options.networkMode,
303 303
 		Squash:         options.squash,
304 304
 		ExtraHosts:     options.extraHosts.GetAll(),
305
+		Target:         options.target,
305 306
 	}
306 307
 
307 308
 	response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
... ...
@@ -95,6 +95,7 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
95 95
 	query.Set("cgroupparent", options.CgroupParent)
96 96
 	query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10))
97 97
 	query.Set("dockerfile", options.Dockerfile)
98
+	query.Set("target", options.Target)
98 99
 
99 100
 	ulimitsJSON, err := json.Marshal(options.Ulimits)
100 101
 	if err != nil {
... ...
@@ -6210,6 +6210,33 @@ func (s *DockerSuite) TestBuildCopyFromWindowsIsCaseInsensitive(c *check.C) {
6210 6210
 	result.Assert(c, exp)
6211 6211
 }
6212 6212
 
6213
+func (s *DockerSuite) TestBuildIntermediateTarget(c *check.C) {
6214
+	dockerfile := `
6215
+		FROM busybox AS build-env
6216
+		CMD ["/dev"]
6217
+		FROM busybox
6218
+		CMD ["/dist"]
6219
+		`
6220
+	ctx := fakeContext(c, dockerfile, map[string]string{
6221
+		"Dockerfile": dockerfile,
6222
+	})
6223
+	defer ctx.Close()
6224
+
6225
+	result := buildImage("build1", withExternalBuildContext(ctx),
6226
+		cli.WithFlags("--target", "build-env"))
6227
+	result.Assert(c, icmd.Success)
6228
+
6229
+	res := inspectFieldJSON(c, "build1", "Config.Cmd")
6230
+	c.Assert(res, checker.Equals, `["/dev"]`)
6231
+
6232
+	result = buildImage("build1", withExternalBuildContext(ctx),
6233
+		cli.WithFlags("--target", "nosuchtarget"))
6234
+	result.Assert(c, icmd.Expected{
6235
+		ExitCode: 1,
6236
+		Err:      "failed to reach build target",
6237
+	})
6238
+}
6239
+
6213 6240
 // TestBuildOpaqueDirectory tests that a build succeeds which
6214 6241
 // creates opaque directories.
6215 6242
 // See https://github.com/docker/docker/issues/25244