func (rb *RepositoryBackend) getAncestryV1(imgID, registry string, repoData *RepoData) ([]string, error) { client := util.GetTLSClient(rb.insecure.SkipVerify) req, err := http.NewRequest("GET", rb.schema+path.Join(registry, "images", imgID, "ancestry"), nil) if err != nil { return nil, err } setAuthTokenV1(req, repoData.Tokens) setCookieV1(req, repoData.Cookie) res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != 200 { return nil, &httpStatusErr{res.StatusCode, req.URL} } var ancestry []string j, err := ioutil.ReadAll(res.Body) if err != nil { return nil, fmt.Errorf("Failed to read downloaded json: %s (%s)", err, j) } if err := json.Unmarshal(j, &ancestry); err != nil { return nil, fmt.Errorf("error unmarshaling: %v", err) } return ancestry, nil }
func (rb *RepositoryBackend) getJsonV1(imgID, registry string, repoData *RepoData) ([]byte, int64, error) { client := util.GetTLSClient(rb.insecure.SkipVerify) req, err := http.NewRequest("GET", rb.schema+path.Join(registry, "images", imgID, "json"), nil) if err != nil { return nil, -1, err } setAuthTokenV1(req, repoData.Tokens) setCookieV1(req, repoData.Cookie) res, err := client.Do(req) if err != nil { return nil, -1, err } defer res.Body.Close() if res.StatusCode != 200 { return nil, -1, &httpStatusErr{res.StatusCode, req.URL} } imageSize := int64(-1) if hdr := res.Header.Get("X-Docker-Size"); hdr != "" { imageSize, err = strconv.ParseInt(hdr, 10, 64) if err != nil { return nil, -1, err } } b, err := ioutil.ReadAll(res.Body) if err != nil { return nil, -1, fmt.Errorf("failed to read downloaded json: %v (%s)", err, b) } return b, imageSize, nil }
func (rb *RepositoryBackend) getRepoDataV1(indexURL string, remote string) (*RepoData, error) { client := util.GetTLSClient(rb.insecure.SkipVerify) repositoryURL := rb.schema + path.Join(indexURL, "v1", "repositories", remote, "images") req, err := http.NewRequest("GET", repositoryURL, nil) if err != nil { return nil, err } if rb.username != "" && rb.password != "" { req.SetBasicAuth(rb.username, rb.password) } req.Header.Set("X-Docker-Token", "true") res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != 200 { return nil, &httpStatusErr{res.StatusCode, req.URL} } var tokens []string if res.Header.Get("X-Docker-Token") != "" { tokens = res.Header["X-Docker-Token"] } var cookies []string if res.Header.Get("Set-Cookie") != "" { cookies = res.Header["Set-Cookie"] } var endpoints []string if res.Header.Get("X-Docker-Endpoints") != "" { endpoints = makeEndpointsListV1(res.Header["X-Docker-Endpoints"]) } else { // Assume same endpoint endpoints = append(endpoints, indexURL) } return &RepoData{ Endpoints: endpoints, Tokens: tokens, Cookie: cookies, }, nil }
func (rb *RepositoryBackend) supportsRegistry(indexURL string, version registryVersion) (schema string, ok bool, err error) { var URLPath string switch version { case registryV1: // the v1 API defines this URL to check if the registry's status URLPath = "v1/_ping" case registryV2: URLPath = "v2" } URLStr := path.Join(indexURL, URLPath) fetch := func(schema string) (res *http.Response, err error) { url := schema + "://" + URLStr req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } rb.setBasicAuth(req) client := util.GetTLSClient(rb.insecure) res, err = client.Do(req) return } schema = "https" res, err := fetch(schema) if err == nil { ok, err = checkRegistryStatus(res.StatusCode, res.Header, version) defer res.Body.Close() } if err != nil || !ok { if rb.insecure { schema = "http" res, err = fetch(schema) if err == nil { ok, err = checkRegistryStatus(res.StatusCode, res.Header, version) defer res.Body.Close() } } return schema, ok, err } return schema, ok, err }
func (rb *RepositoryBackend) supportsRegistry(indexURL string, version registryVersion) (schema string, ok bool, err error) { var URLPath string switch version { case registryV1: URLPath = "v1/_ping" case registryV2: URLPath = "v2/" } fetch := func(schema string) (res *http.Response, err error) { u := url.URL{Scheme: schema, Host: indexURL, Path: URLPath} req, err := http.NewRequest("GET", u.String(), nil) if err != nil { return nil, err } rb.setBasicAuth(req) client := util.GetTLSClient(rb.insecure.SkipVerify) res, err = client.Do(req) return } schema = "https" res, err := fetch(schema) if err == nil { ok, err = checkRegistryStatus(res.StatusCode, res.Header, version) defer res.Body.Close() } if err != nil || !ok { if rb.insecure.AllowHTTP { schema = "http" res, err = fetch(schema) if err == nil { ok, err = checkRegistryStatus(res.StatusCode, res.Header, version) defer res.Body.Close() } } return schema, ok, err } return schema, ok, err }
func (rb *RepositoryBackend) getImageIDFromTagV1(registry string, appName string, tag string, repoData *RepoData) (string, error) { client := util.GetTLSClient(rb.insecure.SkipVerify) // we get all the tags instead of directly getting the imageID of the // requested one (.../tags/TAG) because even though it's specified in the // Docker API, some registries (e.g. Google Container Registry) don't // implement it. req, err := http.NewRequest("GET", rb.schema+path.Join(registry, "repositories", appName, "tags"), nil) if err != nil { return "", fmt.Errorf("failed to get Image ID: %s, URL: %s", err, req.URL) } setAuthTokenV1(req, repoData.Tokens) setCookieV1(req, repoData.Cookie) res, err := client.Do(req) if err != nil { return "", fmt.Errorf("failed to get Image ID: %s, URL: %s", err, req.URL) } defer res.Body.Close() if res.StatusCode != 200 { return "", &httpStatusErr{res.StatusCode, req.URL} } j, err := ioutil.ReadAll(res.Body) if err != nil { return "", err } var tags map[string]string if err := json.Unmarshal(j, &tags); err != nil { return "", fmt.Errorf("error unmarshaling: %v", err) } imageID, ok := tags[tag] if !ok { return "", fmt.Errorf("tag %s not found", tag) } return imageID, nil }
func (rb *RepositoryBackend) getLayerV1(imgID, registry string, repoData *RepoData, imgSize int64, tmpDir string) (*os.File, error) { client := util.GetTLSClient(rb.insecure.SkipVerify) req, err := http.NewRequest("GET", rb.schema+path.Join(registry, "images", imgID, "layer"), nil) if err != nil { return nil, err } setAuthTokenV1(req, repoData.Tokens) setCookieV1(req, repoData.Cookie) res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != 200 { res.Body.Close() return nil, &httpStatusErr{res.StatusCode, req.URL} } // if we didn't receive the size via X-Docker-Size when we retrieved the // layer's json, try Content-Length if imgSize == -1 { if hdr := res.Header.Get("Content-Length"); hdr != "" { imgSize, err = strconv.ParseInt(hdr, 10, 64) if err != nil { return nil, err } } } prefix := "Downloading " + imgID[:12] fmtBytesSize := 18 barSize := int64(80 - len(prefix) - fmtBytesSize) bar := ioprogress.DrawTextFormatBarForW(barSize, os.Stderr) fmtfunc := func(progress, total int64) string { return fmt.Sprintf( "%s: %s %s", prefix, bar(progress, total), ioprogress.DrawTextFormatBytes(progress, total), ) } progressReader := &ioprogress.Reader{ Reader: res.Body, Size: imgSize, DrawFunc: ioprogress.DrawTerminalf(os.Stderr, fmtfunc), DrawInterval: 500 * time.Millisecond, } layerFile, err := ioutil.TempFile(tmpDir, "dockerlayer-") if err != nil { return nil, err } _, err = io.Copy(layerFile, progressReader) if err != nil { return nil, err } if err := layerFile.Sync(); err != nil { return nil, err } return layerFile, nil }
func (rb *RepositoryBackend) makeRequest(req *http.Request, repo string) (*http.Response, error) { setBearerHeader := false hostAuthTokens, ok := rb.hostsV2AuthTokens[req.URL.Host] if ok { authToken, ok := hostAuthTokens[repo] if ok { req.Header.Set("Authorization", "Bearer "+authToken) setBearerHeader = true } } client := util.GetTLSClient(rb.insecure) res, err := client.Do(req) if err != nil { return nil, err } if res.StatusCode == http.StatusUnauthorized && setBearerHeader { return res, err } hdr := res.Header.Get("www-authenticate") if res.StatusCode != http.StatusUnauthorized || hdr == "" { return res, err } tokens := strings.Split(hdr, ",") if len(tokens) != 3 || !strings.HasPrefix(strings.ToLower(tokens[0]), "bearer realm") { return res, err } res.Body.Close() var realm, service, scope string for _, token := range tokens { if strings.HasPrefix(strings.ToLower(token), "bearer realm") { realm = strings.Trim(token[len("bearer realm="):], "\"") } if strings.HasPrefix(token, "service") { service = strings.Trim(token[len("service="):], "\"") } if strings.HasPrefix(token, "scope") { scope = strings.Trim(token[len("scope="):], "\"") } } if realm == "" { return nil, fmt.Errorf("missing realm in bearer auth challenge") } if service == "" { return nil, fmt.Errorf("missing service in bearer auth challenge") } // The scope can be empty if we're not getting a token for a specific repo if scope == "" && repo != "" { // If the scope is empty and it shouldn't be, we can infer it based on the repo scope = fmt.Sprintf("repository:%s:pull", repo) } authReq, err := http.NewRequest("GET", realm, nil) if err != nil { return nil, err } getParams := authReq.URL.Query() getParams.Add("service", service) if scope != "" { getParams.Add("scope", scope) } authReq.URL.RawQuery = getParams.Encode() rb.setBasicAuth(authReq) res, err = client.Do(authReq) if err != nil { return nil, err } defer res.Body.Close() switch res.StatusCode { case http.StatusUnauthorized: return nil, fmt.Errorf("unable to retrieve auth token: 401 unauthorized") case http.StatusOK: break default: return nil, fmt.Errorf("unexpected http code: %d, URL: %s", res.StatusCode, authReq.URL) } tokenBlob, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } tokenStruct := struct { Token string `json:"token"` }{} err = json.Unmarshal(tokenBlob, &tokenStruct) if err != nil { return nil, err } hostAuthTokens, ok = rb.hostsV2AuthTokens[req.URL.Host] if !ok { hostAuthTokens = make(map[string]string) rb.hostsV2AuthTokens[req.URL.Host] = hostAuthTokens } hostAuthTokens[repo] = tokenStruct.Token return rb.makeRequest(req, repo) }