Skip to content

Commit

Permalink
Add ability to add multiple tags with docker build
Browse files Browse the repository at this point in the history
Signed-off-by: Shijiang Wei <[email protected]>
  • Loading branch information
mountkin committed Oct 23, 2015
1 parent 967e49b commit c2eb37f
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 40 deletions.
40 changes: 23 additions & 17 deletions api/client/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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 {
Expand Down
60 changes: 47 additions & 13 deletions api/server/router/local/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -418,15 +410,57 @@ 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)
}
}

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
Expand Down
5 changes: 3 additions & 2 deletions docs/reference/api/docker_remote_api_v1.21.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
5 changes: 3 additions & 2 deletions docs/reference/api/docker_remote_api_v1.22.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion docs/reference/commandline/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 .
Expand Down
19 changes: 19 additions & 0 deletions integration-cli/docker_cli_build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
1 change: 0 additions & 1 deletion integration-cli/docker_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
12 changes: 10 additions & 2 deletions man/docker-build.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*]]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 0 additions & 2 deletions opts/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down

0 comments on commit c2eb37f

Please sign in to comment.