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