50 lines
1.5 KiB
Go
50 lines
1.5 KiB
Go
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
|
|
}
|