|
| 1 | +package httpclient |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "net/http" |
| 7 | + "time" |
| 8 | + |
| 9 | + "github.com/go-resty/resty/v2" |
| 10 | + |
| 11 | + logx "github.com/instill-ai/x/log" |
| 12 | +) |
| 13 | + |
| 14 | +const ( |
| 15 | + reqTimeout = time.Second * 30 |
| 16 | + maxRetryCount = 3 |
| 17 | + retryDelay = 100 * time.Millisecond |
| 18 | +) |
| 19 | + |
| 20 | +// RegistryClient interacts with the Docker Registry HTTP V2 API. |
| 21 | +type RegistryClient struct { |
| 22 | + *resty.Client |
| 23 | +} |
| 24 | + |
| 25 | +// NewRegistryClient returns an initialized registry HTTP client. |
| 26 | +func NewRegistryClient(ctx context.Context, registryHost string, registryPort int) *RegistryClient { |
| 27 | + logger, _ := logx.GetZapLogger(ctx) |
| 28 | + baseURL := fmt.Sprintf("http://%s:%d", registryHost, registryPort) |
| 29 | + |
| 30 | + r := resty.New(). |
| 31 | + SetLogger(logger.Sugar()). |
| 32 | + SetBaseURL(baseURL). |
| 33 | + SetTimeout(reqTimeout). |
| 34 | + SetTransport(&http.Transport{ |
| 35 | + DisableKeepAlives: true, |
| 36 | + }). |
| 37 | + SetRetryCount(maxRetryCount). |
| 38 | + SetRetryWaitTime(retryDelay) |
| 39 | + |
| 40 | + return &RegistryClient{Client: r} |
| 41 | +} |
| 42 | + |
| 43 | +type tagList struct { |
| 44 | + Tags []string `json:"tags"` |
| 45 | +} |
| 46 | + |
| 47 | +// ListTags calls the GET /v2/<name>/tags/list endpoint, where <name> is a |
| 48 | +// repository. |
| 49 | +func (c *RegistryClient) ListTags(ctx context.Context, repository string) ([]string, error) { |
| 50 | + var resp tagList |
| 51 | + |
| 52 | + tagsPath := fmt.Sprintf("/v2/%s/tags/list", repository) |
| 53 | + r := c.R().SetContext(ctx).SetResult(&resp) |
| 54 | + if _, err := r.Get(tagsPath); err != nil { |
| 55 | + return nil, fmt.Errorf("couldn't connect with registry: %w", err) |
| 56 | + } |
| 57 | + |
| 58 | + return resp.Tags, nil |
| 59 | +} |
| 60 | + |
| 61 | +// DeleteTag calls the DELETE /v2/<name>/manifests/<reference> endpoint, where <name> is a |
| 62 | +// repository, and <reference> is the digest |
| 63 | +func (c *RegistryClient) DeleteTag(ctx context.Context, repository string, digest string) error { |
| 64 | + |
| 65 | + deletePath := fmt.Sprintf("/v2/%s/manifests/%s", repository, digest) |
| 66 | + r := c.R().SetContext(ctx) |
| 67 | + if _, err := r.Delete(deletePath); err != nil { |
| 68 | + return fmt.Errorf("couldn't delete the image with registry: %w", err) |
| 69 | + } |
| 70 | + |
| 71 | + return nil |
| 72 | +} |
| 73 | + |
| 74 | +// GetTagDigest calls the HEAD /v2/<name>/manifests/<reference> endpoint, where <name> is a |
| 75 | +// repository, and <reference> is the tag |
| 76 | +func (c *RegistryClient) GetTagDigest(ctx context.Context, repository string, tag string) (string, error) { |
| 77 | + |
| 78 | + digestPath := fmt.Sprintf("/v2/%s/manifests/%s", repository, tag) |
| 79 | + r := c.R().SetContext(ctx).SetHeader("Accept", "application/vnd.docker.distribution.manifest.v2+json") |
| 80 | + resp, err := r.Head(digestPath) |
| 81 | + if err != nil { |
| 82 | + return "", fmt.Errorf("couldn't get the image digest: %w", err) |
| 83 | + } |
| 84 | + |
| 85 | + return resp.Header().Get("Docker-Content-Digest"), nil |
| 86 | +} |
0 commit comments