github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/build.go (about) 1 package client 2 3 import ( 4 "archive/tar" 5 "context" 6 "crypto/rand" 7 "crypto/sha256" 8 "encoding/hex" 9 "encoding/json" 10 "fmt" 11 "io" 12 "os" 13 "path/filepath" 14 "sort" 15 "strconv" 16 "strings" 17 "time" 18 19 "github.com/Masterminds/semver" 20 "github.com/buildpacks/imgutil" 21 "github.com/buildpacks/imgutil/layout" 22 "github.com/buildpacks/imgutil/local" 23 "github.com/buildpacks/imgutil/remote" 24 "github.com/buildpacks/lifecycle/platform/files" 25 types "github.com/docker/docker/api/types/image" 26 "github.com/google/go-containerregistry/pkg/name" 27 "github.com/pkg/errors" 28 ignore "github.com/sabhiram/go-gitignore" 29 30 "github.com/buildpacks/pack/buildpackage" 31 "github.com/buildpacks/pack/internal/build" 32 "github.com/buildpacks/pack/internal/builder" 33 internalConfig "github.com/buildpacks/pack/internal/config" 34 "github.com/buildpacks/pack/internal/layer" 35 pname "github.com/buildpacks/pack/internal/name" 36 "github.com/buildpacks/pack/internal/paths" 37 "github.com/buildpacks/pack/internal/stack" 38 "github.com/buildpacks/pack/internal/stringset" 39 "github.com/buildpacks/pack/internal/style" 40 "github.com/buildpacks/pack/internal/termui" 41 "github.com/buildpacks/pack/pkg/archive" 42 "github.com/buildpacks/pack/pkg/buildpack" 43 "github.com/buildpacks/pack/pkg/cache" 44 "github.com/buildpacks/pack/pkg/dist" 45 "github.com/buildpacks/pack/pkg/image" 46 "github.com/buildpacks/pack/pkg/logging" 47 projectTypes "github.com/buildpacks/pack/pkg/project/types" 48 v02 "github.com/buildpacks/pack/pkg/project/v02" 49 ) 50 51 const ( 52 minLifecycleVersionSupportingCreator = "0.7.4" 53 prevLifecycleVersionSupportingImage = "0.6.1" 54 minLifecycleVersionSupportingImage = "0.7.5" 55 minLifecycleVersionSupportingCreatorWithExtensions = "0.19.0" 56 ) 57 58 // LifecycleExecutor executes the lifecycle which satisfies the Cloud Native Buildpacks Lifecycle specification. 59 // Implementations of the Lifecycle must execute the following phases by calling the 60 // phase-specific lifecycle binary in order: 61 // 62 // Detection: /cnb/lifecycle/detector 63 // Analysis: /cnb/lifecycle/analyzer 64 // Cache Restoration: /cnb/lifecycle/restorer 65 // Build: /cnb/lifecycle/builder 66 // Export: /cnb/lifecycle/exporter 67 // 68 // or invoke the single creator binary: 69 // 70 // Creator: /cnb/lifecycle/creator 71 type LifecycleExecutor interface { 72 // Execute is responsible for invoking each of these binaries 73 // with the desired configuration. 74 Execute(ctx context.Context, opts build.LifecycleOptions) error 75 } 76 77 type IsTrustedBuilder func(string) bool 78 79 // BuildOptions defines configuration settings for a Build. 80 type BuildOptions struct { 81 // The base directory to use to resolve relative assets 82 RelativeBaseDir string 83 84 // required. Name of output image. 85 Image string 86 87 // required. Builder image name. 88 Builder string 89 90 // Name of the buildpack registry. Used to 91 // add buildpacks to a build. 92 Registry string 93 94 // AppPath is the path to application bits. 95 // If unset it defaults to current working directory. 96 AppPath string 97 98 // Specify the run image the Image will be 99 // built atop. 100 RunImage string 101 102 // Address of docker daemon exposed to build container 103 // e.g. tcp://example.com:1234, unix:///run/user/1000/podman/podman.sock 104 DockerHost string 105 106 // Used to determine a run-image mirror if Run Image is empty. 107 // Used in combination with Builder metadata to determine to the 'best' mirror. 108 // 'best' is defined as: 109 // - if Publish is true, the best mirror matches registry we are publishing to. 110 // - if Publish is false, the best mirror matches a registry specified in Image. 111 // - otherwise if both of the above did not match, use mirror specified in 112 // the builder metadata 113 AdditionalMirrors map[string][]string 114 115 // User provided environment variables to the buildpacks. 116 // Buildpacks may both read and overwrite these values. 117 Env map[string]string 118 119 // Used to configure various cache available options 120 Cache cache.CacheOpts 121 122 // Option only valid if Publish is true 123 // Create an additional image that contains cache=true layers and push it to the registry. 124 CacheImage string 125 126 // Option passed directly to the lifecycle. 127 // If true, publishes Image directly to a registry. 128 // Assumes Image contains a valid registry with credentials 129 // provided by the docker client. 130 Publish bool 131 132 // Clear the build cache from previous builds. 133 ClearCache bool 134 135 // Launch a terminal UI to depict the build process 136 Interactive bool 137 138 // List of buildpack images or archives to add to a builder. 139 // These buildpacks may overwrite those on the builder if they 140 // share both an ID and Version with a buildpack on the builder. 141 Buildpacks []string 142 143 // List of extension images or archives to add to a builder. 144 // These extensions may overwrite those on the builder if they 145 // share both an ID and Version with an extension on the builder. 146 Extensions []string 147 148 // Additional image tags to push to, each will contain contents identical to Image 149 AdditionalTags []string 150 151 // Configure the proxy environment variables, 152 // These variables will only be set in the build image 153 // and will not be used if proxy env vars are already set. 154 ProxyConfig *ProxyConfig 155 156 // Configure network and volume mounts for the build containers. 157 ContainerConfig ContainerConfig 158 159 // Process type that will be used when setting container start command. 160 DefaultProcessType string 161 162 // Strategy for updating local images before a build. 163 PullPolicy image.PullPolicy 164 165 // ProjectDescriptorBaseDir is the base directory to find relative resources referenced by the ProjectDescriptor 166 ProjectDescriptorBaseDir string 167 168 // ProjectDescriptor describes the project and any configuration specific to the project 169 ProjectDescriptor projectTypes.Descriptor 170 171 // List of buildpack images or archives to add to a builder. 172 // these buildpacks will be prepended to the builder's order 173 PreBuildpacks []string 174 175 // List of buildpack images or archives to add to a builder. 176 // these buildpacks will be appended to the builder's order 177 PostBuildpacks []string 178 179 // The lifecycle image that will be used for the analysis, restore and export phases 180 // when using an untrusted builder. 181 LifecycleImage string 182 183 // The location at which to mount the AppDir in the build image. 184 Workspace string 185 186 // User's group id used to build the image 187 GroupID int 188 189 // User's user id used to build the image 190 UserID int 191 192 // A previous image to set to a particular tag reference, digest reference, or (when performing a daemon build) image ID; 193 PreviousImage string 194 195 // TrustBuilder when true optimizes builds by running 196 // all lifecycle phases in a single container. 197 // This places registry credentials on the builder's build image. 198 // Only trust builders from reputable sources. 199 TrustBuilder IsTrustedBuilder 200 201 // Directory to output any SBOM artifacts 202 SBOMDestinationDir string 203 204 // Directory to output the report.toml metadata artifact 205 ReportDestinationDir string 206 207 // Desired create time in the output image config 208 CreationTime *time.Time 209 210 // Configuration to export to OCI layout format 211 LayoutConfig *LayoutConfig 212 } 213 214 func (b *BuildOptions) Layout() bool { 215 if b.LayoutConfig != nil { 216 return b.LayoutConfig.Enable() 217 } 218 return false 219 } 220 221 // ProxyConfig specifies proxy setting to be set as environment variables in a container. 222 type ProxyConfig struct { 223 HTTPProxy string // Used to set HTTP_PROXY env var. 224 HTTPSProxy string // Used to set HTTPS_PROXY env var. 225 NoProxy string // Used to set NO_PROXY env var. 226 } 227 228 // ContainerConfig is additional configuration of the docker container that all build steps 229 // occur within. 230 type ContainerConfig struct { 231 // Configure network settings of the build containers. 232 // The value of Network is handed directly to the docker client. 233 // For valid values of this field see: 234 // https://docs.docker.com/network/#network-drivers 235 Network string 236 237 // Volumes are accessible during both detect build phases 238 // should have the form: /path/in/host:/path/in/container. 239 // For more about volume mounts, and their permissions see: 240 // https://docs.docker.com/storage/volumes/ 241 // 242 // It is strongly recommended you do not override any of the 243 // paths with volume mounts at the following locations: 244 // - /cnb 245 // - /layers 246 // - anything below /cnb/** 247 Volumes []string 248 } 249 250 type LayoutConfig struct { 251 // Application image reference provided by the user 252 InputImage InputImageReference 253 254 // Previous image reference provided by the user 255 PreviousInputImage InputImageReference 256 257 // Local root path to save the run-image in OCI layout format 258 LayoutRepoDir string 259 260 // Configure the OCI layout fetch mode to avoid saving layers on disk 261 Sparse bool 262 } 263 264 func (l *LayoutConfig) Enable() bool { 265 return l.InputImage.Layout() 266 } 267 268 type layoutPathConfig struct { 269 hostImagePath string 270 hostPreviousImagePath string 271 hostRunImagePath string 272 targetImagePath string 273 targetPreviousImagePath string 274 targetRunImagePath string 275 } 276 277 var IsTrustedBuilderFunc = func(b string) bool { 278 for _, knownBuilder := range builder.KnownBuilders { 279 if b == knownBuilder.Image && knownBuilder.Trusted { 280 return true 281 } 282 } 283 return false 284 } 285 286 // Build configures settings for the build container(s) and lifecycle. 287 // It then invokes the lifecycle to build an app image. 288 // If any configuration is deemed invalid, or if any lifecycle phases fail, 289 // an error will be returned and no image produced. 290 func (c *Client) Build(ctx context.Context, opts BuildOptions) error { 291 var pathsConfig layoutPathConfig 292 293 imageRef, err := c.parseReference(opts) 294 if err != nil { 295 return errors.Wrapf(err, "invalid image name '%s'", opts.Image) 296 } 297 imgRegistry := imageRef.Context().RegistryStr() 298 imageName := imageRef.Name() 299 300 if opts.Layout() { 301 pathsConfig, err = c.processLayoutPath(opts.LayoutConfig.InputImage, opts.LayoutConfig.PreviousInputImage) 302 if err != nil { 303 if opts.LayoutConfig.PreviousInputImage != nil { 304 return errors.Wrapf(err, "invalid layout paths image name '%s' or previous-image name '%s'", opts.LayoutConfig.InputImage.Name(), 305 opts.LayoutConfig.PreviousInputImage.Name()) 306 } 307 return errors.Wrapf(err, "invalid layout paths image name '%s'", opts.LayoutConfig.InputImage.Name()) 308 } 309 } 310 311 appPath, err := c.processAppPath(opts.AppPath) 312 if err != nil { 313 return errors.Wrapf(err, "invalid app path '%s'", opts.AppPath) 314 } 315 316 proxyConfig := c.processProxyConfig(opts.ProxyConfig) 317 318 builderRef, err := c.processBuilderName(opts.Builder) 319 if err != nil { 320 return errors.Wrapf(err, "invalid builder '%s'", opts.Builder) 321 } 322 323 rawBuilderImage, err := c.imageFetcher.Fetch(ctx, builderRef.Name(), image.FetchOptions{Daemon: true, PullPolicy: opts.PullPolicy}) 324 if err != nil { 325 return errors.Wrapf(err, "failed to fetch builder image '%s'", builderRef.Name()) 326 } 327 328 builderOS, err := rawBuilderImage.OS() 329 if err != nil { 330 return errors.Wrapf(err, "getting builder OS") 331 } 332 333 builderArch, err := rawBuilderImage.Architecture() 334 if err != nil { 335 return errors.Wrapf(err, "getting builder architecture") 336 } 337 338 bldr, err := c.getBuilder(rawBuilderImage) 339 if err != nil { 340 return errors.Wrapf(err, "invalid builder %s", style.Symbol(opts.Builder)) 341 } 342 343 fetchOptions := image.FetchOptions{ 344 Daemon: !opts.Publish, 345 PullPolicy: opts.PullPolicy, 346 Platform: fmt.Sprintf("%s/%s", builderOS, builderArch), 347 } 348 runImageName := c.resolveRunImage(opts.RunImage, imgRegistry, builderRef.Context().RegistryStr(), bldr.DefaultRunImage(), opts.AdditionalMirrors, opts.Publish, fetchOptions) 349 350 if opts.Layout() { 351 targetRunImagePath, err := layout.ParseRefToPath(runImageName) 352 if err != nil { 353 return err 354 } 355 hostRunImagePath := filepath.Join(opts.LayoutConfig.LayoutRepoDir, targetRunImagePath) 356 targetRunImagePath = filepath.Join(paths.RootDir, "layout-repo", targetRunImagePath) 357 fetchOptions.LayoutOption = image.LayoutOption{ 358 Path: hostRunImagePath, 359 Sparse: opts.LayoutConfig.Sparse, 360 } 361 fetchOptions.Daemon = false 362 pathsConfig.targetRunImagePath = targetRunImagePath 363 pathsConfig.hostRunImagePath = hostRunImagePath 364 } 365 runImage, err := c.validateRunImage(ctx, runImageName, fetchOptions, bldr.StackID) 366 if err != nil { 367 return errors.Wrapf(err, "invalid run-image '%s'", runImageName) 368 } 369 370 var runMixins []string 371 if _, err := dist.GetLabel(runImage, stack.MixinsLabel, &runMixins); err != nil { 372 return err 373 } 374 375 fetchedBPs, order, err := c.processBuildpacks(ctx, bldr.Image(), bldr.Buildpacks(), bldr.Order(), bldr.StackID, opts) 376 if err != nil { 377 return err 378 } 379 380 fetchedExs, orderExtensions, err := c.processExtensions(ctx, bldr.Image(), bldr.Extensions(), bldr.OrderExtensions(), bldr.StackID, opts) 381 if err != nil { 382 return err 383 } 384 385 // Default mode: if the TrustBuilder option is not set, trust the suggested builders. 386 if opts.TrustBuilder == nil { 387 opts.TrustBuilder = IsTrustedBuilderFunc 388 } 389 390 // Ensure the builder's platform APIs are supported 391 var builderPlatformAPIs builder.APISet 392 builderPlatformAPIs = append(builderPlatformAPIs, bldr.LifecycleDescriptor().APIs.Platform.Deprecated...) 393 builderPlatformAPIs = append(builderPlatformAPIs, bldr.LifecycleDescriptor().APIs.Platform.Supported...) 394 if !supportsPlatformAPI(builderPlatformAPIs) { 395 c.logger.Debugf("pack %s supports Platform API(s): %s", c.version, strings.Join(build.SupportedPlatformAPIVersions.AsStrings(), ", ")) 396 c.logger.Debugf("Builder %s supports Platform API(s): %s", style.Symbol(opts.Builder), strings.Join(builderPlatformAPIs.AsStrings(), ", ")) 397 return errors.Errorf("Builder %s is incompatible with this version of pack", style.Symbol(opts.Builder)) 398 } 399 400 // Get the platform API version to use 401 lifecycleVersion := bldr.LifecycleDescriptor().Info.Version 402 useCreator := supportsCreator(lifecycleVersion) && opts.TrustBuilder(opts.Builder) 403 var ( 404 lifecycleOptsLifecycleImage string 405 lifecycleAPIs []string 406 ) 407 if !(useCreator) { 408 // fetch the lifecycle image 409 if supportsLifecycleImage(lifecycleVersion) { 410 lifecycleImageName := opts.LifecycleImage 411 if lifecycleImageName == "" { 412 lifecycleImageName = fmt.Sprintf("%s:%s", internalConfig.DefaultLifecycleImageRepo, lifecycleVersion.String()) 413 } 414 415 lifecycleImage, err := c.imageFetcher.Fetch( 416 ctx, 417 lifecycleImageName, 418 image.FetchOptions{ 419 Daemon: true, 420 PullPolicy: opts.PullPolicy, 421 Platform: fmt.Sprintf("%s/%s", builderOS, builderArch), 422 }, 423 ) 424 if err != nil { 425 return fmt.Errorf("fetching lifecycle image: %w", err) 426 } 427 428 // if lifecyle container os isn't windows, use ephemeral lifecycle to add /workspace with correct ownership 429 imageOS, err := lifecycleImage.OS() 430 if err != nil { 431 return errors.Wrap(err, "getting lifecycle image OS") 432 } 433 if imageOS != "windows" { 434 // obtain uid/gid from builder to use when extending lifecycle image 435 uid, gid, err := userAndGroupIDs(rawBuilderImage) 436 if err != nil { 437 return fmt.Errorf("obtaining build uid/gid from builder image: %w", err) 438 } 439 440 c.logger.Debugf("Creating ephemeral lifecycle from %s with uid %d and gid %d. With workspace dir %s", lifecycleImage.Name(), uid, gid, opts.Workspace) 441 // extend lifecycle image with mountpoints, and use it instead of current lifecycle image 442 lifecycleImage, err = c.createEphemeralLifecycle(lifecycleImage, opts.Workspace, uid, gid) 443 if err != nil { 444 return err 445 } 446 c.logger.Debugf("Selecting ephemeral lifecycle image %s for build", lifecycleImage.Name()) 447 // cleanup the extended lifecycle image when done 448 defer c.docker.ImageRemove(context.Background(), lifecycleImage.Name(), types.RemoveOptions{Force: true}) 449 } 450 451 lifecycleOptsLifecycleImage = lifecycleImage.Name() 452 labels, err := lifecycleImage.Labels() 453 if err != nil { 454 return fmt.Errorf("reading labels of lifecycle image: %w", err) 455 } 456 457 lifecycleAPIs, err = extractSupportedLifecycleApis(labels) 458 if err != nil { 459 return fmt.Errorf("reading api versions of lifecycle image: %w", err) 460 } 461 } 462 } 463 464 usingPlatformAPI, err := build.FindLatestSupported(append( 465 bldr.LifecycleDescriptor().APIs.Platform.Deprecated, 466 bldr.LifecycleDescriptor().APIs.Platform.Supported...), 467 lifecycleAPIs) 468 if err != nil { 469 return fmt.Errorf("finding latest supported Platform API: %w", err) 470 } 471 if usingPlatformAPI.LessThan("0.12") { 472 if err = c.validateMixins(fetchedBPs, bldr, runImageName, runMixins); err != nil { 473 return fmt.Errorf("validating stack mixins: %w", err) 474 } 475 } 476 477 buildEnvs := map[string]string{} 478 for _, envVar := range opts.ProjectDescriptor.Build.Env { 479 buildEnvs[envVar.Name] = envVar.Value 480 } 481 482 for k, v := range opts.Env { 483 buildEnvs[k] = v 484 } 485 486 ephemeralBuilder, err := c.createEphemeralBuilder(rawBuilderImage, buildEnvs, order, fetchedBPs, orderExtensions, fetchedExs, usingPlatformAPI.LessThan("0.12"), opts.RunImage) 487 if err != nil { 488 return err 489 } 490 defer c.docker.ImageRemove(context.Background(), ephemeralBuilder.Name(), types.RemoveOptions{Force: true}) 491 492 if len(bldr.OrderExtensions()) > 0 || len(ephemeralBuilder.OrderExtensions()) > 0 { 493 if builderOS == "windows" { 494 return fmt.Errorf("builder contains image extensions which are not supported for Windows builds") 495 } 496 if !(opts.PullPolicy == image.PullAlways) { 497 return fmt.Errorf("pull policy must be 'always' when builder contains image extensions") 498 } 499 } 500 501 if opts.Layout() { 502 opts.ContainerConfig.Volumes = appendLayoutVolumes(opts.ContainerConfig.Volumes, pathsConfig) 503 } 504 505 processedVolumes, warnings, err := processVolumes(builderOS, opts.ContainerConfig.Volumes) 506 if err != nil { 507 return err 508 } 509 510 for _, warning := range warnings { 511 c.logger.Warn(warning) 512 } 513 514 fileFilter, err := getFileFilter(opts.ProjectDescriptor) 515 if err != nil { 516 return err 517 } 518 519 runImageName, err = pname.TranslateRegistry(runImageName, c.registryMirrors, c.logger) 520 if err != nil { 521 return err 522 } 523 524 projectMetadata := files.ProjectMetadata{} 525 if c.experimental { 526 version := opts.ProjectDescriptor.Project.Version 527 sourceURL := opts.ProjectDescriptor.Project.SourceURL 528 if version != "" || sourceURL != "" { 529 projectMetadata.Source = &files.ProjectSource{ 530 Type: "project", 531 Version: map[string]interface{}{"declared": version}, 532 Metadata: map[string]interface{}{"url": sourceURL}, 533 } 534 } else { 535 projectMetadata.Source = v02.GitMetadata(opts.AppPath) 536 } 537 } 538 539 lifecycleOpts := build.LifecycleOptions{ 540 AppPath: appPath, 541 Image: imageRef, 542 Builder: ephemeralBuilder, 543 BuilderImage: builderRef.Name(), 544 LifecycleImage: ephemeralBuilder.Name(), 545 RunImage: runImageName, 546 ProjectMetadata: projectMetadata, 547 ClearCache: opts.ClearCache, 548 Publish: opts.Publish, 549 TrustBuilder: opts.TrustBuilder(opts.Builder), 550 UseCreator: useCreator, 551 UseCreatorWithExtensions: supportsCreatorWithExtensions(lifecycleVersion), 552 DockerHost: opts.DockerHost, 553 Cache: opts.Cache, 554 CacheImage: opts.CacheImage, 555 HTTPProxy: proxyConfig.HTTPProxy, 556 HTTPSProxy: proxyConfig.HTTPSProxy, 557 NoProxy: proxyConfig.NoProxy, 558 Network: opts.ContainerConfig.Network, 559 AdditionalTags: opts.AdditionalTags, 560 Volumes: processedVolumes, 561 DefaultProcessType: opts.DefaultProcessType, 562 FileFilter: fileFilter, 563 Workspace: opts.Workspace, 564 GID: opts.GroupID, 565 UID: opts.UserID, 566 PreviousImage: opts.PreviousImage, 567 Interactive: opts.Interactive, 568 Termui: termui.NewTermui(imageName, ephemeralBuilder, runImageName), 569 ReportDestinationDir: opts.ReportDestinationDir, 570 SBOMDestinationDir: opts.SBOMDestinationDir, 571 CreationTime: opts.CreationTime, 572 Layout: opts.Layout(), 573 Keychain: c.keychain, 574 } 575 576 switch { 577 case useCreator: 578 lifecycleOpts.UseCreator = true 579 case supportsLifecycleImage(lifecycleVersion): 580 lifecycleOpts.LifecycleImage = lifecycleOptsLifecycleImage 581 lifecycleOpts.LifecycleApis = lifecycleAPIs 582 case !opts.TrustBuilder(opts.Builder): 583 return errors.Errorf("Lifecycle %s does not have an associated lifecycle image. Builder must be trusted.", lifecycleVersion.String()) 584 } 585 586 lifecycleOpts.FetchRunImageWithLifecycleLayer = func(runImageName string) (string, error) { 587 ephemeralRunImageName := fmt.Sprintf("pack.local/run-image/%x:latest", randString(10)) 588 runImage, err := c.imageFetcher.Fetch(ctx, runImageName, fetchOptions) 589 if err != nil { 590 return "", err 591 } 592 ephemeralRunImage, err := local.NewImage(ephemeralRunImageName, c.docker, local.FromBaseImage(runImage.Name())) 593 if err != nil { 594 return "", err 595 } 596 tmpDir, err := os.MkdirTemp("", "extend-run-image-scratch") // we need to write to disk because manifest.json is last in the tar 597 if err != nil { 598 return "", err 599 } 600 defer os.RemoveAll(tmpDir) 601 lifecycleImageTar, err := func() (string, error) { 602 lifecycleImageTar := filepath.Join(tmpDir, "lifecycle-image.tar") 603 lifecycleImageReader, err := c.docker.ImageSave(context.Background(), []string{lifecycleOpts.LifecycleImage}) // this is fast because the lifecycle image is based on distroless static 604 if err != nil { 605 return "", err 606 } 607 defer lifecycleImageReader.Close() 608 lifecycleImageWriter, err := os.Create(lifecycleImageTar) 609 if err != nil { 610 return "", err 611 } 612 defer lifecycleImageWriter.Close() 613 if _, err = io.Copy(lifecycleImageWriter, lifecycleImageReader); err != nil { 614 return "", err 615 } 616 return lifecycleImageTar, nil 617 }() 618 if err != nil { 619 return "", err 620 } 621 advanceTarToEntryWithName := func(tarReader *tar.Reader, wantName string) (*tar.Header, error) { 622 var ( 623 header *tar.Header 624 err error 625 ) 626 for { 627 header, err = tarReader.Next() 628 if err == io.EOF { 629 break 630 } 631 if err != nil { 632 return nil, err 633 } 634 if header.Name != wantName { 635 continue 636 } 637 return header, nil 638 } 639 return nil, fmt.Errorf("failed to find header with name: %s", wantName) 640 } 641 lifecycleLayerName, err := func() (string, error) { 642 lifecycleImageReader, err := os.Open(lifecycleImageTar) 643 if err != nil { 644 return "", err 645 } 646 defer lifecycleImageReader.Close() 647 tarReader := tar.NewReader(lifecycleImageReader) 648 if _, err = advanceTarToEntryWithName(tarReader, "manifest.json"); err != nil { 649 return "", err 650 } 651 type descriptor struct { 652 Layers []string 653 } 654 type manifestJSON []descriptor 655 var manifestContents manifestJSON 656 if err = json.NewDecoder(tarReader).Decode(&manifestContents); err != nil { 657 return "", err 658 } 659 if len(manifestContents) < 1 { 660 return "", errors.New("missing manifest entries") 661 } 662 // we can assume the lifecycle layer is the last in the tar, except if the lifecycle has been extended as an ephemeral lifecycle 663 layerOffset := 1 664 if strings.Contains(lifecycleOpts.LifecycleImage, "pack.local/lifecycle") { 665 layerOffset = 2 666 } 667 668 if (len(manifestContents[0].Layers) - layerOffset) < 0 { 669 return "", errors.New("Lifecycle image did not contain expected layer count") 670 } 671 672 return manifestContents[0].Layers[len(manifestContents[0].Layers)-layerOffset], nil 673 }() 674 if err != nil { 675 return "", err 676 } 677 if lifecycleLayerName == "" { 678 return "", errors.New("failed to find lifecycle layer") 679 } 680 lifecycleLayerTar, err := func() (string, error) { 681 lifecycleImageReader, err := os.Open(lifecycleImageTar) 682 if err != nil { 683 return "", err 684 } 685 defer lifecycleImageReader.Close() 686 tarReader := tar.NewReader(lifecycleImageReader) 687 var header *tar.Header 688 if header, err = advanceTarToEntryWithName(tarReader, lifecycleLayerName); err != nil { 689 return "", err 690 } 691 lifecycleLayerTar := filepath.Join(filepath.Dir(lifecycleImageTar), filepath.Dir(lifecycleLayerName)+".tar") 692 lifecycleLayerWriter, err := os.OpenFile(lifecycleLayerTar, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) 693 if err != nil { 694 return "", err 695 } 696 defer lifecycleLayerWriter.Close() 697 if _, err = io.Copy(lifecycleLayerWriter, tarReader); err != nil { 698 return "", err 699 } 700 return lifecycleLayerTar, nil 701 }() 702 if err != nil { 703 return "", err 704 } 705 diffID, err := func() (string, error) { 706 lifecycleLayerReader, err := os.Open(lifecycleLayerTar) 707 if err != nil { 708 return "", err 709 } 710 defer lifecycleLayerReader.Close() 711 hasher := sha256.New() 712 if _, err = io.Copy(hasher, lifecycleLayerReader); err != nil { 713 return "", err 714 } 715 // it's weird that this doesn't match lifecycleLayerTar 716 return hex.EncodeToString(hasher.Sum(nil)), nil 717 }() 718 if err != nil { 719 return "", err 720 } 721 if err = ephemeralRunImage.AddLayerWithDiffID(lifecycleLayerTar, "sha256:"+diffID); err != nil { 722 return "", err 723 } 724 if err = ephemeralRunImage.Save(); err != nil { 725 return "", err 726 } 727 return ephemeralRunImageName, nil 728 } 729 730 if err = c.lifecycleExecutor.Execute(ctx, lifecycleOpts); err != nil { 731 return fmt.Errorf("executing lifecycle: %w", err) 732 } 733 return c.logImageNameAndSha(ctx, opts.Publish, imageRef) 734 } 735 736 func extractSupportedLifecycleApis(labels map[string]string) ([]string, error) { 737 // sample contents of labels: 738 // {io.buildpacks.builder.metadata:\"{\"lifecycle\":{\"version\":\"0.15.3\"},\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}}", 739 // io.buildpacks.lifecycle.apis":"{\"buildpack\":{\"deprecated\":[],\"supported\":[\"0.2\",\"0.3\",\"0.4\",\"0.5\",\"0.6\",\"0.7\",\"0.8\",\"0.9\"]},\"platform\":{\"deprecated\":[],\"supported\":[\"0.3\",\"0.4\",\"0.5\",\"0.6\",\"0.7\",\"0.8\",\"0.9\",\"0.10\"]}}\",\"io.buildpacks.lifecycle.version\":\"0.15.3\"}") 740 741 // This struct is defined in lifecycle-repository/tools/image/main.go#Descriptor -- we could consider moving it from the main package to an importable location. 742 var bpPlatformAPI struct { 743 Platform struct { 744 Deprecated []string 745 Supported []string 746 } 747 } 748 if len(labels["io.buildpacks.lifecycle.apis"]) > 0 { 749 err := json.Unmarshal([]byte(labels["io.buildpacks.lifecycle.apis"]), &bpPlatformAPI) 750 if err != nil { 751 return nil, err 752 } 753 return append(bpPlatformAPI.Platform.Deprecated, bpPlatformAPI.Platform.Supported...), nil 754 } 755 return []string{}, nil 756 } 757 758 func getFileFilter(descriptor projectTypes.Descriptor) (func(string) bool, error) { 759 if len(descriptor.Build.Exclude) > 0 { 760 excludes := ignore.CompileIgnoreLines(descriptor.Build.Exclude...) 761 return func(fileName string) bool { 762 return !excludes.MatchesPath(fileName) 763 }, nil 764 } 765 if len(descriptor.Build.Include) > 0 { 766 includes := ignore.CompileIgnoreLines(descriptor.Build.Include...) 767 return includes.MatchesPath, nil 768 } 769 770 return nil, nil 771 } 772 773 func supportsCreator(lifecycleVersion *builder.Version) bool { 774 // Technically the creator is supported as of platform API version 0.3 (lifecycle version 0.7.0+) but earlier versions 775 // have bugs that make using the creator problematic. 776 return !lifecycleVersion.LessThan(semver.MustParse(minLifecycleVersionSupportingCreator)) 777 } 778 779 func supportsCreatorWithExtensions(lifecycleVersion *builder.Version) bool { 780 return !lifecycleVersion.LessThan(semver.MustParse(minLifecycleVersionSupportingCreatorWithExtensions)) 781 } 782 783 func supportsLifecycleImage(lifecycleVersion *builder.Version) bool { 784 return lifecycleVersion.Equal(builder.VersionMustParse(prevLifecycleVersionSupportingImage)) || 785 !lifecycleVersion.LessThan(semver.MustParse(minLifecycleVersionSupportingImage)) 786 } 787 788 // supportsPlatformAPI determines whether pack can build using the builder based on the builder's supported Platform API versions. 789 func supportsPlatformAPI(builderPlatformAPIs builder.APISet) bool { 790 for _, packSupportedAPI := range build.SupportedPlatformAPIVersions { 791 for _, builderSupportedAPI := range builderPlatformAPIs { 792 supportsPlatform := packSupportedAPI.Compare(builderSupportedAPI) == 0 793 if supportsPlatform { 794 return true 795 } 796 } 797 } 798 799 return false 800 } 801 802 func (c *Client) processBuilderName(builderName string) (name.Reference, error) { 803 if builderName == "" { 804 return nil, errors.New("builder is a required parameter if the client has no default builder") 805 } 806 return name.ParseReference(builderName, name.WeakValidation) 807 } 808 809 func (c *Client) getBuilder(img imgutil.Image) (*builder.Builder, error) { 810 bldr, err := builder.FromImage(img) 811 if err != nil { 812 return nil, err 813 } 814 if bldr.Stack().RunImage.Image == "" && len(bldr.RunImages()) == 0 { 815 return nil, errors.New("builder metadata is missing run-image") 816 } 817 818 lifecycleDescriptor := bldr.LifecycleDescriptor() 819 if lifecycleDescriptor.Info.Version == nil { 820 return nil, errors.New("lifecycle version must be specified in builder") 821 } 822 if len(lifecycleDescriptor.APIs.Buildpack.Supported) == 0 { 823 return nil, errors.New("supported Lifecycle Buildpack APIs not specified") 824 } 825 if len(lifecycleDescriptor.APIs.Platform.Supported) == 0 { 826 return nil, errors.New("supported Lifecycle Platform APIs not specified") 827 } 828 829 return bldr, nil 830 } 831 832 func (c *Client) validateRunImage(context context.Context, name string, opts image.FetchOptions, expectedStack string) (imgutil.Image, error) { 833 if name == "" { 834 return nil, errors.New("run image must be specified") 835 } 836 img, err := c.imageFetcher.Fetch(context, name, opts) 837 if err != nil { 838 return nil, err 839 } 840 stackID, err := img.Label("io.buildpacks.stack.id") 841 if err != nil { 842 return nil, err 843 } 844 if stackID != expectedStack { 845 return nil, fmt.Errorf("run-image stack id '%s' does not match builder stack '%s'", stackID, expectedStack) 846 } 847 return img, nil 848 } 849 850 func (c *Client) validateMixins(additionalBuildpacks []buildpack.BuildModule, bldr *builder.Builder, runImageName string, runMixins []string) error { 851 if err := stack.ValidateMixins(bldr.Image().Name(), bldr.Mixins(), runImageName, runMixins); err != nil { 852 return err 853 } 854 855 bps, err := allBuildpacks(bldr.Image(), additionalBuildpacks) 856 if err != nil { 857 return err 858 } 859 mixins := assembleAvailableMixins(bldr.Mixins(), runMixins) 860 861 for _, bp := range bps { 862 if err := bp.EnsureStackSupport(bldr.StackID, mixins, true); err != nil { 863 return err 864 } 865 } 866 return nil 867 } 868 869 // assembleAvailableMixins returns the set of mixins that are common between the two provided sets, plus build-only mixins and run-only mixins. 870 func assembleAvailableMixins(buildMixins, runMixins []string) []string { 871 // NOTE: We cannot simply union the two mixin sets, as this could introduce a mixin that is only present on one stack 872 // image but not the other. A buildpack that happens to require the mixin would fail to run properly, even though validation 873 // would pass. 874 // 875 // For example: 876 // 877 // Incorrect: 878 // Run image mixins: [A, B] 879 // Build image mixins: [A] 880 // Merged: [A, B] 881 // Buildpack requires: [A, B] 882 // Match? Yes 883 // 884 // Correct: 885 // Run image mixins: [A, B] 886 // Build image mixins: [A] 887 // Merged: [A] 888 // Buildpack requires: [A, B] 889 // Match? No 890 891 buildOnly := stack.FindStageMixins(buildMixins, "build") 892 runOnly := stack.FindStageMixins(runMixins, "run") 893 _, _, common := stringset.Compare(buildMixins, runMixins) 894 895 return append(common, append(buildOnly, runOnly...)...) 896 } 897 898 // allBuildpacks aggregates all buildpacks declared on the image with additional buildpacks passed in. They are sorted 899 // by ID then Version. 900 func allBuildpacks(builderImage imgutil.Image, additionalBuildpacks []buildpack.BuildModule) ([]buildpack.Descriptor, error) { 901 var all []buildpack.Descriptor 902 var bpLayers dist.ModuleLayers 903 if _, err := dist.GetLabel(builderImage, dist.BuildpackLayersLabel, &bpLayers); err != nil { 904 return nil, err 905 } 906 for id, bps := range bpLayers { 907 for ver, bp := range bps { 908 desc := dist.BuildpackDescriptor{ 909 WithInfo: dist.ModuleInfo{ 910 ID: id, 911 Version: ver, 912 }, 913 WithStacks: bp.Stacks, 914 WithTargets: bp.Targets, 915 WithOrder: bp.Order, 916 } 917 all = append(all, &desc) 918 } 919 } 920 for _, bp := range additionalBuildpacks { 921 all = append(all, bp.Descriptor()) 922 } 923 924 sort.Slice(all, func(i, j int) bool { 925 if all[i].Info().ID != all[j].Info().ID { 926 return all[i].Info().ID < all[j].Info().ID 927 } 928 return all[i].Info().Version < all[j].Info().Version 929 }) 930 931 return all, nil 932 } 933 934 func (c *Client) processAppPath(appPath string) (string, error) { 935 var ( 936 resolvedAppPath string 937 err error 938 ) 939 940 if appPath == "" { 941 if appPath, err = os.Getwd(); err != nil { 942 return "", errors.Wrap(err, "get working dir") 943 } 944 } 945 946 if resolvedAppPath, err = filepath.EvalSymlinks(appPath); err != nil { 947 return "", errors.Wrap(err, "evaluate symlink") 948 } 949 950 if resolvedAppPath, err = filepath.Abs(resolvedAppPath); err != nil { 951 return "", errors.Wrap(err, "resolve absolute path") 952 } 953 954 fi, err := os.Stat(resolvedAppPath) 955 if err != nil { 956 return "", errors.Wrap(err, "stat file") 957 } 958 959 if !fi.IsDir() { 960 isZip, err := archive.IsZip(filepath.Clean(resolvedAppPath)) 961 if err != nil { 962 return "", errors.Wrap(err, "check zip") 963 } 964 965 if !isZip { 966 return "", errors.New("app path must be a directory or zip") 967 } 968 } 969 970 return resolvedAppPath, nil 971 } 972 973 // processLayoutPath given an image reference and a previous image reference this method calculates the 974 // local full path and the expected path in the lifecycle container for both images provides. Those values 975 // can be used to mount the correct volumes 976 func (c *Client) processLayoutPath(inputImageRef, previousImageRef InputImageReference) (layoutPathConfig, error) { 977 var ( 978 hostImagePath, hostPreviousImagePath, targetImagePath, targetPreviousImagePath string 979 err error 980 ) 981 hostImagePath, err = fullImagePath(inputImageRef, true) 982 if err != nil { 983 return layoutPathConfig{}, err 984 } 985 targetImagePath, err = layout.ParseRefToPath(inputImageRef.Name()) 986 if err != nil { 987 return layoutPathConfig{}, err 988 } 989 targetImagePath = filepath.Join(paths.RootDir, "layout-repo", targetImagePath) 990 c.logger.Debugf("local image path %s will be mounted into the container at path %s", hostImagePath, targetImagePath) 991 992 if previousImageRef != nil && previousImageRef.Name() != "" { 993 hostPreviousImagePath, err = fullImagePath(previousImageRef, false) 994 if err != nil { 995 return layoutPathConfig{}, err 996 } 997 targetPreviousImagePath, err = layout.ParseRefToPath(previousImageRef.Name()) 998 if err != nil { 999 return layoutPathConfig{}, err 1000 } 1001 targetPreviousImagePath = filepath.Join(paths.RootDir, "layout-repo", targetPreviousImagePath) 1002 c.logger.Debugf("local previous image path %s will be mounted into the container at path %s", hostPreviousImagePath, targetPreviousImagePath) 1003 } 1004 return layoutPathConfig{ 1005 hostImagePath: hostImagePath, 1006 targetImagePath: targetImagePath, 1007 hostPreviousImagePath: hostPreviousImagePath, 1008 targetPreviousImagePath: targetPreviousImagePath, 1009 }, nil 1010 } 1011 1012 func (c *Client) parseReference(opts BuildOptions) (name.Reference, error) { 1013 if !opts.Layout() { 1014 return c.parseTagReference(opts.Image) 1015 } 1016 base := filepath.Base(opts.Image) 1017 return c.parseTagReference(base) 1018 } 1019 1020 func (c *Client) processProxyConfig(config *ProxyConfig) ProxyConfig { 1021 var ( 1022 httpProxy, httpsProxy, noProxy string 1023 ok bool 1024 ) 1025 if config != nil { 1026 return *config 1027 } 1028 if httpProxy, ok = os.LookupEnv("HTTP_PROXY"); !ok { 1029 httpProxy = os.Getenv("http_proxy") 1030 } 1031 if httpsProxy, ok = os.LookupEnv("HTTPS_PROXY"); !ok { 1032 httpsProxy = os.Getenv("https_proxy") 1033 } 1034 if noProxy, ok = os.LookupEnv("NO_PROXY"); !ok { 1035 noProxy = os.Getenv("no_proxy") 1036 } 1037 return ProxyConfig{ 1038 HTTPProxy: httpProxy, 1039 HTTPSProxy: httpsProxy, 1040 NoProxy: noProxy, 1041 } 1042 } 1043 1044 // processBuildpacks computes an order group based on the existing builder order and declared buildpacks. Additionally, 1045 // it returns buildpacks that should be added to the builder. 1046 // 1047 // Visual examples: 1048 // 1049 // BUILDER ORDER 1050 // ---------- 1051 // - group: 1052 // - A 1053 // - B 1054 // - group: 1055 // - A 1056 // 1057 // WITH DECLARED: "from=builder", X 1058 // ---------- 1059 // - group: 1060 // - A 1061 // - B 1062 // - X 1063 // - group: 1064 // - A 1065 // - X 1066 // 1067 // WITH DECLARED: X, "from=builder", Y 1068 // ---------- 1069 // - group: 1070 // - X 1071 // - A 1072 // - B 1073 // - Y 1074 // - group: 1075 // - X 1076 // - A 1077 // - Y 1078 // 1079 // WITH DECLARED: X 1080 // ---------- 1081 // - group: 1082 // - X 1083 // 1084 // WITH DECLARED: A 1085 // ---------- 1086 // - group: 1087 // - A 1088 func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Image, builderBPs []dist.ModuleInfo, builderOrder dist.Order, stackID string, opts BuildOptions) (fetchedBPs []buildpack.BuildModule, order dist.Order, err error) { 1089 relativeBaseDir := opts.RelativeBaseDir 1090 declaredBPs := opts.Buildpacks 1091 1092 // declare buildpacks provided by project descriptor when no buildpacks are declared 1093 if len(declaredBPs) == 0 && len(opts.ProjectDescriptor.Build.Buildpacks) != 0 { 1094 relativeBaseDir = opts.ProjectDescriptorBaseDir 1095 1096 for _, bp := range opts.ProjectDescriptor.Build.Buildpacks { 1097 buildpackLocator, err := getBuildpackLocator(bp, stackID) 1098 if err != nil { 1099 return nil, nil, err 1100 } 1101 declaredBPs = append(declaredBPs, buildpackLocator) 1102 } 1103 } 1104 1105 order = dist.Order{{Group: []dist.ModuleRef{}}} 1106 for _, bp := range declaredBPs { 1107 locatorType, err := buildpack.GetLocatorType(bp, relativeBaseDir, builderBPs) 1108 if err != nil { 1109 return nil, nil, err 1110 } 1111 1112 switch locatorType { 1113 case buildpack.FromBuilderLocator: 1114 switch { 1115 case len(order) == 0 || len(order[0].Group) == 0: 1116 order = builderOrder 1117 case len(order) > 1: 1118 // This should only ever be possible if they are using from=builder twice which we don't allow 1119 return nil, nil, errors.New("buildpacks from builder can only be defined once") 1120 default: 1121 newOrder := dist.Order{} 1122 groupToAdd := order[0].Group 1123 for _, bOrderEntry := range builderOrder { 1124 newEntry := dist.OrderEntry{Group: append(groupToAdd, bOrderEntry.Group...)} 1125 newOrder = append(newOrder, newEntry) 1126 } 1127 1128 order = newOrder 1129 } 1130 default: 1131 newFetchedBPs, moduleInfo, err := c.fetchBuildpack(ctx, bp, relativeBaseDir, builderImage, builderBPs, opts, buildpack.KindBuildpack) 1132 if err != nil { 1133 return fetchedBPs, order, err 1134 } 1135 fetchedBPs = append(fetchedBPs, newFetchedBPs...) 1136 order = appendBuildpackToOrder(order, *moduleInfo) 1137 } 1138 } 1139 1140 if (len(order) == 0 || len(order[0].Group) == 0) && len(builderOrder) > 0 { 1141 preBuildpacks := opts.PreBuildpacks 1142 postBuildpacks := opts.PostBuildpacks 1143 if len(preBuildpacks) == 0 && len(opts.ProjectDescriptor.Build.Pre.Buildpacks) > 0 { 1144 for _, bp := range opts.ProjectDescriptor.Build.Pre.Buildpacks { 1145 buildpackLocator, err := getBuildpackLocator(bp, stackID) 1146 if err != nil { 1147 return nil, nil, errors.Wrap(err, "get pre-buildpack locator") 1148 } 1149 preBuildpacks = append(preBuildpacks, buildpackLocator) 1150 } 1151 } 1152 if len(postBuildpacks) == 0 && len(opts.ProjectDescriptor.Build.Post.Buildpacks) > 0 { 1153 for _, bp := range opts.ProjectDescriptor.Build.Post.Buildpacks { 1154 buildpackLocator, err := getBuildpackLocator(bp, stackID) 1155 if err != nil { 1156 return nil, nil, errors.Wrap(err, "get post-buildpack locator") 1157 } 1158 postBuildpacks = append(postBuildpacks, buildpackLocator) 1159 } 1160 } 1161 1162 if len(preBuildpacks) > 0 || len(postBuildpacks) > 0 { 1163 order = builderOrder 1164 for _, bp := range preBuildpacks { 1165 newFetchedBPs, moduleInfo, err := c.fetchBuildpack(ctx, bp, relativeBaseDir, builderImage, builderBPs, opts, buildpack.KindBuildpack) 1166 if err != nil { 1167 return fetchedBPs, order, err 1168 } 1169 fetchedBPs = append(fetchedBPs, newFetchedBPs...) 1170 order = prependBuildpackToOrder(order, *moduleInfo) 1171 } 1172 1173 for _, bp := range postBuildpacks { 1174 newFetchedBPs, moduleInfo, err := c.fetchBuildpack(ctx, bp, relativeBaseDir, builderImage, builderBPs, opts, buildpack.KindBuildpack) 1175 if err != nil { 1176 return fetchedBPs, order, err 1177 } 1178 fetchedBPs = append(fetchedBPs, newFetchedBPs...) 1179 order = appendBuildpackToOrder(order, *moduleInfo) 1180 } 1181 } 1182 } 1183 1184 return fetchedBPs, order, nil 1185 } 1186 1187 func (c *Client) fetchBuildpack(ctx context.Context, bp string, relativeBaseDir string, builderImage imgutil.Image, builderBPs []dist.ModuleInfo, opts BuildOptions, kind string) ([]buildpack.BuildModule, *dist.ModuleInfo, error) { 1188 pullPolicy := opts.PullPolicy 1189 publish := opts.Publish 1190 registry := opts.Registry 1191 1192 locatorType, err := buildpack.GetLocatorType(bp, relativeBaseDir, builderBPs) 1193 if err != nil { 1194 return nil, nil, err 1195 } 1196 1197 fetchedBPs := []buildpack.BuildModule{} 1198 var moduleInfo *dist.ModuleInfo 1199 switch locatorType { 1200 case buildpack.IDLocator: 1201 id, version := buildpack.ParseIDLocator(bp) 1202 moduleInfo = &dist.ModuleInfo{ 1203 ID: id, 1204 Version: version, 1205 } 1206 default: 1207 builderOS, err := builderImage.OS() 1208 if err != nil { 1209 return nil, nil, errors.Wrapf(err, "getting builder OS") 1210 } 1211 1212 builderArch, err := builderImage.Architecture() 1213 if err != nil { 1214 return nil, nil, errors.Wrapf(err, "getting builder architecture") 1215 } 1216 downloadOptions := buildpack.DownloadOptions{ 1217 RegistryName: registry, 1218 ImageOS: builderOS, 1219 Platform: fmt.Sprintf("%s/%s", builderOS, builderArch), 1220 RelativeBaseDir: relativeBaseDir, 1221 Daemon: !publish, 1222 PullPolicy: pullPolicy, 1223 } 1224 if kind == buildpack.KindExtension { 1225 downloadOptions.ModuleKind = kind 1226 } 1227 mainBP, depBPs, err := c.buildpackDownloader.Download(ctx, bp, downloadOptions) 1228 if err != nil { 1229 return nil, nil, errors.Wrap(err, "downloading buildpack") 1230 } 1231 fetchedBPs = append(append(fetchedBPs, mainBP), depBPs...) 1232 mainBPInfo := mainBP.Descriptor().Info() 1233 moduleInfo = &mainBPInfo 1234 1235 packageCfgPath := filepath.Join(bp, "package.toml") 1236 _, err = os.Stat(packageCfgPath) 1237 if err == nil { 1238 fetchedDeps, err := c.fetchBuildpackDependencies(ctx, bp, packageCfgPath, downloadOptions) 1239 if err != nil { 1240 return nil, nil, errors.Wrapf(err, "fetching package.toml dependencies (path=%s)", style.Symbol(packageCfgPath)) 1241 } 1242 fetchedBPs = append(fetchedBPs, fetchedDeps...) 1243 } 1244 } 1245 return fetchedBPs, moduleInfo, nil 1246 } 1247 1248 func (c *Client) fetchBuildpackDependencies(ctx context.Context, bp string, packageCfgPath string, downloadOptions buildpack.DownloadOptions) ([]buildpack.BuildModule, error) { 1249 packageReader := buildpackage.NewConfigReader() 1250 packageCfg, err := packageReader.Read(packageCfgPath) 1251 if err == nil { 1252 fetchedBPs := []buildpack.BuildModule{} 1253 for _, dep := range packageCfg.Dependencies { 1254 mainBP, deps, err := c.buildpackDownloader.Download(ctx, dep.URI, buildpack.DownloadOptions{ 1255 RegistryName: downloadOptions.RegistryName, 1256 ImageOS: downloadOptions.ImageOS, 1257 Platform: downloadOptions.Platform, 1258 Daemon: downloadOptions.Daemon, 1259 PullPolicy: downloadOptions.PullPolicy, 1260 RelativeBaseDir: filepath.Join(bp, packageCfg.Buildpack.URI), 1261 }) 1262 1263 if err != nil { 1264 return nil, errors.Wrapf(err, "fetching dependencies (uri=%s,image=%s)", style.Symbol(dep.URI), style.Symbol(dep.ImageName)) 1265 } 1266 1267 fetchedBPs = append(append(fetchedBPs, mainBP), deps...) 1268 } 1269 return fetchedBPs, nil 1270 } 1271 return nil, err 1272 } 1273 1274 func getBuildpackLocator(bp projectTypes.Buildpack, stackID string) (string, error) { 1275 switch { 1276 case bp.ID != "" && bp.Script.Inline != "" && bp.URI == "": 1277 if bp.Script.API == "" { 1278 return "", errors.New("Missing API version for inline buildpack") 1279 } 1280 1281 pathToInlineBuildpack, err := createInlineBuildpack(bp, stackID) 1282 if err != nil { 1283 return "", errors.Wrap(err, "Could not create temporary inline buildpack") 1284 } 1285 return pathToInlineBuildpack, nil 1286 case bp.URI != "": 1287 return bp.URI, nil 1288 case bp.ID != "" && bp.Version != "": 1289 return fmt.Sprintf("%s@%s", bp.ID, bp.Version), nil 1290 case bp.ID != "" && bp.Version == "": 1291 return bp.ID, nil 1292 default: 1293 return "", errors.New("Invalid buildpack definition") 1294 } 1295 } 1296 1297 func appendBuildpackToOrder(order dist.Order, bpInfo dist.ModuleInfo) (newOrder dist.Order) { 1298 for _, orderEntry := range order { 1299 newEntry := orderEntry 1300 newEntry.Group = append(newEntry.Group, dist.ModuleRef{ 1301 ModuleInfo: bpInfo, 1302 Optional: false, 1303 }) 1304 newOrder = append(newOrder, newEntry) 1305 } 1306 1307 return newOrder 1308 } 1309 1310 func prependBuildpackToOrder(order dist.Order, bpInfo dist.ModuleInfo) (newOrder dist.Order) { 1311 for _, orderEntry := range order { 1312 newEntry := orderEntry 1313 newGroup := []dist.ModuleRef{{ 1314 ModuleInfo: bpInfo, 1315 Optional: false, 1316 }} 1317 newEntry.Group = append(newGroup, newEntry.Group...) 1318 newOrder = append(newOrder, newEntry) 1319 } 1320 1321 return newOrder 1322 } 1323 1324 func (c *Client) processExtensions(ctx context.Context, builderImage imgutil.Image, builderExs []dist.ModuleInfo, builderOrder dist.Order, stackID string, opts BuildOptions) (fetchedExs []buildpack.BuildModule, orderExtensions dist.Order, err error) { 1325 relativeBaseDir := opts.RelativeBaseDir 1326 declaredExs := opts.Extensions 1327 1328 orderExtensions = dist.Order{{Group: []dist.ModuleRef{}}} 1329 for _, ex := range declaredExs { 1330 locatorType, err := buildpack.GetLocatorType(ex, relativeBaseDir, builderExs) 1331 if err != nil { 1332 return nil, nil, err 1333 } 1334 1335 switch locatorType { 1336 case buildpack.RegistryLocator: 1337 return nil, nil, errors.New("RegistryLocator type is not valid for extensions") 1338 case buildpack.FromBuilderLocator: 1339 return nil, nil, errors.New("from builder is not supported for extensions") 1340 default: 1341 newFetchedExs, moduleInfo, err := c.fetchBuildpack(ctx, ex, relativeBaseDir, builderImage, builderExs, opts, buildpack.KindExtension) 1342 if err != nil { 1343 return fetchedExs, orderExtensions, err 1344 } 1345 fetchedExs = append(fetchedExs, newFetchedExs...) 1346 orderExtensions = prependBuildpackToOrder(orderExtensions, *moduleInfo) 1347 } 1348 } 1349 1350 return fetchedExs, orderExtensions, nil 1351 } 1352 1353 func userAndGroupIDs(img imgutil.Image) (int, int, error) { 1354 sUID, err := img.Env(builder.EnvUID) 1355 if err != nil { 1356 return 0, 0, errors.Wrap(err, "reading builder env variables") 1357 } else if sUID == "" { 1358 return 0, 0, fmt.Errorf("image %s missing required env var %s", style.Symbol(img.Name()), style.Symbol(builder.EnvUID)) 1359 } 1360 1361 sGID, err := img.Env(builder.EnvGID) 1362 if err != nil { 1363 return 0, 0, errors.Wrap(err, "reading builder env variables") 1364 } else if sGID == "" { 1365 return 0, 0, fmt.Errorf("image %s missing required env var %s", style.Symbol(img.Name()), style.Symbol(builder.EnvGID)) 1366 } 1367 1368 var uid, gid int 1369 uid, err = strconv.Atoi(sUID) 1370 if err != nil { 1371 return 0, 0, fmt.Errorf("failed to parse %s, value %s should be an integer", style.Symbol(builder.EnvUID), style.Symbol(sUID)) 1372 } 1373 1374 gid, err = strconv.Atoi(sGID) 1375 if err != nil { 1376 return 0, 0, fmt.Errorf("failed to parse %s, value %s should be an integer", style.Symbol(builder.EnvGID), style.Symbol(sGID)) 1377 } 1378 1379 return uid, gid, nil 1380 } 1381 1382 func workspacePathForOS(os, workspace string) string { 1383 if workspace == "" { 1384 workspace = "workspace" 1385 } 1386 if os == "windows" { 1387 // note we don't use ephemeral lifecycle when os is windows.. 1388 return "c:\\" + workspace 1389 } 1390 return "/" + workspace 1391 } 1392 1393 func (c *Client) addUserMountpoints(lifecycleImage imgutil.Image, dest string, workspace string, uid int, gid int) (string, error) { 1394 // today only workspace needs to be added, easy to add future dirs if required. 1395 1396 imageOS, err := lifecycleImage.OS() 1397 if err != nil { 1398 return "", errors.Wrap(err, "getting image OS") 1399 } 1400 layerWriterFactory, err := layer.NewWriterFactory(imageOS) 1401 if err != nil { 1402 return "", err 1403 } 1404 1405 workspace = workspacePathForOS(imageOS, workspace) 1406 1407 fh, err := os.Create(filepath.Join(dest, "dirs.tar")) 1408 if err != nil { 1409 return "", err 1410 } 1411 defer fh.Close() 1412 1413 lw := layerWriterFactory.NewWriter(fh) 1414 defer lw.Close() 1415 1416 for _, path := range []string{workspace} { 1417 if err := lw.WriteHeader(&tar.Header{ 1418 Typeflag: tar.TypeDir, 1419 Name: path, 1420 Mode: 0755, 1421 ModTime: archive.NormalizedDateTime, 1422 Uid: uid, 1423 Gid: gid, 1424 }); err != nil { 1425 return "", errors.Wrapf(err, "creating %s mountpoint dir in layer", style.Symbol(path)) 1426 } 1427 } 1428 1429 return fh.Name(), nil 1430 } 1431 1432 func (c *Client) createEphemeralLifecycle(lifecycleImage imgutil.Image, workspace string, uid int, gid int) (imgutil.Image, error) { 1433 lifecycleImage.Rename(fmt.Sprintf("pack.local/lifecycle/%x:latest", randString(10))) 1434 1435 tmpDir, err := os.MkdirTemp("", "create-lifecycle-scratch") 1436 if err != nil { 1437 return nil, err 1438 } 1439 defer os.RemoveAll(tmpDir) 1440 dirsTar, err := c.addUserMountpoints(lifecycleImage, tmpDir, workspace, uid, gid) 1441 if err != nil { 1442 return nil, err 1443 } 1444 if err := lifecycleImage.AddLayer(dirsTar); err != nil { 1445 return nil, errors.Wrap(err, "adding mountpoint dirs layer") 1446 } 1447 1448 err = lifecycleImage.Save() 1449 if err != nil { 1450 return nil, err 1451 } 1452 1453 return lifecycleImage, nil 1454 } 1455 1456 func (c *Client) createEphemeralBuilder( 1457 rawBuilderImage imgutil.Image, 1458 env map[string]string, 1459 order dist.Order, 1460 buildpacks []buildpack.BuildModule, 1461 orderExtensions dist.Order, 1462 extensions []buildpack.BuildModule, 1463 validateMixins bool, 1464 runImage string, 1465 ) (*builder.Builder, error) { 1466 origBuilderName := rawBuilderImage.Name() 1467 bldr, err := builder.New(rawBuilderImage, fmt.Sprintf("pack.local/builder/%x:latest", randString(10)), builder.WithRunImage(runImage)) 1468 if err != nil { 1469 return nil, errors.Wrapf(err, "invalid builder %s", style.Symbol(origBuilderName)) 1470 } 1471 1472 bldr.SetEnv(env) 1473 for _, bp := range buildpacks { 1474 bpInfo := bp.Descriptor().Info() 1475 c.logger.Debugf("Adding buildpack %s version %s to builder", style.Symbol(bpInfo.ID), style.Symbol(bpInfo.Version)) 1476 bldr.AddBuildpack(bp) 1477 } 1478 if len(order) > 0 && len(order[0].Group) > 0 { 1479 c.logger.Debug("Setting custom order") 1480 bldr.SetOrder(order) 1481 } 1482 1483 for _, ex := range extensions { 1484 exInfo := ex.Descriptor().Info() 1485 c.logger.Debugf("Adding extension %s version %s to builder", style.Symbol(exInfo.ID), style.Symbol(exInfo.Version)) 1486 bldr.AddExtension(ex) 1487 } 1488 if len(orderExtensions) > 0 && len(orderExtensions[0].Group) > 0 { 1489 c.logger.Debug("Setting custom order for extensions") 1490 bldr.SetOrderExtensions(orderExtensions) 1491 } 1492 1493 bldr.SetValidateMixins(validateMixins) 1494 1495 if err := bldr.Save(c.logger, builder.CreatorMetadata{Version: c.version}); err != nil { 1496 return nil, err 1497 } 1498 return bldr, nil 1499 } 1500 1501 // Returns a string iwith lowercase a-z, of length n 1502 func randString(n int) string { 1503 b := make([]byte, n) 1504 _, err := rand.Read(b) 1505 if err != nil { 1506 panic(err) 1507 } 1508 for i := range b { 1509 b[i] = 'a' + (b[i] % 26) 1510 } 1511 return string(b) 1512 } 1513 1514 func (c *Client) logImageNameAndSha(ctx context.Context, publish bool, imageRef name.Reference) error { 1515 // The image name and sha are printed in the lifecycle logs, and there is no need to print it again, unless output is suppressed. 1516 if !logging.IsQuiet(c.logger) { 1517 return nil 1518 } 1519 1520 img, err := c.imageFetcher.Fetch(ctx, imageRef.Name(), image.FetchOptions{Daemon: !publish, PullPolicy: image.PullNever}) 1521 if err != nil { 1522 return fmt.Errorf("fetching built image: %w", err) 1523 } 1524 1525 id, err := img.Identifier() 1526 if err != nil { 1527 return fmt.Errorf("reading image sha: %w", err) 1528 } 1529 1530 // Remove tag, if it exists, from the image name 1531 imgName := strings.TrimSuffix(imageRef.String(), imageRef.Identifier()) 1532 imgNameAndSha := fmt.Sprintf("%s@%s\n", imgName, parseDigestFromImageID(id)) 1533 1534 // Access the logger's Writer directly to bypass ReportSuccessfulQuietBuild mode 1535 _, err = c.logger.Writer().Write([]byte(imgNameAndSha)) 1536 return err 1537 } 1538 1539 func parseDigestFromImageID(id imgutil.Identifier) string { 1540 var digest string 1541 switch v := id.(type) { 1542 case local.IDIdentifier: 1543 digest = v.String() 1544 case remote.DigestIdentifier: 1545 digest = v.Digest.DigestStr() 1546 } 1547 1548 digest = strings.TrimPrefix(digest, "sha256:") 1549 return fmt.Sprintf("sha256:%s", digest) 1550 } 1551 1552 func createInlineBuildpack(bp projectTypes.Buildpack, stackID string) (string, error) { 1553 pathToInlineBuilpack, err := os.MkdirTemp("", "inline-cnb") 1554 if err != nil { 1555 return pathToInlineBuilpack, err 1556 } 1557 1558 if bp.Version == "" { 1559 bp.Version = "0.0.0" 1560 } 1561 1562 if err = createBuildpackTOML(pathToInlineBuilpack, bp.ID, bp.Version, bp.Script.API, []dist.Stack{{ID: stackID}}, []dist.Target{}, nil); err != nil { 1563 return pathToInlineBuilpack, err 1564 } 1565 1566 shell := bp.Script.Shell 1567 if shell == "" { 1568 shell = "/bin/sh" 1569 } 1570 1571 binBuild := fmt.Sprintf(`#!%s 1572 1573 %s 1574 `, shell, bp.Script.Inline) 1575 1576 binDetect := fmt.Sprintf(`#!%s 1577 1578 exit 0 1579 `, shell) 1580 1581 if err = createBinScript(pathToInlineBuilpack, "build", binBuild, nil); err != nil { 1582 return pathToInlineBuilpack, err 1583 } 1584 1585 if err = createBinScript(pathToInlineBuilpack, "build.bat", bp.Script.Inline, nil); err != nil { 1586 return pathToInlineBuilpack, err 1587 } 1588 1589 if err = createBinScript(pathToInlineBuilpack, "detect", binDetect, nil); err != nil { 1590 return pathToInlineBuilpack, err 1591 } 1592 1593 if err = createBinScript(pathToInlineBuilpack, "detect.bat", bp.Script.Inline, nil); err != nil { 1594 return pathToInlineBuilpack, err 1595 } 1596 1597 return pathToInlineBuilpack, nil 1598 } 1599 1600 // fullImagePath parses the inputImageReference provided by the user and creates the directory 1601 // structure if create value is true 1602 func fullImagePath(inputImageRef InputImageReference, create bool) (string, error) { 1603 imagePath, err := inputImageRef.FullName() 1604 if err != nil { 1605 return "", errors.Wrapf(err, "evaluating image %s destination path", inputImageRef.Name()) 1606 } 1607 1608 if create { 1609 if err := os.MkdirAll(imagePath, os.ModePerm); err != nil { 1610 return "", errors.Wrapf(err, "creating %s layout application destination", imagePath) 1611 } 1612 } 1613 1614 return imagePath, nil 1615 } 1616 1617 // appendLayoutVolumes mount host volume into the build container, in the form '<host path>:<target path>[:<options>]' 1618 // the volumes mounted are: 1619 // - The path where the user wants the image to be exported in OCI layout format 1620 // - The previous image path if it exits 1621 // - The run-image path 1622 func appendLayoutVolumes(volumes []string, config layoutPathConfig) []string { 1623 if config.hostPreviousImagePath != "" { 1624 volumes = append(volumes, readOnlyVolume(config.hostPreviousImagePath, config.targetPreviousImagePath), 1625 readOnlyVolume(config.hostRunImagePath, config.targetRunImagePath), 1626 writableVolume(config.hostImagePath, config.targetImagePath)) 1627 } else { 1628 volumes = append(volumes, readOnlyVolume(config.hostRunImagePath, config.targetRunImagePath), 1629 writableVolume(config.hostImagePath, config.targetImagePath)) 1630 } 1631 return volumes 1632 } 1633 1634 func writableVolume(hostPath, targetPath string) string { 1635 tp := targetPath 1636 if !filepath.IsAbs(targetPath) { 1637 tp = filepath.Join(string(filepath.Separator), targetPath) 1638 } 1639 return fmt.Sprintf("%s:%s:rw", hostPath, tp) 1640 } 1641 1642 func readOnlyVolume(hostPath, targetPath string) string { 1643 tp := targetPath 1644 if !filepath.IsAbs(targetPath) { 1645 tp = filepath.Join(string(filepath.Separator), targetPath) 1646 } 1647 return fmt.Sprintf("%s:%s", hostPath, tp) 1648 }