Jelajahi Sumber

Add ability to add multiple tags with docker build

Signed-off-by: Shijiang Wei <mountkin@gmail.com>
Shijiang Wei 9 tahun lalu
induk
melakukan
c2eb37f9ae

+ 23 - 17
api/client/build.go

@@ -49,7 +49,8 @@ const (
 // Usage: docker build [OPTIONS] PATH | URL | -
 func (cli *DockerCli) CmdBuild(args ...string) error {
 	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")
 	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")
@@ -207,24 +208,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 			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 {
 		v.Set("q", "1")
 	}
@@ -324,6 +312,24 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 	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
 // false on Linux.
 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
 	}
 
-	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")
@@ -369,7 +362,6 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
 	var (
 		context        builder.ModifiableContext
 		dockerfileName string
-		err            error
 	)
 	context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, pReader)
 	if err != nil {
@@ -418,8 +410,8 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
 		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)
 		}
 	}
@@ -427,6 +419,48 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
 	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 {
 	if err := httputils.ParseForm(r); err != nil {
 		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
         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 
         URI specifies a filename, the file's contents are placed into a file 
 		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
         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 
         URI specifies a filename, the file's contents are placed into a file 
 		called `Dockerfile`.

+ 5 - 0
docs/reference/builder.md

@@ -62,6 +62,11 @@ the build succeeds:
 
     $ 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,
 committing the result of each instruction
 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
       -q, --quiet=false               Suppress the verbose output generated by the containers
       --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
 
 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
 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)
 
     $ 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")
 	}
 }
+
+// #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.Stdin = strings.NewReader(dockerfile)
 	return buildCmd
-
 }
 
 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*]]
 [**-q**|**--quiet**[=*false*]]
 [**--rm**[=*true*]]
-[**-t**|**--tag**[=*TAG*]]
+[**-t**|**--tag**[=*[]*]]
 [**-m**|**--memory**[=*MEMORY*]]
 [**--memory-swap**[=*MEMORY-SWAP*]]
 [**--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*.
 
 **-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*
   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`.
 
+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 
 a useful convention that makes sense for consumers and should also take
 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
 // duplicates.
-// FIXME: can we remove this?
 func (opts *ListOpts) GetMap() map[string]struct{} {
 	ret := make(map[string]struct{})
 	for _, k := range *opts.values {
@@ -96,7 +95,6 @@ func (opts *ListOpts) GetMap() map[string]struct{} {
 }
 
 // GetAll returns the values of slice.
-// FIXME: Can we remove this?
 func (opts *ListOpts) GetAll() []string {
 	return (*opts.values)
 }