github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/internal/commands/builder_create.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  
     7  	"github.com/pkg/errors"
     8  	"github.com/spf13/cobra"
     9  
    10  	"github.com/buildpacks/pack/builder"
    11  	"github.com/buildpacks/pack/internal/config"
    12  	"github.com/buildpacks/pack/internal/style"
    13  	"github.com/buildpacks/pack/pkg/buildpack"
    14  	"github.com/buildpacks/pack/pkg/client"
    15  	"github.com/buildpacks/pack/pkg/image"
    16  	"github.com/buildpacks/pack/pkg/logging"
    17  )
    18  
    19  // BuilderCreateFlags define flags provided to the CreateBuilder command
    20  type BuilderCreateFlags struct {
    21  	Publish         bool
    22  	BuilderTomlPath string
    23  	Registry        string
    24  	Policy          string
    25  	Flatten         []string
    26  	Label           map[string]string
    27  }
    28  
    29  // CreateBuilder creates a builder image, based on a builder config
    30  func BuilderCreate(logger logging.Logger, cfg config.Config, pack PackClient) *cobra.Command {
    31  	var flags BuilderCreateFlags
    32  
    33  	cmd := &cobra.Command{
    34  		Use:     "create <image-name> --config <builder-config-path>",
    35  		Args:    cobra.ExactArgs(1),
    36  		Short:   "Create builder image",
    37  		Example: "pack builder create my-builder:bionic --config ./builder.toml",
    38  		Long: `A builder is an image that bundles all the bits and information on how to build your apps, such as buildpacks, an implementation of the lifecycle, and a build-time environment that pack uses when executing the lifecycle. When building an app, you can use community builders; you can see our suggestions by running
    39  
    40  	pack builders suggest
    41  
    42  Creating a custom builder allows you to control what buildpacks are used and what image apps are based on. For more on how to create a builder, see: https://buildpacks.io/docs/operator-guide/create-a-builder/.
    43  `,
    44  		RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
    45  			if err := validateCreateFlags(&flags, cfg); err != nil {
    46  				return err
    47  			}
    48  
    49  			stringPolicy := flags.Policy
    50  			if stringPolicy == "" {
    51  				stringPolicy = cfg.PullPolicy
    52  			}
    53  			pullPolicy, err := image.ParsePullPolicy(stringPolicy)
    54  			if err != nil {
    55  				return errors.Wrapf(err, "parsing pull policy %s", flags.Policy)
    56  			}
    57  
    58  			builderConfig, warns, err := builder.ReadConfig(flags.BuilderTomlPath)
    59  			if err != nil {
    60  				return errors.Wrap(err, "invalid builder toml")
    61  			}
    62  			for _, w := range warns {
    63  				logger.Warnf("builder configuration: %s", w)
    64  			}
    65  
    66  			if hasExtensions(builderConfig) {
    67  				if !cfg.Experimental {
    68  					return errors.New("builder config contains image extensions; support for image extensions is currently experimental")
    69  				}
    70  			}
    71  
    72  			relativeBaseDir, err := filepath.Abs(filepath.Dir(flags.BuilderTomlPath))
    73  			if err != nil {
    74  				return errors.Wrap(err, "getting absolute path for config")
    75  			}
    76  
    77  			envMap, warnings, err := builder.ParseBuildConfigEnv(builderConfig.Build.Env, flags.BuilderTomlPath)
    78  			for _, v := range warnings {
    79  				logger.Warn(v)
    80  			}
    81  			if err != nil {
    82  				return err
    83  			}
    84  
    85  			toFlatten, err := buildpack.ParseFlattenBuildModules(flags.Flatten)
    86  			if err != nil {
    87  				return err
    88  			}
    89  
    90  			imageName := args[0]
    91  			if err := pack.CreateBuilder(cmd.Context(), client.CreateBuilderOptions{
    92  				RelativeBaseDir: relativeBaseDir,
    93  				BuildConfigEnv:  envMap,
    94  				BuilderName:     imageName,
    95  				Config:          builderConfig,
    96  				Publish:         flags.Publish,
    97  				Registry:        flags.Registry,
    98  				PullPolicy:      pullPolicy,
    99  				Flatten:         toFlatten,
   100  				Labels:          flags.Label,
   101  			}); err != nil {
   102  				return err
   103  			}
   104  			logger.Infof("Successfully created builder image %s", style.Symbol(imageName))
   105  			logging.Tip(logger, "Run %s to use this builder", style.Symbol(fmt.Sprintf("pack build <image-name> --builder %s", imageName)))
   106  			return nil
   107  		}),
   108  	}
   109  
   110  	cmd.Flags().StringVarP(&flags.Registry, "buildpack-registry", "R", cfg.DefaultRegistryName, "Buildpack Registry by name")
   111  	if !cfg.Experimental {
   112  		cmd.Flags().MarkHidden("buildpack-registry")
   113  	}
   114  	cmd.Flags().StringVarP(&flags.BuilderTomlPath, "config", "c", "", "Path to builder TOML file (required)")
   115  	cmd.Flags().BoolVar(&flags.Publish, "publish", false, "Publish the builder directly to the container registry specified in <image-name>, instead of the daemon.")
   116  	cmd.Flags().StringVar(&flags.Policy, "pull-policy", "", "Pull policy to use. Accepted values are always, never, and if-not-present. The default is always")
   117  	cmd.Flags().StringArrayVar(&flags.Flatten, "flatten", nil, "List of buildpacks to flatten together into a single layer (format: '<buildpack-id>@<buildpack-version>,<buildpack-id>@<buildpack-version>'")
   118  	cmd.Flags().StringToStringVarP(&flags.Label, "label", "l", nil, "Labels to add to the builder image, in the form of '<name>=<value>'")
   119  
   120  	AddHelpFlag(cmd, "create")
   121  	return cmd
   122  }
   123  
   124  func hasExtensions(builderConfig builder.Config) bool {
   125  	return len(builderConfig.Extensions) > 0 || len(builderConfig.OrderExtensions) > 0
   126  }
   127  
   128  func validateCreateFlags(flags *BuilderCreateFlags, cfg config.Config) error {
   129  	if flags.Publish && flags.Policy == image.PullNever.String() {
   130  		return errors.Errorf("--publish and --pull-policy never cannot be used together. The --publish flag requires the use of remote images.")
   131  	}
   132  
   133  	if flags.Registry != "" && !cfg.Experimental {
   134  		return client.NewExperimentError("Support for buildpack registries is currently experimental.")
   135  	}
   136  
   137  	if flags.BuilderTomlPath == "" {
   138  		return errors.Errorf("Please provide a builder config path, using --config.")
   139  	}
   140  
   141  	return nil
   142  }