feat(master): registry digest lookup and polling loop (#9)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
// Package registry looks up the digest a tag points at in a container
|
||||
// registry, using HEAD requests only.
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
|
||||
"github.com/shcizo/package-updater/internal/imageref"
|
||||
)
|
||||
|
||||
// Client looks up tag digests with HEAD requests only. Docker Hub counts a
|
||||
// GET of a manifest as a pull against the rate limit and a HEAD as nothing,
|
||||
// so Client never issues a manifest GET.
|
||||
type Client struct {
|
||||
keychain authn.Keychain
|
||||
userAgent string
|
||||
}
|
||||
|
||||
// New returns a Client that finds credentials the way the Docker CLI does:
|
||||
// in $DOCKER_CONFIG/config.json, else ~/.docker/config.json. Registries
|
||||
// without an entry are accessed anonymously. userAgent is added to the
|
||||
// User-Agent header of every request.
|
||||
func New(userAgent string) *Client {
|
||||
return &Client{keychain: authn.DefaultKeychain, userAgent: userAgent}
|
||||
}
|
||||
|
||||
// Digest returns the digest that "repo:tag" points at, as the registry
|
||||
// reports it: for a multi-arch image that is the digest of the index, the
|
||||
// same one `docker inspect` shows in RepoDigests. A digest suffix in ref is
|
||||
// ignored. It sends HEAD /v2/<repo>/manifests/<tag> and nothing else to the
|
||||
// manifest endpoint; it does not fall back to GET when the registry omits
|
||||
// the digest header.
|
||||
func (c *Client) Digest(ctx context.Context, ref string) (string, error) {
|
||||
p := imageref.Parse(ref)
|
||||
if p.Tag == "" {
|
||||
return "", fmt.Errorf("image %q has no tag", ref)
|
||||
}
|
||||
r, err := name.ParseReference(p.Repo + ":" + p.Tag)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse %s: %w", ref, err)
|
||||
}
|
||||
desc, err := remote.Head(r,
|
||||
remote.WithContext(ctx),
|
||||
remote.WithAuthFromKeychain(c.keychain),
|
||||
remote.WithUserAgent(c.userAgent))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("head %s: %w", r, err)
|
||||
}
|
||||
return desc.Digest.String(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user