github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/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  	httpTransport, err := c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint)
   142  	if err != nil {
   143  		if strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") {
   144  			return nil, ErrHTTPProto{OrigErr: err.Error()}
   145  		}
   146  	}
   147  	repoName, err := reference.WithName(repoEndpoint.Name())
   148  	if err != nil {
   149  		return nil, errors.Wrapf(err, "failed to parse repo name from %s", ref)
   150  	}
   151  	return distributionclient.NewRepository(repoName, repoEndpoint.BaseURL(), httpTransport)
   152  }
   153  
   154  func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoint repositoryEndpoint) (http.RoundTripper, error) {
   155  	httpTransport, err := getHTTPTransport(
   156  		c.authConfigResolver(ctx, repoEndpoint.info.Index),
   157  		repoEndpoint.endpoint,
   158  		repoEndpoint.Name(),
   159  		c.userAgent)
   160  	return httpTransport, errors.Wrap(err, "failed to configure transport")
   161  }
   162  
   163  // GetManifest returns an ImageManifest for the reference
   164  func (c *client) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
   165  	var result manifesttypes.ImageManifest
   166  	fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) {
   167  		var err error
   168  		result, err = fetchManifest(ctx, repo, ref)
   169  		return result.Ref != nil, err
   170  	}
   171  
   172  	err := c.iterateEndpoints(ctx, ref, fetch)
   173  	return result, err
   174  }
   175  
   176  // GetManifestList returns a list of ImageManifest for the reference
   177  func (c *client) GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
   178  	result := []manifesttypes.ImageManifest{}
   179  	fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) {
   180  		var err error
   181  		result, err = fetchList(ctx, repo, ref)
   182  		return len(result) > 0, err
   183  	}
   184  
   185  	err := c.iterateEndpoints(ctx, ref, fetch)
   186  	return result, err
   187  }
   188  
   189  func getManifestOptionsFromReference(ref reference.Named) (digest.Digest, []distribution.ManifestServiceOption, error) {
   190  	if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
   191  		tag := tagged.Tag()
   192  		return "", []distribution.ManifestServiceOption{distribution.WithTag(tag)}, nil
   193  	}
   194  	if digested, isDigested := ref.(reference.Canonical); isDigested {
   195  		return digested.Digest(), []distribution.ManifestServiceOption{}, nil
   196  	}
   197  	return "", nil, errors.Errorf("%s no tag or digest", ref)
   198  }
   199  
   200  // GetRegistryAuth returns the auth config given an input image
   201  func GetRegistryAuth(ctx context.Context, resolver AuthConfigResolver, imageName string) (*types.AuthConfig, error) {
   202  	distributionRef, err := reference.ParseNormalizedNamed(imageName)
   203  	if err != nil {
   204  		return nil, fmt.Errorf("Failed to parse image name: %s: %s", imageName, err)
   205  	}
   206  	imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, resolver, distributionRef.String())
   207  	if err != nil {
   208  		return nil, fmt.Errorf("Failed to get imgRefAndAuth: %s", err)
   209  	}
   210  	return imgRefAndAuth.AuthConfig(), nil
   211  }