The sweep holds and removes through the registry seats, so the store's LetGo, holds and holder manifests, and the unused GetBlob and HoldsManifest, have no caller. One table test reads the door's two remaining answers in both reference forms. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
77 lines
2.3 KiB
Go
77 lines
2.3 KiB
Go
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}
|
|
}
|