github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/internal/commands/buildpack_package.go (about) 1 package commands 2 3 import ( 4 "context" 5 "path/filepath" 6 "strings" 7 8 "github.com/pkg/errors" 9 "github.com/spf13/cobra" 10 11 pubbldpkg "github.com/buildpacks/pack/buildpackage" 12 "github.com/buildpacks/pack/internal/config" 13 "github.com/buildpacks/pack/internal/style" 14 "github.com/buildpacks/pack/pkg/client" 15 "github.com/buildpacks/pack/pkg/image" 16 "github.com/buildpacks/pack/pkg/logging" 17 ) 18 19 // BuildpackPackageFlags define flags provided to the BuildpackPackage command 20 type BuildpackPackageFlags struct { 21 PackageTomlPath string 22 Format string 23 Policy string 24 BuildpackRegistry string 25 Path string 26 FlattenExclude []string 27 Label map[string]string 28 Publish bool 29 Flatten bool 30 } 31 32 // BuildpackPackager packages buildpacks 33 type BuildpackPackager interface { 34 PackageBuildpack(ctx context.Context, options client.PackageBuildpackOptions) error 35 } 36 37 // PackageConfigReader reads BuildpackPackage configs 38 type PackageConfigReader interface { 39 Read(path string) (pubbldpkg.Config, error) 40 } 41 42 // BuildpackPackage packages (a) buildpack(s) into OCI format, based on a package config 43 func BuildpackPackage(logger logging.Logger, cfg config.Config, packager BuildpackPackager, packageConfigReader PackageConfigReader) *cobra.Command { 44 var flags BuildpackPackageFlags 45 cmd := &cobra.Command{ 46 Use: "package <name> --config <config-path>", 47 Short: "Package a buildpack in OCI format.", 48 Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), 49 Example: "pack buildpack package my-buildpack --config ./package.toml\npack buildpack package my-buildpack.cnb --config ./package.toml --f file", 50 Long: "buildpack package allows users to package (a) buildpack(s) into OCI format, which can then to be hosted in " + 51 "image repositories or persisted on disk as a '.cnb' file. You can also package a number of buildpacks " + 52 "together, to enable easier distribution of a set of buildpacks. " + 53 "Packaged buildpacks can be used as inputs to `pack build` (using the `--buildpack` flag), " + 54 "and they can be included in the configs used in `pack builder create` and `pack buildpack package`. For more " + 55 "on how to package a buildpack, see: https://buildpacks.io/docs/buildpack-author-guide/package-a-buildpack/.", 56 RunE: logError(logger, func(cmd *cobra.Command, args []string) error { 57 if err := validateBuildpackPackageFlags(cfg, &flags); err != nil { 58 return err 59 } 60 61 stringPolicy := flags.Policy 62 if stringPolicy == "" { 63 stringPolicy = cfg.PullPolicy 64 } 65 pullPolicy, err := image.ParsePullPolicy(stringPolicy) 66 if err != nil { 67 return errors.Wrap(err, "parsing pull policy") 68 } 69 bpPackageCfg := pubbldpkg.DefaultConfig() 70 var bpPath string 71 if flags.Path != "" { 72 if bpPath, err = filepath.Abs(flags.Path); err != nil { 73 return errors.Wrap(err, "resolving buildpack path") 74 } 75 bpPackageCfg.Buildpack.URI = bpPath 76 } 77 relativeBaseDir := "" 78 if flags.PackageTomlPath != "" { 79 bpPackageCfg, err = packageConfigReader.Read(flags.PackageTomlPath) 80 if err != nil { 81 return errors.Wrap(err, "reading config") 82 } 83 84 relativeBaseDir, err = filepath.Abs(filepath.Dir(flags.PackageTomlPath)) 85 if err != nil { 86 return errors.Wrap(err, "getting absolute path for config") 87 } 88 } 89 name := args[0] 90 if flags.Format == client.FormatFile { 91 switch ext := filepath.Ext(name); ext { 92 case client.CNBExtension: 93 case "": 94 name += client.CNBExtension 95 default: 96 logger.Warnf("%s is not a valid extension for a packaged buildpack. Packaged buildpacks must have a %s extension", style.Symbol(ext), style.Symbol(client.CNBExtension)) 97 } 98 } 99 if flags.Flatten { 100 logger.Warn("Flattening a buildpack package could break the distribution specification. Please use it with caution.") 101 } 102 103 if err := packager.PackageBuildpack(cmd.Context(), client.PackageBuildpackOptions{ 104 RelativeBaseDir: relativeBaseDir, 105 Name: name, 106 Format: flags.Format, 107 Config: bpPackageCfg, 108 Publish: flags.Publish, 109 PullPolicy: pullPolicy, 110 Registry: flags.BuildpackRegistry, 111 Flatten: flags.Flatten, 112 FlattenExclude: flags.FlattenExclude, 113 Labels: flags.Label, 114 }); err != nil { 115 return err 116 } 117 118 action := "created" 119 location := "docker daemon" 120 if flags.Publish { 121 action = "published" 122 location = "registry" 123 } 124 if flags.Format == client.FormatFile { 125 location = "file" 126 } 127 logger.Infof("Successfully %s package %s and saved to %s", action, style.Symbol(name), location) 128 return nil 129 }), 130 } 131 132 cmd.Flags().StringVarP(&flags.PackageTomlPath, "config", "c", "", "Path to package TOML config") 133 cmd.Flags().StringVarP(&flags.Format, "format", "f", "", `Format to save package as ("image" or "file")`) 134 cmd.Flags().BoolVar(&flags.Publish, "publish", false, `Publish the buildpack directly to the container registry specified in <name>, instead of the daemon (applies to "--format=image" only).`) 135 cmd.Flags().StringVar(&flags.Policy, "pull-policy", "", "Pull policy to use. Accepted values are always, never, and if-not-present. The default is always") 136 cmd.Flags().StringVarP(&flags.Path, "path", "p", "", "Path to the Buildpack that needs to be packaged") 137 cmd.Flags().StringVarP(&flags.BuildpackRegistry, "buildpack-registry", "r", "", "Buildpack Registry name") 138 cmd.Flags().BoolVar(&flags.Flatten, "flatten", false, "Flatten the buildpack into a single layer") 139 cmd.Flags().StringSliceVarP(&flags.FlattenExclude, "flatten-exclude", "e", nil, "Buildpacks to exclude from flattening, in the form of '<buildpack-id>@<buildpack-version>'") 140 cmd.Flags().StringToStringVarP(&flags.Label, "label", "l", nil, "Labels to add to packaged Buildpack, in the form of '<name>=<value>'") 141 if !cfg.Experimental { 142 cmd.Flags().MarkHidden("flatten") 143 cmd.Flags().MarkHidden("flatten-exclude") 144 } 145 AddHelpFlag(cmd, "package") 146 return cmd 147 } 148 149 func validateBuildpackPackageFlags(cfg config.Config, p *BuildpackPackageFlags) error { 150 if p.Publish && p.Policy == image.PullNever.String() { 151 return errors.Errorf("--publish and --pull-policy never cannot be used together. The --publish flag requires the use of remote images.") 152 } 153 if p.PackageTomlPath != "" && p.Path != "" { 154 return errors.Errorf("--config and --path cannot be used together. Please specify the relative path to the Buildpack directory in the package config file.") 155 } 156 157 if p.Flatten { 158 if !cfg.Experimental { 159 return client.NewExperimentError("Flattening a buildpack package is currently experimental.") 160 } 161 162 if len(p.FlattenExclude) > 0 { 163 for _, exclude := range p.FlattenExclude { 164 if strings.Count(exclude, "@") != 1 { 165 return errors.Errorf("invalid format %s; please use '<buildpack-id>@<buildpack-version>' to exclude buildpack from flattening", exclude) 166 } 167 } 168 } 169 } 170 return nil 171 }