github.com/YousefHaggyHeroku/pack@v1.5.5/internal/buildpackage/oci_layout_package.go (about) 1 package buildpackage 2 3 import ( 4 "archive/tar" 5 "compress/gzip" 6 "encoding/json" 7 "io" 8 "path" 9 "strings" 10 11 "github.com/docker/docker/pkg/ioutils" 12 v1 "github.com/opencontainers/image-spec/specs-go/v1" 13 "github.com/pkg/errors" 14 15 "github.com/YousefHaggyHeroku/pack/internal/archive" 16 blob2 "github.com/YousefHaggyHeroku/pack/internal/blob" 17 "github.com/YousefHaggyHeroku/pack/internal/dist" 18 "github.com/YousefHaggyHeroku/pack/internal/style" 19 ) 20 21 // IsOCILayoutBlob checks whether a blob is in OCI layout format. 22 func IsOCILayoutBlob(blob blob2.Blob) (bool, error) { 23 readCloser, err := blob.Open() 24 if err != nil { 25 return false, err 26 } 27 defer readCloser.Close() 28 29 _, _, err = archive.ReadTarEntry(readCloser, "/oci-layout") 30 if err != nil { 31 if archive.IsEntryNotExist(err) { 32 return false, nil 33 } 34 35 return false, err 36 } 37 38 return true, nil 39 } 40 41 // BuildpackFromOCILayoutBlob constructs buildpacks from a blob in OCI layout format. 42 func BuildpacksFromOCILayoutBlob(blob dist.Blob) (mainBP dist.Buildpack, dependencies []dist.Buildpack, err error) { 43 layoutPackage, err := newOCILayoutPackage(blob) 44 if err != nil { 45 return nil, nil, err 46 } 47 48 return ExtractBuildpacks(layoutPackage) 49 } 50 51 func ConfigFromOCILayoutBlob(blob dist.Blob) (config v1.ImageConfig, err error) { 52 layoutPackage, err := newOCILayoutPackage(blob) 53 if err != nil { 54 return v1.ImageConfig{}, err 55 } 56 return layoutPackage.imageInfo.Config, nil 57 } 58 59 type ociLayoutPackage struct { 60 imageInfo v1.Image 61 manifest v1.Manifest 62 blob dist.Blob 63 } 64 65 func newOCILayoutPackage(blob dist.Blob) (*ociLayoutPackage, error) { 66 index := &v1.Index{} 67 68 if err := unmarshalJSONFromBlob(blob, "/index.json", index); err != nil { 69 return nil, err 70 } 71 72 var manifestDescriptor *v1.Descriptor 73 for _, m := range index.Manifests { 74 if m.MediaType == "application/vnd.docker.distribution.manifest.v2+json" { 75 manifestDescriptor = &m // nolint:scopelint 76 break 77 } 78 } 79 80 if manifestDescriptor == nil { 81 return nil, errors.New("unable to find manifest") 82 } 83 84 manifest := &v1.Manifest{} 85 if err := unmarshalJSONFromBlob(blob, pathFromDescriptor(*manifestDescriptor), manifest); err != nil { 86 return nil, err 87 } 88 89 imageInfo := &v1.Image{} 90 if err := unmarshalJSONFromBlob(blob, pathFromDescriptor(manifest.Config), imageInfo); err != nil { 91 return nil, err 92 } 93 94 layersLabel := imageInfo.Config.Labels[dist.BuildpackLayersLabel] 95 if layersLabel == "" { 96 return nil, errors.Errorf("label %s not found", style.Symbol(dist.BuildpackLayersLabel)) 97 } 98 99 bpLayers := dist.BuildpackLayers{} 100 if err := json.Unmarshal([]byte(layersLabel), &bpLayers); err != nil { 101 return nil, errors.Wrap(err, "unmarshaling layers label") 102 } 103 104 return &ociLayoutPackage{ 105 imageInfo: *imageInfo, 106 manifest: *manifest, 107 blob: blob, 108 }, nil 109 } 110 111 func (o *ociLayoutPackage) Label(name string) (value string, err error) { 112 return o.imageInfo.Config.Labels[name], nil 113 } 114 115 func (o *ociLayoutPackage) GetLayer(diffID string) (io.ReadCloser, error) { 116 index := -1 117 for i, dID := range o.imageInfo.RootFS.DiffIDs { 118 if dID.String() == diffID { 119 index = i 120 break 121 } 122 } 123 if index == -1 { 124 return nil, errors.Errorf("layer %s not found in rootfs", style.Symbol(diffID)) 125 } 126 127 layerDescriptor := o.manifest.Layers[index] 128 layerPath := pathFromDescriptor(layerDescriptor) 129 130 blobReader, err := o.blob.Open() 131 if err != nil { 132 return nil, err 133 } 134 135 tr := tar.NewReader(blobReader) 136 for { 137 header, err := tr.Next() 138 if err == io.EOF { 139 break 140 } 141 if err != nil { 142 return nil, errors.Wrap(err, "failed to get next tar entry") 143 } 144 145 if path.Clean(header.Name) == path.Clean(layerPath) { 146 finalReader := blobReader 147 148 if strings.HasSuffix(layerDescriptor.MediaType, ".gzip") { 149 finalReader, err = gzip.NewReader(tr) 150 if err != nil { 151 return nil, err 152 } 153 } 154 155 return ioutils.NewReadCloserWrapper(finalReader, func() error { 156 if err := finalReader.Close(); err != nil { 157 return err 158 } 159 160 return blobReader.Close() 161 }), nil 162 } 163 } 164 165 if err := blobReader.Close(); err != nil { 166 return nil, err 167 } 168 169 return nil, errors.Errorf("layer blob %s not found", style.Symbol(layerPath)) 170 } 171 172 func pathFromDescriptor(descriptor v1.Descriptor) string { 173 return path.Join("/blobs", descriptor.Digest.Algorithm().String(), descriptor.Digest.Encoded()) 174 } 175 176 func unmarshalJSONFromBlob(blob dist.Blob, path string, obj interface{}) error { 177 reader, err := blob.Open() 178 if err != nil { 179 return err 180 } 181 defer reader.Close() 182 183 _, contents, err := archive.ReadTarEntry(reader, path) 184 if err != nil { 185 return err 186 } 187 188 if err = json.Unmarshal(contents, obj); err != nil { 189 return err 190 } 191 192 return nil 193 }