github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/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  	"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 != nil {
   107  		return nil, err
   108  	}
   109  	if _, err := verifier.Write(configJSON); err != nil {
   110  		return nil, err
   111  	}
   112  	if !verifier.Verified() {
   113  		return nil, errors.Errorf("image config verification failed for digest %s", dgst)
   114  	}
   115  	return configJSON, nil
   116  }
   117  
   118  // validateManifestDigest computes the manifest digest, and, if pulling by
   119  // digest, ensures that it matches the requested digest.
   120  func validateManifestDigest(ref reference.Named, mfst distribution.Manifest) (ocispec.Descriptor, error) {
   121  	mediaType, canonical, err := mfst.Payload()
   122  	if err != nil {
   123  		return ocispec.Descriptor{}, err
   124  	}
   125  	desc := ocispec.Descriptor{
   126  		Digest:    digest.FromBytes(canonical),
   127  		Size:      int64(len(canonical)),
   128  		MediaType: mediaType,
   129  	}
   130  
   131  	// If pull by digest, then verify the manifest digest.
   132  	if digested, isDigested := ref.(reference.Canonical); isDigested {
   133  		if digested.Digest() != desc.Digest {
   134  			err := fmt.Errorf("manifest verification failed for digest %s", digested.Digest())
   135  			return ocispec.Descriptor{}, err
   136  		}
   137  	}
   138  
   139  	return desc, nil
   140  }
   141  
   142  // pullManifestList handles "manifest lists" which point to various
   143  // platform-specific manifests.
   144  func pullManifestList(ctx context.Context, ref reference.Named, repo distribution.Repository, mfstList manifestlist.DeserializedManifestList) ([]types.ImageManifest, error) {
   145  	infos := []types.ImageManifest{}
   146  
   147  	if _, err := validateManifestDigest(ref, mfstList); err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	for _, manifestDescriptor := range mfstList.Manifests {
   152  		manSvc, err := repo.Manifests(ctx)
   153  		if err != nil {
   154  			return nil, err
   155  		}
   156  		manifest, err := manSvc.Get(ctx, manifestDescriptor.Digest)
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  		v, ok := manifest.(*schema2.DeserializedManifest)
   161  		if !ok {
   162  			return nil, fmt.Errorf("unsupported manifest format: %v", v)
   163  		}
   164  
   165  		manifestRef, err := reference.WithDigest(ref, manifestDescriptor.Digest)
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  		imageManifest, err := pullManifestSchemaV2(ctx, manifestRef, repo, *v)
   170  		if err != nil {
   171  			return nil, err
   172  		}
   173  
   174  		// Replace platform from config
   175  		imageManifest.Descriptor.Platform = types.OCIPlatform(&manifestDescriptor.Platform)
   176  
   177  		infos = append(infos, imageManifest)
   178  	}
   179  	return infos, nil
   180  }
   181  
   182  func continueOnError(err error) bool {
   183  	switch v := err.(type) {
   184  	case errcode.Errors:
   185  		if len(v) == 0 {
   186  			return true
   187  		}
   188  		return continueOnError(v[0])
   189  	case errcode.Error:
   190  		e := err.(errcode.Error)
   191  		switch e.Code {
   192  		case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
   193  			return true
   194  		}
   195  		return false
   196  	case *distclient.UnexpectedHTTPResponseError:
   197  		return true
   198  	}
   199  	return false
   200  }
   201  
   202  func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, each func(context.Context, distribution.Repository, reference.Named) (bool, error)) error {
   203  	endpoints, err := allEndpoints(namedRef, c.insecureRegistry)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	repoInfo, err := registry.ParseRepositoryInfo(namedRef)
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	confirmedTLSRegistries := make(map[string]bool)
   214  	for _, endpoint := range endpoints {
   215  
   216  		if endpoint.Version == registry.APIVersion1 {
   217  			logrus.Debugf("skipping v1 endpoint %s", endpoint.URL)
   218  			continue
   219  		}
   220  
   221  		if endpoint.URL.Scheme != "https" {
   222  			if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
   223  				logrus.Debugf("skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
   224  				continue
   225  			}
   226  		}
   227  
   228  		if c.insecureRegistry {
   229  			endpoint.TLSConfig.InsecureSkipVerify = true
   230  		}
   231  		repoEndpoint := repositoryEndpoint{endpoint: endpoint, info: repoInfo}
   232  		repo, err := c.getRepositoryForReference(ctx, namedRef, repoEndpoint)
   233  		if err != nil {
   234  			logrus.Debugf("error with repo endpoint %s: %s", repoEndpoint, err)
   235  			if _, ok := err.(ErrHTTPProto); ok {
   236  				continue
   237  			}
   238  			return err
   239  		}
   240  
   241  		if endpoint.URL.Scheme == "http" && !c.insecureRegistry {
   242  			logrus.Debugf("skipping non-tls registry endpoint: %s", endpoint.URL)
   243  			continue
   244  		}
   245  		done, err := each(ctx, repo, namedRef)
   246  		if err != nil {
   247  			if continueOnError(err) {
   248  				if endpoint.URL.Scheme == "https" {
   249  					confirmedTLSRegistries[endpoint.URL.Host] = true
   250  				}
   251  				logrus.Debugf("continuing on error (%T) %s", err, err)
   252  				continue
   253  			}
   254  			logrus.Debugf("not continuing on error (%T) %s", err, err)
   255  			return err
   256  		}
   257  		if done {
   258  			return nil
   259  		}
   260  	}
   261  	return newNotFoundError(namedRef.String())
   262  }
   263  
   264  // allEndpoints returns a list of endpoints ordered by priority (v2, https, v1).
   265  func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) {
   266  	repoInfo, err := registry.ParseRepositoryInfo(namedRef)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  
   271  	var serviceOpts registry.ServiceOptions
   272  	if insecure {
   273  		logrus.Debugf("allowing insecure registry for: %s", reference.Domain(namedRef))
   274  		serviceOpts.InsecureRegistries = []string{reference.Domain(namedRef)}
   275  	}
   276  	registryService, err := registry.NewService(serviceOpts)
   277  	if err != nil {
   278  		return []registry.APIEndpoint{}, err
   279  	}
   280  	endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
   281  	logrus.Debugf("endpoints for %s: %v", namedRef, endpoints)
   282  	return endpoints, err
   283  }
   284  
   285  type notFoundError struct {
   286  	object string
   287  }
   288  
   289  func newNotFoundError(ref string) *notFoundError {
   290  	return &notFoundError{object: ref}
   291  }
   292  
   293  func (n *notFoundError) Error() string {
   294  	return fmt.Sprintf("no such manifest: %s", n.object)
   295  }
   296  
   297  // NotFound interface
   298  func (n *notFoundError) NotFound() {}
   299  
   300  // IsNotFound returns true if the error is a not found error
   301  func IsNotFound(err error) bool {
   302  	_, ok := err.(notFound)
   303  	return ok
   304  }
   305  
   306  type notFound interface {
   307  	NotFound()
   308  }