github.com/buildpack/pack@v0.5.0/create_builder.go (about)

     1  package pack
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/Masterminds/semver"
     8  	"github.com/buildpack/imgutil"
     9  	"github.com/pkg/errors"
    10  
    11  	"github.com/buildpack/pack/builder"
    12  	"github.com/buildpack/pack/dist"
    13  	"github.com/buildpack/pack/image"
    14  	"github.com/buildpack/pack/style"
    15  )
    16  
    17  type CreateBuilderOptions struct {
    18  	BuilderName   string
    19  	BuilderConfig builder.Config
    20  	Publish       bool
    21  	NoPull        bool
    22  }
    23  
    24  func (c *Client) CreateBuilder(ctx context.Context, opts CreateBuilderOptions) error {
    25  	if err := validateBuilderConfig(opts.BuilderConfig); err != nil {
    26  		return errors.Wrap(err, "invalid builder config")
    27  	}
    28  
    29  	if err := c.validateRunImageConfig(ctx, opts); err != nil {
    30  		return err
    31  	}
    32  
    33  	baseImage, err := c.imageFetcher.Fetch(ctx, opts.BuilderConfig.Stack.BuildImage, !opts.Publish, !opts.NoPull)
    34  	if err != nil {
    35  		return errors.Wrap(err, "fetch build image")
    36  	}
    37  
    38  	c.logger.Debugf("Creating builder %s from build-image %s", style.Symbol(opts.BuilderName), style.Symbol(baseImage.Name()))
    39  	builderImage, err := builder.New(baseImage, opts.BuilderName)
    40  	if err != nil {
    41  		return errors.Wrap(err, "invalid build-image")
    42  	}
    43  
    44  	builderImage.SetDescription(opts.BuilderConfig.Description)
    45  
    46  	if builderImage.StackID != opts.BuilderConfig.Stack.ID {
    47  		return fmt.Errorf(
    48  			"stack %s from builder config is incompatible with stack %s from build image",
    49  			style.Symbol(opts.BuilderConfig.Stack.ID),
    50  			style.Symbol(builderImage.StackID),
    51  		)
    52  	}
    53  
    54  	lifecycle, err := c.fetchLifecycle(ctx, opts.BuilderConfig.Lifecycle)
    55  	if err != nil {
    56  		return errors.Wrap(err, "fetch lifecycle")
    57  	}
    58  
    59  	if err := builderImage.SetLifecycle(lifecycle); err != nil {
    60  		return errors.Wrap(err, "setting lifecycle")
    61  	}
    62  
    63  	for _, b := range opts.BuilderConfig.Buildpacks {
    64  		err := ensureBPSupport(b.URI)
    65  		if err != nil {
    66  			return err
    67  		}
    68  
    69  		blob, err := c.downloader.Download(ctx, b.URI)
    70  		if err != nil {
    71  			return errors.Wrapf(err, "downloading buildpack from %s", style.Symbol(b.URI))
    72  		}
    73  
    74  		fetchedBp, err := dist.NewBuildpack(blob)
    75  		if err != nil {
    76  			return errors.Wrapf(err, "creating buildpack from %s", style.Symbol(b.URI))
    77  		}
    78  
    79  		err = validateBuildpack(fetchedBp, b.URI, b.ID, b.Version)
    80  		if err != nil {
    81  			return errors.Wrap(err, "invalid buildpack")
    82  		}
    83  
    84  		builderImage.AddBuildpack(fetchedBp)
    85  	}
    86  
    87  	builderImage.SetOrder(opts.BuilderConfig.Order)
    88  	builderImage.SetStackInfo(opts.BuilderConfig.Stack)
    89  
    90  	return builderImage.Save(c.logger)
    91  }
    92  
    93  func validateBuildpack(bp dist.Buildpack, source, expectedID, expectedBPVersion string) error {
    94  	if expectedID != "" && bp.Descriptor().Info.ID != expectedID {
    95  		return fmt.Errorf(
    96  			"buildpack from URI %s has ID %s which does not match ID %s from builder config",
    97  			style.Symbol(source),
    98  			style.Symbol(bp.Descriptor().Info.ID),
    99  			style.Symbol(expectedID),
   100  		)
   101  	}
   102  
   103  	if expectedBPVersion != "" && bp.Descriptor().Info.Version != expectedBPVersion {
   104  		return fmt.Errorf(
   105  			"buildpack from URI %s has version %s which does not match version %s from builder config",
   106  			style.Symbol(source),
   107  			style.Symbol(bp.Descriptor().Info.Version),
   108  			style.Symbol(expectedBPVersion),
   109  		)
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  func (c *Client) fetchLifecycle(ctx context.Context, config builder.LifecycleConfig) (builder.Lifecycle, error) {
   116  	if config.Version != "" && config.URI != "" {
   117  		return nil, errors.Errorf(
   118  			"%s can only declare %s or %s, not both",
   119  			style.Symbol("lifecycle"), style.Symbol("version"), style.Symbol("uri"),
   120  		)
   121  	}
   122  
   123  	var uri string
   124  	switch {
   125  	case config.Version != "":
   126  		v, err := semver.NewVersion(config.Version)
   127  		if err != nil {
   128  			return nil, errors.Wrapf(err, "%s must be a valid semver", style.Symbol("lifecycle.version"))
   129  		}
   130  
   131  		uri = uriFromLifecycleVersion(*v)
   132  	case config.URI != "":
   133  		uri = config.URI
   134  	default:
   135  		uri = uriFromLifecycleVersion(*semver.MustParse(builder.DefaultLifecycleVersion))
   136  	}
   137  
   138  	b, err := c.downloader.Download(ctx, uri)
   139  	if err != nil {
   140  		return nil, errors.Wrap(err, "downloading lifecycle")
   141  	}
   142  
   143  	lifecycle, err := builder.NewLifecycle(b)
   144  	if err != nil {
   145  		return nil, errors.Wrap(err, "invalid lifecycle")
   146  	}
   147  
   148  	return lifecycle, nil
   149  }
   150  
   151  func uriFromLifecycleVersion(version semver.Version) string {
   152  	return fmt.Sprintf("https://github.com/buildpack/lifecycle/releases/download/v%s/lifecycle-v%s+linux.x86-64.tgz", version.String(), version.String())
   153  }
   154  
   155  func validateBuilderConfig(conf builder.Config) error {
   156  	if conf.Stack.ID == "" {
   157  		return errors.New("stack.id is required")
   158  	}
   159  
   160  	if conf.Stack.BuildImage == "" {
   161  		return errors.New("stack.build-image is required")
   162  	}
   163  
   164  	if conf.Stack.RunImage == "" {
   165  		return errors.New("stack.run-image is required")
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  func (c *Client) validateRunImageConfig(ctx context.Context, opts CreateBuilderOptions) error {
   172  	var runImages []imgutil.Image
   173  	for _, i := range append([]string{opts.BuilderConfig.Stack.RunImage}, opts.BuilderConfig.Stack.RunImageMirrors...) {
   174  		if !opts.Publish {
   175  			img, err := c.imageFetcher.Fetch(ctx, i, true, false)
   176  			if err != nil {
   177  				if errors.Cause(err) != image.ErrNotFound {
   178  					return err
   179  				}
   180  			} else {
   181  				runImages = append(runImages, img)
   182  				continue
   183  			}
   184  		}
   185  
   186  		img, err := c.imageFetcher.Fetch(ctx, i, false, false)
   187  		if err != nil {
   188  			if errors.Cause(err) != image.ErrNotFound {
   189  				return err
   190  			}
   191  			c.logger.Warnf("run image %s is not accessible", style.Symbol(i))
   192  		} else {
   193  			runImages = append(runImages, img)
   194  		}
   195  	}
   196  
   197  	for _, img := range runImages {
   198  		stackID, err := img.Label("io.buildpacks.stack.id")
   199  		if err != nil {
   200  			return err
   201  		}
   202  
   203  		if stackID != opts.BuilderConfig.Stack.ID {
   204  			return fmt.Errorf(
   205  				"stack %s from builder config is incompatible with stack %s from run image %s",
   206  				style.Symbol(opts.BuilderConfig.Stack.ID),
   207  				style.Symbol(stackID),
   208  				style.Symbol(img.Name()),
   209  			)
   210  		}
   211  	}
   212  
   213  	return nil
   214  }