github.com/thajeztah/cli@v0.0.0-20240223162942-dc6bfac81a8b/cli/registry/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"github.com/distribution/reference"
    10  	manifesttypes "github.com/docker/cli/cli/manifest/types"
    11  	"github.com/docker/cli/cli/trust"
    12  	"github.com/docker/distribution"
    13  	distributionclient "github.com/docker/distribution/registry/client"
    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) registrytypes.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  	repoEndpoint.actions = trust.ActionsPushAndPull
    82  	repo, err := c.getRepositoryForReference(ctx, targetRef, repoEndpoint)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	lu, err := repo.Blobs(ctx).Create(ctx, distributionclient.WithMountFrom(sourceRef))
    87  	switch err.(type) {
    88  	case distribution.ErrBlobMounted:
    89  		logrus.Debugf("mount of blob %s succeeded", sourceRef)
    90  		return nil
    91  	case nil:
    92  	default:
    93  		return errors.Wrapf(err, "failed to mount blob %s to %s", sourceRef, targetRef)
    94  	}
    95  	lu.Cancel(ctx)
    96  	logrus.Debugf("mount of blob %s created", sourceRef)
    97  	return ErrBlobCreated{From: sourceRef, Target: targetRef}
    98  }
    99  
   100  // PutManifest sends the manifest to a registry and returns the new digest
   101  func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) {
   102  	repoEndpoint, err := newDefaultRepositoryEndpoint(ref, c.insecureRegistry)
   103  	if err != nil {
   104  		return digest.Digest(""), err
   105  	}
   106  
   107  	repoEndpoint.actions = trust.ActionsPushAndPull
   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) getRepositoryForReference(ctx context.Context, ref reference.Named, repoEndpoint repositoryEndpoint) (distribution.Repository, error) {
   128  	repoName, err := reference.WithName(repoEndpoint.Name())
   129  	if err != nil {
   130  		return nil, errors.Wrapf(err, "failed to parse repo name from %s", ref)
   131  	}
   132  	httpTransport, err := c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint)
   133  	if err != nil {
   134  		if !strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") {
   135  			return nil, err
   136  		}
   137  		if !repoEndpoint.endpoint.TLSConfig.InsecureSkipVerify {
   138  			return nil, ErrHTTPProto{OrigErr: err.Error()}
   139  		}
   140  		// --insecure was set; fall back to plain HTTP
   141  		if url := repoEndpoint.endpoint.URL; url != nil && url.Scheme == "https" {
   142  			url.Scheme = "http"
   143  			httpTransport, err = c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint)
   144  			if err != nil {
   145  				return nil, err
   146  			}
   147  		}
   148  	}
   149  	return distributionclient.NewRepository(repoName, repoEndpoint.BaseURL(), httpTransport)
   150  }
   151  
   152  func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoint repositoryEndpoint) (http.RoundTripper, error) {
   153  	httpTransport, err := getHTTPTransport(
   154  		c.authConfigResolver(ctx, repoEndpoint.info.Index),
   155  		repoEndpoint.endpoint,
   156  		repoEndpoint.Name(),
   157  		c.userAgent,
   158  		repoEndpoint.actions,
   159  	)
   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  }