瀏覽代碼

builder: add an option for specifying build target

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Tonis Tiigi 8 年之前
父節點
當前提交
33e07f41ad

+ 1 - 0
api/server/router/build/build_routes.go

@@ -56,6 +56,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
 	options.ExtraHosts = r.Form["extrahosts"]
 	options.ExtraHosts = r.Form["extrahosts"]
 	options.SecurityOpt = r.Form["securityopt"]
 	options.SecurityOpt = r.Form["securityopt"]
 	options.Squash = httputils.BoolValue(r, "squash")
 	options.Squash = httputils.BoolValue(r, "squash")
+	options.Target = r.FormValue("target")
 
 
 	if r.Form.Get("shmsize") != "" {
 	if r.Form.Get("shmsize") != "" {
 		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
 		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)

+ 1 - 0
api/types/client.go

@@ -176,6 +176,7 @@ type ImageBuildOptions struct {
 	CacheFrom   []string
 	CacheFrom   []string
 	SecurityOpt []string
 	SecurityOpt []string
 	ExtraHosts  []string // List of extra hosts
 	ExtraHosts  []string // List of extra hosts
+	Target      string
 }
 }
 
 
 // ImageBuildResponse holds information
 // ImageBuildResponse holds information

+ 9 - 0
builder/dockerfile/builder.go

@@ -16,6 +16,7 @@ import (
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/builder"
 	"github.com/docker/docker/builder"
+	"github.com/docker/docker/builder/dockerfile/command"
 	"github.com/docker/docker/builder/dockerfile/parser"
 	"github.com/docker/docker/builder/dockerfile/parser"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
@@ -253,6 +254,10 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
 			// Not cancelled yet, keep going...
 			// Not cancelled yet, keep going...
 		}
 		}
 
 
+		if command.From == n.Value && b.imageContexts.isCurrentTarget(b.options.Target) {
+			break
+		}
+
 		if err := b.dispatch(i, total, n); err != nil {
 		if err := b.dispatch(i, total, n); err != nil {
 			if b.options.ForceRemove {
 			if b.options.ForceRemove {
 				b.clearTmp()
 				b.clearTmp()
@@ -267,6 +272,10 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
 		}
 		}
 	}
 	}
 
 
+	if b.options.Target != "" && !b.imageContexts.isCurrentTarget(b.options.Target) {
+		return "", perrors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
+	}
+
 	b.warnOnUnusedBuildArgs()
 	b.warnOnUnusedBuildArgs()
 
 
 	if b.image == "" {
 	if b.image == "" {

+ 13 - 4
builder/dockerfile/imagecontext.go

@@ -15,10 +15,11 @@ import (
 // imageContexts is a helper for stacking up built image rootfs and reusing
 // imageContexts is a helper for stacking up built image rootfs and reusing
 // them as contexts
 // them as contexts
 type imageContexts struct {
 type imageContexts struct {
-	b      *Builder
-	list   []*imageMount
-	byName map[string]*imageMount
-	cache  *pathCache
+	b           *Builder
+	list        []*imageMount
+	byName      map[string]*imageMount
+	cache       *pathCache
+	currentName string
 }
 }
 
 
 func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) {
 func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) {
@@ -35,6 +36,7 @@ func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) {
 	if increment {
 	if increment {
 		ic.list = append(ic.list, im)
 		ic.list = append(ic.list, im)
 	}
 	}
+	ic.currentName = name
 	return im, nil
 	return im, nil
 }
 }
 
 
@@ -88,6 +90,13 @@ func (ic *imageContexts) unmount() (retErr error) {
 	return
 	return
 }
 }
 
 
+func (ic *imageContexts) isCurrentTarget(target string) bool {
+	if target == "" {
+		return false
+	}
+	return strings.EqualFold(ic.currentName, target)
+}
+
 func (ic *imageContexts) getCache(id, path string) (interface{}, bool) {
 func (ic *imageContexts) getCache(id, path string) (interface{}, bool) {
 	if ic.cache != nil {
 	if ic.cache != nil {
 		if id == "" {
 		if id == "" {

+ 3 - 0
cli/command/image/build.go

@@ -64,6 +64,7 @@ type buildOptions struct {
 	securityOpt    []string
 	securityOpt    []string
 	networkMode    string
 	networkMode    string
 	squash         bool
 	squash         bool
+	target         string
 }
 }
 
 
 // NewBuildCommand creates a new `docker build` command
 // NewBuildCommand creates a new `docker build` command
@@ -115,6 +116,7 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
 	flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
 	flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
 	flags.SetAnnotation("network", "version", []string{"1.25"})
 	flags.SetAnnotation("network", "version", []string{"1.25"})
 	flags.Var(&options.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
 	flags.Var(&options.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
+	flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
 
 
 	command.AddTrustVerificationFlags(flags)
 	command.AddTrustVerificationFlags(flags)
 
 
@@ -302,6 +304,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
 		NetworkMode:    options.networkMode,
 		NetworkMode:    options.networkMode,
 		Squash:         options.squash,
 		Squash:         options.squash,
 		ExtraHosts:     options.extraHosts.GetAll(),
 		ExtraHosts:     options.extraHosts.GetAll(),
+		Target:         options.target,
 	}
 	}
 
 
 	response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
 	response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)

+ 1 - 0
client/image_build.go

@@ -95,6 +95,7 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
 	query.Set("cgroupparent", options.CgroupParent)
 	query.Set("cgroupparent", options.CgroupParent)
 	query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10))
 	query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10))
 	query.Set("dockerfile", options.Dockerfile)
 	query.Set("dockerfile", options.Dockerfile)
+	query.Set("target", options.Target)
 
 
 	ulimitsJSON, err := json.Marshal(options.Ulimits)
 	ulimitsJSON, err := json.Marshal(options.Ulimits)
 	if err != nil {
 	if err != nil {

+ 27 - 0
integration-cli/docker_cli_build_test.go

@@ -6210,6 +6210,33 @@ func (s *DockerSuite) TestBuildCopyFromWindowsIsCaseInsensitive(c *check.C) {
 	result.Assert(c, exp)
 	result.Assert(c, exp)
 }
 }
 
 
+func (s *DockerSuite) TestBuildIntermediateTarget(c *check.C) {
+	dockerfile := `
+		FROM busybox AS build-env
+		CMD ["/dev"]
+		FROM busybox
+		CMD ["/dist"]
+		`
+	ctx := fakeContext(c, dockerfile, map[string]string{
+		"Dockerfile": dockerfile,
+	})
+	defer ctx.Close()
+
+	result := buildImage("build1", withExternalBuildContext(ctx),
+		cli.WithFlags("--target", "build-env"))
+	result.Assert(c, icmd.Success)
+
+	res := inspectFieldJSON(c, "build1", "Config.Cmd")
+	c.Assert(res, checker.Equals, `["/dev"]`)
+
+	result = buildImage("build1", withExternalBuildContext(ctx),
+		cli.WithFlags("--target", "nosuchtarget"))
+	result.Assert(c, icmd.Expected{
+		ExitCode: 1,
+		Err:      "failed to reach build target",
+	})
+}
+
 // TestBuildOpaqueDirectory tests that a build succeeds which
 // TestBuildOpaqueDirectory tests that a build succeeds which
 // creates opaque directories.
 // creates opaque directories.
 // See https://github.com/docker/docker/issues/25244
 // See https://github.com/docker/docker/issues/25244