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  }