Signed-off-by: Shijiang Wei <mountkin@gmail.com>
Shijiang Wei authored on 2015/08/30 22:48:03... | ... |
@@ -49,7 +49,8 @@ const ( |
49 | 49 |
// Usage: docker build [OPTIONS] PATH | URL | - |
50 | 50 |
func (cli *DockerCli) CmdBuild(args ...string) error { |
51 | 51 |
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, Cli.DockerCommands["build"].Description, true) |
52 |
- tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image") |
|
52 |
+ flTags := opts.NewListOpts(validateTag) |
|
53 |
+ cmd.Var(&flTags, []string{"t", "-tag"}, "Name and optionally a tag in the 'name:tag' format") |
|
53 | 54 |
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers") |
54 | 55 |
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image") |
55 | 56 |
rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build") |
... | ... |
@@ -207,24 +208,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { |
207 | 207 |
memorySwap = parsedMemorySwap |
208 | 208 |
} |
209 | 209 |
} |
210 |
- // Send the build context |
|
211 |
- v := &url.Values{} |
|
212 | 210 |
|
213 |
- //Check if the given image name can be resolved |
|
214 |
- if *tag != "" { |
|
215 |
- repository, tag := parsers.ParseRepositoryTag(*tag) |
|
216 |
- if err := registry.ValidateRepositoryName(repository); err != nil { |
|
217 |
- return err |
|
218 |
- } |
|
219 |
- if len(tag) > 0 { |
|
220 |
- if err := tags.ValidateTagName(tag); err != nil { |
|
221 |
- return err |
|
222 |
- } |
|
223 |
- } |
|
211 |
+ // Send the build context |
|
212 |
+ v := url.Values{ |
|
213 |
+ "t": flTags.GetAll(), |
|
224 | 214 |
} |
225 |
- |
|
226 |
- v.Set("t", *tag) |
|
227 |
- |
|
228 | 215 |
if *suppressOutput { |
229 | 216 |
v.Set("q", "1") |
230 | 217 |
} |
... | ... |
@@ -324,6 +312,24 @@ func (cli *DockerCli) CmdBuild(args ...string) error { |
324 | 324 |
return nil |
325 | 325 |
} |
326 | 326 |
|
327 |
+// validateTag checks if the given image name can be resolved. |
|
328 |
+func validateTag(rawRepo string) (string, error) { |
|
329 |
+ repository, tag := parsers.ParseRepositoryTag(rawRepo) |
|
330 |
+ if err := registry.ValidateRepositoryName(repository); err != nil { |
|
331 |
+ return "", err |
|
332 |
+ } |
|
333 |
+ |
|
334 |
+ if len(tag) == 0 { |
|
335 |
+ return rawRepo, nil |
|
336 |
+ } |
|
337 |
+ |
|
338 |
+ if err := tags.ValidateTagName(tag); err != nil { |
|
339 |
+ return "", err |
|
340 |
+ } |
|
341 |
+ |
|
342 |
+ return rawRepo, nil |
|
343 |
+} |
|
344 |
+ |
|
327 | 345 |
// isUNC returns true if the path is UNC (one starting \\). It always returns |
328 | 346 |
// false on Linux. |
329 | 347 |
func isUNC(path string) bool { |
... | ... |
@@ -308,16 +308,9 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R |
308 | 308 |
buildConfig.Pull = true |
309 | 309 |
} |
310 | 310 |
|
311 |
- repoName, tag := parsers.ParseRepositoryTag(r.FormValue("t")) |
|
312 |
- if repoName != "" { |
|
313 |
- if err := registry.ValidateRepositoryName(repoName); err != nil { |
|
314 |
- return errf(err) |
|
315 |
- } |
|
316 |
- if len(tag) > 0 { |
|
317 |
- if err := tags.ValidateTagName(tag); err != nil { |
|
318 |
- return errf(err) |
|
319 |
- } |
|
320 |
- } |
|
311 |
+ repoAndTags, err := sanitizeRepoAndTags(r.Form["t"]) |
|
312 |
+ if err != nil { |
|
313 |
+ return errf(err) |
|
321 | 314 |
} |
322 | 315 |
|
323 | 316 |
buildConfig.DockerfileName = r.FormValue("dockerfile") |
... | ... |
@@ -369,7 +362,6 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R |
369 | 369 |
var ( |
370 | 370 |
context builder.ModifiableContext |
371 | 371 |
dockerfileName string |
372 |
- err error |
|
373 | 372 |
) |
374 | 373 |
context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, pReader) |
375 | 374 |
if err != nil { |
... | ... |
@@ -418,8 +410,8 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R |
418 | 418 |
return errf(err) |
419 | 419 |
} |
420 | 420 |
|
421 |
- if repoName != "" { |
|
422 |
- if err := s.daemon.TagImage(repoName, tag, string(imgID), true); err != nil { |
|
421 |
+ for _, rt := range repoAndTags { |
|
422 |
+ if err := s.daemon.TagImage(rt.repo, rt.tag, string(imgID), true); err != nil { |
|
423 | 423 |
return errf(err) |
424 | 424 |
} |
425 | 425 |
} |
... | ... |
@@ -427,6 +419,48 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R |
427 | 427 |
return nil |
428 | 428 |
} |
429 | 429 |
|
430 |
+// repoAndTag is a helper struct for holding the parsed repositories and tags of |
|
431 |
+// the input "t" argument. |
|
432 |
+type repoAndTag struct { |
|
433 |
+ repo, tag string |
|
434 |
+} |
|
435 |
+ |
|
436 |
+// sanitizeRepoAndTags parses the raw "t" parameter received from the client |
|
437 |
+// to a slice of repoAndTag. |
|
438 |
+// It also validates each repoName and tag. |
|
439 |
+func sanitizeRepoAndTags(names []string) ([]repoAndTag, error) { |
|
440 |
+ var ( |
|
441 |
+ repoAndTags []repoAndTag |
|
442 |
+ // This map is used for deduplicating the "-t" paramter. |
|
443 |
+ uniqNames = make(map[string]struct{}) |
|
444 |
+ ) |
|
445 |
+ for _, repo := range names { |
|
446 |
+ name, tag := parsers.ParseRepositoryTag(repo) |
|
447 |
+ if name == "" { |
|
448 |
+ continue |
|
449 |
+ } |
|
450 |
+ |
|
451 |
+ if err := registry.ValidateRepositoryName(name); err != nil { |
|
452 |
+ return nil, err |
|
453 |
+ } |
|
454 |
+ |
|
455 |
+ nameWithTag := name |
|
456 |
+ if len(tag) > 0 { |
|
457 |
+ if err := tags.ValidateTagName(tag); err != nil { |
|
458 |
+ return nil, err |
|
459 |
+ } |
|
460 |
+ nameWithTag += ":" + tag |
|
461 |
+ } else { |
|
462 |
+ nameWithTag += ":" + tags.DefaultTag |
|
463 |
+ } |
|
464 |
+ if _, exists := uniqNames[nameWithTag]; !exists { |
|
465 |
+ uniqNames[nameWithTag] = struct{}{} |
|
466 |
+ repoAndTags = append(repoAndTags, repoAndTag{repo: name, tag: tag}) |
|
467 |
+ } |
|
468 |
+ } |
|
469 |
+ return repoAndTags, nil |
|
470 |
+} |
|
471 |
+ |
|
430 | 472 |
func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
431 | 473 |
if err := httputils.ParseForm(r); err != nil { |
432 | 474 |
return err |
... | ... |
@@ -1386,8 +1386,9 @@ Query Parameters: |
1386 | 1386 |
|
1387 | 1387 |
- **dockerfile** - Path within the build context to the Dockerfile. This is |
1388 | 1388 |
ignored if `remote` is specified and points to an individual filename. |
1389 |
-- **t** – A repository name (and optionally a tag) to apply to |
|
1390 |
- the resulting image in case of success. |
|
1389 |
+- **t** – A name and optional tag to apply to the image in the `name:tag` format. |
|
1390 |
+ If you omit the `tag` the default `latest` value is assumed. |
|
1391 |
+ You can provide one or more `t` parameters. |
|
1391 | 1392 |
- **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the |
1392 | 1393 |
URI specifies a filename, the file's contents are placed into a file |
1393 | 1394 |
called `Dockerfile`. |
... | ... |
@@ -1386,8 +1386,9 @@ Query Parameters: |
1386 | 1386 |
|
1387 | 1387 |
- **dockerfile** - Path within the build context to the Dockerfile. This is |
1388 | 1388 |
ignored if `remote` is specified and points to an individual filename. |
1389 |
-- **t** – A repository name (and optionally a tag) to apply to |
|
1390 |
- the resulting image in case of success. |
|
1389 |
+- **t** – A name and optional tag to apply to the image in the `name:tag` format. |
|
1390 |
+ If you omit the `tag` the default `latest` value is assumed. |
|
1391 |
+ You can provide one or more `t` parameters. |
|
1391 | 1392 |
- **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the |
1392 | 1393 |
URI specifies a filename, the file's contents are placed into a file |
1393 | 1394 |
called `Dockerfile`. |
... | ... |
@@ -62,6 +62,11 @@ the build succeeds: |
62 | 62 |
|
63 | 63 |
$ docker build -t shykes/myapp . |
64 | 64 |
|
65 |
+To tag the image into multiple repositories after the build, |
|
66 |
+add multiple `-t` parameters when you run the `build` command: |
|
67 |
+ |
|
68 |
+ $ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest . |
|
69 |
+ |
|
65 | 70 |
The Docker daemon runs the instructions in the `Dockerfile` one-by-one, |
66 | 71 |
committing the result of each instruction |
67 | 72 |
to a new image if necessary, before finally outputting the ID of your |
... | ... |
@@ -31,7 +31,7 @@ parent = "smn_cli" |
31 | 31 |
--pull=false Always attempt to pull a newer version of the image |
32 | 32 |
-q, --quiet=false Suppress the verbose output generated by the containers |
33 | 33 |
--rm=true Remove intermediate containers after a successful build |
34 |
- -t, --tag="" Repository name (and optionally a tag) for the image |
|
34 |
+ -t, --tag=[] Name and optionally a tag in the 'name:tag' format |
|
35 | 35 |
--ulimit=[] Ulimit options |
36 | 36 |
|
37 | 37 |
Builds Docker images from a Dockerfile and a "context". A build's context is |
... | ... |
@@ -227,6 +227,14 @@ uploaded context. The builder reference contains detailed information on |
227 | 227 |
This will build like the previous example, but it will then tag the resulting |
228 | 228 |
image. The repository name will be `vieux/apache` and the tag will be `2.0` |
229 | 229 |
|
230 |
+You can apply multiple tags to an image. For example, you can apply the `latest` |
|
231 |
+tag to a newly built image and add another tag that references a specific |
|
232 |
+version. |
|
233 |
+For example, to tag an image both as `whenry/fedora-jboss:latest` and |
|
234 |
+`whenry/fedora-jboss:v2.1`, use the following: |
|
235 |
+ |
|
236 |
+ $ docker build -t whenry/fedora-jboss:latest -t whenry/fedora-jboss:v2.1 . |
|
237 |
+ |
|
230 | 238 |
### Specify Dockerfile (-f) |
231 | 239 |
|
232 | 240 |
$ docker build -f Dockerfile.debug . |
... | ... |
@@ -6258,3 +6258,22 @@ func (s *DockerSuite) TestBuildTagEvent(c *check.C) { |
6258 | 6258 |
c.Fatal("The 'tag' event not heard from the server") |
6259 | 6259 |
} |
6260 | 6260 |
} |
6261 |
+ |
|
6262 |
+// #15780 |
|
6263 |
+func (s *DockerSuite) TestBuildMultipleTags(c *check.C) { |
|
6264 |
+ dockerfile := ` |
|
6265 |
+ FROM busybox |
|
6266 |
+ MAINTAINER test-15780 |
|
6267 |
+ ` |
|
6268 |
+ cmd := exec.Command(dockerBinary, "build", "-t", "tag1", "-t", "tag2:v2", |
|
6269 |
+ "-t", "tag1:latest", "-t", "tag1", "--no-cache", "-") |
|
6270 |
+ cmd.Stdin = strings.NewReader(dockerfile) |
|
6271 |
+ _, err := runCommand(cmd) |
|
6272 |
+ c.Assert(err, check.IsNil) |
|
6273 |
+ |
|
6274 |
+ id1, err := getIDByName("tag1") |
|
6275 |
+ c.Assert(err, check.IsNil) |
|
6276 |
+ id2, err := getIDByName("tag2:v2") |
|
6277 |
+ c.Assert(err, check.IsNil) |
|
6278 |
+ c.Assert(id1, check.Equals, id2) |
|
6279 |
+} |
... | ... |
@@ -1122,7 +1122,6 @@ func buildImageCmd(name, dockerfile string, useCache bool, buildFlags ...string) |
1122 | 1122 |
buildCmd := exec.Command(dockerBinary, args...) |
1123 | 1123 |
buildCmd.Stdin = strings.NewReader(dockerfile) |
1124 | 1124 |
return buildCmd |
1125 |
- |
|
1126 | 1125 |
} |
1127 | 1126 |
|
1128 | 1127 |
func buildImageWithOut(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, error) { |
... | ... |
@@ -16,7 +16,7 @@ docker-build - Build a new image from the source code at PATH |
16 | 16 |
[**--pull**[=*false*]] |
17 | 17 |
[**-q**|**--quiet**[=*false*]] |
18 | 18 |
[**--rm**[=*true*]] |
19 |
-[**-t**|**--tag**[=*TAG*]] |
|
19 |
+[**-t**|**--tag**[=*[]*]] |
|
20 | 20 |
[**-m**|**--memory**[=*MEMORY*]] |
21 | 21 |
[**--memory-swap**[=*MEMORY-SWAP*]] |
22 | 22 |
[**--cpu-period**[=*0*]] |
... | ... |
@@ -82,7 +82,7 @@ set as the **URL**, the repository is cloned locally and then sent as the contex |
82 | 82 |
Remove intermediate containers after a successful build. The default is *true*. |
83 | 83 |
|
84 | 84 |
**-t**, **--tag**="" |
85 |
- Repository name (and optionally a tag) to be applied to the resulting image in case of success |
|
85 |
+ Repository names (and optionally with tags) to be applied to the resulting image in case of success. |
|
86 | 86 |
|
87 | 87 |
**-m**, **--memory**=*MEMORY* |
88 | 88 |
Memory limit |
... | ... |
@@ -235,6 +235,14 @@ If you do not provide a version tag then Docker will assign `latest`: |
235 | 235 |
|
236 | 236 |
When you list the images, the image above will have the tag `latest`. |
237 | 237 |
|
238 |
+You can apply multiple tags to an image. For example, you can apply the `latest` |
|
239 |
+tag to a newly built image and add another tag that references a specific |
|
240 |
+version. |
|
241 |
+For example, to tag an image both as `whenry/fedora-jboss:latest` and |
|
242 |
+`whenry/fedora-jboss:v2.1`, use the following: |
|
243 |
+ |
|
244 |
+ docker build -t whenry/fedora-jboss:latest -t whenry/fedora-jboss:v2.1 . |
|
245 |
+ |
|
238 | 246 |
So renaming an image is arbitrary but consideration should be given to |
239 | 247 |
a useful convention that makes sense for consumers and should also take |
240 | 248 |
into account Docker community conventions. |
... | ... |
@@ -86,7 +86,6 @@ func (opts *ListOpts) Delete(key string) { |
86 | 86 |
|
87 | 87 |
// GetMap returns the content of values in a map in order to avoid |
88 | 88 |
// duplicates. |
89 |
-// FIXME: can we remove this? |
|
90 | 89 |
func (opts *ListOpts) GetMap() map[string]struct{} { |
91 | 90 |
ret := make(map[string]struct{}) |
92 | 91 |
for _, k := range *opts.values { |
... | ... |
@@ -96,7 +95,6 @@ func (opts *ListOpts) GetMap() map[string]struct{} { |
96 | 96 |
} |
97 | 97 |
|
98 | 98 |
// GetAll returns the values of slice. |
99 |
-// FIXME: Can we remove this? |
|
100 | 99 |
func (opts *ListOpts) GetAll() []string { |
101 | 100 |
return (*opts.values) |
102 | 101 |
} |