github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/rebase.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 9 "github.com/BurntSushi/toml" 10 "github.com/buildpacks/lifecycle/phase" 11 "github.com/buildpacks/lifecycle/platform" 12 "github.com/buildpacks/lifecycle/platform/files" 13 "github.com/pkg/errors" 14 15 "github.com/buildpacks/pack/internal/build" 16 "github.com/buildpacks/pack/internal/builder" 17 "github.com/buildpacks/pack/internal/style" 18 "github.com/buildpacks/pack/pkg/dist" 19 "github.com/buildpacks/pack/pkg/image" 20 ) 21 22 // RebaseOptions is a configuration struct that controls image rebase behavior. 23 type RebaseOptions struct { 24 // Name of image we wish to rebase. 25 RepoName string 26 27 // Flag to publish image to remote registry after rebase completion. 28 Publish bool 29 30 // Strategy for pulling images during rebase. 31 PullPolicy image.PullPolicy 32 33 // Image to rebase against. This image must have 34 // the same StackID as the previous run image. 35 RunImage string 36 37 // A mapping from StackID to an array of mirrors. 38 // This mapping used only if both RunImage is omitted and Publish is true. 39 // AdditionalMirrors gives us inputs to recalculate the 'best' run image 40 // based on the registry we are publishing to. 41 AdditionalMirrors map[string][]string 42 43 // If provided, directory to which report.toml will be copied 44 ReportDestinationDir string 45 46 // Pass-through force flag to lifecycle rebase command to skip target data 47 // validated (will not have any effect if API < 0.12). 48 Force bool 49 50 // Image reference to use as the previous image for rebase. 51 PreviousImage string 52 } 53 54 // Rebase updates the run image layers in an app image. 55 // This operation mutates the image specified in opts. 56 func (c *Client) Rebase(ctx context.Context, opts RebaseOptions) error { 57 imageRef, err := c.parseTagReference(opts.RepoName) 58 if err != nil { 59 return errors.Wrapf(err, "invalid image name '%s'", opts.RepoName) 60 } 61 62 repoName := opts.RepoName 63 64 if opts.PreviousImage != "" { 65 repoName = opts.PreviousImage 66 } 67 68 appImage, err := c.imageFetcher.Fetch(ctx, repoName, image.FetchOptions{Daemon: !opts.Publish, PullPolicy: opts.PullPolicy}) 69 if err != nil { 70 return err 71 } 72 73 appOS, err := appImage.OS() 74 if err != nil { 75 return errors.Wrapf(err, "getting app OS") 76 } 77 78 appArch, err := appImage.Architecture() 79 if err != nil { 80 return errors.Wrapf(err, "getting app architecture") 81 } 82 83 var md files.LayersMetadataCompat 84 if ok, err := dist.GetLabel(appImage, platform.LifecycleMetadataLabel, &md); err != nil { 85 return err 86 } else if !ok { 87 return errors.Errorf("could not find label %s on image", style.Symbol(platform.LifecycleMetadataLabel)) 88 } 89 var runImageMD builder.RunImageMetadata 90 if md.RunImage.Image != "" { 91 runImageMD = builder.RunImageMetadata{ 92 Image: md.RunImage.Image, 93 Mirrors: md.RunImage.Mirrors, 94 } 95 } else if md.Stack != nil { 96 runImageMD = builder.RunImageMetadata{ 97 Image: md.Stack.RunImage.Image, 98 Mirrors: md.Stack.RunImage.Mirrors, 99 } 100 } 101 102 fetchOptions := image.FetchOptions{ 103 Daemon: !opts.Publish, 104 PullPolicy: opts.PullPolicy, 105 Platform: fmt.Sprintf("%s/%s", appOS, appArch), 106 } 107 108 runImageName := c.resolveRunImage( 109 opts.RunImage, 110 imageRef.Context().RegistryStr(), 111 "", 112 runImageMD, 113 opts.AdditionalMirrors, 114 opts.Publish, 115 fetchOptions, 116 ) 117 118 if runImageName == "" { 119 return errors.New("run image must be specified") 120 } 121 122 baseImage, err := c.imageFetcher.Fetch(ctx, runImageName, fetchOptions) 123 if err != nil { 124 return err 125 } 126 127 c.logger.Infof("Rebasing %s on run image %s", style.Symbol(appImage.Name()), style.Symbol(baseImage.Name())) 128 rebaser := &phase.Rebaser{Logger: c.logger, PlatformAPI: build.SupportedPlatformAPIVersions.Latest(), Force: opts.Force} 129 report, err := rebaser.Rebase(appImage, baseImage, opts.RepoName, nil) 130 if err != nil { 131 return err 132 } 133 134 appImageIdentifier, err := appImage.Identifier() 135 if err != nil { 136 return err 137 } 138 139 c.logger.Infof("Rebased Image: %s", style.Symbol(appImageIdentifier.String())) 140 141 if opts.ReportDestinationDir != "" { 142 reportPath := filepath.Join(opts.ReportDestinationDir, "report.toml") 143 reportFile, err := os.OpenFile(reportPath, os.O_RDWR|os.O_CREATE, 0644) 144 if err != nil { 145 c.logger.Warnf("unable to open %s for writing rebase report", reportPath) 146 return err 147 } 148 149 defer reportFile.Close() 150 err = toml.NewEncoder(reportFile).Encode(report) 151 if err != nil { 152 c.logger.Warnf("unable to write rebase report to %s", reportPath) 153 return err 154 } 155 } 156 return nil 157 }