builder/digest_remote.go (89 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package builder
import (
"context"
"fmt"
"net/http"
"github.com/Azure/acr-builder/graph"
"github.com/Azure/acr-builder/pkg/image"
"github.com/containerd/containerd/remotes/docker"
"github.com/docker/distribution/reference"
"github.com/pkg/errors"
)
type remoteDigest struct {
registryCreds graph.RegistryLoginCredentials
}
func newRemoteDigest(creds graph.RegistryLoginCredentials) *remoteDigest {
return &remoteDigest{
registryCreds: creds,
}
}
var _ DigestHelper = &remoteDigest{}
func (d *remoteDigest) PopulateDigest(ctx context.Context, ref *image.Reference) error {
if ref == nil {
return nil
}
if ref.Digest != "" {
return nil
}
if ref.Reference == NoBaseImageSpecifierLatest {
return nil
}
config := docker.RegistryHost{
Client: http.DefaultClient,
Scheme: "https",
Path: "/v2",
// NOTE: it requires both pull and resolve capabilities to get the digest
Capabilities: docker.HostCapabilityPull | docker.HostCapabilityResolve,
}
if cred, ok := d.registryCreds[ref.Registry]; ok {
if cred.Username.ResolvedValue == "" || cred.Password.ResolvedValue == "" {
return fmt.Errorf("error fetching credentials for '%s'", ref.Registry)
}
config.Authorizer = docker.NewDockerAuthorizer(
docker.WithAuthCreds(func(hostName string) (string, string, error) {
if hostName != ref.Registry {
return "", "", fmt.Errorf("hostName '%s' does not match the registry '%s'", hostName, ref.Registry)
}
return cred.Username.ResolvedValue, cred.Password.ResolvedValue, nil
}),
)
} else {
config.Authorizer = docker.NewDockerAuthorizer(
docker.WithAuthCreds(func(hostName string) (string, string, error) {
if hostName != ref.Registry {
return "", "", fmt.Errorf("hostName '%s' does not match the registry '%s'", hostName, ref.Registry)
}
// NOTE: empty credential for anonymous access
return "", "", nil
}),
)
}
opts := docker.ResolverOptions{
Hosts: func(hostName string) ([]docker.RegistryHost, error) {
config.Host = hostName
return []docker.RegistryHost{config}, nil
},
}
resolver := docker.NewResolver(opts)
imageRef, err := getReferencePath(ref)
if err != nil {
return err
}
_, desc, err := resolver.Resolve(ctx, imageRef)
if err != nil {
return errors.Wrapf(err, "Failed to Resolve the reference '%s'", ref.Reference)
}
ref.Digest = desc.Digest.String()
return nil
}
func getReferencePath(ref *image.Reference) (string, error) {
fullRefPath := fmt.Sprintf("%s/%s", ref.Registry, ref.Repository)
tag := "latest"
if ref.Tag != "" {
tag = ref.Tag
}
fullRefPath = fmt.Sprintf("%s:%s", fullRefPath, tag)
fullRef, err := reference.Parse(fullRefPath)
if err != nil {
return "", errors.Wrapf(err, "Failed to parse the reference %s", ref.Reference)
}
return fullRef.String(), nil
}