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 }