package updater import ( "context" "errors" "fmt" "github.com/docker/docker/api/types/registry" "github.com/shcizo/package-updater/internal/imageref" ) // DistributionClient is the subset of the Docker SDK the Resolver needs. // *client.Client satisfies it. type DistributionClient interface { DistributionInspect(ctx context.Context, imageRef, encodedRegistryAuth string) (registry.DistributionInspect, error) } // Resolver turns "repo:tag" into "repo:tag@digest" by asking the local // daemon, which contacts the registry. type Resolver struct { cli DistributionClient auth *RegistryAuth } // NewResolver returns a Resolver. auth may be nil (anonymous access). func NewResolver(cli DistributionClient, auth *RegistryAuth) *Resolver { return &Resolver{cli: cli, auth: auth} } // Resolve returns ref pinned to the digest its tag currently points at. It // fails for a ref without a tag or with a digest already. func (r *Resolver) Resolve(ctx context.Context, ref string) (string, error) { p := imageref.Parse(ref) switch { case p.Digest != "": return "", fmt.Errorf("image %q already has a digest", ref) case p.Tag == "": return "", fmt.Errorf("image %q has no tag to resolve", ref) } info, err := r.cli.DistributionInspect(ctx, ref, r.auth.For(p.Repo)) if err != nil { return "", fmt.Errorf("resolve %s: %w", ref, err) } if info.Descriptor.Digest == "" { return "", errors.New("resolve " + ref + ": registry returned no digest") } return ref + "@" + info.Descriptor.Digest.String(), nil }