diff --git a/cmd/beta.go b/cmd/beta.go index e5154206..6ec614f4 100644 --- a/cmd/beta.go +++ b/cmd/beta.go @@ -15,25 +15,28 @@ import ( "github.com/konstructio/kubefirst/cmd/k3s" "github.com/konstructio/kubefirst/cmd/vultr" "github.com/konstructio/kubefirst/internal/progress" + "github.com/konstructio/kubefirst/internal/teawrapper" "github.com/spf13/cobra" ) -// betaCmd represents the beta command tree -var betaCmd = &cobra.Command{ - Use: "beta", - Short: "access Kubefirst beta features", - Long: `access Kubefirst beta features`, - Run: func(_ *cobra.Command, _ []string) { - fmt.Println("To learn more about Kubefirst, run:") - fmt.Println(" kubefirst help") - - if progress.Progress != nil { - progress.Progress.Quit() - } - }, -} +func getBetaCommand() *cobra.Command { + // betaCmd represents the beta command tree + betaCmd := &cobra.Command{ + Use: "beta", + Short: "access Kubefirst beta features", + Long: `access Kubefirst beta features`, + RunE: teawrapper.WrapBubbleTea(func(_ *cobra.Command, _ []string) error { + fmt.Println("To learn more about Kubefirst, run:") + fmt.Println(" kubefirst help") + + if progress.Progress != nil { + progress.Progress.Quit() + } + + return nil + }), + } -func init() { cobra.OnInitialize() betaCmd.AddCommand( akamai.NewCommand(), @@ -42,4 +45,6 @@ func init() { google.NewCommand(), vultr.NewCommand(), ) + + return betaCmd } diff --git a/cmd/info.go b/cmd/info.go index f99d4ecf..fc339277 100755 --- a/cmd/info.go +++ b/cmd/info.go @@ -14,6 +14,7 @@ import ( "github.com/konstructio/kubefirst-api/pkg/configs" "github.com/konstructio/kubefirst/internal/progress" + "github.com/konstructio/kubefirst/internal/teawrapper" "github.com/spf13/cobra" ) @@ -22,7 +23,7 @@ var infoCmd = &cobra.Command{ Use: "info", Short: "provides general Kubefirst setup data", Long: `Provides machine data, files and folders paths`, - RunE: func(_ *cobra.Command, _ []string) error { + RunE: teawrapper.WrapBubbleTea(func(_ *cobra.Command, _ []string) error { config, err := configs.ReadConfig() if err != nil { return fmt.Errorf("failed to read config: %w", err) @@ -44,12 +45,9 @@ var infoCmd = &cobra.Command{ fmt.Fprintf(tw, "Kubefirst config file\t%s\n", config.KubefirstConfigFilePath) fmt.Fprintf(tw, "Kubefirst config folder\t%s\n", config.K1FolderPath) fmt.Fprintf(tw, "Kubefirst Version\t%s\n", configs.K1Version) + tw.Flush() progress.Success(buf.String()) return nil - }, -} - -func init() { - rootCmd.AddCommand(infoCmd) + }), } diff --git a/cmd/logs.go b/cmd/logs.go index 426db037..3500006e 100755 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -44,7 +44,3 @@ var logsCmd = &cobra.Command{ return nil }, } - -func init() { - rootCmd.AddCommand(logsCmd) -} diff --git a/cmd/reset.go b/cmd/reset.go index 853afa4a..37943934 100755 --- a/cmd/reset.go +++ b/cmd/reset.go @@ -71,10 +71,6 @@ var resetCmd = &cobra.Command{ }, } -func init() { - rootCmd.AddCommand(resetCmd) -} - // parseConfigEntryKubefirstChecks gathers the kubefirst-checks section of the Viper // config file and parses as a map[string]bool func parseConfigEntryKubefirstChecks(checks map[string]interface{}) (map[string]bool, error) { diff --git a/cmd/root.go b/cmd/root.go index 3a9b4059..fee8c41b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -20,45 +20,31 @@ import ( "github.com/spf13/cobra" ) -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "kubefirst", - Short: "kubefirst management cluster installer base command", - Long: `kubefirst management cluster installer provisions an - open source application delivery platform in under an hour. - checkout the docs at https://kubefirst.konstruct.io/docs/.`, - PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { - // wire viper config for flags for all commands - return configs.InitializeViperConfig(cmd) - }, - Run: func(_ *cobra.Command, _ []string) { - fmt.Println("To learn more about kubefirst, run:") - fmt.Println(" kubefirst help") - progress.Progress.Quit() - }, -} - // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - // This will allow all child commands to have informUser available for free. - // Refers: https://github.com/konstructio/runtime/issues/525 - // Before removing next line, please read ticket above. - common.CheckForVersionUpdate() - progressPrinter.GetInstance() - if err := rootCmd.Execute(); err != nil { - fmt.Println("Error occurred during command execution:", err) - fmt.Println("If a detailed error message was available, please make the necessary corrections before retrying.") - fmt.Println("You can re-run the last command to try the operation again.") - progress.Progress.Quit() +func Execute() error { + // rootCmd represents the base command when called without any subcommands + rootCmd := &cobra.Command{ + Use: "kubefirst", + Short: "kubefirst management cluster installer base command", + Long: `kubefirst management cluster installer provisions an +open source application delivery platform in under an hour. +checkout the docs at https://kubefirst.konstruct.io/docs/.`, + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + // wire viper config for flags for all commands + return configs.InitializeViperConfig(cmd) + }, + SilenceUsage: true, + Run: func(_ *cobra.Command, _ []string) { + fmt.Println("To learn more about kubefirst, run:") + fmt.Println(" kubefirst help") + progress.Progress.Quit() + }, } -} -func init() { cobra.OnInitialize() - rootCmd.SilenceUsage = true rootCmd.AddCommand( - betaCmd, + getBetaCommand(), aws.NewCommand(), civo.NewCommand(), digitalocean.NewCommand(), @@ -67,5 +53,14 @@ func init() { LaunchCommand(), LetsEncryptCommand(), TerraformCommand(), + infoCmd, + logsCmd, + resetCmd, + versionCmd, ) + + common.CheckForVersionUpdate() + progressPrinter.GetInstance() + + return rootCmd.Execute() } diff --git a/cmd/version.go b/cmd/version.go index f446bb0e..b5c64c80 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -12,10 +12,6 @@ import ( "github.com/spf13/cobra" ) -func init() { - rootCmd.AddCommand(versionCmd) -} - var versionCmd = &cobra.Command{ Use: "version", Short: "print the version number for kubefirst-cli", diff --git a/internal/progress/progress.go b/internal/progress/progress.go index 6d0b42c7..5fed7e8f 100644 --- a/internal/progress/progress.go +++ b/internal/progress/progress.go @@ -8,13 +8,18 @@ package progress import ( "fmt" + "os" + "sync" tea "github.com/charmbracelet/bubbletea" "github.com/konstructio/kubefirst-api/pkg/types" "github.com/spf13/viper" ) -var Progress *tea.Program +var ( + Progress *tea.Program + prOnce sync.Once +) //nolint:revive // will be removed after refactoring func NewModel() progressModel { @@ -25,7 +30,13 @@ func NewModel() progressModel { // Bubbletea functions func InitializeProgressTerminal() { - Progress = tea.NewProgram(NewModel()) + prOnce.Do(func() { + if os.Getenv("CI") == "true" { + Progress = tea.NewProgram(NewModel(), tea.WithoutRenderer(), tea.WithOutput(os.Stdout)) + } else { + Progress = tea.NewProgram(NewModel()) + } + }) } func (m progressModel) Init() tea.Cmd { diff --git a/internal/teawrapper/teawrapper.go b/internal/teawrapper/teawrapper.go new file mode 100644 index 00000000..bfd3b3d7 --- /dev/null +++ b/internal/teawrapper/teawrapper.go @@ -0,0 +1,42 @@ +package teawrapper + +import ( + "github.com/konstructio/kubefirst/internal/progress" + "github.com/spf13/cobra" +) + +// WrapBubbleTea wraps the main user's function with the progress terminal +// so its errors can be handled while still allowing the main command function +// to handle additional, outside-of-the-progress-terminal errors. +func WrapBubbleTea(fn func(*cobra.Command, []string) error) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + // Initialize the progress terminal + progress.InitializeProgressTerminal() + + // Run the progress terminal, and listen for errors + chTeaError := make(chan error, 1) + go func() { + _, err := progress.Progress.Run() + chTeaError <- err + }() + + // Run the main user's function + if err := fn(cmd, args); err != nil { + // print the error and send it to the progress terminal, but don't + // return here, we want the error to be handled by bubbletea + progress.Error(err.Error()) + } + + // Quit the progress terminal if the execution succeeded + // so it can stop `progress.Run()` + progress.Progress.Quit() + + // Receive the error from the progress terminal, and check + // if it's not nil, then return it + if err := <-chTeaError; err != nil { + return err + } + + return nil + } +} diff --git a/main.go b/main.go index e030707e..93894168 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,6 @@ import ( "github.com/konstructio/kubefirst-api/pkg/configs" utils "github.com/konstructio/kubefirst-api/pkg/utils" "github.com/konstructio/kubefirst/cmd" - "github.com/konstructio/kubefirst/internal/progress" zeroLog "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/viper" @@ -23,28 +22,22 @@ import ( ) func main() { - argsWithProg := os.Args - - bubbleTeaBlacklist := []string{"completion", "help", "--help", "-h", "quota", "logs"} - canRunBubbleTea := true - - for _, arg := range argsWithProg { - isBlackListed := slices.Contains(bubbleTeaBlacklist, arg) - - if isBlackListed { - canRunBubbleTea = false - } + if err := run(); err != nil { + log.Error().Msg(err.Error()) + os.Exit(1) } +} + +func run() error { + argsWithProg := os.Args config, err := configs.ReadConfig() if err != nil { - log.Error().Msgf("failed to read config: %v", err) - return + return fmt.Errorf("failed to read config: %w", err) } if err := utils.SetupViper(config, true); err != nil { - log.Error().Msgf("failed to setup Viper: %v", err) - return + return fmt.Errorf("failed to setup Viper: %w", err) } now := time.Now() @@ -78,8 +71,7 @@ func main() { homePath, err := os.UserHomeDir() if err != nil { - log.Error().Msgf("failed to get user home directory: %v", err) - return + return fmt.Errorf("failed to get user home directory: %w", err) } k1Dir := fmt.Sprintf("%s/.k1", homePath) @@ -87,8 +79,7 @@ func main() { // * create k1Dir if it doesn't exist if _, err := os.Stat(k1Dir); os.IsNotExist(err) { if err := os.MkdirAll(k1Dir, os.ModePerm); err != nil { - log.Error().Msgf("error creating directory %q: %v", k1Dir, err) - return + return fmt.Errorf("error creating directory %q: %w", k1Dir, err) } } @@ -96,8 +87,7 @@ func main() { logsFolder := fmt.Sprintf("%s/logs", k1Dir) if _, err := os.Stat(logsFolder); os.IsNotExist(err) { if err := os.Mkdir(logsFolder, 0o700); err != nil { - log.Error().Msgf("error creating logs directory: %v", err) - return + return fmt.Errorf("error creating logs directory: %w", err) } } @@ -105,8 +95,7 @@ func main() { logfile := fmt.Sprintf("%s/%s", logsFolder, logfileName) logFileObj, err := utils.OpenLogFile(logfile) if err != nil { - log.Error().Msgf("unable to store log location, error is: %v - please verify the current user has write access to this directory", err) - return + return fmt.Errorf("unable to store log location, error is: %w - please verify the current user has write access to this directory", err) } // handle file close request @@ -129,19 +118,8 @@ func main() { viper.Set("k1-paths.log-file-name", logfileName) if err := viper.WriteConfig(); err != nil { - log.Error().Msgf("failed to write config: %v", err) - return + return fmt.Errorf("failed to write config: %w", err) } - if canRunBubbleTea { - progress.InitializeProgressTerminal() - - go func() { - cmd.Execute() - }() - - progress.Progress.Run() - } else { - cmd.Execute() - } + return cmd.Execute() }