Merge pull request #15780 from mountkin/build-multi-tags

Add ability to add multiple tags with docker build
This commit is contained in:
Sebastiaan van Stijn 2015-10-23 13:50:35 -07:00
commit 448398c2a8
10 changed files with 119 additions and 40 deletions

View file

@ -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
}
}
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 {

View file

@ -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

View file

@ -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`.

View file

@ -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`.

View file

@ -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

View file

@ -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 .

View file

@ -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)
}

View file

@ -1163,7 +1163,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) {

View file

@ -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.

View file

@ -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)
}