Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| ... | ... |
@@ -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)
|
| ... | ... |
@@ -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 |