github.com/cspotcode/docker-cli@v20.10.0-rc1.0.20201201121459-3faad7acc5b8+incompatible/cli/registry/client/fetcher.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	"github.com/docker/cli/cli/manifest/types"
     9  	"github.com/docker/distribution"
    10  	"github.com/docker/distribution/manifest/manifestlist"
    11  	"github.com/docker/distribution/manifest/schema2"
    12  	"github.com/docker/distribution/reference"
    13  	"github.com/docker/distribution/registry/api/errcode"
    14  	v2 "github.com/docker/distribution/registry/api/v2"
    15  	distclient "github.com/docker/distribution/registry/client"
    16  	"github.com/docker/docker/registry"
    17  	digest "github.com/opencontainers/go-digest"
    18  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    19  	"github.com/pkg/errors"
    20  	"github.com/sirupsen/logrus"
    21  )
    22  
    23  // fetchManifest pulls a manifest from a registry and returns it. An error
    24  // is returned if no manifest is found matching namedRef.
    25  func fetchManifest(ctx context.Context, repo distribution.Repository, ref reference.Named) (types.ImageManifest, error) {
    26  	manifest, err := getManifest(ctx, repo, ref)
    27  	if err != nil {
    28  		return types.ImageManifest{}, err
    29  	}
    30  
    31  	switch v := manifest.(type) {
    32  	// Removed Schema 1 support
    33  	case *schema2.DeserializedManifest:
    34  		imageManifest, err := pullManifestSchemaV2(ctx, ref, repo, *v)
    35  		if err != nil {
    36  			return types.ImageManifest{}, err
    37  		}
    38  		return imageManifest, nil
    39  	case *manifestlist.DeserializedManifestList:
    40  		return types.ImageManifest{}, errors.Errorf("%s is a manifest list", ref)
    41  	}
    42  	return types.ImageManifest{}, errors.Errorf("%s is not a manifest", ref)
    43  }
    44  
    45  func fetchList(ctx context.Context, repo distribution.Repository, ref reference.Named) ([]types.ImageManifest, error) {
    46  	manifest, err := getManifest(ctx, repo, ref)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	switch v := manifest.(type) {
    52  	case *manifestlist.DeserializedManifestList:
    53  		imageManifests, err := pullManifestList(ctx, ref, repo, *v)
    54  		if err != nil {
    55  			return nil, err
    56  		}
    57  		return imageManifests, nil
    58  	default:
    59  		return nil, errors.Errorf("unsupported manifest format: %v", v)
    60  	}
    61  }
    62  
    63  func getManifest(ctx context.Context, repo distribution.Repository, ref reference.Named) (distribution.Manifest, error) {
    64  	manSvc, err := repo.Manifests(ctx)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	dgst, opts, err := getManifestOptionsFromReference(ref)
    70  	if err != nil {
    71  		return nil, errors.Errorf("image manifest for %q does not exist", ref)
    72  	}
    73  	return manSvc.Get(ctx, dgst, opts...)
    74  }
    75  
    76  func pullManifestSchemaV2(ctx context.Context, ref reference.Named, repo distribution.Repository, mfst schema2.DeserializedManifest) (types.ImageManifest, error) {
    77  	manifestDesc, err := validateManifestDigest(ref, mfst)
    78  	if err != nil {
    79  		return types.ImageManifest{}, err
    80  	}
    81  	configJSON, err := pullManifestSchemaV2ImageConfig(ctx, mfst.Target().Digest, repo)
    82  	if err != nil {
    83  		return types.ImageManifest{}, err
    84  	}
    85  
    86  	if manifestDesc.Platform == nil {
    87  		manifestDesc.Platform = &ocispec.Platform{}
    88  	}
    89  
    90  	// Fill in os and architecture fields from config JSON
    91  	if err := json.Unmarshal(configJSON, manifestDesc.Platform); err != nil {
    92  		return types.ImageManifest{}, err
    93  	}
    94  
    95  	return types.NewImageManifest(ref, manifestDesc, &mfst), nil
    96  }
    97  
    98  func pullManifestSchemaV2ImageConfig(ctx context.Context, dgst digest.Digest, repo distribution.Repository) ([]byte, error) {
    99  	blobs := repo.Blobs(ctx)
   100  	configJSON, err := blobs.Get(ctx, dgst)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	verifier := dgst.Verifier()
   106  	if _, err := verifier.Write(configJSON); err != nil {
   107  		return nil, err
   108  	}
   109  	if !verifier.Verified() {
   110  		return nil, errors.Errorf("image config verification failed for digest %s", dgst)
   111  	}
   112  	return configJSON, nil
   113  }
   114  
   115  // validateManifestDigest computes the manifest digest, and, if pulling by
   116  // digest, ensures that it matches the requested digest.
   117  func validateManifestDigest(ref reference.Named, mfst distribution.Manifest) (ocispec.Descriptor, error) {
   118  	mediaType, canonical, err := mfst.Payload()
   119  	if err != nil {
   120  		return ocispec.Descriptor{}, err
   121  	}
   122  	desc := ocispec.Descriptor{
   123  		Digest:    digest.FromBytes(canonical),
   124  		Size:      int64(len(canonical)),
   125  		MediaType: mediaType,
   126  	}
   127  
   128  	// If pull by digest, then verify the manifest digest.
   129  	if digested, isDigested := ref.(reference.Canonical); isDigested {
   130  		if digested.Digest() != desc.Digest {
   131  			err := fmt.Errorf("manifest verification failed for digest %s", digested.Digest())
   132  			return ocispec.Descriptor{}, err
   133  		}
   134  	}
   135  
   136  	return desc, nil
   137  }
   138  
   139  // pullManifestList handles "manifest lists" which point to various
   140  // platform-specific manifests.
   141  func pullManifestList(ctx context.Context, ref reference.Named, repo distribution.Repository, mfstList manifestlist.DeserializedManifestList) ([]types.ImageManifest, error) {
   142  	infos := []types.ImageManifest{}
   143  
   144  	if _, err := validateManifestDigest(ref, mfstList); err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	for _, manifestDescriptor := range mfstList.Manifests {
   149  		manSvc, err := repo.Manifests(ctx)
   150  		if err != nil {
   151  			return nil, err
   152  		}
   153  		manifest, err := manSvc.Get(ctx, manifestDescriptor.Digest)
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  		v, ok := manifest.(*schema2.DeserializedManifest)
   158  		if !ok {
   159  			return nil, fmt.Errorf("unsupported manifest format: %v", v)
   160  		}
   161  
   162  		manifestRef, err := reference.WithDigest(ref, manifestDescriptor.Digest)
   163  		if err != nil {
   164  			return nil, err
   165  		}
   166  		imageManifest, err := pullManifestSchemaV2(ctx, manifestRef, repo, *v)
   167  		if err != nil {
   168  			return nil, err
   169  		}
   170  
   171  		// Replace platform from config
   172  		imageManifest.Descriptor.Platform = types.OCIPlatform(&manifestDescriptor.Platform)
   173  
   174  		infos = append(infos, imageManifest)
   175  	}
   176  	return infos, nil
   177  }
   178  
   179  func continueOnError(err error) bool {
   180  	switch v := err.(type) {
   181  	case errcode.Errors:
   182  		if len(v) == 0 {
   183  			return true
   184  		}
   185  		return continueOnError(v[0])
   186  	case errcode.Error:
   187  		e := err.(errcode.Error)
   188  		switch e.Code {
   189  		case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
   190  			return true
   191  		}
   192  		return false
   193  	case *distclient.UnexpectedHTTPResponseError:
   194  		return true
   195  	}
   196  	return false
   197  }
   198  
   199  func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, each func(context.Context, distribution.Repository, reference.Named) (bool, error)) error {
   200  	endpoints, err := allEndpoints(namedRef, c.insecureRegistry)
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	repoInfo, err := registry.ParseRepositoryInfo(namedRef)
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	confirmedTLSRegistries := make(map[string]bool)
   211  	for _, endpoint := range endpoints {
   212  		if endpoint.Version == registry.APIVersion1 {
   213  			logrus.Debugf("skipping v1 endpoint %s", endpoint.URL)
   214  			continue
   215  		}
   216  
   217  		if endpoint.URL.Scheme != "https" {
   218  			if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
   219  				logrus.Debugf("skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
   220  				continue
   221  			}
   222  		}
   223  
   224  		if c.insecureRegistry {
   225  			endpoint.TLSConfig.InsecureSkipVerify = true
   226  		}
   227  		repoEndpoint := repositoryEndpoint{endpoint: endpoint, info: repoInfo}
   228  		repo, err := c.getRepositoryForReference(ctx, namedRef, repoEndpoint)
   229  		if err != nil {
   230  			logrus.Debugf("error %s with repo endpoint %+v", err, repoEndpoint)
   231  			if _, ok := err.(ErrHTTPProto); ok {
   232  				continue
   233  			}
   234  			return err
   235  		}
   236  
   237  		if endpoint.URL.Scheme == "http" && !c.insecureRegistry {
   238  			logrus.Debugf("skipping non-tls registry endpoint: %s", endpoint.URL)
   239  			continue
   240  		}
   241  		done, err := each(ctx, repo, namedRef)
   242  		if err != nil {
   243  			if continueOnError(err) {
   244  				if endpoint.URL.Scheme == "https" {
   245  					confirmedTLSRegistries[endpoint.URL.Host] = true
   246  				}
   247  				logrus.Debugf("continuing on error (%T) %s", err, err)
   248  				continue
   249  			}
   250  			logrus.Debugf("not continuing on error (%T) %s", err, err)
   251  			return err
   252  		}
   253  		if done {
   254  			return nil
   255  		}
   256  	}
   257  	return newNotFoundError(namedRef.String())
   258  }
   259  
   260  // allEndpoints returns a list of endpoints ordered by priority (v2, https, v1).
   261  func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) {
   262  	repoInfo, err := registry.ParseRepositoryInfo(namedRef)
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	var serviceOpts registry.ServiceOptions
   268  	if insecure {
   269  		logrus.Debugf("allowing insecure registry for: %s", reference.Domain(namedRef))
   270  		serviceOpts.InsecureRegistries = []string{reference.Domain(namedRef)}
   271  	}
   272  	registryService, err := registry.NewService(serviceOpts)
   273  	if err != nil {
   274  		return []registry.APIEndpoint{}, err
   275  	}
   276  	endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
   277  	logrus.Debugf("endpoints for %s: %v", namedRef, endpoints)
   278  	return endpoints, err
   279  }
   280  
   281  type notFoundError struct {
   282  	object string
   283  }
   284  
   285  func newNotFoundError(ref string) *notFoundError {
   286  	return &notFoundError{object: ref}
   287  }
   288  
   289  func (n *notFoundError) Error() string {
   290  	return fmt.Sprintf("no such manifest: %s", n.object)
   291  }
   292  
   293  // NotFound interface
   294  func (n *notFoundError) NotFound() {}
   295  
   296  // IsNotFound returns true if the error is a not found error
   297  func IsNotFound(err error) bool {
   298  	_, ok := err.(notFound)
   299  	return ok
   300  }
   301  
   302  type notFound interface {
   303  	NotFound()
   304  }