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