github.com/YousefHaggyHeroku/pack@v1.5.5/internal/builder/builder.go (about) 1 package builder 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path" 11 "path/filepath" 12 "regexp" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/BurntSushi/toml" 18 "github.com/buildpacks/imgutil" 19 "github.com/pkg/errors" 20 21 "github.com/YousefHaggyHeroku/pack/builder" 22 "github.com/YousefHaggyHeroku/pack/internal/archive" 23 "github.com/YousefHaggyHeroku/pack/internal/dist" 24 "github.com/YousefHaggyHeroku/pack/internal/layer" 25 "github.com/YousefHaggyHeroku/pack/internal/stack" 26 "github.com/YousefHaggyHeroku/pack/internal/style" 27 "github.com/YousefHaggyHeroku/pack/logging" 28 ) 29 30 const ( 31 packName = "Pack CLI" 32 33 cnbDir = "/cnb" 34 35 orderPath = "/cnb/order.toml" 36 stackPath = "/cnb/stack.toml" 37 platformDir = "/platform" 38 lifecycleDir = "/cnb/lifecycle" 39 compatLifecycleDir = "/lifecycle" 40 workspaceDir = "/workspace" 41 layersDir = "/layers" 42 43 metadataLabel = "io.buildpacks.builder.metadata" 44 stackLabel = "io.buildpacks.stack.id" 45 46 EnvUID = "CNB_USER_ID" 47 EnvGID = "CNB_GROUP_ID" 48 ) 49 50 // Builder represents a pack builder, used to build images 51 type Builder struct { 52 baseImageName string 53 image imgutil.Image 54 layerWriterFactory archive.TarWriterFactory 55 lifecycle Lifecycle 56 lifecycleDescriptor LifecycleDescriptor 57 additionalBuildpacks []dist.Buildpack 58 metadata Metadata 59 mixins []string 60 env map[string]string 61 uid, gid int 62 StackID string 63 replaceOrder bool 64 order dist.Order 65 } 66 67 type orderTOML struct { 68 Order dist.Order `toml:"order"` 69 } 70 71 // FromImage constructs a builder from a builder image 72 func FromImage(img imgutil.Image) (*Builder, error) { 73 var metadata Metadata 74 if ok, err := dist.GetLabel(img, metadataLabel, &metadata); err != nil { 75 return nil, errors.Wrapf(err, "getting label %s", metadataLabel) 76 } else if !ok { 77 return nil, fmt.Errorf("builder %s missing label %s -- try recreating builder", style.Symbol(img.Name()), style.Symbol(metadataLabel)) 78 } 79 return constructBuilder(img, "", metadata) 80 } 81 82 // New constructs a new builder from a base image 83 func New(baseImage imgutil.Image, name string) (*Builder, error) { 84 var metadata Metadata 85 if _, err := dist.GetLabel(baseImage, metadataLabel, &metadata); err != nil { 86 return nil, errors.Wrapf(err, "getting label %s", metadataLabel) 87 } 88 return constructBuilder(baseImage, name, metadata) 89 } 90 91 func constructBuilder(img imgutil.Image, newName string, metadata Metadata) (*Builder, error) { 92 imageOS, err := img.OS() 93 if err != nil { 94 return nil, errors.Wrap(err, "getting image OS") 95 } 96 layerWriterFactory, err := layer.NewWriterFactory(imageOS) 97 if err != nil { 98 return nil, err 99 } 100 101 bldr := &Builder{ 102 baseImageName: img.Name(), 103 image: img, 104 layerWriterFactory: layerWriterFactory, 105 metadata: metadata, 106 lifecycleDescriptor: constructLifecycleDescriptor(metadata), 107 env: map[string]string{}, 108 } 109 110 if err := addImgLabelsToBuildr(bldr); err != nil { 111 return nil, errors.Wrap(err, "adding image labels to builder") 112 } 113 114 if newName != "" && img.Name() != newName { 115 img.Rename(newName) 116 } 117 118 return bldr, nil 119 } 120 121 func constructLifecycleDescriptor(metadata Metadata) LifecycleDescriptor { 122 return CompatDescriptor(LifecycleDescriptor{ 123 Info: LifecycleInfo{ 124 Version: metadata.Lifecycle.Version, 125 }, 126 API: metadata.Lifecycle.API, 127 APIs: metadata.Lifecycle.APIs, 128 }) 129 } 130 131 func addImgLabelsToBuildr(bldr *Builder) error { 132 var err error 133 bldr.uid, bldr.gid, err = userAndGroupIDs(bldr.image) 134 if err != nil { 135 return err 136 } 137 138 bldr.StackID, err = bldr.image.Label(stackLabel) 139 if err != nil { 140 return errors.Wrapf(err, "get label %s from image %s", style.Symbol(stackLabel), style.Symbol(bldr.image.Name())) 141 } 142 if bldr.StackID == "" { 143 return fmt.Errorf("image %s missing label %s", style.Symbol(bldr.image.Name()), style.Symbol(stackLabel)) 144 } 145 146 if _, err = dist.GetLabel(bldr.image, stack.MixinsLabel, &bldr.mixins); err != nil { 147 return errors.Wrapf(err, "getting label %s", stack.MixinsLabel) 148 } 149 150 if _, err = dist.GetLabel(bldr.image, OrderLabel, &bldr.order); err != nil { 151 return errors.Wrapf(err, "getting label %s", OrderLabel) 152 } 153 154 return nil 155 } 156 157 // Getters 158 159 // Description returns the builder description 160 func (b *Builder) Description() string { 161 return b.metadata.Description 162 } 163 164 // LifecycleDescriptor returns the LifecycleDescriptor 165 func (b *Builder) LifecycleDescriptor() LifecycleDescriptor { 166 return b.lifecycleDescriptor 167 } 168 169 // Buildpacks returns the buildpack list 170 func (b *Builder) Buildpacks() []dist.BuildpackInfo { 171 return b.metadata.Buildpacks 172 } 173 174 // CreatedBy returns metadata around the creation of the builder 175 func (b *Builder) CreatedBy() CreatorMetadata { 176 return b.metadata.CreatedBy 177 } 178 179 // Order returns the order 180 func (b *Builder) Order() dist.Order { 181 return b.order 182 } 183 184 // Name returns the name of the builder 185 func (b *Builder) Name() string { 186 return b.image.Name() 187 } 188 189 // Image returns the base image 190 func (b *Builder) Image() imgutil.Image { 191 return b.image 192 } 193 194 // Stack returns the stack metadata 195 func (b *Builder) Stack() StackMetadata { 196 return b.metadata.Stack 197 } 198 199 // Mixins returns the mixins of the builder 200 func (b *Builder) Mixins() []string { 201 return b.mixins 202 } 203 204 // UID returns the UID of the builder 205 func (b *Builder) UID() int { 206 return b.uid 207 } 208 209 // GID returns the GID of the builder 210 func (b *Builder) GID() int { 211 return b.gid 212 } 213 214 // Setters 215 216 // AddBuildpack adds a buildpack to the builder 217 func (b *Builder) AddBuildpack(bp dist.Buildpack) { 218 b.additionalBuildpacks = append(b.additionalBuildpacks, bp) 219 b.metadata.Buildpacks = append(b.metadata.Buildpacks, bp.Descriptor().Info) 220 } 221 222 // SetLifecycle sets the lifecycle of the builder 223 func (b *Builder) SetLifecycle(lifecycle Lifecycle) { 224 b.lifecycle = lifecycle 225 b.lifecycleDescriptor = lifecycle.Descriptor() 226 } 227 228 // SetEnv sets an environment variable to a value 229 func (b *Builder) SetEnv(env map[string]string) { 230 b.env = env 231 } 232 233 // SetOrder sets the order of the builder 234 func (b *Builder) SetOrder(order dist.Order) { 235 b.order = order 236 b.replaceOrder = true 237 } 238 239 // SetDescription sets the description of the builder 240 func (b *Builder) SetDescription(description string) { 241 b.metadata.Description = description 242 } 243 244 // SetStack sets the stack of the builder 245 func (b *Builder) SetStack(stackConfig builder.StackConfig) { 246 b.metadata.Stack = StackMetadata{ 247 RunImage: RunImageMetadata{ 248 Image: stackConfig.RunImage, 249 Mirrors: stackConfig.RunImageMirrors, 250 }, 251 } 252 } 253 254 // Save saves the builder 255 func (b *Builder) Save(logger logging.Logger, creatorMetadata CreatorMetadata) error { 256 logger.Debugf("Creating builder with the following buildpacks:") 257 for _, bpInfo := range b.metadata.Buildpacks { 258 logger.Debugf("-> %s", style.Symbol(bpInfo.FullName())) 259 } 260 261 resolvedOrder, err := processOrder(b.metadata.Buildpacks, b.order) 262 if err != nil { 263 return errors.Wrap(err, "processing order") 264 } 265 266 tmpDir, err := ioutil.TempDir("", "create-builder-scratch") 267 if err != nil { 268 return err 269 } 270 defer os.RemoveAll(tmpDir) 271 272 dirsTar, err := b.defaultDirsLayer(tmpDir) 273 if err != nil { 274 return err 275 } 276 if err := b.image.AddLayer(dirsTar); err != nil { 277 return errors.Wrap(err, "adding default dirs layer") 278 } 279 280 if b.lifecycle != nil { 281 lifecycleDescriptor := b.lifecycle.Descriptor() 282 b.metadata.Lifecycle.LifecycleInfo = lifecycleDescriptor.Info 283 b.metadata.Lifecycle.API = lifecycleDescriptor.API 284 b.metadata.Lifecycle.APIs = lifecycleDescriptor.APIs 285 lifecycleTar, err := b.lifecycleLayer(tmpDir) 286 if err != nil { 287 return err 288 } 289 if err := b.image.AddLayer(lifecycleTar); err != nil { 290 return errors.Wrap(err, "adding lifecycle layer") 291 } 292 } 293 294 if err := validateBuildpacks(b.StackID, b.Mixins(), b.LifecycleDescriptor(), b.Buildpacks(), b.additionalBuildpacks); err != nil { 295 return errors.Wrap(err, "validating buildpacks") 296 } 297 298 bpLayers := dist.BuildpackLayers{} 299 if _, err := dist.GetLabel(b.image, dist.BuildpackLayersLabel, &bpLayers); err != nil { 300 return errors.Wrapf(err, "getting label %s", dist.BuildpackLayersLabel) 301 } 302 303 for _, bp := range b.additionalBuildpacks { 304 bpLayerTar, err := dist.BuildpackToLayerTar(tmpDir, bp) 305 if err != nil { 306 return err 307 } 308 309 if err := b.image.AddLayer(bpLayerTar); err != nil { 310 return errors.Wrapf(err, 311 "adding layer tar for buildpack %s", 312 style.Symbol(bp.Descriptor().Info.FullName()), 313 ) 314 } 315 316 diffID, err := dist.LayerDiffID(bpLayerTar) 317 if err != nil { 318 return errors.Wrapf(err, 319 "getting content hashes for buildpack %s", 320 style.Symbol(bp.Descriptor().Info.FullName()), 321 ) 322 } 323 324 bpInfo := bp.Descriptor().Info 325 if _, ok := bpLayers[bpInfo.ID][bpInfo.Version]; ok { 326 logger.Debugf( 327 "buildpack %s already exists on builder and will be overwritten", 328 style.Symbol(bpInfo.FullName()), 329 ) 330 } 331 332 dist.AddBuildpackToLayersMD(bpLayers, bp.Descriptor(), diffID.String()) 333 } 334 335 if err := dist.SetLabel(b.image, dist.BuildpackLayersLabel, bpLayers); err != nil { 336 return err 337 } 338 339 if b.replaceOrder { 340 orderTar, err := b.orderLayer(resolvedOrder, tmpDir) 341 if err != nil { 342 return err 343 } 344 if err := b.image.AddLayer(orderTar); err != nil { 345 return errors.Wrap(err, "adding order.tar layer") 346 } 347 348 if err := dist.SetLabel(b.image, OrderLabel, b.order); err != nil { 349 return err 350 } 351 } 352 353 stackTar, err := b.stackLayer(tmpDir) 354 if err != nil { 355 return err 356 } 357 if err := b.image.AddLayer(stackTar); err != nil { 358 return errors.Wrap(err, "adding stack.tar layer") 359 } 360 361 envTar, err := b.envLayer(tmpDir, b.env) 362 if err != nil { 363 return err 364 } 365 if err := b.image.AddLayer(envTar); err != nil { 366 return errors.Wrap(err, "adding env layer") 367 } 368 369 if creatorMetadata.Name == "" { 370 creatorMetadata.Name = packName 371 } 372 373 b.metadata.CreatedBy = creatorMetadata 374 375 if err := dist.SetLabel(b.image, metadataLabel, b.metadata); err != nil { 376 return err 377 } 378 379 if err := dist.SetLabel(b.image, stack.MixinsLabel, b.mixins); err != nil { 380 return err 381 } 382 383 if err := b.image.SetWorkingDir(layersDir); err != nil { 384 return errors.Wrap(err, "failed to set working dir") 385 } 386 387 return b.image.Save() 388 } 389 390 // Helpers 391 392 func processOrder(buildpacks []dist.BuildpackInfo, order dist.Order) (dist.Order, error) { 393 resolvedOrder := dist.Order{} 394 395 for gi, g := range order { 396 resolvedOrder = append(resolvedOrder, dist.OrderEntry{}) 397 398 for _, bpRef := range g.Group { 399 var matchingBps []dist.BuildpackInfo 400 for _, bp := range buildpacks { 401 if bpRef.ID == bp.ID { 402 matchingBps = append(matchingBps, bp) 403 } 404 } 405 406 if len(matchingBps) == 0 { 407 return dist.Order{}, fmt.Errorf("no versions of buildpack %s were found on the builder", style.Symbol(bpRef.ID)) 408 } 409 410 if bpRef.Version == "" { 411 if len(uniqueVersions(matchingBps)) > 1 { 412 return dist.Order{}, fmt.Errorf("unable to resolve version: multiple versions of %s - must specify an explicit version", style.Symbol(bpRef.ID)) 413 } 414 415 bpRef.Version = matchingBps[0].Version 416 } 417 418 if !hasBuildpackWithVersion(matchingBps, bpRef.Version) { 419 return dist.Order{}, fmt.Errorf("buildpack %s with version %s was not found on the builder", style.Symbol(bpRef.ID), style.Symbol(bpRef.Version)) 420 } 421 422 resolvedOrder[gi].Group = append(resolvedOrder[gi].Group, bpRef) 423 } 424 } 425 426 return resolvedOrder, nil 427 } 428 429 func hasBuildpackWithVersion(bps []dist.BuildpackInfo, version string) bool { 430 for _, bp := range bps { 431 if bp.Version == version { 432 return true 433 } 434 } 435 return false 436 } 437 438 func validateBuildpacks(stackID string, mixins []string, lifecycleDescriptor LifecycleDescriptor, allBuildpacks []dist.BuildpackInfo, bpsToValidate []dist.Buildpack) error { 439 bpLookup := map[string]interface{}{} 440 441 for _, bp := range allBuildpacks { 442 bpLookup[bp.FullName()] = nil 443 } 444 445 for _, bp := range bpsToValidate { 446 bpd := bp.Descriptor() 447 448 // TODO: Warn when Buildpack API is deprecated - https://github.com/YousefHaggyHeroku/pack/issues/788 449 compatible := false 450 for _, version := range append(lifecycleDescriptor.APIs.Buildpack.Supported, lifecycleDescriptor.APIs.Buildpack.Deprecated...) { 451 compatible = version.Compare(bpd.API) == 0 452 if compatible { 453 break 454 } 455 } 456 457 if !compatible { 458 return fmt.Errorf( 459 "buildpack %s (Buildpack API %s) is incompatible with lifecycle %s (Buildpack API(s) %s)", 460 style.Symbol(bpd.Info.FullName()), 461 bpd.API.String(), 462 style.Symbol(lifecycleDescriptor.Info.Version.String()), 463 strings.Join(lifecycleDescriptor.APIs.Buildpack.Supported.AsStrings(), ", "), 464 ) 465 } 466 467 if len(bpd.Stacks) >= 1 { // standard buildpack 468 if err := bpd.EnsureStackSupport(stackID, mixins, false); err != nil { 469 return err 470 } 471 } else { // order buildpack 472 for _, g := range bpd.Order { 473 for _, r := range g.Group { 474 if _, ok := bpLookup[r.FullName()]; !ok { 475 return fmt.Errorf( 476 "buildpack %s not found on the builder", 477 style.Symbol(r.FullName()), 478 ) 479 } 480 } 481 } 482 } 483 } 484 485 return nil 486 } 487 488 func userAndGroupIDs(img imgutil.Image) (int, int, error) { 489 sUID, err := img.Env(EnvUID) 490 if err != nil { 491 return 0, 0, errors.Wrap(err, "reading builder env variables") 492 } else if sUID == "" { 493 return 0, 0, fmt.Errorf("image %s missing required env var %s", style.Symbol(img.Name()), style.Symbol(EnvUID)) 494 } 495 496 sGID, err := img.Env(EnvGID) 497 if err != nil { 498 return 0, 0, errors.Wrap(err, "reading builder env variables") 499 } else if sGID == "" { 500 return 0, 0, fmt.Errorf("image %s missing required env var %s", style.Symbol(img.Name()), style.Symbol(EnvGID)) 501 } 502 503 var uid, gid int 504 uid, err = strconv.Atoi(sUID) 505 if err != nil { 506 return 0, 0, fmt.Errorf("failed to parse %s, value %s should be an integer", style.Symbol(EnvUID), style.Symbol(sUID)) 507 } 508 509 gid, err = strconv.Atoi(sGID) 510 if err != nil { 511 return 0, 0, fmt.Errorf("failed to parse %s, value %s should be an integer", style.Symbol(EnvGID), style.Symbol(sGID)) 512 } 513 514 return uid, gid, nil 515 } 516 517 func uniqueVersions(buildpacks []dist.BuildpackInfo) []string { 518 results := []string{} 519 set := map[string]interface{}{} 520 for _, bpInfo := range buildpacks { 521 _, ok := set[bpInfo.Version] 522 if !ok { 523 results = append(results, bpInfo.Version) 524 set[bpInfo.Version] = true 525 } 526 } 527 return results 528 } 529 530 func (b *Builder) defaultDirsLayer(dest string) (string, error) { 531 fh, err := os.Create(filepath.Join(dest, "dirs.tar")) 532 if err != nil { 533 return "", err 534 } 535 defer fh.Close() 536 537 lw := b.layerWriterFactory.NewWriter(fh) 538 defer lw.Close() 539 540 ts := archive.NormalizedDateTime 541 542 for _, path := range []string{workspaceDir, layersDir} { 543 if err := lw.WriteHeader(b.packOwnedDir(path, ts)); err != nil { 544 return "", errors.Wrapf(err, "creating %s dir in layer", style.Symbol(path)) 545 } 546 } 547 548 // can't use filepath.Join(), to ensure Windows doesn't transform it to Windows join 549 for _, path := range []string{cnbDir, dist.BuildpacksDir, platformDir, platformDir + "/env"} { 550 if err := lw.WriteHeader(b.rootOwnedDir(path, ts)); err != nil { 551 return "", errors.Wrapf(err, "creating %s dir in layer", style.Symbol(path)) 552 } 553 } 554 555 return fh.Name(), nil 556 } 557 558 func (b *Builder) packOwnedDir(path string, time time.Time) *tar.Header { 559 return &tar.Header{ 560 Typeflag: tar.TypeDir, 561 Name: path, 562 Mode: 0755, 563 ModTime: time, 564 Uid: b.uid, 565 Gid: b.gid, 566 } 567 } 568 569 func (b *Builder) rootOwnedDir(path string, time time.Time) *tar.Header { 570 return &tar.Header{ 571 Typeflag: tar.TypeDir, 572 Name: path, 573 Mode: 0755, 574 ModTime: time, 575 } 576 } 577 578 func (b *Builder) lifecycleLayer(dest string) (string, error) { 579 fh, err := os.Create(filepath.Join(dest, "lifecycle.tar")) 580 if err != nil { 581 return "", err 582 } 583 defer fh.Close() 584 585 lw := b.layerWriterFactory.NewWriter(fh) 586 defer lw.Close() 587 588 if err := lw.WriteHeader(&tar.Header{ 589 Typeflag: tar.TypeDir, 590 Name: lifecycleDir, 591 Mode: 0755, 592 ModTime: archive.NormalizedDateTime, 593 }); err != nil { 594 return "", err 595 } 596 597 err = b.embedLifecycleTar(lw) 598 if err != nil { 599 return "", errors.Wrap(err, "embedding lifecycle tar") 600 } 601 602 if err := lw.WriteHeader(&tar.Header{ 603 Name: compatLifecycleDir, 604 Linkname: lifecycleDir, 605 Typeflag: tar.TypeSymlink, 606 Mode: 0644, 607 ModTime: archive.NormalizedDateTime, 608 }); err != nil { 609 return "", errors.Wrapf(err, "creating %s symlink", style.Symbol(compatLifecycleDir)) 610 } 611 612 return fh.Name(), nil 613 } 614 615 func (b *Builder) embedLifecycleTar(tw archive.TarWriter) error { 616 var regex = regexp.MustCompile(`^[^/]+/([^/]+)$`) 617 618 lr, err := b.lifecycle.Open() 619 if err != nil { 620 return errors.Wrap(err, "failed to open lifecycle") 621 } 622 defer lr.Close() 623 tr := tar.NewReader(lr) 624 for { 625 header, err := tr.Next() 626 if err == io.EOF { 627 break 628 } 629 if err != nil { 630 return errors.Wrap(err, "failed to get next tar entry") 631 } 632 633 pathMatches := regex.FindStringSubmatch(path.Clean(header.Name)) 634 if pathMatches != nil { 635 binaryName := pathMatches[1] 636 637 header.Name = lifecycleDir + "/" + binaryName 638 err = tw.WriteHeader(header) 639 if err != nil { 640 return errors.Wrapf(err, "failed to write header for '%s'", header.Name) 641 } 642 643 buf, err := ioutil.ReadAll(tr) 644 if err != nil { 645 return errors.Wrapf(err, "failed to read contents of '%s'", header.Name) 646 } 647 648 _, err = tw.Write(buf) 649 if err != nil { 650 return errors.Wrapf(err, "failed to write contents to '%s'", header.Name) 651 } 652 } 653 } 654 655 return nil 656 } 657 658 func (b *Builder) orderLayer(order dist.Order, dest string) (string, error) { 659 contents, err := orderFileContents(order) 660 if err != nil { 661 return "", err 662 } 663 664 layerTar := filepath.Join(dest, "order.tar") 665 err = layer.CreateSingleFileTar(layerTar, orderPath, contents, b.layerWriterFactory) 666 if err != nil { 667 return "", errors.Wrapf(err, "failed to create order.toml layer tar") 668 } 669 670 return layerTar, nil 671 } 672 673 func orderFileContents(order dist.Order) (string, error) { 674 buf := &bytes.Buffer{} 675 676 tomlData := orderTOML{Order: order} 677 if err := toml.NewEncoder(buf).Encode(tomlData); err != nil { 678 return "", errors.Wrapf(err, "failed to marshal order.toml") 679 } 680 return buf.String(), nil 681 } 682 683 func (b *Builder) stackLayer(dest string) (string, error) { 684 buf := &bytes.Buffer{} 685 err := toml.NewEncoder(buf).Encode(b.metadata.Stack) 686 if err != nil { 687 return "", errors.Wrapf(err, "failed to marshal stack.toml") 688 } 689 690 layerTar := filepath.Join(dest, "stack.tar") 691 err = layer.CreateSingleFileTar(layerTar, stackPath, buf.String(), b.layerWriterFactory) 692 if err != nil { 693 return "", errors.Wrapf(err, "failed to create stack.toml layer tar") 694 } 695 696 return layerTar, nil 697 } 698 699 func (b *Builder) envLayer(dest string, env map[string]string) (string, error) { 700 fh, err := os.Create(filepath.Join(dest, "env.tar")) 701 if err != nil { 702 return "", err 703 } 704 defer fh.Close() 705 706 lw := b.layerWriterFactory.NewWriter(fh) 707 defer lw.Close() 708 709 for k, v := range env { 710 if err := lw.WriteHeader(&tar.Header{ 711 Name: path.Join(platformDir, "env", k), 712 Size: int64(len(v)), 713 Mode: 0644, 714 ModTime: archive.NormalizedDateTime, 715 }); err != nil { 716 return "", err 717 } 718 if _, err := lw.Write([]byte(v)); err != nil { 719 return "", err 720 } 721 } 722 723 return fh.Name(), nil 724 }