github.com/docker/cnab-to-oci@v0.3.0-beta4/remotes/pull.go (about)

     1  package remotes
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  
     9  	"github.com/cnabio/cnab-go/bundle"
    10  	"github.com/containerd/containerd/errdefs"
    11  	"github.com/containerd/containerd/images"
    12  	"github.com/containerd/containerd/log"
    13  	"github.com/containerd/containerd/remotes"
    14  	"github.com/docker/cli/opts"
    15  	"github.com/docker/cnab-to-oci/converter"
    16  	"github.com/docker/cnab-to-oci/relocation"
    17  	"github.com/docker/distribution/reference"
    18  	"github.com/docker/distribution/registry/client/auth"
    19  	ocischemav1 "github.com/opencontainers/image-spec/specs-go/v1"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  // Pull pulls a bundle from an OCI Image Index manifest
    24  func Pull(ctx context.Context, ref reference.Named, resolver remotes.Resolver) (*bundle.Bundle, relocation.ImageRelocationMap, error) {
    25  	log.G(ctx).Debugf("Pulling CNAB Bundle %s", ref)
    26  	index, err := getIndex(ctx, ref, resolver)
    27  	if err != nil {
    28  		return nil, nil, err
    29  	}
    30  	b, err := getBundle(ctx, ref, resolver, index)
    31  	if err != nil {
    32  		return nil, nil, err
    33  	}
    34  	relocationMap, err := converter.GenerateRelocationMap(&index, b, ref)
    35  	if err != nil {
    36  		return nil, nil, err
    37  	}
    38  	return b, relocationMap, nil
    39  }
    40  
    41  func getIndex(ctx context.Context, ref auth.Scope, resolver remotes.Resolver) (ocischemav1.Index, error) {
    42  	logger := log.G(ctx)
    43  
    44  	logger.Debug("Getting OCI Index Descriptor")
    45  	resolvedRef, indexDescriptor, err := resolver.Resolve(withMutedContext(ctx), ref.String())
    46  	if err != nil {
    47  		if errors.Cause(err) == errdefs.ErrNotFound {
    48  			return ocischemav1.Index{}, err
    49  		}
    50  		return ocischemav1.Index{}, fmt.Errorf("failed to resolve bundle manifest %q: %s", ref, err)
    51  	}
    52  	if indexDescriptor.MediaType != ocischemav1.MediaTypeImageIndex && indexDescriptor.MediaType != images.MediaTypeDockerSchema2ManifestList {
    53  		return ocischemav1.Index{}, fmt.Errorf("invalid media type %q for bundle manifest", indexDescriptor.MediaType)
    54  	}
    55  	logPayload(logger, indexDescriptor)
    56  
    57  	logger.Debugf("Fetching OCI Index %s", indexDescriptor.Digest)
    58  	indexPayload, err := pullPayload(ctx, resolver, resolvedRef, indexDescriptor)
    59  	if err != nil {
    60  		return ocischemav1.Index{}, fmt.Errorf("failed to pull bundle manifest %q: %s", ref, err)
    61  	}
    62  	var index ocischemav1.Index
    63  	if err := json.Unmarshal(indexPayload, &index); err != nil {
    64  		return ocischemav1.Index{}, fmt.Errorf("failed to pull bundle manifest %q: %s", ref, err)
    65  	}
    66  	logPayload(logger, index)
    67  
    68  	return index, nil
    69  }
    70  
    71  func getBundle(ctx context.Context, ref opts.NamedOption, resolver remotes.Resolver, index ocischemav1.Index) (*bundle.Bundle, error) {
    72  	repoOnly, err := reference.ParseNormalizedNamed(ref.Name())
    73  	if err != nil {
    74  		return nil, fmt.Errorf("invalid bundle manifest reference name %q: %s", ref, err)
    75  	}
    76  
    77  	// config is wrapped in an image manifest. So we first pull the manifest
    78  	// and then the config blob within it
    79  	configManifestDescriptor, err := getConfigManifestDescriptor(ctx, ref, index)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	manifest, err := getConfigManifest(ctx, ref, repoOnly, resolver, configManifestDescriptor)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	// Pull now the bundle itself
    90  	return getBundleConfig(ctx, ref, repoOnly, resolver, manifest)
    91  }
    92  
    93  func getConfigManifestDescriptor(ctx context.Context, ref opts.NamedOption, index ocischemav1.Index) (ocischemav1.Descriptor, error) {
    94  	logger := log.G(ctx)
    95  
    96  	logger.Debug("Getting Bundle Config Manifest Descriptor")
    97  	configManifestDescriptor, err := converter.GetBundleConfigManifestDescriptor(&index)
    98  	if err != nil {
    99  		return ocischemav1.Descriptor{}, fmt.Errorf("failed to get bundle config manifest from %q: %s", ref, err)
   100  	}
   101  	logPayload(logger, configManifestDescriptor)
   102  
   103  	return configManifestDescriptor, nil
   104  }
   105  
   106  func getConfigManifest(ctx context.Context, ref opts.NamedOption, repoOnly reference.Named, resolver remotes.Resolver, configManifestDescriptor ocischemav1.Descriptor) (ocischemav1.Manifest, error) {
   107  	logger := log.G(ctx)
   108  
   109  	logger.Debugf("Getting Bundle Config Manifest %s", configManifestDescriptor.Digest)
   110  	configManifestRef, err := reference.WithDigest(repoOnly, configManifestDescriptor.Digest)
   111  	if err != nil {
   112  		return ocischemav1.Manifest{}, fmt.Errorf("invalid bundle config manifest reference name %q: %s", ref, err)
   113  	}
   114  	configManifestPayload, err := pullPayload(ctx, resolver, configManifestRef.String(), configManifestDescriptor)
   115  	if err != nil {
   116  		return ocischemav1.Manifest{}, fmt.Errorf("failed to pull bundle config manifest %q: %s", ref, err)
   117  	}
   118  	var manifest ocischemav1.Manifest
   119  	if err := json.Unmarshal(configManifestPayload, &manifest); err != nil {
   120  		return ocischemav1.Manifest{}, err
   121  	}
   122  	logPayload(logger, manifest)
   123  
   124  	return manifest, err
   125  }
   126  
   127  func getBundleConfig(ctx context.Context, ref opts.NamedOption, repoOnly reference.Named, resolver remotes.Resolver, manifest ocischemav1.Manifest) (*bundle.Bundle, error) {
   128  	logger := log.G(ctx)
   129  
   130  	logger.Debugf("Fetching Bundle %s", manifest.Config.Digest)
   131  	configRef, err := reference.WithDigest(repoOnly, manifest.Config.Digest)
   132  	if err != nil {
   133  		return nil, fmt.Errorf("invalid bundle reference name %q: %s", ref, err)
   134  	}
   135  	configPayload, err := pullPayload(ctx, resolver, configRef.String(), ocischemav1.Descriptor{
   136  		Digest:    manifest.Config.Digest,
   137  		MediaType: manifest.Config.MediaType,
   138  		Size:      manifest.Config.Size,
   139  	})
   140  	if err != nil {
   141  		return nil, fmt.Errorf("failed to pull bundle %q: %s", ref, err)
   142  	}
   143  	var b bundle.Bundle
   144  	if err := json.Unmarshal(configPayload, &b); err != nil {
   145  		return nil, fmt.Errorf("failed to pull bundle %q: %s", ref, err)
   146  	}
   147  	logPayload(logger, b)
   148  
   149  	return &b, nil
   150  }
   151  
   152  func pullPayload(ctx context.Context, resolver remotes.Resolver, reference string, descriptor ocischemav1.Descriptor) ([]byte, error) {
   153  	ctx = withMutedContext(ctx)
   154  	fetcher, err := resolver.Fetcher(ctx, reference)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	reader, err := fetcher.Fetch(ctx, descriptor)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	defer reader.Close()
   163  
   164  	result, err := ioutil.ReadAll(reader)
   165  	return result, err
   166  }