package registry import ( "context" "fmt" "net/http" "strconv" "time" ) // blobSize is how large the store says a blob is; Gone when it does not have it. func (s Store) blobSize(ctx context.Context, repository, digest string) (int64, error) { request, err := http.NewRequestWithContext(ctx, http.MethodHead, s.url(repository, "blobs", digest), nil) if err != nil { return 0, err } response, err := s.client().Do(request) if err != nil { return 0, fmt.Errorf("cannot reach the artifact store at %s: %w", s.Address, err) } defer response.Body.Close() switch response.StatusCode { case http.StatusOK: case http.StatusNotFound: return 0, Gone default: return 0, fmt.Errorf("the artifact store answered %s for %s/blobs/%s", response.Status, repository, digest) } // Read from the header rather than ContentLength: a HEAD's ContentLength is what the response // says it would have sent, which Go reports faithfully, but a proxy in between is free to drop // it, and the header is what the registry itself wrote. if length := response.Header.Get("Content-Length"); length != "" { if n, err := strconv.ParseInt(length, 10, 64); err == nil && n >= 0 { return n, nil } } if response.ContentLength >= 0 { return response.ContentLength, nil } return 0, fmt.Errorf("the artifact store holds %s/blobs/%s and will not say how large it is", repository, digest) } // has is whether the store answers 200 for a HEAD at that URL. func (s Store) has(ctx context.Context, url string, accept ...string) (bool, error) { request, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil) if err != nil { return false, err } for _, media := range accept { request.Header.Add("Accept", media) } response, err := s.client().Do(request) if err != nil { return false, fmt.Errorf("cannot reach the artifact store at %s: %w", s.Address, err) } defer response.Body.Close() switch response.StatusCode { case http.StatusOK: return true, nil case http.StatusNotFound: return false, nil default: return false, fmt.Errorf("the artifact store answered %s for %s", response.Status, url) } } func (s Store) url(repository, kind, digest string) string { return "http://" + s.Address + "/v2/" + repository + "/" + kind + "/" + digest } func (s Store) client() *http.Client { if s.HTTP != nil { return s.HTTP } return &http.Client{Timeout: 30 * time.Second} }