diff --git a/api/client/commands.go b/api/client/commands.go index 49cd07700f..ef9796b747 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1000,7 +1000,7 @@ func (cli *DockerCli) CmdImport(args ...string) error { } func (cli *DockerCli) CmdPush(args ...string) error { - cmd := cli.Subcmd("push", "NAME", "Push an image or a repository to the registry") + cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry") if err := cmd.Parse(args); err != nil { return nil } @@ -1013,8 +1013,10 @@ func (cli *DockerCli) CmdPush(args ...string) error { cli.LoadConfigFile() + remote, tag := utils.ParseRepositoryTag(name) + // Resolve the Repository name from fqn to hostname + name - hostname, _, err := registry.ResolveRepositoryName(name) + hostname, _, err := registry.ResolveRepositoryName(remote) if err != nil { return err } @@ -1033,6 +1035,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { } v := url.Values{} + v.Set("tag", tag) push := func(authConfig registry.AuthConfig) error { buf, err := json.Marshal(authConfig) if err != nil { @@ -1042,7 +1045,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { base64.URLEncoding.EncodeToString(buf), } - return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ + return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ "X-Registry-Auth": registryAuthHeader, }) } diff --git a/api/server/server.go b/api/server/server.go index 18aefe42cd..5597d8b92c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -517,6 +517,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response job := eng.Job("push", vars["name"]) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) + job.Setenv("tag", r.Form.Get("tag")) if version.GreaterThan("1.0") { job.SetenvBool("json", true) streamJSON(job, w, true) diff --git a/server/server.go b/server/server.go index 2cb3328d55..3e97481e0e 100644 --- a/server/server.go +++ b/server/server.go @@ -1401,7 +1401,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { } // Retrieve the all the images to be uploaded in the correct order -func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[string][]string, error) { +func (srv *Server) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) { var ( imageList []string imagesSeen map[string]bool = make(map[string]bool) @@ -1409,6 +1409,9 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[stri ) for tag, id := range localRepo { + if requestedTag != "" && requestedTag != tag { + continue + } var imageListForThisTag []string tagsByImage[id] = append(tagsByImage[id], tag) @@ -1435,25 +1438,29 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[stri // append to main image list imageList = append(imageList, imageListForThisTag...) } - + if len(imageList) == 0 { + return nil, nil, fmt.Errorf("No images found for the requested repository / tag") + } utils.Debugf("Image list: %v", imageList) utils.Debugf("Tags by image: %v", tagsByImage) return imageList, tagsByImage, nil } -func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, sf *utils.StreamFormatter) error { +func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) utils.Debugf("Local repo: %s", localRepo) - imgList, tagsByImage, err := srv.getImageList(localRepo) + imgList, tagsByImage, err := srv.getImageList(localRepo, tag) if err != nil { return err } out.Write(sf.FormatStatus("", "Sending image list")) - var repoData *registry.RepositoryData - var imageIndex []*registry.ImgData + var ( + repoData *registry.RepositoryData + imageIndex []*registry.ImgData + ) for _, imgId := range imgList { if tags, exists := tagsByImage[imgId]; exists { @@ -1488,8 +1495,12 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName return err } + nTag := 1 + if tag == "" { + nTag = len(localRepo) + } for _, ep := range repoData.Endpoints { - out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, len(localRepo))) + out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, nTag)) for _, imgId := range imgList { if r.LookupRemoteImage(imgId, ep, repoData.Tokens) { @@ -1575,6 +1586,7 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { metaHeaders map[string][]string ) + tag := job.Getenv("tag") job.GetenvJson("authConfig", authConfig) job.GetenvJson("metaHeaders", metaHeaders) if _, err := srv.poolAdd("push", localName); err != nil { @@ -1600,11 +1612,14 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { } if err != nil { - reposLen := len(srv.runtime.Repositories().Repositories[localName]) + reposLen := 1 + if tag == "" { + reposLen = len(srv.runtime.Repositories().Repositories[localName]) + } job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen)) // If it fails, try to get the repository if localRepo, exists := srv.runtime.Repositories().Repositories[localName]; exists { - if err := srv.pushRepository(r, job.Stdout, localName, remoteName, localRepo, sf); err != nil { + if err := srv.pushRepository(r, job.Stdout, localName, remoteName, localRepo, tag, sf); err != nil { return job.Error(err) } return engine.StatusOK