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 }