github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/package_buildpack.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/pkg/errors"
     7  
     8  	pubbldpkg "github.com/buildpacks/pack/buildpackage"
     9  	"github.com/buildpacks/pack/internal/layer"
    10  	"github.com/buildpacks/pack/internal/paths"
    11  	"github.com/buildpacks/pack/internal/style"
    12  	"github.com/buildpacks/pack/pkg/blob"
    13  	"github.com/buildpacks/pack/pkg/buildpack"
    14  	"github.com/buildpacks/pack/pkg/image"
    15  )
    16  
    17  const (
    18  	// Packaging indicator that format of inputs/outputs will be an OCI image on the registry.
    19  	FormatImage = "image"
    20  
    21  	// Packaging indicator that format of output will be a file on the host filesystem.
    22  	FormatFile = "file"
    23  
    24  	// CNBExtension is the file extension for a cloud native buildpack tar archive
    25  	CNBExtension = ".cnb"
    26  )
    27  
    28  // PackageBuildpackOptions is a configuration object used to define
    29  // the behavior of PackageBuildpack.
    30  type PackageBuildpackOptions struct {
    31  	// The base director to resolve relative assest from
    32  	RelativeBaseDir string
    33  
    34  	// The name of the output buildpack artifact.
    35  	Name string
    36  
    37  	// Type of output format, The options are the either the const FormatImage, or FormatFile.
    38  	Format string
    39  
    40  	// Defines the Buildpacks configuration.
    41  	Config pubbldpkg.Config
    42  
    43  	// Push resulting builder image up to a registry
    44  	// specified in the Name variable.
    45  	Publish bool
    46  
    47  	// Strategy for updating images before packaging.
    48  	PullPolicy image.PullPolicy
    49  
    50  	// Name of the buildpack registry. Used to
    51  	// add buildpacks to a package.
    52  	Registry string
    53  
    54  	// Flatten layers
    55  	Flatten bool
    56  
    57  	// List of buildpack images to exclude from the package been flatten.
    58  	FlattenExclude []string
    59  
    60  	// Map of labels to add to the Buildpack
    61  	Labels map[string]string
    62  }
    63  
    64  // PackageBuildpack packages buildpack(s) into either an image or file.
    65  func (c *Client) PackageBuildpack(ctx context.Context, opts PackageBuildpackOptions) error {
    66  	if opts.Format == "" {
    67  		opts.Format = FormatImage
    68  	}
    69  
    70  	if opts.Config.Platform.OS == "windows" && !c.experimental {
    71  		return NewExperimentError("Windows buildpackage support is currently experimental.")
    72  	}
    73  
    74  	err := c.validateOSPlatform(ctx, opts.Config.Platform.OS, opts.Publish, opts.Format)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	writerFactory, err := layer.NewWriterFactory(opts.Config.Platform.OS)
    80  	if err != nil {
    81  		return errors.Wrap(err, "creating layer writer factory")
    82  	}
    83  
    84  	var packageBuilderOpts []buildpack.PackageBuilderOption
    85  	if opts.Flatten {
    86  		packageBuilderOpts = append(packageBuilderOpts, buildpack.DoNotFlatten(opts.FlattenExclude),
    87  			buildpack.WithLayerWriterFactory(writerFactory), buildpack.WithLogger(c.logger))
    88  	}
    89  	packageBuilder := buildpack.NewBuilder(c.imageFactory, packageBuilderOpts...)
    90  
    91  	bpURI := opts.Config.Buildpack.URI
    92  	if bpURI == "" {
    93  		return errors.New("buildpack URI must be provided")
    94  	}
    95  
    96  	mainBlob, err := c.downloadBuildpackFromURI(ctx, bpURI, opts.RelativeBaseDir)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	bp, err := buildpack.FromBuildpackRootBlob(mainBlob, writerFactory, c.logger)
   102  	if err != nil {
   103  		return errors.Wrapf(err, "creating buildpack from %s", style.Symbol(bpURI))
   104  	}
   105  
   106  	packageBuilder.SetBuildpack(bp)
   107  
   108  	for _, dep := range opts.Config.Dependencies {
   109  		mainBP, deps, err := c.buildpackDownloader.Download(ctx, dep.URI, buildpack.DownloadOptions{
   110  			RegistryName:    opts.Registry,
   111  			RelativeBaseDir: opts.RelativeBaseDir,
   112  			ImageOS:         opts.Config.Platform.OS,
   113  			ImageName:       dep.ImageName,
   114  			Daemon:          !opts.Publish,
   115  			PullPolicy:      opts.PullPolicy,
   116  		})
   117  
   118  		if err != nil {
   119  			return errors.Wrapf(err, "packaging dependencies (uri=%s,image=%s)", style.Symbol(dep.URI), style.Symbol(dep.ImageName))
   120  		}
   121  
   122  		packageBuilder.AddDependencies(mainBP, deps)
   123  	}
   124  
   125  	switch opts.Format {
   126  	case FormatFile:
   127  		return packageBuilder.SaveAsFile(opts.Name, opts.Config.Platform.OS, opts.Labels)
   128  	case FormatImage:
   129  		_, err = packageBuilder.SaveAsImage(opts.Name, opts.Publish, opts.Config.Platform.OS, opts.Labels)
   130  		return errors.Wrapf(err, "saving image")
   131  	default:
   132  		return errors.Errorf("unknown format: %s", style.Symbol(opts.Format))
   133  	}
   134  }
   135  
   136  func (c *Client) downloadBuildpackFromURI(ctx context.Context, uri, relativeBaseDir string) (blob.Blob, error) {
   137  	absPath, err := paths.FilePathToURI(uri, relativeBaseDir)
   138  	if err != nil {
   139  		return nil, errors.Wrapf(err, "making absolute: %s", style.Symbol(uri))
   140  	}
   141  	uri = absPath
   142  
   143  	c.logger.Debugf("Downloading buildpack from URI: %s", style.Symbol(uri))
   144  	blob, err := c.downloader.Download(ctx, uri)
   145  	if err != nil {
   146  		return nil, errors.Wrapf(err, "downloading buildpack from %s", style.Symbol(uri))
   147  	}
   148  
   149  	return blob, nil
   150  }
   151  
   152  func (c *Client) validateOSPlatform(ctx context.Context, os string, publish bool, format string) error {
   153  	if publish || format == FormatFile {
   154  		return nil
   155  	}
   156  
   157  	info, err := c.docker.Info(ctx)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	if info.OSType != os {
   163  		return errors.Errorf("invalid %s specified: DOCKER_OS is %s", style.Symbol("platform.os"), style.Symbol(info.OSType))
   164  	}
   165  
   166  	return nil
   167  }