github.com/YousefHaggyHeroku/pack@v1.5.5/internal/buildpackage/builder.go (about)

     1  package buildpackage
     2  
     3  import (
     4  	"archive/tar"
     5  	"compress/gzip"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  
    10  	"github.com/buildpacks/imgutil/layer"
    11  
    12  	"github.com/buildpacks/imgutil"
    13  	v1 "github.com/google/go-containerregistry/pkg/v1"
    14  	"github.com/google/go-containerregistry/pkg/v1/empty"
    15  	"github.com/google/go-containerregistry/pkg/v1/layout"
    16  	"github.com/google/go-containerregistry/pkg/v1/mutate"
    17  	"github.com/google/go-containerregistry/pkg/v1/tarball"
    18  	"github.com/pkg/errors"
    19  
    20  	"github.com/YousefHaggyHeroku/pack/internal/archive"
    21  	"github.com/YousefHaggyHeroku/pack/internal/dist"
    22  	"github.com/YousefHaggyHeroku/pack/internal/stack"
    23  	"github.com/YousefHaggyHeroku/pack/internal/style"
    24  )
    25  
    26  type ImageFactory interface {
    27  	NewImage(repoName string, local bool) (imgutil.Image, error)
    28  }
    29  
    30  type WorkableImage interface {
    31  	SetOS(string) error
    32  	SetLabel(string, string) error
    33  	AddLayerWithDiffID(path, diffID string) error
    34  }
    35  
    36  type layoutImage struct {
    37  	v1.Image
    38  }
    39  
    40  func (i *layoutImage) SetLabel(key string, val string) error {
    41  	configFile, err := i.ConfigFile()
    42  	if err != nil {
    43  		return err
    44  	}
    45  	config := *configFile.Config.DeepCopy()
    46  	if config.Labels == nil {
    47  		config.Labels = map[string]string{}
    48  	}
    49  	config.Labels[key] = val
    50  	i.Image, err = mutate.Config(i.Image, config)
    51  	return err
    52  }
    53  
    54  func (i *layoutImage) SetOS(osVal string) error {
    55  	configFile, err := i.ConfigFile()
    56  	if err != nil {
    57  		return err
    58  	}
    59  	configFile.OS = osVal
    60  	i.Image, err = mutate.ConfigFile(i.Image, configFile)
    61  	return err
    62  }
    63  
    64  func (i *layoutImage) AddLayerWithDiffID(path, _ string) error {
    65  	tarLayer, err := tarball.LayerFromFile(path, tarball.WithCompressionLevel(gzip.DefaultCompression))
    66  	if err != nil {
    67  		return err
    68  	}
    69  	i.Image, err = mutate.AppendLayers(i.Image, tarLayer)
    70  	if err != nil {
    71  		return errors.Wrap(err, "add layer")
    72  	}
    73  	return nil
    74  }
    75  
    76  type PackageBuilder struct {
    77  	buildpack    dist.Buildpack
    78  	dependencies []dist.Buildpack
    79  	imageFactory ImageFactory
    80  }
    81  
    82  func NewBuilder(imageFactory ImageFactory) *PackageBuilder {
    83  	return &PackageBuilder{
    84  		imageFactory: imageFactory,
    85  	}
    86  }
    87  
    88  func (b *PackageBuilder) SetBuildpack(buildpack dist.Buildpack) {
    89  	b.buildpack = buildpack
    90  }
    91  
    92  func (b *PackageBuilder) AddDependency(buildpack dist.Buildpack) {
    93  	b.dependencies = append(b.dependencies, buildpack)
    94  }
    95  
    96  func (b *PackageBuilder) finalizeImage(image WorkableImage, imageOS, tmpDir string) error {
    97  	if err := dist.SetLabel(image, MetadataLabel, &Metadata{
    98  		BuildpackInfo: b.buildpack.Descriptor().Info,
    99  		Stacks:        b.resolvedStacks(),
   100  	}); err != nil {
   101  		return err
   102  	}
   103  
   104  	if err := image.SetOS(imageOS); err != nil {
   105  		return err
   106  	}
   107  
   108  	if imageOS == "windows" {
   109  		if err := addWindowsShimBaseLayer(image, tmpDir); err != nil {
   110  			return err
   111  		}
   112  	}
   113  
   114  	bpLayers := dist.BuildpackLayers{}
   115  	for _, bp := range append(b.dependencies, b.buildpack) {
   116  		bpLayerTar, err := dist.BuildpackToLayerTar(tmpDir, bp)
   117  		if err != nil {
   118  			return err
   119  		}
   120  
   121  		diffID, err := dist.LayerDiffID(bpLayerTar)
   122  		if err != nil {
   123  			return errors.Wrapf(err,
   124  				"getting content hashes for buildpack %s",
   125  				style.Symbol(bp.Descriptor().Info.FullName()),
   126  			)
   127  		}
   128  
   129  		if err := image.AddLayerWithDiffID(bpLayerTar, diffID.String()); err != nil {
   130  			return errors.Wrapf(err, "adding layer tar for buildpack %s", style.Symbol(bp.Descriptor().Info.FullName()))
   131  		}
   132  
   133  		dist.AddBuildpackToLayersMD(bpLayers, bp.Descriptor(), diffID.String())
   134  	}
   135  
   136  	if err := dist.SetLabel(image, dist.BuildpackLayersLabel, bpLayers); err != nil {
   137  		return err
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  func addWindowsShimBaseLayer(image WorkableImage, tmpDir string) error {
   144  	baseLayerFile, err := ioutil.TempFile(tmpDir, "windows-baselayer")
   145  	if err != nil {
   146  		return err
   147  	}
   148  	defer baseLayerFile.Close()
   149  
   150  	baseLayer, err := layer.WindowsBaseLayer()
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	if _, err := io.Copy(baseLayerFile, baseLayer); err != nil {
   156  		return err
   157  	}
   158  
   159  	if err := baseLayerFile.Close(); err != nil {
   160  		return err
   161  	}
   162  
   163  	baseLayerPath := baseLayerFile.Name()
   164  	diffID, err := dist.LayerDiffID(baseLayerPath)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	if err := image.AddLayerWithDiffID(baseLayerPath, diffID.String()); err != nil {
   170  		return err
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  func (b *PackageBuilder) validate() error {
   177  	if b.buildpack == nil {
   178  		return errors.New("buildpack must be set")
   179  	}
   180  
   181  	if err := validateBuildpacks(b.buildpack, b.dependencies); err != nil {
   182  		return err
   183  	}
   184  
   185  	if len(b.resolvedStacks()) == 0 {
   186  		return errors.Errorf("no compatible stacks among provided buildpacks")
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  func (b *PackageBuilder) resolvedStacks() []dist.Stack {
   193  	stacks := b.buildpack.Descriptor().Stacks
   194  	for _, bp := range b.dependencies {
   195  		bpd := bp.Descriptor()
   196  
   197  		if len(stacks) == 0 {
   198  			stacks = bpd.Stacks
   199  		} else if len(bpd.Stacks) > 0 { // skip over "meta-buildpacks"
   200  			stacks = stack.MergeCompatible(stacks, bpd.Stacks)
   201  		}
   202  	}
   203  
   204  	return stacks
   205  }
   206  
   207  func (b *PackageBuilder) SaveAsFile(path, imageOS string) error {
   208  	if err := b.validate(); err != nil {
   209  		return err
   210  	}
   211  
   212  	layoutImage := &layoutImage{
   213  		Image: empty.Image,
   214  	}
   215  
   216  	tmpDir, err := ioutil.TempDir("", "package-buildpack")
   217  	if err != nil {
   218  		return err
   219  	}
   220  	defer os.RemoveAll(tmpDir)
   221  
   222  	if err := b.finalizeImage(layoutImage, imageOS, tmpDir); err != nil {
   223  		return err
   224  	}
   225  
   226  	layoutDir, err := ioutil.TempDir(tmpDir, "oci-layout")
   227  	if err != nil {
   228  		return errors.Wrap(err, "creating oci-layout temp dir")
   229  	}
   230  
   231  	p, err := layout.Write(layoutDir, empty.Index)
   232  	if err != nil {
   233  		return errors.Wrap(err, "writing index")
   234  	}
   235  
   236  	if err := p.AppendImage(layoutImage); err != nil {
   237  		return errors.Wrap(err, "writing layout")
   238  	}
   239  
   240  	outputFile, err := os.Create(path)
   241  	if err != nil {
   242  		return errors.Wrap(err, "creating output file")
   243  	}
   244  	defer outputFile.Close()
   245  
   246  	tw := tar.NewWriter(outputFile)
   247  	defer tw.Close()
   248  
   249  	return archive.WriteDirToTar(tw, layoutDir, "/", 0, 0, 0755, true, nil)
   250  }
   251  
   252  func (b *PackageBuilder) SaveAsImage(repoName string, publish bool, imageOS string) (imgutil.Image, error) {
   253  	if err := b.validate(); err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	image, err := b.imageFactory.NewImage(repoName, !publish)
   258  	if err != nil {
   259  		return nil, errors.Wrapf(err, "creating image")
   260  	}
   261  
   262  	tmpDir, err := ioutil.TempDir("", "package-buildpack")
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	defer os.RemoveAll(tmpDir)
   267  
   268  	if err := b.finalizeImage(image, imageOS, tmpDir); err != nil {
   269  		return nil, err
   270  	}
   271  
   272  	if err := image.Save(); err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	return image, nil
   277  }
   278  
   279  func validateBuildpacks(mainBP dist.Buildpack, depBPs []dist.Buildpack) error {
   280  	depsWithRefs := map[string][]dist.BuildpackInfo{}
   281  
   282  	for _, bp := range depBPs {
   283  		depsWithRefs[bp.Descriptor().Info.FullName()] = nil
   284  	}
   285  
   286  	for _, bp := range append([]dist.Buildpack{mainBP}, depBPs...) { // List of everything
   287  		bpd := bp.Descriptor()
   288  		for _, orderEntry := range bpd.Order {
   289  			for _, groupEntry := range orderEntry.Group {
   290  				if _, ok := depsWithRefs[groupEntry.BuildpackInfo.FullName()]; !ok {
   291  					return errors.Errorf(
   292  						"buildpack %s references buildpack %s which is not present",
   293  						style.Symbol(bpd.Info.FullName()),
   294  						style.Symbol(groupEntry.FullName()),
   295  					)
   296  				}
   297  
   298  				depsWithRefs[groupEntry.BuildpackInfo.FullName()] = append(depsWithRefs[groupEntry.BuildpackInfo.FullName()], bpd.Info)
   299  			}
   300  		}
   301  	}
   302  
   303  	for bp, refs := range depsWithRefs {
   304  		if len(refs) == 0 {
   305  			return errors.Errorf(
   306  				"buildpack %s is not used by buildpack %s",
   307  				style.Symbol(bp),
   308  				style.Symbol(mainBP.Descriptor().Info.FullName()),
   309  			)
   310  		}
   311  	}
   312  
   313  	return nil
   314  }