github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/registry/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"strings"
     8  
     9  	manifesttypes "github.com/docker/cli/cli/manifest/types"
    10  	"github.com/docker/distribution"
    11  	"github.com/docker/distribution/reference"
    12  	distributionclient "github.com/docker/distribution/registry/client"
    13  	"github.com/docker/docker/api/types"
    14  	registrytypes "github.com/docker/docker/api/types/registry"
    15  	"github.com/opencontainers/go-digest"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  // RegistryClient is a client used to communicate with a Docker distribution
    21  // registry
    22  type RegistryClient interface {
    23  	GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error)
    24  	GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error)
    25  	MountBlob(ctx context.Context, source reference.Canonical, target reference.Named) error
    26  	PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error)
    27  }
    28  
    29  // NewRegistryClient returns a new RegistryClient with a resolver
    30  func NewRegistryClient(resolver AuthConfigResolver, userAgent string, insecure bool) RegistryClient {
    31  	return &client{
    32  		authConfigResolver: resolver,
    33  		insecureRegistry:   insecure,
    34  		userAgent:          userAgent,
    35  	}
    36  }
    37  
    38  // AuthConfigResolver returns Auth Configuration for an index
    39  type AuthConfigResolver func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig
    40  
    41  // PutManifestOptions is the data sent to push a manifest
    42  type PutManifestOptions struct {
    43  	MediaType string
    44  	Payload   []byte
    45  }
    46  
    47  type client struct {
    48  	authConfigResolver AuthConfigResolver
    49  	insecureRegistry   bool
    50  	userAgent          string
    51  }
    52  
    53  // ErrBlobCreated returned when a blob mount request was created
    54  type ErrBlobCreated struct {
    55  	From   reference.Named
    56  	Target reference.Named
    57  }
    58  
    59  func (err ErrBlobCreated) Error() string {
    60  	return fmt.Sprintf("blob mounted from: %v to: %v",
    61  		err.From, err.Target)
    62  }
    63  
    64  // ErrHTTPProto returned if attempting to use TLS with a non-TLS registry
    65  type ErrHTTPProto struct {
    66  	OrigErr string
    67  }
    68  
    69  func (err ErrHTTPProto) Error() string {
    70  	return err.OrigErr
    71  }
    72  
    73  var _ RegistryClient = &client{}
    74  
    75  // MountBlob into the registry, so it can be referenced by a manifest
    76  func (c *client) MountBlob(ctx context.Context, sourceRef reference.Canonical, targetRef reference.Named) error {
    77  	repoEndpoint, err := newDefaultRepositoryEndpoint(targetRef, c.insecureRegistry)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	repo, err := c.getRepositoryForReference(ctx, targetRef, repoEndpoint)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	lu, err := repo.Blobs(ctx).Create(ctx, distributionclient.WithMountFrom(sourceRef))
    86  	switch err.(type) {
    87  	case distribution.ErrBlobMounted:
    88  		logrus.Debugf("mount of blob %s succeeded", sourceRef)
    89  		return nil
    90  	case nil:
    91  	default:
    92  		return errors.Wrapf(err, "failed to mount blob %s to %s", sourceRef, targetRef)
    93  	}
    94  	lu.Cancel(ctx)
    95  	logrus.Debugf("mount of blob %s created", sourceRef)
    96  	return ErrBlobCreated{From: sourceRef, Target: targetRef}
    97  }
    98  
    99  // PutManifest sends the manifest to a registry and returns the new digest
   100  func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) {
   101  	repoEndpoint, err := newDefaultRepositoryEndpoint(ref, c.insecureRegistry)
   102  	if err != nil {
   103  		return digest.Digest(""), err
   104  	}
   105  
   106  	repo, err := c.getRepositoryForReference(ctx, ref, repoEndpoint)
   107  	if err != nil {
   108  		return digest.Digest(""), err
   109  	}
   110  
   111  	manifestService, err := repo.Manifests(ctx)
   112  	if err != nil {
   113  		return digest.Digest(""), err
   114  	}
   115  
   116  	_, opts, err := getManifestOptionsFromReference(ref)
   117  	if err != nil {
   118  		return digest.Digest(""), err
   119  	}
   120  
   121  	dgst, err := manifestService.Put(ctx, manifest, opts...)
   122  	return dgst, errors.Wrapf(err, "failed to put manifest %s", ref)
   123  }
   124  
   125  func (c *client) getRepositoryForReference(ctx context.Context, ref reference.Named, repoEndpoint repositoryEndpoint) (distribution.Repository, error) {
   126  	repoName, err := reference.WithName(repoEndpoint.Name())
   127  	if err != nil {
   128  		return nil, errors.Wrapf(err, "failed to parse repo name from %s", ref)
   129  	}
   130  	httpTransport, err := c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint)
   131  	if err != nil {
   132  		if !strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") {
   133  			return nil, err
   134  		}
   135  		if !repoEndpoint.endpoint.TLSConfig.InsecureSkipVerify {
   136  			return nil, ErrHTTPProto{OrigErr: err.Error()}
   137  		}
   138  		// --insecure was set; fall back to plain HTTP
   139  		if url := repoEndpoint.endpoint.URL; url != nil && url.Scheme == "https" {
   140  			url.Scheme = "http"
   141  			httpTransport, err = c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint)
   142  			if err != nil {
   143  				return nil, err
   144  			}
   145  		}
   146  	}
   147  	return distributionclient.NewRepository(repoName, repoEndpoint.BaseURL(), httpTransport)
   148  }
   149  
   150  func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoint repositoryEndpoint) (http.RoundTripper, error) {
   151  	httpTransport, err := getHTTPTransport(
   152  		c.authConfigResolver(ctx, repoEndpoint.info.Index),
   153  		repoEndpoint.endpoint,
   154  		repoEndpoint.Name(),
   155  		c.userAgent)
   156  	return httpTransport, errors.Wrap(err, "failed to configure transport")
   157  }
   158  
   159  // GetManifest returns an ImageManifest for the reference
   160  func (c *client) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
   161  	var result manifesttypes.ImageManifest
   162  	fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) {
   163  		var err error
   164  		result, err = fetchManifest(ctx, repo, ref)
   165  		return result.Ref != nil, err
   166  	}
   167  
   168  	err := c.iterateEndpoints(ctx, ref, fetch)
   169  	return result, err
   170  }
   171  
   172  // GetManifestList returns a list of ImageManifest for the reference
   173  func (c *client) GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
   174  	result := []manifesttypes.ImageManifest{}
   175  	fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) {
   176  		var err error
   177  		result, err = fetchList(ctx, repo, ref)
   178  		return len(result) > 0, err
   179  	}
   180  
   181  	err := c.iterateEndpoints(ctx, ref, fetch)
   182  	return result, err
   183  }
   184  
   185  func getManifestOptionsFromReference(ref reference.Named) (digest.Digest, []distribution.ManifestServiceOption, error) {
   186  	if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
   187  		tag := tagged.Tag()
   188  		return "", []distribution.ManifestServiceOption{distribution.WithTag(tag)}, nil
   189  	}
   190  	if digested, isDigested := ref.(reference.Canonical); isDigested {
   191  		return digested.Digest(), []distribution.ManifestServiceOption{}, nil
   192  	}
   193  	return "", nil, errors.Errorf("%s no tag or digest", ref)
   194  }