mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Adding support for nicer URLs to support standalone registry (+ some registry code cleaning)
This commit is contained in:
parent
c9e1c65c64
commit
66a9d06d9f
6 changed files with 150 additions and 177 deletions
6
api.go
6
api.go
|
@ -342,8 +342,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
|
||||||
}
|
}
|
||||||
sf := utils.NewStreamFormatter(version > 1.0)
|
sf := utils.NewStreamFormatter(version > 1.0)
|
||||||
if image != "" { //pull
|
if image != "" { //pull
|
||||||
registry := r.Form.Get("registry")
|
if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}); err != nil {
|
||||||
if err := srv.ImagePull(image, tag, registry, w, sf, &auth.AuthConfig{}); err != nil {
|
|
||||||
if sf.Used() {
|
if sf.Used() {
|
||||||
w.Write(sf.FormatError(err))
|
w.Write(sf.FormatError(err))
|
||||||
return nil
|
return nil
|
||||||
|
@ -426,7 +425,6 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
|
||||||
if err := parseForm(r); err != nil {
|
if err := parseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
registry := r.Form.Get("registry")
|
|
||||||
|
|
||||||
if vars == nil {
|
if vars == nil {
|
||||||
return fmt.Errorf("Missing parameter")
|
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")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
}
|
}
|
||||||
sf := utils.NewStreamFormatter(version > 1.0)
|
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() {
|
if sf.Used() {
|
||||||
w.Write(sf.FormatError(err))
|
w.Write(sf.FormatError(err))
|
||||||
return nil
|
return nil
|
||||||
|
|
10
auth/auth.go
10
auth/auth.go
|
@ -15,8 +15,8 @@ import (
|
||||||
// Where we store the config file
|
// Where we store the config file
|
||||||
const CONFIGFILE = ".dockercfg"
|
const CONFIGFILE = ".dockercfg"
|
||||||
|
|
||||||
// the registry server we want to login against
|
// Only used for user auth + account creation
|
||||||
const INDEXSERVER = "https://index.docker.io/v1"
|
const INDEXSERVER = "https://index.docker.io/v1/"
|
||||||
|
|
||||||
//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
|
//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
|
||||||
|
|
||||||
func IndexServerAddress() string {
|
func IndexServerAddress() string {
|
||||||
if os.Getenv("DOCKER_INDEX_URL") != "" {
|
if os.Getenv("DOCKER_INDEX_URL") != "" {
|
||||||
return os.Getenv("DOCKER_INDEX_URL") + "/v1"
|
return os.Getenv("DOCKER_INDEX_URL") + "/v1/"
|
||||||
}
|
}
|
||||||
return INDEXSERVER
|
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.
|
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
|
||||||
b := strings.NewReader(string(jsonBody))
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Server Error: %s", err)
|
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.")
|
"Please check your e-mail for a confirmation link.")
|
||||||
} else if reqStatusCode == 400 {
|
} else if reqStatusCode == 400 {
|
||||||
if string(reqBody) == "\"Username or email already exists\"" {
|
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)
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -61,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||||
remote = name
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
35
commands.go
35
commands.go
|
@ -19,7 +19,6 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -721,7 +720,6 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
||||||
|
|
||||||
func (cli *DockerCli) CmdPush(args ...string) error {
|
func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
cmd := Subcmd("push", "[OPTION] NAME", "Push an image or a repository to the registry")
|
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 {
|
if err := cmd.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -732,28 +730,16 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cli.checkIfLogged(*registry == "", "push"); err != nil {
|
if err := cli.checkIfLogged("push"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if *registry == "" {
|
// If we're not using a custom registry, we know the restrictions
|
||||||
// If we're not using a custom registry, we know the restrictions
|
// applied to repository names and can warn the user in advance.
|
||||||
// applied to repository names and can warn the user in advance.
|
// Custom repositories can have different rules, and we must also
|
||||||
// Custom repositories can have different rules, and we must also
|
// allow pushing by image ID.
|
||||||
// allow pushing by image ID.
|
if len(strings.SplitN(name, "/", 2)) == 1 {
|
||||||
if len(strings.SplitN(name, "/", 2)) == 1 {
|
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.authConfig.Username, name)
|
||||||
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (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])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := json.Marshal(cli.authConfig)
|
buf, err := json.Marshal(cli.authConfig)
|
||||||
|
@ -762,7 +748,6 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("registry", *registry)
|
|
||||||
if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil {
|
if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -772,7 +757,6 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||||
cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
|
cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
|
||||||
tag := cmd.String("t", "", "Download tagged image in repository")
|
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 {
|
if err := cmd.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -792,7 +776,6 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("fromImage", remote)
|
v.Set("fromImage", remote)
|
||||||
v.Set("tag", *tag)
|
v.Set("tag", *tag)
|
||||||
v.Set("registry", *registry)
|
|
||||||
|
|
||||||
if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out); err != nil {
|
if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1329,9 +1312,9 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
return nil
|
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 AND the login failed
|
||||||
if condition && cli.authConfig.Username == "" {
|
if cli.authConfig.Username == "" {
|
||||||
if err := cli.CmdLogin(""); err != nil {
|
if err := cli.CmdLogin(""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,18 +12,70 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrAlreadyExists = errors.New("Image already exists")
|
var ErrAlreadyExists = errors.New("Image already exists")
|
||||||
|
|
||||||
func UrlScheme() string {
|
func pingRegistryEndpoint(endpoint string) error {
|
||||||
u, err := url.Parse(auth.IndexServerAddress())
|
// FIXME: implement the check to discover if it should be http or https
|
||||||
|
resp, err := http.Get(endpoint)
|
||||||
if err != nil {
|
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) {
|
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.
|
// Retrieve the history of a given image from the Registry.
|
||||||
// Return a list of the parent's json (requested image included)
|
// Return a list of the parent's json (requested image included)
|
||||||
func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]string, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
func (r *Registry) LookupRemoteImage(imgId, registry string, token []string) bool {
|
||||||
rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
|
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 {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -79,44 +131,10 @@ func (r *Registry) LookupRemoteImage(imgId, registry string, token []string) boo
|
||||||
return res.StatusCode == 200
|
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.
|
// Retrieve an image from the Registry.
|
||||||
func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, int, error) {
|
func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, int, error) {
|
||||||
// Get the JSON
|
// 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 {
|
if err != nil {
|
||||||
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
|
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
|
repository = "library/" + repository
|
||||||
}
|
}
|
||||||
for _, host := range registries {
|
for _, host := range registries {
|
||||||
endpoint := fmt.Sprintf("%s/v1/repositories/%s/tags", host, repository)
|
endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
|
||||||
if !(strings.HasPrefix(endpoint, "http://") || strings.HasPrefix(endpoint, "https://")) {
|
|
||||||
endpoint = fmt.Sprintf("%s://%s", UrlScheme(), endpoint)
|
|
||||||
}
|
|
||||||
req, err := r.opaqueRequest("GET", endpoint, nil)
|
req, err := r.opaqueRequest("GET", endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
return nil, fmt.Errorf("Could not reach any registry endpoint")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
|
func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, error) {
|
||||||
repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images"
|
repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote)
|
||||||
|
|
||||||
req, err := r.opaqueRequest("GET", repositoryTarget, nil)
|
req, err := r.opaqueRequest("GET", repositoryTarget, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -230,8 +245,12 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var endpoints []string
|
var endpoints []string
|
||||||
|
var urlScheme = indexEp[:strings.Index(indexEp, ":")]
|
||||||
if res.Header.Get("X-Docker-Endpoints") != "" {
|
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 {
|
} else {
|
||||||
return nil, fmt.Errorf("Index response didn't contain any endpoints")
|
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
|
// Push a local image to the registry
|
||||||
func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
|
func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
|
||||||
registry = registry + "/v1"
|
|
||||||
// FIXME: try json with UTF8
|
// 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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
|
||||||
// "jsonify" the string
|
// "jsonify" the string
|
||||||
revision = "\"" + revision + "\""
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -354,7 +370,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
|
||||||
return nil
|
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)
|
imgListJSON, err := json.Marshal(imgList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -364,9 +380,10 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
|
||||||
suffix = "images"
|
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)
|
utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON)
|
||||||
|
req, err := r.opaqueRequest("PUT", u, bytes.NewReader(imgListJSON))
|
||||||
req, err := r.opaqueRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -404,6 +421,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokens, endpoints []string
|
var tokens, endpoints []string
|
||||||
|
var urlScheme = indexEp[:strings.Index(indexEp, ":")]
|
||||||
if !validate {
|
if !validate {
|
||||||
if res.StatusCode != 200 && res.StatusCode != 201 {
|
if res.StatusCode != 200 && res.StatusCode != 201 {
|
||||||
errBody, err := ioutil.ReadAll(res.Body)
|
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") != "" {
|
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 {
|
} else {
|
||||||
return nil, fmt.Errorf("Index response didn't contain any endpoints")
|
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) {
|
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)
|
req, err := http.NewRequest("GET", u, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
133
server.go
133
server.go
|
@ -351,44 +351,32 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, remote, askedTag, registryEp string, sf *utils.StreamFormatter) error {
|
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", local, auth.IndexServerAddress()))
|
out.Write(sf.FormatStatus("Pulling repository %s from %s", name, indexEp))
|
||||||
|
|
||||||
var repoData *registry.RepositoryData
|
repoData, err := r.GetRepositoryData(indexEp, name)
|
||||||
var err error
|
if err != nil {
|
||||||
if registryEp == "" {
|
return err
|
||||||
repoData, err = r.GetRepositoryData(remote)
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.Debugf("Updating checksums")
|
utils.Debugf("Updating checksums")
|
||||||
// Reload the json file to make sure not to overwrite faster sums
|
// Reload the json file to make sure not to overwrite faster sums
|
||||||
if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil {
|
if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
} else {
|
|
||||||
repoData = ®istry.RepositoryData{
|
|
||||||
Tokens: []string{},
|
|
||||||
ImgList: make(map[string]*registry.ImgData),
|
|
||||||
Endpoints: []string{registryEp},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Debugf("Retrieving the tag list")
|
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 {
|
if err != nil {
|
||||||
utils.Debugf("%v", err)
|
utils.Debugf("%v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if registryEp != "" {
|
for tag, id := range tagsList {
|
||||||
for tag, id := range tagsList {
|
repoData.ImgList[id] = ®istry.ImgData{
|
||||||
repoData.ImgList[id] = ®istry.ImgData{
|
ID: id,
|
||||||
ID: id,
|
Tag: tag,
|
||||||
Tag: tag,
|
Checksum: "",
|
||||||
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
|
// Otherwise, check that the tag exists and use only that one
|
||||||
id, exists := tagsList[askedTag]
|
id, exists := tagsList[askedTag]
|
||||||
if !exists {
|
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
|
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)
|
utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
|
||||||
continue
|
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
|
success := false
|
||||||
for _, ep := range repoData.Endpoints {
|
for _, ep := range repoData.Endpoints {
|
||||||
if !(strings.HasPrefix(ep, "http://") || strings.HasPrefix(ep, "https://")) {
|
if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
|
||||||
ep = fmt.Sprintf("%s://%s", registry.UrlScheme(), ep)
|
|
||||||
}
|
|
||||||
if err := srv.pullImage(r, out, img.ID, ep+"/v1", repoData.Tokens, sf); err != nil {
|
|
||||||
out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
|
out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -438,7 +423,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
|
||||||
if askedTag != "" && tag != askedTag {
|
if askedTag != "" && tag != askedTag {
|
||||||
continue
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -483,7 +468,8 @@ func (srv *Server) poolRemove(kind, key string) error {
|
||||||
}
|
}
|
||||||
return nil
|
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)
|
r, err := registry.NewRegistry(srv.runtime.root, authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
defer srv.poolRemove("pull", name+":"+tag)
|
||||||
|
|
||||||
remote := name
|
// Resolve the Repository name from fqn to endpoint + name
|
||||||
parts := strings.Split(name, "/")
|
var endpoint string
|
||||||
if len(parts) > 2 {
|
endpoint, name, err = registry.ResolveRepositoryName(name)
|
||||||
remote = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
out = utils.NewWriteFlusher(out)
|
out = utils.NewWriteFlusher(out)
|
||||||
err = srv.pullRepository(r, out, name, remote, tag, endpoint, sf)
|
err = srv.pullRepository(r, out, name, tag, endpoint, sf)
|
||||||
if err != nil && endpoint != "" {
|
if err != nil {
|
||||||
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
|
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -576,7 +564,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
|
||||||
return imgList, nil
|
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 = utils.NewWriteFlusher(out)
|
||||||
out.Write(sf.FormatStatus("Processing checksums"))
|
out.Write(sf.FormatStatus("Processing checksums"))
|
||||||
imgList, err := srv.getImageList(localRepo)
|
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
|
var repoData *registry.RepositoryData
|
||||||
if registryEp == "" {
|
repoData, err = r.PushImageJSONIndex(indexEp, name, imgList, false, nil)
|
||||||
repoData, err = r.PushImageJSONIndex(name, imgList, false, nil)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
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: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ep := range repoData.Endpoints {
|
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)))
|
out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo)))
|
||||||
// For each image within the repo, push them
|
// For each image within the repo, push them
|
||||||
for _, elem := range imgList {
|
for _, elem := range imgList {
|
||||||
if _, exists := repoData.ImgList[elem.ID]; exists {
|
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 on registry, skipping", name))
|
||||||
continue
|
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 {
|
if err := srv.pushImage(r, out, name, elem.ID, ep, repoData.Tokens, sf); err != nil {
|
||||||
// FIXME: Continue on error?
|
// FIXME: Continue on error?
|
||||||
return err
|
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 {
|
if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if registryEp == "" {
|
if _, err := r.PushImageJSONIndex(indexEp, name, imgList, true, repoData.Endpoints); err != nil {
|
||||||
if _, err := r.PushImageJSONIndex(name, imgList, true, repoData.Endpoints); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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.
|
// 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 {
|
if err := srv.poolAdd("push", name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer srv.poolRemove("push", name)
|
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)
|
out = utils.NewWriteFlusher(out)
|
||||||
img, err := srv.runtime.graph.Get(name)
|
img, err := srv.runtime.graph.Get(name)
|
||||||
r, err2 := registry.NewRegistry(srv.runtime.root, authConfig)
|
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])))
|
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 it fails, try to get the repository
|
||||||
if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var token []string
|
||||||
out.Write(sf.FormatStatus("The push refers to an image: [%s]", name))
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Add table
Reference in a new issue