From 0660a0363c502aeb325bd11f1606591d76cde026 Mon Sep 17 00:00:00 2001 From: Joao Pereira Date: Mon, 14 Jan 2019 16:15:36 -0500 Subject: [PATCH] Move commands to `commands` package - Rename some file in main package to match the structs Signed-off-by: Andrew Meyer Signed-off-by: Joao Pereira --- build.go => build_factory.go | 0 build_test.go => build_factory_test.go | 6 +- create_builder.go => builder_factory.go | 0 ...builder_test.go => builder_factory_test.go | 6 +- inspect_builder.go => builder_inspector.go | 0 ...ilder_test.go => builder_inspector_test.go | 0 cmd/pack/main.go | 509 +----------------- commands/add_stack.go | 42 ++ commands/build.go | 49 ++ commands/commands.go | 49 ++ commands/configure_builder.go | 34 ++ commands/create_builder.go | 64 +++ commands/delete_stack.go | 30 ++ commands/inspect_builder.go | 76 +++ commands/rebase.go | 50 ++ commands/run.go | 33 ++ commands/set_default_builder.go | 31 ++ commands/set_default_stack.go | 31 ++ commands/show_stacks.go | 48 ++ commands/update_stack.go | 39 ++ commands/version.go | 23 + rebase.go => rebase_factory.go | 0 rebase_test.go => rebase_factory_test.go | 6 +- run.go | 2 + 24 files changed, 632 insertions(+), 496 deletions(-) rename build.go => build_factory.go (100%) rename build_test.go => build_factory_test.go (99%) rename create_builder.go => builder_factory.go (100%) rename create_builder_test.go => builder_factory_test.go (98%) rename inspect_builder.go => builder_inspector.go (100%) rename inspect_builder_test.go => builder_inspector_test.go (100%) create mode 100644 commands/add_stack.go create mode 100644 commands/build.go create mode 100644 commands/commands.go create mode 100644 commands/configure_builder.go create mode 100644 commands/create_builder.go create mode 100644 commands/delete_stack.go create mode 100644 commands/inspect_builder.go create mode 100644 commands/rebase.go create mode 100644 commands/run.go create mode 100644 commands/set_default_builder.go create mode 100644 commands/set_default_stack.go create mode 100644 commands/show_stacks.go create mode 100644 commands/update_stack.go create mode 100644 commands/version.go rename rebase.go => rebase_factory.go (100%) rename rebase_test.go => rebase_factory_test.go (96%) diff --git a/build.go b/build_factory.go similarity index 100% rename from build.go rename to build_factory.go diff --git a/build_test.go b/build_factory_test.go similarity index 99% rename from build_test.go rename to build_factory_test.go index f2ce58929..6caba4e39 100644 --- a/build_test.go +++ b/build_factory_test.go @@ -36,7 +36,7 @@ import ( var registryPort string -func TestBuild(t *testing.T) { +func TestBuildFactory(t *testing.T) { color.NoColor = true rand.Seed(time.Now().UTC().UnixNano()) @@ -48,10 +48,10 @@ func TestBuild(t *testing.T) { h.ConfigurePackHome(t, packHome, registryPort) defer h.CleanDefaultImages(t, registryPort) - spec.Run(t, "build", testBuild, spec.Report(report.Terminal{})) + spec.Run(t, "build_factory", testBuildFactory, spec.Report(report.Terminal{})) } -func testBuild(t *testing.T, when spec.G, it spec.S) { +func testBuildFactory(t *testing.T, when spec.G, it spec.S) { var subject *pack.BuildConfig var outBuf bytes.Buffer var errBuf bytes.Buffer diff --git a/create_builder.go b/builder_factory.go similarity index 100% rename from create_builder.go rename to builder_factory.go diff --git a/create_builder_test.go b/builder_factory_test.go similarity index 98% rename from create_builder_test.go rename to builder_factory_test.go index dcc8aaf65..c3097e38f 100644 --- a/create_builder_test.go +++ b/builder_factory_test.go @@ -27,15 +27,15 @@ import ( h "github.com/buildpack/pack/testhelpers" ) -func TestCreateBuilder(t *testing.T) { +func TestBuilderFactory(t *testing.T) { color.NoColor = true if runtime.GOOS == "windows" { t.Skip("create builder is not implemented on windows") } - spec.Run(t, "create-builder", testCreateBuilder, spec.Parallel(), spec.Report(report.Terminal{})) + spec.Run(t, "builder_factory", testBuilderFactory, spec.Parallel(), spec.Report(report.Terminal{})) } -func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { +func testBuilderFactory(t *testing.T, when spec.G, it spec.S) { when("#BuilderFactory", func() { const ( defaultStack = "some.default.stack" diff --git a/inspect_builder.go b/builder_inspector.go similarity index 100% rename from inspect_builder.go rename to builder_inspector.go diff --git a/inspect_builder_test.go b/builder_inspector_test.go similarity index 100% rename from inspect_builder_test.go rename to builder_inspector_test.go diff --git a/cmd/pack/main.go b/cmd/pack/main.go index e2371d3cb..b5d92026f 100644 --- a/cmd/pack/main.go +++ b/cmd/pack/main.go @@ -1,517 +1,52 @@ package main import ( - "bytes" - "fmt" "os" - "os/signal" - "runtime" - "strings" - "syscall" - "text/tabwriter" - "github.com/buildpack/pack/logging" - "github.com/buildpack/pack/style" "github.com/fatih/color" - "github.com/pkg/errors" - - "github.com/buildpack/pack" - "github.com/buildpack/pack/config" - "github.com/buildpack/pack/fs" - - "github.com/buildpack/lifecycle/image" "github.com/spf13/cobra" + + "github.com/buildpack/pack/commands" + "github.com/buildpack/pack/logging" ) var ( Version = "0.0.0" timestamps, quiet bool - logger *logging.Logger + logger logging.Logger ) func main() { + cobra.EnableCommandSorting = false rootCmd := &cobra.Command{ Use: "pack", PersistentPreRun: func(cmd *cobra.Command, args []string) { - logger = logging.NewLogger(os.Stdout, os.Stderr, !quiet, timestamps) + logger = *logging.NewLogger(os.Stdout, os.Stderr, !quiet, timestamps) }, } rootCmd.PersistentFlags().BoolVar(&color.NoColor, "no-color", false, "Disable color output") rootCmd.PersistentFlags().BoolVar(×tamps, "timestamps", false, "Enable timestamps in output") rootCmd.PersistentFlags().BoolVarP(&quiet, "quiet", "q", false, "Show less output") - addHelpFlag(rootCmd, "pack") - for _, f := range []func() *cobra.Command{ - buildCommand, - runCommand, - rebaseCommand, - createBuilderCommand, - addStackCommand, - updateStackCommand, - deleteStackCommand, - showStacksCommand, - setDefaultStackCommand, - setDefaultBuilderCommand, - configureBuilderCommand, - inspectBuilderCommand, - versionCommand, - } { - rootCmd.AddCommand(f()) - } - if err := rootCmd.Execute(); err != nil { - os.Exit(1) - } -} + commands.AddHelpFlag(rootCmd, "pack") -func buildCommand() *cobra.Command { - var buildFlags pack.BuildFlags - cmd := &cobra.Command{ - Use: "build ", - Args: cobra.ExactArgs(1), - Short: "Generate app image from source code", - RunE: logError(func(cmd *cobra.Command, args []string) error { - buildFlags.RepoName = args[0] - bf, err := pack.DefaultBuildFactory(logger) - if err != nil { - return err - } - b, err := bf.BuildConfigFromFlags(&buildFlags) - if err != nil { - return err - } - if err := b.Run(); err != nil { - return err - } - logger.Info("Successfully built image %s", style.Symbol(b.RepoName)) - return nil - }), - } - buildCommandFlags(cmd, &buildFlags) - cmd.Flags().BoolVar(&buildFlags.Publish, "publish", false, "Publish to registry") - addHelpFlag(cmd, "build") - return cmd -} + rootCmd.AddCommand(commands.Build(&logger)) + rootCmd.AddCommand(commands.Run(&logger)) + rootCmd.AddCommand(commands.Rebase(&logger)) -func runCommand() *cobra.Command { - var runFlags pack.RunFlags - cmd := &cobra.Command{ - Use: "run", - Args: cobra.NoArgs, - Short: "Build and run app image (recommended for development only)", - RunE: logError(func(cmd *cobra.Command, args []string) error { - bf, err := pack.DefaultBuildFactory(logger) - if err != nil { - return err - } - r, err := bf.RunConfigFromFlags(&runFlags) - if err != nil { - return err - } - return r.Run(makeStopChannelForSignals) - }), - } + rootCmd.AddCommand(commands.CreateBuilder(&logger)) + rootCmd.AddCommand(commands.ConfigureBuilder(&logger)) + rootCmd.AddCommand(commands.InspectBuilder(&logger)) + rootCmd.AddCommand(commands.SetDefaultBuilder(&logger)) - buildCommandFlags(cmd, &runFlags.BuildFlags) - cmd.Flags().StringSliceVar(&runFlags.Ports, "port", nil, "Port to publish (defaults to port(s) exposed by container)"+multiValueHelp("port")) - addHelpFlag(cmd, "run") - return cmd -} + rootCmd.AddCommand(commands.AddStack(&logger)) + rootCmd.AddCommand(commands.UpdateStack(&logger)) + rootCmd.AddCommand(commands.DeleteStack(&logger)) + rootCmd.AddCommand(commands.ShowStacks(&logger)) + rootCmd.AddCommand(commands.SetDefaultStack(&logger)) -func buildCommandFlags(cmd *cobra.Command, buildFlags *pack.BuildFlags) { - cmd.Flags().StringVarP(&buildFlags.AppDir, "path", "p", "", "Path to app dir (defaults to current working directory)") - cmd.Flags().StringVar(&buildFlags.Builder, "builder", "", "Builder (defaults to builder configured by 'set-default-builder')") - cmd.Flags().StringVar(&buildFlags.RunImage, "run-image", "", "Run image (defaults to default stack's run image)") - cmd.Flags().StringVar(&buildFlags.EnvFile, "env-file", "", "Build-time environment variables file\nOne variable per line, of the form 'VAR=VALUE' or 'VAR'\nWhen using latter value-less form, value will be taken from current\n environment at the time this command is executed") - cmd.Flags().BoolVar(&buildFlags.NoPull, "no-pull", false, "Skip pulling images before use") - cmd.Flags().BoolVar(&buildFlags.ClearCache, "clear-cache", false, "Clear image's associated cache before building") - _ = cmd.Flags().MarkHidden("clear-cache") - cmd.Flags().StringSliceVar(&buildFlags.Buildpacks, "buildpack", nil, "Buildpack ID, path to directory, or path/URL to .tgz file"+multiValueHelp("buildpack")) -} - -func rebaseCommand() *cobra.Command { - var flags pack.RebaseFlags - cmd := &cobra.Command{ - Use: "rebase ", - Args: cobra.ExactArgs(1), - Short: "Rebase app image with latest run image", - RunE: logError(func(cmd *cobra.Command, args []string) error { - flags.RepoName = args[0] - - imageFactory, err := image.DefaultFactory() - if err != nil { - return err - } - cfg, err := config.NewDefault() - if err != nil { - return err - } - factory := pack.RebaseFactory{ - Logger: logger, - Config: cfg, - ImageFactory: imageFactory, - } - rebaseConfig, err := factory.RebaseConfigFromFlags(flags) - if err != nil { - return err - } - if err := factory.Rebase(rebaseConfig); err != nil { - return err - } - logger.Info("Successfully rebased image %s", style.Symbol(rebaseConfig.Image.Name())) - return nil - }), - } - cmd.Flags().BoolVar(&flags.Publish, "publish", false, "Publish to registry") - cmd.Flags().BoolVar(&flags.NoPull, "no-pull", false, "Skip pulling images before use") - addHelpFlag(cmd, "rebase") - return cmd -} + rootCmd.AddCommand(commands.Version(&logger, Version)) -func createBuilderCommand() *cobra.Command { - flags := pack.CreateBuilderFlags{} - cmd := &cobra.Command{ - Use: "create-builder --builder-config ", - Args: cobra.ExactArgs(1), - Short: "Create builder image", - RunE: logError(func(cmd *cobra.Command, args []string) error { - flags.RepoName = args[0] - - if runtime.GOOS == "windows" { - return fmt.Errorf("%s is not implemented on Windows", style.Symbol("create-builder")) - } - - cfg, err := config.NewDefault() - if err != nil { - return err - } - imageFactory, err := image.DefaultFactory() - if err != nil { - return err - } - builderFactory := pack.BuilderFactory{ - FS: &fs.FS{}, - Logger: logger, - Config: cfg, - ImageFactory: imageFactory, - } - builderConfig, err := builderFactory.BuilderConfigFromFlags(flags) - if err != nil { - return err - } - if err := builderFactory.Create(builderConfig); err != nil { - return err - } - imageName := builderConfig.Repo.Name() - logger.Info("Successfully created builder image %s", style.Symbol(imageName)) - logger.Tip("Run %s to use this builder", style.Symbol(fmt.Sprintf("pack build --builder %s", imageName))) - return nil - }), - } - cmd.Flags().BoolVar(&flags.NoPull, "no-pull", false, "Skip pulling stack image before use") - cmd.Flags().StringVarP(&flags.BuilderTomlPath, "builder-config", "b", "", "Path to builder TOML file (required)") - cmd.MarkFlagRequired("builder-config") - cmd.Flags().StringVarP(&flags.StackID, "stack", "s", "", "Stack ID (defaults to stack configured by 'set-default-stack')") - cmd.Flags().BoolVar(&flags.Publish, "publish", false, "Publish to registry") - addHelpFlag(cmd, "create-builder") - return cmd -} - -func addStackCommand() *cobra.Command { - flags := struct { - BuildImage string - RunImages []string - }{} - cmd := &cobra.Command{ - Use: "add-stack --build-image --run-image ", - Args: cobra.ExactArgs(1), - Short: "Add stack to list of available stacks", - RunE: logError(func(cmd *cobra.Command, args []string) error { - cfg, err := config.NewDefault() - if err != nil { - return err - } - if err := cfg.AddStack(config.Stack{ - ID: args[0], - BuildImage: flags.BuildImage, - RunImages: flags.RunImages, - }); err != nil { - return err - } - logger.Info("Stack %s added", style.Symbol(args[0])) - return nil - }), - } - cmd.Flags().StringVarP(&flags.BuildImage, "build-image", "b", "", "Build image to associate with stack (required)") - cmd.MarkFlagRequired("build-image") - cmd.Flags().StringSliceVarP(&flags.RunImages, "run-image", "r", nil, "Run image to associate with stack (required)"+multiValueHelp("run image")) - cmd.MarkFlagRequired("run-image") - addHelpFlag(cmd, "add-stack") - return cmd -} - -func setDefaultStackCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "set-default-stack ", - Args: cobra.ExactArgs(1), - Short: "Set default stack used by other commands", - RunE: logError(func(cmd *cobra.Command, args []string) error { - cfg, err := config.NewDefault() - if err != nil { - return err - } - err = cfg.SetDefaultStack(args[0]) - if err != nil { - return err - } - logger.Info("Stack %s is now the default stack", style.Symbol(args[0])) - return nil - }), - } - addHelpFlag(cmd, "set-default-stack") - return cmd -} - -func updateStackCommand() *cobra.Command { - flags := struct { - BuildImage string - RunImages []string - }{} - cmd := &cobra.Command{ - Use: "update-stack --build-image --run-image ", - Args: cobra.ExactArgs(1), - Short: "Update stack build and run images", - RunE: logError(func(cmd *cobra.Command, args []string) error { - cfg, err := config.NewDefault() - if err != nil { - return err - } - if err := cfg.UpdateStack(args[0], config.Stack{ - BuildImage: flags.BuildImage, - RunImages: flags.RunImages, - }); err != nil { - return err - } - logger.Info("Stack %s updated", style.Symbol(args[0])) - return nil - }), - } - cmd.Flags().StringVarP(&flags.BuildImage, "build-image", "b", "", "Build image to associate with stack") - cmd.Flags().StringSliceVarP(&flags.RunImages, "run-image", "r", nil, "Run image to associate with stack"+multiValueHelp("run image")) - addHelpFlag(cmd, "update-stack") - return cmd -} - -func deleteStackCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "delete-stack ", - Args: cobra.ExactArgs(1), - Short: "Delete stack from list of available stacks", - RunE: logError(func(cmd *cobra.Command, args []string) error { - cfg, err := config.NewDefault() - if err != nil { - return err - } - if err := cfg.DeleteStack(args[0]); err != nil { - return err - } - logger.Info("Stack %s deleted", style.Symbol(args[0])) - return nil - }), - } - addHelpFlag(cmd, "delete-stack") - return cmd -} - -func showStacksCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "stacks", - Args: cobra.NoArgs, - Short: "Show information about available stacks", - RunE: logError(func(cmd *cobra.Command, args []string) error { - cfg, err := config.NewDefault() - if err != nil { - return err - } - var buf bytes.Buffer - w := tabwriter.NewWriter(&buf, 0, 0, 4, ' ', 0) - // Note: Nop style is needed to keep color control characters from interfering with table formatting - // See https://stackoverflow.com/questions/35398497/how-do-i-get-colors-to-work-with-golang-tabwriter - fmt.Fprintf(w, "%s\t%s\t%s\n", style.Noop("Stack ID"), style.Noop("Build Image"), style.Noop("Run Image(s)")) - fmt.Fprintf(w, "%s\t%s\t%s\n", style.Noop("--------"), style.Noop("-----------"), style.Noop("------------")) - for _, stack := range cfg.Stacks { - displayID := style.Key(stack.ID) - if stack.ID == cfg.DefaultStackID { - displayID = fmt.Sprintf("%s (default)", displayID) - } - fmt.Fprintf(w, "%s\t%s\t%s\n", displayID, style.Noop(stack.BuildImage), style.Noop(strings.Join(stack.RunImages, ", "))) - } - if err := w.Flush(); err != nil { - return err - } - logger.Info(buf.String()) - return nil - }), - } - addHelpFlag(cmd, "stacks") - return cmd -} - -func setDefaultBuilderCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "set-default-builder ", - Short: "Set default builder used by other commands", - Args: cobra.ExactArgs(1), - RunE: logError(func(cmd *cobra.Command, args []string) error { - cfg, err := config.NewDefault() - if err != nil { - return err - } - err = cfg.SetDefaultBuilder(args[0]) - if err != nil { - return err - } - logger.Info("Builder %s is now the default builder", style.Symbol(args[0])) - return nil - }), - } - addHelpFlag(cmd, "set-default-builder") - return cmd -} - -func configureBuilderCommand() *cobra.Command { - var runImages []string - - cmd := &cobra.Command{ - Use: "configure-builder --run-image ", - Short: "Override a builder's default run images with one or more overrides", - Args: cobra.ExactArgs(1), - RunE: logError(func(cmd *cobra.Command, args []string) error { - cfg, err := config.NewDefault() - if err != nil { - return err - } - - builder := args[0] - cfg.ConfigureBuilder(builder, runImages) - logger.Info("Builder %s configured", style.Symbol(builder)) - return nil - }), - } - cmd.Flags().StringSliceVarP(&runImages, "run-image", "r", nil, "Overriding run image"+multiValueHelp("run image")) - cmd.MarkFlagRequired("run-image") - addHelpFlag(cmd, "configure-builder") - return cmd -} - -func inspectBuilderCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "inspect-builder ", - Short: "Show information about a builder", - Args: cobra.ExactArgs(1), - RunE: logError(func(cmd *cobra.Command, args []string) error { - inspector, err := pack.DefaultBuilderInspector() - if err != nil { - return err - } - - imageFactory, err := image.DefaultFactory() - if err != nil { - return err - } - - imageName := args[0] - for _, remote := range []bool{true, false} { - inspectBuilderOutput(imageName, remote, imageFactory, inspector) - logger.Info("") - } - return nil - }), - } - addHelpFlag(cmd, "inspect-builder") - return cmd -} - -func inspectBuilderOutput(imageName string, remote bool, imageFactory *image.Factory, inspector *pack.BuilderInspector) { - var builderImage image.Image - var err error - if remote { - builderImage, err = imageFactory.NewRemote(imageName) - logger.Info("Remote\n------") - } else { - builderImage, err = imageFactory.NewLocal(imageName, false) - logger.Info("Local\n-----") - } - if err != nil { - logger.Error(errors.Wrapf(err, "failed to get image %s", style.Symbol(imageName)).Error()) - return - } - if found, err := builderImage.Found(); err != nil { - logger.Error(err.Error()) - return - } else if !found { - logger.Info("Not present") - return - } - - builder, err := inspector.Inspect(builderImage) - if err != nil { - logger.Error(err.Error()) - return - } - - logger.Info("Run Images:") - for _, r := range builder.LocalRunImages { - logger.Info("\t%s (user-configured)", r) - } - for _, r := range builder.DefaultRunImages { - logger.Info("\t%s", r) - } -} - -func versionCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "version", - Args: cobra.NoArgs, - Short: "Show current 'pack' version", - RunE: logError(func(cmd *cobra.Command, args []string) error { - logger.Info(strings.TrimSpace(Version)) - return nil - }), - } - addHelpFlag(cmd, "version") - return cmd -} - -func makeStopChannelForSignals() <-chan struct{} { - sigsCh := make(chan os.Signal, 1) - stopCh := make(chan struct{}, 1) - signal.Notify(sigsCh, syscall.SIGINT, syscall.SIGTERM) - go func() { - // convert chan os.Signal to chan struct{} - for { - <-sigsCh - stopCh <- struct{}{} - } - }() - return stopCh -} - -func addHelpFlag(cmd *cobra.Command, commandName string) { - cmd.Flags().BoolP("help", "h", false, fmt.Sprintf("Help for '%s'", commandName)) -} - -func logError(f func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error { - return func(cmd *cobra.Command, args []string) error { - cmd.SilenceErrors = true - cmd.SilenceUsage = true - err := f(cmd, args) - if err != nil { - logger.Error(err.Error()) - return err - } - return nil + if err := rootCmd.Execute(); err != nil { + os.Exit(1) } } - -func multiValueHelp(name string) string { - return fmt.Sprintf("\nRepeat for each %s in order,\n or supply once by comma-separated list", name) -} diff --git a/commands/add_stack.go b/commands/add_stack.go new file mode 100644 index 000000000..594cd282a --- /dev/null +++ b/commands/add_stack.go @@ -0,0 +1,42 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/buildpack/pack/config" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func AddStack(logger *logging.Logger) *cobra.Command { + flags := struct { + BuildImage string + RunImages []string + }{} + cmd := &cobra.Command{ + Use: "add-stack --build-image --run-image ", + Args: cobra.ExactArgs(1), + Short: "Add stack to list of available stacks", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + cfg, err := config.NewDefault() + if err != nil { + return err + } + if err := cfg.AddStack(config.Stack{ + ID: args[0], + BuildImage: flags.BuildImage, + RunImages: flags.RunImages, + }); err != nil { + return err + } + logger.Info("Stack %s added", style.Symbol(args[0])) + return nil + }), + } + cmd.Flags().StringVarP(&flags.BuildImage, "build-image", "b", "", "Build image to associate with stack (required)") + cmd.MarkFlagRequired("build-image") + cmd.Flags().StringSliceVarP(&flags.RunImages, "run-image", "r", nil, "Run image to associate with stack (required)"+multiValueHelp("run image")) + cmd.MarkFlagRequired("run-image") + AddHelpFlag(cmd, "add-stack") + return cmd +} diff --git a/commands/build.go b/commands/build.go new file mode 100644 index 000000000..65e4ac044 --- /dev/null +++ b/commands/build.go @@ -0,0 +1,49 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/buildpack/pack" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func Build(logger *logging.Logger) *cobra.Command { + var buildFlags pack.BuildFlags + cmd := &cobra.Command{ + Use: "build ", + Args: cobra.ExactArgs(1), + Short: "Generate app image from source code", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + buildFlags.RepoName = args[0] + bf, err := pack.DefaultBuildFactory(logger) + if err != nil { + return err + } + b, err := bf.BuildConfigFromFlags(&buildFlags) + if err != nil { + return err + } + if err := b.Run(); err != nil { + return err + } + logger.Info("Successfully built image %s", style.Symbol(b.RepoName)) + return nil + }), + } + buildCommandFlags(cmd, &buildFlags) + cmd.Flags().BoolVar(&buildFlags.Publish, "publish", false, "Publish to registry") + AddHelpFlag(cmd, "build") + return cmd +} + +func buildCommandFlags(cmd *cobra.Command, buildFlags *pack.BuildFlags) { + cmd.Flags().StringVarP(&buildFlags.AppDir, "path", "p", "", "Path to app dir (defaults to current working directory)") + cmd.Flags().StringVar(&buildFlags.Builder, "builder", "", "Builder (defaults to builder configured by 'set-default-builder')") + cmd.Flags().StringVar(&buildFlags.RunImage, "run-image", "", "Run image (defaults to default stack's run image)") + cmd.Flags().StringVar(&buildFlags.EnvFile, "env-file", "", "Build-time environment variables file\nOne variable per line, of the form 'VAR=VALUE' or 'VAR'\nWhen using latter value-less form, value will be taken from current\n environment at the time this command is executed") + cmd.Flags().BoolVar(&buildFlags.NoPull, "no-pull", false, "Skip pulling images before use") + cmd.Flags().BoolVar(&buildFlags.ClearCache, "clear-cache", false, "Clear image's associated cache before building") + _ = cmd.Flags().MarkHidden("clear-cache") + cmd.Flags().StringSliceVar(&buildFlags.Buildpacks, "buildpack", nil, "Buildpack ID, path to directory, or path/URL to .tgz file"+multiValueHelp("buildpack")) +} diff --git a/commands/commands.go b/commands/commands.go new file mode 100644 index 000000000..45e0a59b9 --- /dev/null +++ b/commands/commands.go @@ -0,0 +1,49 @@ +package commands + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/spf13/cobra" + + "github.com/buildpack/pack/logging" +) + +// TODO: Check if most recent cobra version fixed bug in help strings. It was not always capitalizing the first +// letter in the help string. If it's fixed, we can remove this. +func AddHelpFlag(cmd *cobra.Command, commandName string) { + cmd.Flags().BoolP("help", "h", false, fmt.Sprintf("Help for '%s'", commandName)) +} + +func logError(logger *logging.Logger, f func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + cmd.SilenceErrors = true + cmd.SilenceUsage = true + err := f(cmd, args) + if err != nil { + logger.Error(err.Error()) + return err + } + return nil + } +} + +func makeStopChannelForSignals() <-chan struct{} { + sigsCh := make(chan os.Signal, 1) + stopCh := make(chan struct{}, 1) + signal.Notify(sigsCh, syscall.SIGINT, syscall.SIGTERM) + go func() { + // convert chan os.Signal to chan struct{} + for { + <-sigsCh + stopCh <- struct{}{} + } + }() + return stopCh +} + +func multiValueHelp(name string) string { + return fmt.Sprintf("\nRepeat for each %s in order,\n or supply once by comma-separated list", name) +} diff --git a/commands/configure_builder.go b/commands/configure_builder.go new file mode 100644 index 000000000..e0bf2623a --- /dev/null +++ b/commands/configure_builder.go @@ -0,0 +1,34 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/buildpack/pack/config" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func ConfigureBuilder(logger *logging.Logger) *cobra.Command { + var runImages []string + + cmd := &cobra.Command{ + Use: "configure-builder --run-image ", + Short: "Override a builder's default run images with one or more overrides", + Args: cobra.ExactArgs(1), + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + cfg, err := config.NewDefault() + if err != nil { + return err + } + + builder := args[0] + cfg.ConfigureBuilder(builder, runImages) + logger.Info("Builder %s configured", style.Symbol(builder)) + return nil + }), + } + cmd.Flags().StringSliceVarP(&runImages, "run-image", "r", nil, "Overriding run image"+multiValueHelp("run image")) + cmd.MarkFlagRequired("run-image") + AddHelpFlag(cmd, "configure-builder") + return cmd +} diff --git a/commands/create_builder.go b/commands/create_builder.go new file mode 100644 index 000000000..303797de5 --- /dev/null +++ b/commands/create_builder.go @@ -0,0 +1,64 @@ +package commands + +import ( + "fmt" + "runtime" + + "github.com/buildpack/lifecycle/image" + "github.com/spf13/cobra" + + "github.com/buildpack/pack" + "github.com/buildpack/pack/config" + "github.com/buildpack/pack/fs" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func CreateBuilder(logger *logging.Logger) *cobra.Command { + flags := pack.CreateBuilderFlags{} + cmd := &cobra.Command{ + Use: "create-builder --builder-config ", + Args: cobra.ExactArgs(1), + Short: "Create builder image", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + flags.RepoName = args[0] + + if runtime.GOOS == "windows" { + return fmt.Errorf("%s is not implemented on Windows", style.Symbol("create-builder")) + } + + cfg, err := config.NewDefault() + if err != nil { + return err + } + imageFactory, err := image.DefaultFactory() + if err != nil { + return err + } + builderFactory := pack.BuilderFactory{ + FS: &fs.FS{}, + Logger: logger, + Config: cfg, + ImageFactory: imageFactory, + } + builderConfig, err := builderFactory.BuilderConfigFromFlags(flags) + if err != nil { + return err + } + if err := builderFactory.Create(builderConfig); err != nil { + return err + } + imageName := builderConfig.Repo.Name() + logger.Info("Successfully created builder image %s", style.Symbol(imageName)) + logger.Tip("Run %s to use this builder", style.Symbol(fmt.Sprintf("pack build --builder %s", imageName))) + return nil + }), + } + cmd.Flags().BoolVar(&flags.NoPull, "no-pull", false, "Skip pulling stack image before use") + cmd.Flags().StringVarP(&flags.BuilderTomlPath, "builder-config", "b", "", "Path to builder TOML file (required)") + cmd.MarkFlagRequired("builder-config") + cmd.Flags().StringVarP(&flags.StackID, "stack", "s", "", "Stack ID (defaults to stack configured by 'set-default-stack')") + cmd.Flags().BoolVar(&flags.Publish, "publish", false, "Publish to registry") + AddHelpFlag(cmd, "create-builder") + return cmd +} diff --git a/commands/delete_stack.go b/commands/delete_stack.go new file mode 100644 index 000000000..d91e3329b --- /dev/null +++ b/commands/delete_stack.go @@ -0,0 +1,30 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/buildpack/pack/config" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func DeleteStack(logger *logging.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "delete-stack ", + Args: cobra.ExactArgs(1), + Short: "Delete stack from list of available stacks", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + cfg, err := config.NewDefault() + if err != nil { + return err + } + if err := cfg.DeleteStack(args[0]); err != nil { + return err + } + logger.Info("Stack %s deleted", style.Symbol(args[0])) + return nil + }), + } + AddHelpFlag(cmd, "delete-stack") + return cmd +} diff --git a/commands/inspect_builder.go b/commands/inspect_builder.go new file mode 100644 index 000000000..0ed11e90a --- /dev/null +++ b/commands/inspect_builder.go @@ -0,0 +1,76 @@ +package commands + +import ( + "github.com/buildpack/lifecycle/image" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/buildpack/pack" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func InspectBuilder(logger *logging.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "inspect-builder ", + Short: "Show information about a builder", + Args: cobra.ExactArgs(1), + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + inspector, err := pack.DefaultBuilderInspector() + if err != nil { + return err + } + + imageFactory, err := image.DefaultFactory() + if err != nil { + return err + } + + imageName := args[0] + for _, remote := range []bool{true, false} { + inspectBuilderOutput(logger, imageName, remote, imageFactory, inspector) + logger.Info("") + } + return nil + }), + } + AddHelpFlag(cmd, "inspect-builder") + return cmd +} + +func inspectBuilderOutput(logger *logging.Logger, imageName string, remote bool, imageFactory *image.Factory, inspector *pack.BuilderInspector) { + var builderImage image.Image + var err error + if remote { + builderImage, err = imageFactory.NewRemote(imageName) + logger.Info("Remote\n------") + } else { + builderImage, err = imageFactory.NewLocal(imageName, false) + logger.Info("Local\n-----") + } + if err != nil { + logger.Error(errors.Wrapf(err, "failed to get image %s", style.Symbol(imageName)).Error()) + return + } + if found, err := builderImage.Found(); err != nil { + logger.Error(err.Error()) + return + } else if !found { + logger.Info("Not present") + return + } + + builder, err := inspector.Inspect(builderImage) + if err != nil { + logger.Error(err.Error()) + return + } + + logger.Info("Run Images:") + for _, r := range builder.LocalRunImages { + logger.Info("\t%s (user-configured)", r) + } + for _, r := range builder.DefaultRunImages { + logger.Info("\t%s", r) + } +} diff --git a/commands/rebase.go b/commands/rebase.go new file mode 100644 index 000000000..98530ff34 --- /dev/null +++ b/commands/rebase.go @@ -0,0 +1,50 @@ +package commands + +import ( + "github.com/buildpack/lifecycle/image" + "github.com/spf13/cobra" + + "github.com/buildpack/pack" + "github.com/buildpack/pack/config" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func Rebase(logger *logging.Logger) *cobra.Command { + var flags pack.RebaseFlags + cmd := &cobra.Command{ + Use: "rebase ", + Args: cobra.ExactArgs(1), + Short: "Rebase app image with latest run image", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + flags.RepoName = args[0] + + imageFactory, err := image.DefaultFactory() + if err != nil { + return err + } + cfg, err := config.NewDefault() + if err != nil { + return err + } + factory := pack.RebaseFactory{ + Logger: logger, + Config: cfg, + ImageFactory: imageFactory, + } + rebaseConfig, err := factory.RebaseConfigFromFlags(flags) + if err != nil { + return err + } + if err := factory.Rebase(rebaseConfig); err != nil { + return err + } + logger.Info("Successfully rebased image %s", style.Symbol(rebaseConfig.Image.Name())) + return nil + }), + } + cmd.Flags().BoolVar(&flags.Publish, "publish", false, "Publish to registry") + cmd.Flags().BoolVar(&flags.NoPull, "no-pull", false, "Skip pulling images before use") + AddHelpFlag(cmd, "rebase") + return cmd +} diff --git a/commands/run.go b/commands/run.go new file mode 100644 index 000000000..c6435ff2d --- /dev/null +++ b/commands/run.go @@ -0,0 +1,33 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/buildpack/pack" + "github.com/buildpack/pack/logging" +) + +func Run(logger *logging.Logger) *cobra.Command { + var runFlags pack.RunFlags + cmd := &cobra.Command{ + Use: "run", + Args: cobra.NoArgs, + Short: "Build and run app image (recommended for development only)", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + bf, err := pack.DefaultBuildFactory(logger) + if err != nil { + return err + } + r, err := bf.RunConfigFromFlags(&runFlags) + if err != nil { + return err + } + return r.Run(makeStopChannelForSignals) + }), + } + + buildCommandFlags(cmd, &runFlags.BuildFlags) + cmd.Flags().StringSliceVar(&runFlags.Ports, "port", nil, "Port to publish (defaults to port(s) exposed by container)"+multiValueHelp("port")) + AddHelpFlag(cmd, "run") + return cmd +} diff --git a/commands/set_default_builder.go b/commands/set_default_builder.go new file mode 100644 index 000000000..e841109bd --- /dev/null +++ b/commands/set_default_builder.go @@ -0,0 +1,31 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/buildpack/pack/config" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func SetDefaultBuilder(logger *logging.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "set-default-builder ", + Short: "Set default builder used by other commands", + Args: cobra.ExactArgs(1), + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + cfg, err := config.NewDefault() + if err != nil { + return err + } + err = cfg.SetDefaultBuilder(args[0]) + if err != nil { + return err + } + logger.Info("Builder %s is now the default builder", style.Symbol(args[0])) + return nil + }), + } + AddHelpFlag(cmd, "set-default-builder") + return cmd +} diff --git a/commands/set_default_stack.go b/commands/set_default_stack.go new file mode 100644 index 000000000..d8f7d95c4 --- /dev/null +++ b/commands/set_default_stack.go @@ -0,0 +1,31 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/buildpack/pack/config" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func SetDefaultStack(logger *logging.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "set-default-stack ", + Args: cobra.ExactArgs(1), + Short: "Set default stack used by other commands", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + cfg, err := config.NewDefault() + if err != nil { + return err + } + err = cfg.SetDefaultStack(args[0]) + if err != nil { + return err + } + logger.Info("Stack %s is now the default stack", style.Symbol(args[0])) + return nil + }), + } + AddHelpFlag(cmd, "set-default-stack") + return cmd +} diff --git a/commands/show_stacks.go b/commands/show_stacks.go new file mode 100644 index 000000000..2ab497c65 --- /dev/null +++ b/commands/show_stacks.go @@ -0,0 +1,48 @@ +package commands + +import ( + "bytes" + "fmt" + "strings" + "text/tabwriter" + + "github.com/spf13/cobra" + + "github.com/buildpack/pack/config" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func ShowStacks(logger *logging.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "stacks", + Args: cobra.NoArgs, + Short: "Show information about available stacks", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + cfg, err := config.NewDefault() + if err != nil { + return err + } + var buf bytes.Buffer + w := tabwriter.NewWriter(&buf, 0, 0, 4, ' ', 0) + // Note: Nop style is needed to keep color control characters from interfering with table formatting + // See https://stackoverflow.com/questions/35398497/how-do-i-get-colors-to-work-with-golang-tabwriter + fmt.Fprintf(w, "%s\t%s\t%s\n", style.Noop("Stack ID"), style.Noop("Build Image"), style.Noop("Run Image(s)")) + fmt.Fprintf(w, "%s\t%s\t%s\n", style.Noop("--------"), style.Noop("-----------"), style.Noop("------------")) + for _, stack := range cfg.Stacks { + displayID := style.Key(stack.ID) + if stack.ID == cfg.DefaultStackID { + displayID = fmt.Sprintf("%s (default)", displayID) + } + fmt.Fprintf(w, "%s\t%s\t%s\n", displayID, style.Noop(stack.BuildImage), style.Noop(strings.Join(stack.RunImages, ", "))) + } + if err := w.Flush(); err != nil { + return err + } + logger.Info(buf.String()) + return nil + }), + } + AddHelpFlag(cmd, "stacks") + return cmd +} diff --git a/commands/update_stack.go b/commands/update_stack.go new file mode 100644 index 000000000..915d747b6 --- /dev/null +++ b/commands/update_stack.go @@ -0,0 +1,39 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/buildpack/pack/config" + "github.com/buildpack/pack/logging" + "github.com/buildpack/pack/style" +) + +func UpdateStack(logger *logging.Logger) *cobra.Command { + flags := struct { + BuildImage string + RunImages []string + }{} + cmd := &cobra.Command{ + Use: "update-stack --build-image --run-image ", + Args: cobra.ExactArgs(1), + Short: "Update stack build and run images", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + cfg, err := config.NewDefault() + if err != nil { + return err + } + if err := cfg.UpdateStack(args[0], config.Stack{ + BuildImage: flags.BuildImage, + RunImages: flags.RunImages, + }); err != nil { + return err + } + logger.Info("Stack %s updated", style.Symbol(args[0])) + return nil + }), + } + cmd.Flags().StringVarP(&flags.BuildImage, "build-image", "b", "", "Build image to associate with stack") + cmd.Flags().StringSliceVarP(&flags.RunImages, "run-image", "r", nil, "Run image to associate with stack"+multiValueHelp("run image")) + AddHelpFlag(cmd, "update-stack") + return cmd +} diff --git a/commands/version.go b/commands/version.go new file mode 100644 index 000000000..227f29038 --- /dev/null +++ b/commands/version.go @@ -0,0 +1,23 @@ +package commands + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/buildpack/pack/logging" +) + +func Version(logger *logging.Logger, version string) *cobra.Command { + cmd := &cobra.Command{ + Use: "version", + Args: cobra.NoArgs, + Short: "Show current 'pack' version", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + logger.Info(strings.TrimSpace(version)) + return nil + }), + } + AddHelpFlag(cmd, "version") + return cmd +} diff --git a/rebase.go b/rebase_factory.go similarity index 100% rename from rebase.go rename to rebase_factory.go diff --git a/rebase_test.go b/rebase_factory_test.go similarity index 96% rename from rebase_test.go rename to rebase_factory_test.go index f7b160ba6..c3edfdd81 100644 --- a/rebase_test.go +++ b/rebase_factory_test.go @@ -18,15 +18,15 @@ import ( h "github.com/buildpack/pack/testhelpers" ) -func TestRebase(t *testing.T) { +func TestRebaseFactory(t *testing.T) { color.NoColor = true - spec.Run(t, "rebase", testRebase, spec.Parallel(), spec.Report(report.Terminal{})) + spec.Run(t, "rebase_factory", testRebaseFactory, spec.Parallel(), spec.Report(report.Terminal{})) } //move somewhere else //go:generate mockgen -package mocks -destination mocks/image.go github.com/buildpack/lifecycle/image Image -func testRebase(t *testing.T, when spec.G, it spec.S) { +func testRebaseFactory(t *testing.T, when spec.G, it spec.S) { when("#RebaseFactory", func() { var ( mockController *gomock.Controller diff --git a/run.go b/run.go index 3f4177214..f78c75deb 100644 --- a/run.go +++ b/run.go @@ -1,3 +1,5 @@ +// TODO: We should rename this file (and its test and test file) to avoid confusion with the one in the `commands` package + package pack import (