github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/util/imagetools/inspect.go (about)

     1  package imagetools
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"io"
     9  	"net/http"
    10  
    11  	"github.com/containerd/containerd/remotes"
    12  	"github.com/containerd/containerd/remotes/docker"
    13  	"github.com/containerd/log"
    14  	"github.com/distribution/reference"
    15  	"github.com/docker/buildx/util/resolver"
    16  	clitypes "github.com/docker/cli/cli/config/types"
    17  	"github.com/moby/buildkit/util/contentutil"
    18  	"github.com/moby/buildkit/util/tracing"
    19  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    20  	"github.com/sirupsen/logrus"
    21  )
    22  
    23  type Auth interface {
    24  	GetAuthConfig(registryHostname string) (clitypes.AuthConfig, error)
    25  }
    26  
    27  type Opt struct {
    28  	Auth           Auth
    29  	RegistryConfig map[string]resolver.RegistryConfig
    30  }
    31  
    32  type Resolver struct {
    33  	auth   docker.Authorizer
    34  	hosts  docker.RegistryHosts
    35  	buffer contentutil.Buffer
    36  }
    37  
    38  func New(opt Opt) *Resolver {
    39  	return &Resolver{
    40  		auth:   docker.NewDockerAuthorizer(docker.WithAuthCreds(toCredentialsFunc(opt.Auth)), docker.WithAuthClient(http.DefaultClient)),
    41  		hosts:  resolver.NewRegistryConfig(opt.RegistryConfig),
    42  		buffer: contentutil.NewBuffer(),
    43  	}
    44  }
    45  
    46  func (r *Resolver) resolver() remotes.Resolver {
    47  	return docker.NewResolver(docker.ResolverOptions{
    48  		Hosts: func(domain string) ([]docker.RegistryHost, error) {
    49  			res, err := r.hosts(domain)
    50  			if err != nil {
    51  				return nil, err
    52  			}
    53  			for i := range res {
    54  				res[i].Authorizer = r.auth
    55  				res[i].Client = tracing.DefaultClient
    56  			}
    57  			return res, nil
    58  		},
    59  	})
    60  }
    61  
    62  func (r *Resolver) Resolve(ctx context.Context, in string) (string, ocispec.Descriptor, error) {
    63  	// discard containerd logger to avoid printing unnecessary info during image reference resolution.
    64  	// https://github.com/containerd/containerd/blob/1a88cf5242445657258e0c744def5017d7cfb492/remotes/docker/resolver.go#L288
    65  	logger := logrus.New()
    66  	logger.Out = io.Discard
    67  	ctx = log.WithLogger(ctx, logrus.NewEntry(logger))
    68  
    69  	ref, err := parseRef(in)
    70  	if err != nil {
    71  		return "", ocispec.Descriptor{}, err
    72  	}
    73  
    74  	in, desc, err := r.resolver().Resolve(ctx, ref.String())
    75  	if err != nil {
    76  		return "", ocispec.Descriptor{}, err
    77  	}
    78  
    79  	return in, desc, nil
    80  }
    81  
    82  func (r *Resolver) Get(ctx context.Context, in string) ([]byte, ocispec.Descriptor, error) {
    83  	in, desc, err := r.Resolve(ctx, in)
    84  	if err != nil {
    85  		return nil, ocispec.Descriptor{}, err
    86  	}
    87  
    88  	dt, err := r.GetDescriptor(ctx, in, desc)
    89  	if err != nil {
    90  		return nil, ocispec.Descriptor{}, err
    91  	}
    92  	return dt, desc, nil
    93  }
    94  
    95  func (r *Resolver) GetDescriptor(ctx context.Context, in string, desc ocispec.Descriptor) ([]byte, error) {
    96  	fetcher, err := r.resolver().Fetcher(ctx, in)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	rc, err := fetcher.Fetch(ctx, desc)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	buf := &bytes.Buffer{}
   107  	_, err = io.Copy(buf, rc)
   108  	rc.Close()
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	return buf.Bytes(), nil
   114  }
   115  
   116  func parseRef(s string) (reference.Named, error) {
   117  	ref, err := reference.ParseNormalizedNamed(s)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	ref = reference.TagNameOnly(ref)
   122  	return ref, nil
   123  }
   124  
   125  func toCredentialsFunc(a Auth) func(string) (string, string, error) {
   126  	return func(host string) (string, string, error) {
   127  		if host == "registry-1.docker.io" {
   128  			host = "https://index.docker.io/v1/"
   129  		}
   130  		ac, err := a.GetAuthConfig(host)
   131  		if err != nil {
   132  			return "", "", err
   133  		}
   134  		if ac.IdentityToken != "" {
   135  			return "", ac.IdentityToken, nil
   136  		}
   137  		return ac.Username, ac.Password, nil
   138  	}
   139  }
   140  
   141  func RegistryAuthForRef(ref string, a Auth) (string, error) {
   142  	if a == nil {
   143  		return "", nil
   144  	}
   145  	r, err := parseRef(ref)
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  	host := reference.Domain(r)
   150  	if host == "docker.io" {
   151  		host = "https://index.docker.io/v1/"
   152  	}
   153  	ac, err := a.GetAuthConfig(host)
   154  	if err != nil {
   155  		return "", err
   156  	}
   157  	buf, err := json.Marshal(ac)
   158  	if err != nil {
   159  		return "", err
   160  	}
   161  	return base64.URLEncoding.EncodeToString(buf), nil
   162  }