diff --git a/auth/auth.go b/auth/auth.go index 2e52af88de..97df928b6b 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -41,9 +41,6 @@ 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 INDEXSERVER } diff --git a/buildfile.go b/buildfile.go index 65f7c28c38..a13dc82c55 100644 --- a/buildfile.go +++ b/buildfile.go @@ -52,20 +52,10 @@ func (b *buildFile) CmdFrom(name string) error { image, err := b.runtime.repositories.LookupImage(name) if err != nil { if b.runtime.graph.IsNotExist(err) { - - var tag, remote string - if strings.Contains(name, ":") { - remoteParts := strings.Split(name, ":") - tag = remoteParts[1] - remote = remoteParts[0] - } else { - remote = name - } - + remote, tag := utils.ParseRepositoryTag(name) if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil { return err } - image, err = b.runtime.repositories.LookupImage(name) if err != nil { return err diff --git a/commands.go b/commands.go index 1fe816d835..feab558259 100644 --- a/commands.go +++ b/commands.go @@ -766,12 +766,8 @@ func (cli *DockerCli) CmdPull(args ...string) error { return nil } - remote := cmd.Arg(0) - if strings.Contains(remote, ":") { - remoteParts := strings.Split(remote, ":") - tag = &remoteParts[1] - remote = remoteParts[0] - } + remote, parsedTag := utils.ParseRepositoryTag(cmd.Arg(0)) + *tag = parsedTag v := url.Values{} v.Set("fromImage", remote) @@ -1246,7 +1242,9 @@ func (cli *DockerCli) CmdRun(args ...string) error { //if image not found try to pull it if statusCode == 404 { v := url.Values{} - v.Set("fromImage", config.Image) + repos, tag := utils.ParseRepositoryTag(config.Image) + v.Set("fromImage", repos) + v.Set("tag", tag) err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err) if err != nil { return err diff --git a/registry/registry.go b/registry/registry.go index c458f616f8..fc84f19ec4 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -18,8 +18,14 @@ import ( ) var ErrAlreadyExists = errors.New("Image already exists") +var ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") func pingRegistryEndpoint(endpoint string) error { + if endpoint == auth.IndexServerAddress() { + // Skip the check, we now this one is valid + // (and we never want to fallback to http in case of error) + return nil + } resp, err := http.Get(endpoint + "_ping") if err != nil { return err @@ -56,19 +62,29 @@ func validateRepositoryName(repositoryName string) error { // Resolves a repository name to a endpoint + name func ResolveRepositoryName(reposName string) (string, string, error) { + if strings.Contains(reposName, "://") { + // It cannot contain a scheme! + return "", "", ErrInvalidRepositoryName + } nameParts := strings.SplitN(reposName, "/", 2) - if !strings.Contains(nameParts[0], ".") { + if !strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") { // This is a Docker Index repos (ex: samalba/hipache or ubuntu) err := validateRepositoryName(reposName) - return "https://index.docker.io/v1/", reposName, err + return auth.IndexServerAddress(), 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 "", "", fmt.Errorf("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") + return "", "", ErrInvalidRepositoryName } hostname := nameParts[0] reposName = nameParts[1] + if strings.Contains(hostname, "index.docker.io") { + return "", "", fmt.Errorf("Invalid repository name, try \"%s\" instead", reposName) + } + if err := validateRepositoryName(reposName); err != nil { + return "", "", err + } endpoint := fmt.Sprintf("https://%s/v1/", hostname) if err := pingRegistryEndpoint(endpoint); err != nil { utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err) diff --git a/server.go b/server.go index 71bcd29ec4..f1c0909516 100644 --- a/server.go +++ b/server.go @@ -351,10 +351,10 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return nil } -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)) +func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag, indexEp string, sf *utils.StreamFormatter) error { + out.Write(sf.FormatStatus("Pulling repository %s", localName)) - repoData, err := r.GetRepositoryData(indexEp, name) + repoData, err := r.GetRepositoryData(indexEp, remoteName) if err != nil { return err } @@ -366,7 +366,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, ask } utils.Debugf("Retrieving the tag list") - tagsList, err := r.GetRemoteTags(repoData.Endpoints, name, repoData.Tokens) + tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens) if err != nil { utils.Debugf("%v", err) return err @@ -390,7 +390,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, ask // 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, name) + return fmt.Errorf("Tag %s not found in repository %s", askedTag, localName) } repoData.ImgList[id].Tag = askedTag } @@ -405,7 +405,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, ask 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, name)) + out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, localName)) success := false for _, ep := range repoData.Endpoints { if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { @@ -423,7 +423,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, ask if askedTag != "" && tag != askedTag { continue } - if err := srv.runtime.repositories.Set(name, tag, id, true); err != nil { + if err := srv.runtime.repositories.Set(localName, tag, id, true); err != nil { return err } } @@ -469,27 +469,31 @@ func (srv *Server) poolRemove(kind, key string) error { return nil } -func (srv *Server) ImagePull(name string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { +func (srv *Server) ImagePull(localName 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 } - if err := srv.poolAdd("pull", name+":"+tag); err != nil { + if err := srv.poolAdd("pull", localName+":"+tag); err != nil { return err } - defer srv.poolRemove("pull", name+":"+tag) + defer srv.poolRemove("pull", localName+":"+tag) // Resolve the Repository name from fqn to endpoint + name - var endpoint string - endpoint, name, err = registry.ResolveRepositoryName(name) + endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { return err } + if endpoint == auth.IndexServerAddress() { + // If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar" + localName = remoteName + } + out = utils.NewWriteFlusher(out) - err = srv.pullRepository(r, out, name, tag, endpoint, sf) + err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf) if err != nil { - if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil { + if err := srv.pullImage(r, out, remoteName, endpoint, nil, sf); err != nil { return err } return nil @@ -564,7 +568,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 string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error { +func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName 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) @@ -572,41 +576,36 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri return err } out.Write(sf.FormatStatus("Sending image list")) - srvName := name - parts := strings.Split(name, "/") - if len(parts) > 2 { - srvName = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/"))) - } var repoData *registry.RepositoryData - repoData, err = r.PushImageJSONIndex(indexEp, name, imgList, false, nil) + repoData, err = r.PushImageJSONIndex(indexEp, remoteName, imgList, false, nil) if err != nil { return err } for _, ep := range repoData.Endpoints { - out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo))) + out.Write(sf.FormatStatus("Pushing repository %s (%d tags)", localName, 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)) + out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID)) continue } else if r.LookupRemoteImage(elem.ID, ep, repoData.Tokens) { - fmt.Fprintf(out, "Image %s already on registry, skipping\n", name) + out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID)) continue } - if err := srv.pushImage(r, out, name, elem.ID, ep, repoData.Tokens, sf); err != nil { + if err := srv.pushImage(r, out, remoteName, 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)) - if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { + out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) + if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { return err } } } - if _, err := r.PushImageJSONIndex(indexEp, name, imgList, true, repoData.Endpoints); err != nil { + if _, err := r.PushImageJSONIndex(indexEp, remoteName, imgList, true, repoData.Endpoints); err != nil { return err } @@ -634,7 +633,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, // Send the json if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil { if err == registry.ErrAlreadyExists { - out.Write(sf.FormatStatus("Image %s already uploaded ; skipping", imgData.ID)) + out.Write(sf.FormatStatus("Image %s already pushed, skipping", imgData.ID)) return nil } return err @@ -674,30 +673,31 @@ 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 string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { - if err := srv.poolAdd("push", name); err != nil { +func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { + if err := srv.poolAdd("push", localName); err != nil { return err } - defer srv.poolRemove("push", name) + defer srv.poolRemove("push", localName) // Resolve the Repository name from fqn to endpoint + name - endpoint, name, err := registry.ResolveRepositoryName(name) + endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { return err } out = utils.NewWriteFlusher(out) - img, err := srv.runtime.graph.Get(name) + img, err := srv.runtime.graph.Get(localName) r, err2 := registry.NewRegistry(srv.runtime.root, authConfig) if err2 != nil { return err2 } if err != nil { - out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name]))) + reposLen := len(srv.runtime.repositories.Repositories[localName]) + out.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[name]; exists { - if err := srv.pushRepository(r, out, name, localRepo, endpoint, sf); err != nil { + if localRepo, exists := srv.runtime.repositories.Repositories[localName]; exists { + if err := srv.pushRepository(r, out, localName, remoteName, localRepo, endpoint, sf); err != nil { return err } return nil @@ -706,8 +706,8 @@ func (srv *Server) ImagePush(name string, out io.Writer, sf *utils.StreamFormatt } 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, token, sf); err != nil { + out.Write(sf.FormatStatus("The push refers to an image: [%s]", localName)) + if err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil { return err } return nil diff --git a/tags.go b/tags.go index d1eb36aa72..9ad9d10d05 100644 --- a/tags.go +++ b/tags.go @@ -70,11 +70,11 @@ func (store *TagStore) LookupImage(name string) (*Image, error) { if err != nil { // FIXME: standardize on returning nil when the image doesn't exist, and err for everything else // (so we can pass all errors here) - repoAndTag := strings.SplitN(name, ":", 2) - if len(repoAndTag) == 1 { - repoAndTag = append(repoAndTag, DEFAULTTAG) + repos, tag := utils.ParseRepositoryTag(name) + if tag == "" { + tag = DEFAULTTAG } - if i, err := store.GetImage(repoAndTag[0], repoAndTag[1]); err != nil { + if i, err := store.GetImage(repos, tag); err != nil { return nil, err } else if i == nil { return nil, fmt.Errorf("Image does not exist: %s", name) @@ -221,9 +221,6 @@ func validateRepoName(name string) error { if name == "" { return fmt.Errorf("Repository name can't be empty") } - if strings.Contains(name, ":") { - return fmt.Errorf("Illegal repository name: %s", name) - } return nil } diff --git a/utils/utils.go b/utils/utils.go index eee6685c8b..df615844a7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -686,3 +686,17 @@ func ParseHost(host string, port int, addr string) string { } return fmt.Sprintf("tcp://%s:%d", host, port) } + +// Get a repos name and returns the right reposName + tag +// The tag can be confusing because of a port in a repository name. +// Ex: localhost.localdomain:5000/samalba/hipache:latest +func ParseRepositoryTag(repos string) (string, string) { + n := strings.LastIndex(repos, ":") + if n < 0 { + return repos, "" + } + if tag := repos[n+1:]; !strings.Contains(tag, "/") { + return repos[:n], tag + } + return repos, "" +}