瀏覽代碼

Add ability to add multiple tags with docker build

Signed-off-by: Shijiang Wei <mountkin@gmail.com>
Shijiang Wei 9 年之前
父節點
當前提交
c2eb37f9ae

+ 23 - 17
api/client/build.go

@@ -49,7 +49,8 @@ const (
 // Usage: docker build [OPTIONS] PATH | URL | -
 // Usage: docker build [OPTIONS] PATH | URL | -
 func (cli *DockerCli) CmdBuild(args ...string) error {
 func (cli *DockerCli) CmdBuild(args ...string) error {
 	cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, Cli.DockerCommands["build"].Description, true)
 	cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, Cli.DockerCommands["build"].Description, true)
-	tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
+	flTags := opts.NewListOpts(validateTag)
+	cmd.Var(&flTags, []string{"t", "-tag"}, "Name and optionally a tag in the 'name:tag' format")
 	suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
 	suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
 	noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
 	noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
 	rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
 	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 {
 			memorySwap = parsedMemorySwap
 			memorySwap = parsedMemorySwap
 		}
 		}
 	}
 	}
-	// Send the build context
-	v := &url.Values{}
 
 
-	//Check if the given image name can be resolved
-	if *tag != "" {
-		repository, tag := parsers.ParseRepositoryTag(*tag)
-		if err := registry.ValidateRepositoryName(repository); err != nil {
-			return err
-		}
-		if len(tag) > 0 {
-			if err := tags.ValidateTagName(tag); err != nil {
-				return err
-			}
-		}
+	// Send the build context
+	v := url.Values{
+		"t": flTags.GetAll(),
 	}
 	}
-
-	v.Set("t", *tag)
-
 	if *suppressOutput {
 	if *suppressOutput {
 		v.Set("q", "1")
 		v.Set("q", "1")
 	}
 	}
@@ -324,6 +312,24 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 	return nil
 	return nil
 }
 }
 
 
+// validateTag checks if the given image name can be resolved.
+func validateTag(rawRepo string) (string, error) {
+	repository, tag := parsers.ParseRepositoryTag(rawRepo)
+	if err := registry.ValidateRepositoryName(repository); err != nil {
+		return "", err
+	}
+
+	if len(tag) == 0 {
+		return rawRepo, nil
+	}
+
+	if err := tags.ValidateTagName(tag); err != nil {
+		return "", err
+	}
+
+	return rawRepo, nil
+}
+
 // isUNC returns true if the path is UNC (one starting \\). It always returns
 // isUNC returns true if the path is UNC (one starting \\). It always returns
 // false on Linux.
 // false on Linux.
 func isUNC(path string) bool {
 func isUNC(path string) bool {

+ 47 - 13
api/server/router/local/image.go

@@ -308,16 +308,9 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
 		buildConfig.Pull = true
 		buildConfig.Pull = true
 	}
 	}
 
 
-	repoName, tag := parsers.ParseRepositoryTag(r.FormValue("t"))
-	if repoName != "" {
-		if err := registry.ValidateRepositoryName(repoName); err != nil {
-			return errf(err)
-		}
-		if len(tag) > 0 {
-			if err := tags.ValidateTagName(tag); err != nil {
-				return errf(err)
-			}
-		}
+	repoAndTags, err := sanitizeRepoAndTags(r.Form["t"])
+	if err != nil {
+		return errf(err)
 	}
 	}
 
 
 	buildConfig.DockerfileName = r.FormValue("dockerfile")
 	buildConfig.DockerfileName = r.FormValue("dockerfile")
@@ -369,7 +362,6 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
 	var (
 	var (
 		context        builder.ModifiableContext
 		context        builder.ModifiableContext
 		dockerfileName string
 		dockerfileName string
-		err            error
 	)
 	)
 	context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, pReader)
 	context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, pReader)
 	if err != nil {
 	if err != nil {
@@ -418,8 +410,8 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
 		return errf(err)
 		return errf(err)
 	}
 	}
 
 
-	if repoName != "" {
-		if err := s.daemon.TagImage(repoName, tag, string(imgID), true); err != nil {
+	for _, rt := range repoAndTags {
+		if err := s.daemon.TagImage(rt.repo, rt.tag, string(imgID), true); err != nil {
 			return errf(err)
 			return errf(err)
 		}
 		}
 	}
 	}
@@ -427,6 +419,48 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
 	return nil
 	return nil
 }
 }
 
 
+// repoAndTag is a helper struct for holding the parsed repositories and tags of
+// the input "t" argument.
+type repoAndTag struct {
+	repo, tag string
+}
+
+// sanitizeRepoAndTags parses the raw "t" parameter received from the client
+// to a slice of repoAndTag.
+// It also validates each repoName and tag.
+func sanitizeRepoAndTags(names []string) ([]repoAndTag, error) {
+	var (
+		repoAndTags []repoAndTag
+		// This map is used for deduplicating the "-t" paramter.
+		uniqNames = make(map[string]struct{})
+	)
+	for _, repo := range names {
+		name, tag := parsers.ParseRepositoryTag(repo)
+		if name == "" {
+			continue
+		}
+
+		if err := registry.ValidateRepositoryName(name); err != nil {
+			return nil, err
+		}
+
+		nameWithTag := name
+		if len(tag) > 0 {
+			if err := tags.ValidateTagName(tag); err != nil {
+				return nil, err
+			}
+			nameWithTag += ":" + tag
+		} else {
+			nameWithTag += ":" + tags.DefaultTag
+		}
+		if _, exists := uniqNames[nameWithTag]; !exists {
+			uniqNames[nameWithTag] = struct{}{}
+			repoAndTags = append(repoAndTags, repoAndTag{repo: name, tag: tag})
+		}
+	}
+	return repoAndTags, nil
+}
+
 func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := httputils.ParseForm(r); err != nil {
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 		return err

+ 3 - 2
docs/reference/api/docker_remote_api_v1.21.md

@@ -1386,8 +1386,9 @@ Query Parameters:
 
 
 -   **dockerfile** - Path within the build context to the Dockerfile. This is
 -   **dockerfile** - Path within the build context to the Dockerfile. This is
         ignored if `remote` is specified and points to an individual filename.
         ignored if `remote` is specified and points to an individual filename.
--   **t** – A repository name (and optionally a tag) to apply to
-        the resulting image in case of success.
+-   **t** – A name and optional tag to apply to the image in the `name:tag` format.
+        If you omit the `tag` the default `latest` value is assumed.
+        You can provide one or more `t` parameters.
 -   **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the 
 -   **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the 
         URI specifies a filename, the file's contents are placed into a file 
         URI specifies a filename, the file's contents are placed into a file 
 		called `Dockerfile`.
 		called `Dockerfile`.

+ 3 - 2
docs/reference/api/docker_remote_api_v1.22.md

@@ -1386,8 +1386,9 @@ Query Parameters:
 
 
 -   **dockerfile** - Path within the build context to the Dockerfile. This is
 -   **dockerfile** - Path within the build context to the Dockerfile. This is
         ignored if `remote` is specified and points to an individual filename.
         ignored if `remote` is specified and points to an individual filename.
--   **t** – A repository name (and optionally a tag) to apply to
-        the resulting image in case of success.
+-   **t** – A name and optional tag to apply to the image in the `name:tag` format.
+        If you omit the `tag` the default `latest` value is assumed.
+        You can provide one or more `t` parameters.
 -   **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the 
 -   **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the 
         URI specifies a filename, the file's contents are placed into a file 
         URI specifies a filename, the file's contents are placed into a file 
 		called `Dockerfile`.
 		called `Dockerfile`.

+ 5 - 0
docs/reference/builder.md

@@ -62,6 +62,11 @@ the build succeeds:
 
 
     $ docker build -t shykes/myapp .
     $ docker build -t shykes/myapp .
 
 
+To tag the image into multiple repositories after the build,
+add multiple `-t` parameters when you run the `build` command:
+
+    $ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
+
 The Docker daemon runs the instructions in the `Dockerfile` one-by-one,
 The Docker daemon runs the instructions in the `Dockerfile` one-by-one,
 committing the result of each instruction
 committing the result of each instruction
 to a new image if necessary, before finally outputting the ID of your
 to a new image if necessary, before finally outputting the ID of your

+ 9 - 1
docs/reference/commandline/build.md

@@ -31,7 +31,7 @@ parent = "smn_cli"
       --pull=false                    Always attempt to pull a newer version of the image
       --pull=false                    Always attempt to pull a newer version of the image
       -q, --quiet=false               Suppress the verbose output generated by the containers
       -q, --quiet=false               Suppress the verbose output generated by the containers
       --rm=true                       Remove intermediate containers after a successful build
       --rm=true                       Remove intermediate containers after a successful build
-      -t, --tag=""                    Repository name (and optionally a tag) for the image
+      -t, --tag=[]                    Name and optionally a tag in the 'name:tag' format
       --ulimit=[]                     Ulimit options
       --ulimit=[]                     Ulimit options
 
 
 Builds Docker images from a Dockerfile and a "context". A build's context is
 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
 This will build like the previous example, but it will then tag the resulting
 This will build like the previous example, but it will then tag the resulting
 image. The repository name will be `vieux/apache` and the tag will be `2.0`
 image. The repository name will be `vieux/apache` and the tag will be `2.0`
 
 
+You can apply multiple tags to an image. For example, you can apply the `latest`
+tag to a newly built image and add another tag that references a specific
+version.
+For example, to tag an image both as `whenry/fedora-jboss:latest` and
+`whenry/fedora-jboss:v2.1`, use the following:
+
+    $ docker build -t whenry/fedora-jboss:latest -t whenry/fedora-jboss:v2.1 .
+
 ### Specify Dockerfile (-f)
 ### Specify Dockerfile (-f)
 
 
     $ docker build -f Dockerfile.debug .
     $ docker build -f Dockerfile.debug .

+ 19 - 0
integration-cli/docker_cli_build_test.go

@@ -6258,3 +6258,22 @@ func (s *DockerSuite) TestBuildTagEvent(c *check.C) {
 		c.Fatal("The 'tag' event not heard from the server")
 		c.Fatal("The 'tag' event not heard from the server")
 	}
 	}
 }
 }
+
+// #15780
+func (s *DockerSuite) TestBuildMultipleTags(c *check.C) {
+	dockerfile := `
+	FROM busybox
+	MAINTAINER test-15780
+	`
+	cmd := exec.Command(dockerBinary, "build", "-t", "tag1", "-t", "tag2:v2",
+		"-t", "tag1:latest", "-t", "tag1", "--no-cache", "-")
+	cmd.Stdin = strings.NewReader(dockerfile)
+	_, err := runCommand(cmd)
+	c.Assert(err, check.IsNil)
+
+	id1, err := getIDByName("tag1")
+	c.Assert(err, check.IsNil)
+	id2, err := getIDByName("tag2:v2")
+	c.Assert(err, check.IsNil)
+	c.Assert(id1, check.Equals, id2)
+}

+ 0 - 1
integration-cli/docker_utils.go

@@ -1122,7 +1122,6 @@ func buildImageCmd(name, dockerfile string, useCache bool, buildFlags ...string)
 	buildCmd := exec.Command(dockerBinary, args...)
 	buildCmd := exec.Command(dockerBinary, args...)
 	buildCmd.Stdin = strings.NewReader(dockerfile)
 	buildCmd.Stdin = strings.NewReader(dockerfile)
 	return buildCmd
 	return buildCmd
-
 }
 }
 
 
 func buildImageWithOut(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, error) {
 func buildImageWithOut(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, error) {

+ 10 - 2
man/docker-build.1.md

@@ -16,7 +16,7 @@ docker-build - Build a new image from the source code at PATH
 [**--pull**[=*false*]]
 [**--pull**[=*false*]]
 [**-q**|**--quiet**[=*false*]]
 [**-q**|**--quiet**[=*false*]]
 [**--rm**[=*true*]]
 [**--rm**[=*true*]]
-[**-t**|**--tag**[=*TAG*]]
+[**-t**|**--tag**[=*[]*]]
 [**-m**|**--memory**[=*MEMORY*]]
 [**-m**|**--memory**[=*MEMORY*]]
 [**--memory-swap**[=*MEMORY-SWAP*]]
 [**--memory-swap**[=*MEMORY-SWAP*]]
 [**--cpu-period**[=*0*]]
 [**--cpu-period**[=*0*]]
@@ -82,7 +82,7 @@ set as the **URL**, the repository is cloned locally and then sent as the contex
    Remove intermediate containers after a successful build. The default is *true*.
    Remove intermediate containers after a successful build. The default is *true*.
 
 
 **-t**, **--tag**=""
 **-t**, **--tag**=""
-   Repository name (and optionally a tag) to be applied to the resulting image in case of success
+   Repository names (and optionally with tags) to be applied to the resulting image in case of success.
 
 
 **-m**, **--memory**=*MEMORY*
 **-m**, **--memory**=*MEMORY*
   Memory limit
   Memory limit
@@ -235,6 +235,14 @@ If you do not provide a version tag then Docker will assign `latest`:
 
 
 When you list the images, the image above will have the tag `latest`.
 When you list the images, the image above will have the tag `latest`.
 
 
+You can apply multiple tags to an image. For example, you can apply the `latest`
+tag to a newly built image and add another tag that references a specific
+version.
+For example, to tag an image both as `whenry/fedora-jboss:latest` and
+`whenry/fedora-jboss:v2.1`, use the following:
+
+    docker build -t whenry/fedora-jboss:latest -t whenry/fedora-jboss:v2.1 .
+
 So renaming an image is arbitrary but consideration should be given to 
 So renaming an image is arbitrary but consideration should be given to 
 a useful convention that makes sense for consumers and should also take
 a useful convention that makes sense for consumers and should also take
 into account Docker community conventions.
 into account Docker community conventions.

+ 0 - 2
opts/opts.go

@@ -86,7 +86,6 @@ func (opts *ListOpts) Delete(key string) {
 
 
 // GetMap returns the content of values in a map in order to avoid
 // GetMap returns the content of values in a map in order to avoid
 // duplicates.
 // duplicates.
-// FIXME: can we remove this?
 func (opts *ListOpts) GetMap() map[string]struct{} {
 func (opts *ListOpts) GetMap() map[string]struct{} {
 	ret := make(map[string]struct{})
 	ret := make(map[string]struct{})
 	for _, k := range *opts.values {
 	for _, k := range *opts.values {
@@ -96,7 +95,6 @@ func (opts *ListOpts) GetMap() map[string]struct{} {
 }
 }
 
 
 // GetAll returns the values of slice.
 // GetAll returns the values of slice.
-// FIXME: Can we remove this?
 func (opts *ListOpts) GetAll() []string {
 func (opts *ListOpts) GetAll() []string {
 	return (*opts.values)
 	return (*opts.values)
 }
 }