11package client
22
33import (
4+ "archive/tar"
45 "context"
56 "crypto/rand"
7+ "crypto/sha256"
8+ "encoding/hex"
69 "encoding/json"
710 "fmt"
11+ "io"
812 "os"
913 "path/filepath"
1014 "runtime"
1115 "sort"
1216 "strings"
1317 "time"
1418
15- "github.com/buildpacks/pack/buildpackage"
16-
1719 "github.com/Masterminds/semver"
1820 "github.com/buildpacks/imgutil"
1921 "github.com/buildpacks/imgutil/layout"
@@ -26,6 +28,7 @@ import (
2628 "github.com/pkg/errors"
2729 ignore "github.com/sabhiram/go-gitignore"
2830
31+ "github.com/buildpacks/pack/buildpackage"
2932 "github.com/buildpacks/pack/internal/build"
3033 "github.com/buildpacks/pack/internal/builder"
3134 internalConfig "github.com/buildpacks/pack/internal/config"
@@ -509,18 +512,13 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
509512 }
510513 }
511514
512- fetchRunImage := func (name string ) error {
513- _ , err := c .imageFetcher .Fetch (ctx , name , fetchOptions )
514- return err
515- }
516515 lifecycleOpts := build.LifecycleOptions {
517516 AppPath : appPath ,
518517 Image : imageRef ,
519518 Builder : ephemeralBuilder ,
520519 BuilderImage : builderRef .Name (),
521520 LifecycleImage : ephemeralBuilder .Name (),
522521 RunImage : runImageName ,
523- FetchRunImage : fetchRunImage ,
524522 ProjectMetadata : projectMetadata ,
525523 ClearCache : opts .ClearCache ,
526524 Publish : opts .Publish ,
@@ -559,6 +557,140 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
559557 return errors .Errorf ("Lifecycle %s does not have an associated lifecycle image. Builder must be trusted." , lifecycleVersion .String ())
560558 }
561559
560+ lifecycleOpts .FetchRunImageWithLifecycleLayer = func (runImageName string ) (string , error ) {
561+ ephemeralRunImageName := fmt .Sprintf ("pack.local/run-image/%x:latest" , randString (10 ))
562+ runImage , err := c .imageFetcher .Fetch (ctx , runImageName , fetchOptions )
563+ if err != nil {
564+ return "" , err
565+ }
566+ ephemeralRunImage , err := local .NewImage (ephemeralRunImageName , c .docker , local .FromBaseImage (runImage .Name ()))
567+ if err != nil {
568+ return "" , err
569+ }
570+ tmpDir , err := os .MkdirTemp ("" , "extend-run-image-scratch" ) // we need to write to disk because manifest.json is last in the tar
571+ if err != nil {
572+ return "" , err
573+ }
574+ defer os .RemoveAll (tmpDir )
575+ lifecycleImageTar , err := func () (string , error ) {
576+ lifecycleImageTar := filepath .Join (tmpDir , "lifecycle-image.tar" )
577+ lifecycleImageReader , err := c .docker .ImageSave (context .Background (), []string {lifecycleOpts .LifecycleImage }) // this is fast because the lifecycle image is based on distroless static
578+ if err != nil {
579+ return "" , err
580+ }
581+ defer lifecycleImageReader .Close ()
582+ lifecycleImageWriter , err := os .Create (lifecycleImageTar )
583+ if err != nil {
584+ return "" , err
585+ }
586+ defer lifecycleImageWriter .Close ()
587+ if _ , err = io .Copy (lifecycleImageWriter , lifecycleImageReader ); err != nil {
588+ return "" , err
589+ }
590+ return lifecycleImageTar , nil
591+ }()
592+ if err != nil {
593+ return "" , err
594+ }
595+ advanceTarToEntryWithName := func (tarReader * tar.Reader , wantName string ) (* tar.Header , error ) {
596+ var (
597+ header * tar.Header
598+ err error
599+ )
600+ for {
601+ header , err = tarReader .Next ()
602+ if err == io .EOF {
603+ break
604+ }
605+ if err != nil {
606+ return nil , err
607+ }
608+ if header .Name != wantName {
609+ continue
610+ }
611+ return header , nil
612+ }
613+ return nil , fmt .Errorf ("failed to find header with name: %s" , wantName )
614+ }
615+ lifecycleLayerName , err := func () (string , error ) {
616+ lifecycleImageReader , err := os .Open (lifecycleImageTar )
617+ if err != nil {
618+ return "" , err
619+ }
620+ defer lifecycleImageReader .Close ()
621+ tarReader := tar .NewReader (lifecycleImageReader )
622+ if _ , err = advanceTarToEntryWithName (tarReader , "manifest.json" ); err != nil {
623+ return "" , err
624+ }
625+ type descriptor struct {
626+ Layers []string
627+ }
628+ type manifestJSON []descriptor
629+ var manifestContents manifestJSON
630+ if err = json .NewDecoder (tarReader ).Decode (& manifestContents ); err != nil {
631+ return "" , err
632+ }
633+ if len (manifestContents ) < 1 {
634+ return "" , errors .New ("missing manifest entries" )
635+ }
636+ return manifestContents [0 ].Layers [len (manifestContents [0 ].Layers )- 1 ], nil // we can assume the lifecycle layer is the last in the tar
637+ }()
638+ if err != nil {
639+ return "" , err
640+ }
641+ if lifecycleLayerName == "" {
642+ return "" , errors .New ("failed to find lifecycle layer" )
643+ }
644+ lifecycleLayerTar , err := func () (string , error ) {
645+ lifecycleImageReader , err := os .Open (lifecycleImageTar )
646+ if err != nil {
647+ return "" , err
648+ }
649+ defer lifecycleImageReader .Close ()
650+ tarReader := tar .NewReader (lifecycleImageReader )
651+ var header * tar.Header
652+ if header , err = advanceTarToEntryWithName (tarReader , lifecycleLayerName ); err != nil {
653+ return "" , err
654+ }
655+ lifecycleLayerTar := filepath .Join (filepath .Dir (lifecycleImageTar ), filepath .Dir (lifecycleLayerName )+ ".tar" )
656+ lifecycleLayerWriter , err := os .OpenFile (lifecycleLayerTar , os .O_CREATE | os .O_RDWR , os .FileMode (header .Mode ))
657+ if err != nil {
658+ return "" , err
659+ }
660+ defer lifecycleLayerWriter .Close ()
661+ if _ , err = io .Copy (lifecycleLayerWriter , tarReader ); err != nil {
662+ return "" , err
663+ }
664+ return lifecycleLayerTar , nil
665+ }()
666+ if err != nil {
667+ return "" , err
668+ }
669+ diffID , err := func () (string , error ) {
670+ lifecycleLayerReader , err := os .Open (lifecycleLayerTar )
671+ if err != nil {
672+ return "" , err
673+ }
674+ defer lifecycleLayerReader .Close ()
675+ hasher := sha256 .New ()
676+ if _ , err = io .Copy (hasher , lifecycleLayerReader ); err != nil {
677+ return "" , err
678+ }
679+ // it's weird that this doesn't match lifecycleLayerTar
680+ return hex .EncodeToString (hasher .Sum (nil )), nil
681+ }()
682+ if err != nil {
683+ return "" , err
684+ }
685+ if err = ephemeralRunImage .AddLayerWithDiffID (lifecycleLayerTar , "sha256:" + diffID ); err != nil {
686+ return "" , err
687+ }
688+ if err = ephemeralRunImage .Save (); err != nil {
689+ return "" , err
690+ }
691+ return ephemeralRunImageName , nil
692+ }
693+
562694 if err = c .lifecycleExecutor .Execute (ctx , lifecycleOpts ); err != nil {
563695 return fmt .Errorf ("executing lifecycle: %w" , err )
564696 }
0 commit comments