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  }