From 66a9d06d9fa7a382c6852cf047e1448e0d3e1782 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 5 Jul 2013 12:20:58 -0700 Subject: [PATCH] Adding support for nicer URLs to support standalone registry (+ some registry code cleaning) --- api.go | 6 +- auth/auth.go | 10 +-- buildfile.go | 2 +- commands.go | 35 +++-------- registry/registry.go | 141 +++++++++++++++++++++++++------------------ server.go | 133 ++++++++++++++++------------------------ 6 files changed, 150 insertions(+), 177 deletions(-) diff --git a/api.go b/api.go index 8772994e47..ff428885d7 100644 --- a/api.go +++ b/api.go @@ -342,8 +342,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht } sf := utils.NewStreamFormatter(version > 1.0) if image != "" { //pull - registry := r.Form.Get("registry") - if err := srv.ImagePull(image, tag, registry, w, sf, &auth.AuthConfig{}); err != nil { + if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}); err != nil { if sf.Used() { w.Write(sf.FormatError(err)) return nil @@ -426,7 +425,6 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http if err := parseForm(r); err != nil { return err } - registry := r.Form.Get("registry") if vars == nil { return fmt.Errorf("Missing parameter") @@ -436,7 +434,7 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http w.Header().Set("Content-Type", "application/json") } sf := utils.NewStreamFormatter(version > 1.0) - if err := srv.ImagePush(name, registry, w, sf, authConfig); err != nil { + if err := srv.ImagePush(name, w, sf, authConfig); err != nil { if sf.Used() { w.Write(sf.FormatError(err)) return nil diff --git a/auth/auth.go b/auth/auth.go index 205b9479f5..2e52af88de 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -15,8 +15,8 @@ import ( // Where we store the config file const CONFIGFILE = ".dockercfg" -// the registry server we want to login against -const INDEXSERVER = "https://index.docker.io/v1" +// Only used for user auth + account creation +const INDEXSERVER = "https://index.docker.io/v1/" //const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/" @@ -42,7 +42,7 @@ func NewAuthConfig(username, password, email, rootPath string) *AuthConfig { func IndexServerAddress() string { if os.Getenv("DOCKER_INDEX_URL") != "" { - return os.Getenv("DOCKER_INDEX_URL") + "/v1" + return os.Getenv("DOCKER_INDEX_URL") + "/v1/" } return INDEXSERVER } @@ -132,7 +132,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { // using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status. b := strings.NewReader(string(jsonBody)) - req1, err := http.Post(IndexServerAddress()+"/users/", "application/json; charset=utf-8", b) + req1, err := http.Post(IndexServerAddress()+"users/", "application/json; charset=utf-8", b) if err != nil { return "", fmt.Errorf("Server Error: %s", err) } @@ -152,7 +152,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { "Please check your e-mail for a confirmation link.") } else if reqStatusCode == 400 { if string(reqBody) == "\"Username or email already exists\"" { - req, err := http.NewRequest("GET", IndexServerAddress()+"/users/", nil) + req, err := http.NewRequest("GET", IndexServerAddress()+"users/", nil) req.SetBasicAuth(authConfig.Username, authConfig.Password) resp, err := client.Do(req) if err != nil { diff --git a/buildfile.go b/buildfile.go index cdc2a6b041..570a4eb72c 100644 --- a/buildfile.go +++ b/buildfile.go @@ -61,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error { remote = name } - if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false), nil); err != nil { + if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil { return err } diff --git a/commands.go b/commands.go index 6e1e5e88c2..b85a52a3d3 100644 --- a/commands.go +++ b/commands.go @@ -19,7 +19,6 @@ import ( "os/signal" "path/filepath" "reflect" - "regexp" "strconv" "strings" "syscall" @@ -721,7 +720,6 @@ func (cli *DockerCli) CmdImport(args ...string) error { func (cli *DockerCli) CmdPush(args ...string) error { cmd := Subcmd("push", "[OPTION] NAME", "Push an image or a repository to the registry") - registry := cmd.String("registry", "", "Registry host to push the image to") if err := cmd.Parse(args); err != nil { return nil } @@ -732,28 +730,16 @@ func (cli *DockerCli) CmdPush(args ...string) error { return nil } - if err := cli.checkIfLogged(*registry == "", "push"); err != nil { + if err := cli.checkIfLogged("push"); err != nil { return err } - if *registry == "" { - // If we're not using a custom registry, we know the restrictions - // applied to repository names and can warn the user in advance. - // Custom repositories can have different rules, and we must also - // allow pushing by image ID. - if len(strings.SplitN(name, "/", 2)) == 1 { - return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.authConfig.Username, name) - } - - nameParts := strings.SplitN(name, "/", 2) - validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`) - if !validNamespace.MatchString(nameParts[0]) { - return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", nameParts[0]) - } - validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`) - if !validRepo.MatchString(nameParts[1]) { - return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", nameParts[1]) - } + // If we're not using a custom registry, we know the restrictions + // applied to repository names and can warn the user in advance. + // Custom repositories can have different rules, and we must also + // allow pushing by image ID. + if len(strings.SplitN(name, "/", 2)) == 1 { + return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.authConfig.Username, name) } buf, err := json.Marshal(cli.authConfig) @@ -762,7 +748,6 @@ func (cli *DockerCli) CmdPush(args ...string) error { } v := url.Values{} - v.Set("registry", *registry) if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil { return err } @@ -772,7 +757,6 @@ func (cli *DockerCli) CmdPush(args ...string) error { func (cli *DockerCli) CmdPull(args ...string) error { cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry") tag := cmd.String("t", "", "Download tagged image in repository") - registry := cmd.String("registry", "", "Registry to download from. Necessary if image is pulled by ID") if err := cmd.Parse(args); err != nil { return nil } @@ -792,7 +776,6 @@ func (cli *DockerCli) CmdPull(args ...string) error { v := url.Values{} v.Set("fromImage", remote) v.Set("tag", *tag) - v.Set("registry", *registry) if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out); err != nil { return err @@ -1329,9 +1312,9 @@ func (cli *DockerCli) CmdRun(args ...string) error { return nil } -func (cli *DockerCli) checkIfLogged(condition bool, action string) error { +func (cli *DockerCli) checkIfLogged(action string) error { // If condition AND the login failed - if condition && cli.authConfig.Username == "" { + if cli.authConfig.Username == "" { if err := cli.CmdLogin(""); err != nil { return err } diff --git a/registry/registry.go b/registry/registry.go index 622c09b3f3..e9d7b2b8db 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -12,18 +12,70 @@ import ( "net/http" "net/http/cookiejar" "net/url" + "regexp" "strconv" "strings" ) var ErrAlreadyExists = errors.New("Image already exists") -func UrlScheme() string { - u, err := url.Parse(auth.IndexServerAddress()) +func pingRegistryEndpoint(endpoint string) error { + // FIXME: implement the check to discover if it should be http or https + resp, err := http.Get(endpoint) if err != nil { - return "https" + return err } - return u.Scheme + if resp.Header.Get("X-Docker-Registry-Version") == "" { + return errors.New("This does not look like a Registry server (\"X-Docker-Registry-Version\" header not found in the response)") + } + return nil +} + +func validateRepositoryName(namespace, name string) error { + validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`) + if !validNamespace.MatchString(namespace) { + return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", namespace) + } + validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`) + if !validRepo.MatchString(name) { + return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", name) + } + return nil +} + +// Resolves a repository name to a endpoint + name +func ResolveRepositoryName(reposName string) (string, string, error) { + nameParts := strings.SplitN(reposName, "/", 2) + if !strings.Contains(nameParts[0], ".") { + // This is a Docker Index repos (ex: samalba/hipache or ubuntu) + var err error + if len(nameParts) < 2 { + err = validateRepositoryName("library", nameParts[0]) + } else { + err = validateRepositoryName(nameParts[0], nameParts[1]) + } + return "https://index.docker.io/v1/", reposName, err + } + if len(nameParts) < 2 { + // There is a dot in repos name (and no registry address) + // Is it a Registry address without repos name? + return "", "", errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") + } + n := strings.LastIndex(reposName, "/") + hostname := nameParts[0] + path := reposName[len(nameParts[0]):n] + reposName = reposName[n+1:] + endpoint := fmt.Sprintf("https://%s%s/v1/", hostname, path) + if err := pingRegistryEndpoint(endpoint); err != nil { + utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err) + endpoint = fmt.Sprintf("http://%s%s/v1/", hostname, path) + if err = pingRegistryEndpoint(endpoint); err != nil { + //TODO: triggering highland build can be done there without "failing" + return "", "", errors.New("Invalid Registry endpoint: " + err.Error()) + } + } + err := validateRepositoryName("library", reposName) + return endpoint, reposName, err } func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { @@ -36,7 +88,7 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { // Retrieve the history of a given image from the Registry. // Return a list of the parent's json (requested image included) func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]string, error) { - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/ancestry", nil) + req, err := http.NewRequest("GET", registry+"images/"+imgId+"/ancestry", nil) if err != nil { return nil, err } @@ -67,7 +119,7 @@ func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]s func (r *Registry) LookupRemoteImage(imgId, registry string, token []string) bool { rt := &http.Transport{Proxy: http.ProxyFromEnvironment} - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil) + req, err := http.NewRequest("GET", registry+"images/"+imgId+"/json", nil) if err != nil { return false } @@ -79,44 +131,10 @@ func (r *Registry) LookupRemoteImage(imgId, registry string, token []string) boo return res.StatusCode == 200 } -func (r *Registry) getImagesInRepository(repository string, authConfig *auth.AuthConfig) ([]map[string]string, error) { - u := auth.IndexServerAddress() + "/repositories/" + repository + "/images" - req, err := http.NewRequest("GET", u, nil) - if err != nil { - return nil, err - } - if authConfig != nil && len(authConfig.Username) > 0 { - req.SetBasicAuth(authConfig.Username, authConfig.Password) - } - res, err := r.client.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - // Repository doesn't exist yet - if res.StatusCode == 404 { - return nil, nil - } - - jsonData, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - - imageList := []map[string]string{} - if err := json.Unmarshal(jsonData, &imageList); err != nil { - utils.Debugf("Body: %s (%s)\n", res.Body, u) - return nil, err - } - - return imageList, nil -} - // Retrieve an image from the Registry. func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, int, error) { // Get the JSON - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil) + req, err := http.NewRequest("GET", registry+"images/"+imgId+"/json", nil) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) } @@ -143,7 +161,7 @@ func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([ } func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, error) { - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil) + req, err := http.NewRequest("GET", registry+"images/"+imgId+"/layer", nil) if err != nil { return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } @@ -162,10 +180,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ repository = "library/" + repository } for _, host := range registries { - endpoint := fmt.Sprintf("%s/v1/repositories/%s/tags", host, repository) - if !(strings.HasPrefix(endpoint, "http://") || strings.HasPrefix(endpoint, "https://")) { - endpoint = fmt.Sprintf("%s://%s", UrlScheme(), endpoint) - } + endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository) req, err := r.opaqueRequest("GET", endpoint, nil) if err != nil { return nil, err @@ -198,8 +213,8 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ return nil, fmt.Errorf("Could not reach any registry endpoint") } -func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { - repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images" +func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, error) { + repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote) req, err := r.opaqueRequest("GET", repositoryTarget, nil) if err != nil { @@ -230,8 +245,12 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { } var endpoints []string + var urlScheme = indexEp[:strings.Index(indexEp, ":")] if res.Header.Get("X-Docker-Endpoints") != "" { - endpoints = res.Header["X-Docker-Endpoints"] + // The Registry's URL scheme has to match the Index' + for _, ep := range res.Header["X-Docker-Endpoints"] { + endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep)) + } } else { return nil, fmt.Errorf("Index response didn't contain any endpoints") } @@ -260,9 +279,8 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { // Push a local image to the registry func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error { - registry = registry + "/v1" // FIXME: try json with UTF8 - req, err := http.NewRequest("PUT", registry+"/images/"+imgData.ID+"/json", strings.NewReader(string(jsonRaw))) + req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", strings.NewReader(string(jsonRaw))) if err != nil { return err } @@ -296,8 +314,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis } func (r *Registry) PushImageLayerRegistry(imgId string, layer io.Reader, registry string, token []string) error { - registry = registry + "/v1" - req, err := http.NewRequest("PUT", registry+"/images/"+imgId+"/layer", layer) + req, err := http.NewRequest("PUT", registry+"images/"+imgId+"/layer", layer) if err != nil { return err } @@ -334,9 +351,8 @@ func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.R func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error { // "jsonify" the string revision = "\"" + revision + "\"" - registry = registry + "/v1" - req, err := r.opaqueRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision)) + req, err := r.opaqueRequest("PUT", registry+"repositories/"+remote+"/tags/"+tag, strings.NewReader(revision)) if err != nil { return err } @@ -354,7 +370,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token return nil } -func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { +func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { imgListJSON, err := json.Marshal(imgList) if err != nil { return nil, err @@ -364,9 +380,10 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat suffix = "images" } + u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix) + utils.Debugf("PUT %s", u) utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON) - - req, err := r.opaqueRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON)) + req, err := r.opaqueRequest("PUT", u, bytes.NewReader(imgListJSON)) if err != nil { return nil, err } @@ -404,6 +421,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat } var tokens, endpoints []string + var urlScheme = indexEp[:strings.Index(indexEp, ":")] if !validate { if res.StatusCode != 200 && res.StatusCode != 201 { errBody, err := ioutil.ReadAll(res.Body) @@ -420,7 +438,10 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat } if res.Header.Get("X-Docker-Endpoints") != "" { - endpoints = res.Header["X-Docker-Endpoints"] + // The Registry's URL scheme has to match the Index' + for _, ep := range res.Header["X-Docker-Endpoints"] { + endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep)) + } } else { return nil, fmt.Errorf("Index response didn't contain any endpoints") } @@ -442,7 +463,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat } func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { - u := auth.IndexServerAddress() + "/search?q=" + url.QueryEscape(term) + u := auth.IndexServerAddress() + "search?q=" + url.QueryEscape(term) req, err := http.NewRequest("GET", u, nil) if err != nil { return nil, err diff --git a/server.go b/server.go index cedd06ad7c..04f43870b4 100644 --- a/server.go +++ b/server.go @@ -351,44 +351,32 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin return nil } -func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, remote, askedTag, registryEp string, sf *utils.StreamFormatter) error { - out.Write(sf.FormatStatus("Pulling repository %s from %s", local, auth.IndexServerAddress())) +func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, askedTag, indexEp string, sf *utils.StreamFormatter) error { + out.Write(sf.FormatStatus("Pulling repository %s from %s", name, indexEp)) - var repoData *registry.RepositoryData - var err error - if registryEp == "" { - repoData, err = r.GetRepositoryData(remote) - if err != nil { - return err - } + repoData, err := r.GetRepositoryData(indexEp, name) + if err != nil { + return err + } - utils.Debugf("Updating checksums") - // Reload the json file to make sure not to overwrite faster sums - if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil { - return err - } - } else { - repoData = ®istry.RepositoryData{ - Tokens: []string{}, - ImgList: make(map[string]*registry.ImgData), - Endpoints: []string{registryEp}, - } + utils.Debugf("Updating checksums") + // Reload the json file to make sure not to overwrite faster sums + if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil { + return err } utils.Debugf("Retrieving the tag list") - tagsList, err := r.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens) + tagsList, err := r.GetRemoteTags(repoData.Endpoints, name, repoData.Tokens) if err != nil { utils.Debugf("%v", err) return err } - if registryEp != "" { - for tag, id := range tagsList { - repoData.ImgList[id] = ®istry.ImgData{ - ID: id, - Tag: tag, - Checksum: "", - } + for tag, id := range tagsList { + repoData.ImgList[id] = ®istry.ImgData{ + ID: id, + Tag: tag, + Checksum: "", } } @@ -402,7 +390,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re // Otherwise, check that the tag exists and use only that one id, exists := tagsList[askedTag] if !exists { - return fmt.Errorf("Tag %s not found in repository %s", askedTag, local) + return fmt.Errorf("Tag %s not found in repository %s", askedTag, name) } repoData.ImgList[id].Tag = askedTag } @@ -417,13 +405,10 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID) continue } - out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, remote)) + out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, name)) success := false for _, ep := range repoData.Endpoints { - if !(strings.HasPrefix(ep, "http://") || strings.HasPrefix(ep, "https://")) { - ep = fmt.Sprintf("%s://%s", registry.UrlScheme(), ep) - } - if err := srv.pullImage(r, out, img.ID, ep+"/v1", repoData.Tokens, sf); err != nil { + if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err)) continue } @@ -438,7 +423,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re if askedTag != "" && tag != askedTag { continue } - if err := srv.runtime.repositories.Set(local, tag, id, true); err != nil { + if err := srv.runtime.repositories.Set(name, tag, id, true); err != nil { return err } } @@ -483,7 +468,8 @@ func (srv *Server) poolRemove(kind, key string) error { } return nil } -func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { + +func (srv *Server) ImagePull(name string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { r, err := registry.NewRegistry(srv.runtime.root, authConfig) if err != nil { return err @@ -493,14 +479,16 @@ func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *util } defer srv.poolRemove("pull", name+":"+tag) - remote := name - parts := strings.Split(name, "/") - if len(parts) > 2 { - remote = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/"))) + // Resolve the Repository name from fqn to endpoint + name + var endpoint string + endpoint, name, err = registry.ResolveRepositoryName(name) + if err != nil { + return err } + out = utils.NewWriteFlusher(out) - err = srv.pullRepository(r, out, name, remote, tag, endpoint, sf) - if err != nil && endpoint != "" { + err = srv.pullRepository(r, out, name, tag, endpoint, sf) + if err != nil { if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil { return err } @@ -576,7 +564,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat return imgList, nil } -func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name, registryEp string, localRepo map[string]string, sf *utils.StreamFormatter) error { +func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) out.Write(sf.FormatStatus("Processing checksums")) imgList, err := srv.getImageList(localRepo) @@ -591,60 +579,32 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name, reg } var repoData *registry.RepositoryData - if registryEp == "" { - repoData, err = r.PushImageJSONIndex(name, imgList, false, nil) - if err != nil { - return err - } - } else { - repoData = ®istry.RepositoryData{ - ImgList: make(map[string]*registry.ImgData), - Tokens: []string{}, - Endpoints: []string{registryEp}, - } - tagsList, err := r.GetRemoteTags(repoData.Endpoints, name, repoData.Tokens) - if err != nil && err.Error() != "Repository not found" { - return err - } else if err == nil { - for tag, id := range tagsList { - repoData.ImgList[id] = ®istry.ImgData{ - ID: id, - Tag: tag, - Checksum: "", - } - } - } + repoData, err = r.PushImageJSONIndex(indexEp, name, imgList, false, nil) + if err != nil { + return err } for _, ep := range repoData.Endpoints { - if !(strings.HasPrefix(ep, "http://") || strings.HasPrefix(ep, "https://")) { - ep = fmt.Sprintf("%s://%s", registry.UrlScheme(), ep) - } out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo))) // For each image within the repo, push them for _, elem := range imgList { if _, exists := repoData.ImgList[elem.ID]; exists { out.Write(sf.FormatStatus("Image %s already on registry, skipping", name)) continue - } else if registryEp != "" && r.LookupRemoteImage(elem.ID, registryEp, repoData.Tokens) { - fmt.Fprintf(out, "Image %s already on registry, skipping\n", name) - continue } if err := srv.pushImage(r, out, name, elem.ID, ep, repoData.Tokens, sf); err != nil { // FIXME: Continue on error? return err } - out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/repositories/"+srvName+"/tags/"+elem.Tag)) + out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+srvName+"/tags/"+elem.Tag)) if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { return err } } } - if registryEp == "" { - if _, err := r.PushImageJSONIndex(name, imgList, true, repoData.Endpoints); err != nil { - return err - } + if _, err := r.PushImageJSONIndex(indexEp, name, imgList, true, repoData.Endpoints); err != nil { + return err } return nil @@ -711,12 +671,22 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, } // FIXME: Allow to interupt current push when new push of same image is done. -func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { +func (srv *Server) ImagePush(name string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { if err := srv.poolAdd("push", name); err != nil { return err } defer srv.poolRemove("push", name) + // Resolve the Repository name from fqn to endpoint + name + var ( + endpoint string + e error + ) + endpoint, name, e = registry.ResolveRepositoryName(name) + if e != nil { + return e + } + out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(name) r, err2 := registry.NewRegistry(srv.runtime.root, authConfig) @@ -728,16 +698,17 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.Str out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name]))) // If it fails, try to get the repository if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists { - if err := srv.pushRepository(r, out, name, endpoint, localRepo, sf); err != nil { + if err := srv.pushRepository(r, out, name, localRepo, endpoint, sf); err != nil { return err } return nil } - return err } + + var token []string out.Write(sf.FormatStatus("The push refers to an image: [%s]", name)) - if err := srv.pushImage(r, out, name, img.ID, endpoint, nil, sf); err != nil { + if err := srv.pushImage(r, out, name, img.ID, endpoint, token, sf); err != nil { return err } return nil