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  }