Skip to content

Commit

Permalink
feat(cli): colorize CDK commands (#859)
Browse files Browse the repository at this point in the history
* feat(cli): colorize CDK commands
* chore(cli): show progress when loading components state

Signed-off-by: Salim Afiune Maya <[email protected]>
  • Loading branch information
afiune committed Jul 15, 2022
1 parent e671857 commit 41e0785
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 8 deletions.
7 changes: 7 additions & 0 deletions cli/cmd/cli_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os"

"github.com/AlecAivazis/survey/v2"
"github.com/fatih/color"
)

// used by configure.go
Expand All @@ -34,6 +35,12 @@ var promptIconsFunc = func(icons *survey.IconSet) {
icons.Question.Text = "▸"
}

// A variety of colorized icons used throughout the code
var (
successIcon = color.HiGreenString("✓")
failureIcon = color.HiRedString("✖") //nolint
)

// Env variables found in GCP, AWS and Azure cloudshell.
// Used to determine if cli is running on cloudshell.
const (
Expand Down
7 changes: 7 additions & 0 deletions cli/cmd/cli_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"os"

"github.com/AlecAivazis/survey/v2"
"github.com/fatih/color"
)

// used by configure.go
Expand All @@ -32,6 +33,12 @@ var promptIconsFunc = func(icons *survey.IconSet) {
icons.Question.Text = ">"
}

// A variety of colorized icons used throughout the code
var (
successIcon = color.HiGreenString("√")
failureIcon = color.HiRedString("×") //nolint
)

// UpdateCommand returns the command that a user should run to update the cli
// to the latest available version (windows specific command)
func (c *cliState) UpdateCommand() string {
Expand Down
76 changes: 70 additions & 6 deletions cli/cmd/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"os"

"github.com/fatih/color"
"github.com/olekukonko/tablewriter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -168,7 +169,9 @@ func (c *cliState) LoadComponents() {
}
}
func runComponentsList(_ *cobra.Command, _ []string) (err error) {
cli.StartProgress("Loading components state...")
cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi)
cli.StopProgress()
if err != nil {
err = errors.Wrap(err, "unable to list components")
return
Expand Down Expand Up @@ -201,8 +204,18 @@ func runComponentsList(_ *cobra.Command, _ []string) (err error) {
func componentsToTable() [][]string {
out := [][]string{}
for _, cdata := range cli.LwComponents.Components {
var colorize *color.Color
switch cdata.Status() {
case lwcomponent.NotInstalled:
colorize = color.New(color.FgWhite, color.Bold)
case lwcomponent.Installed:
colorize = color.New(color.FgGreen, color.Bold)
case lwcomponent.UpdateAvailable:
colorize = color.New(color.FgYellow, color.Bold)
}

out = append(out, []string{
cdata.Status().String(),
colorize.Sprintf(cdata.Status().String()),
cdata.Name,
cdata.LatestVersion.String(),
cdata.Description,
Expand All @@ -212,8 +225,10 @@ func componentsToTable() [][]string {
}

func runComponentsInstall(_ *cobra.Command, args []string) (err error) {
cli.StartProgress("Loading components state...")
// @afiune maybe move the state to the cache and fetch if it if has expired
cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi)
cli.StopProgress()
if err != nil {
err = errors.Wrap(err, "unable to load components")
return
Expand All @@ -232,9 +247,10 @@ func runComponentsInstall(_ *cobra.Command, args []string) (err error) {
err = errors.Wrap(err, "unable to install component")
return
}
cli.OutputChecklist(successIcon, "Component %s installed\n", color.HiYellowString(component.Name))
cli.OutputChecklist(successIcon, "Signature verified\n")

cli.StartProgress(fmt.Sprintf("Configuring component %s...", component.Name))

// component life cycle: initialize
stdout, stderr, errCmd := component.RunAndReturn([]string{"cdk-init"}, nil, cli.envs()...)
if errCmd != nil {
Expand All @@ -243,15 +259,19 @@ func runComponentsInstall(_ *cobra.Command, args []string) (err error) {
} else {
cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr)
}

cli.StopProgress()
cli.OutputHuman("The component %s was installed.\n", args[0])

cli.OutputChecklist(successIcon, "Component configured\n")
cli.OutputHuman("\nInstallation completed.\n")
// @afiune print breadcrumbs of what to do with this component
return
}

func runComponentsUpdate(_ *cobra.Command, args []string) (err error) {
cli.StartProgress("Loading components state...")
// @afiune maybe move the state to the cache and fetch if it if has expired
cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi)
cli.StopProgress()
if err != nil {
err = errors.Wrap(err, "unable to load components")
return
Expand All @@ -275,22 +295,49 @@ func runComponentsUpdate(_ *cobra.Command, args []string) (err error) {
return nil
}

currentVersion, err := component.CurrentVersion()
if err != nil {
return err
}

cli.StartProgress(fmt.Sprintf("Updating component %s...", component.Name))
err = cli.LwComponents.Install(args[0])
cli.StopProgress()
if err != nil {
err = errors.Wrap(err, "unable to update component")
return
}
cli.OutputChecklist(successIcon, "Component %s updated to %s\n",
color.HiYellowString(component.Name),
color.HiCyanString(fmt.Sprintf("v%s", component.LatestVersion.String())))
cli.OutputChecklist(successIcon, "Signature verified\n")

cli.StartProgress(fmt.Sprintf("Reconfiguring %s component...", component.Name))
// component life cycle: reconfigure
stdout, stderr, errCmd := component.RunAndReturn(
[]string{"cdk-reconfigure", currentVersion.String(), component.LatestVersion.String()},
nil, cli.envs()...)
if errCmd != nil {
cli.Log.Warnw("component life cycle",
"error", errCmd.Error(), "stdout", stdout, "stderr", stderr)
} else {
cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr)
}
cli.StopProgress()

cli.OutputHuman("The component %s was updated.\n", args[0])
cli.OutputChecklist(successIcon, "Component reconfigured\n")
cli.OutputHuman("\nUpdate completed.\n")
// @afiune display update breadcrumbs
// maybe tell the user whats new on this version?
return
}

func runComponentsDelete(_ *cobra.Command, args []string) (err error) {
cli.StartProgress("Loading components state...")
// @afiune maybe move the state to the cache and fetch if it if has expired
// @afiune DO WE NEED THIS? It should already be loaded
cli.LwComponents, err = lwcomponent.LocalState()
cli.StopProgress()
if err != nil {
err = errors.Wrap(err, "unable to load components")
return
Expand All @@ -310,6 +357,19 @@ func runComponentsDelete(_ *cobra.Command, args []string) (err error) {
return
}

cli.StartProgress("Cleaning component data...")
// component life cycle: remove
stdout, stderr, errCmd := component.RunAndReturn([]string{"cdk-remove"}, nil, cli.envs()...)
if errCmd != nil {
cli.Log.Warnw("component life cycle",
"error", errCmd.Error(), "stdout", stdout, "stderr", stderr)
} else {
cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr)
}
cli.StopProgress()

cli.OutputChecklist(successIcon, "Component data removed\n")

cli.StartProgress("Deleting component...")
defer cli.StopProgress()

Expand All @@ -326,6 +386,10 @@ func runComponentsDelete(_ *cobra.Command, args []string) (err error) {
}

cli.StopProgress()
cli.OutputHuman("The component %s was deleted.\n", args[0])

cli.OutputChecklist(successIcon, "Component %s deleted\n", color.HiYellowString(component.Name))
cli.OutputHuman("\n- We will do better next time.\n")
cli.OutputHuman("\nDo you want to provide feedback?\n")
cli.OutputHuman("Reach out to us at %s\n", color.HiCyanString("[email protected]"))
return
}
10 changes: 8 additions & 2 deletions cli/cmd/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,20 @@ func (c *cliState) OutputJSON(v interface{}) error {
return nil
}

// OutputChecklist prints out a message with an icon formatted as a checklist,
// note that all strings should come already colorized
func (c *cliState) OutputChecklist(icon, message string, a ...interface{}) {
c.OutputHuman(fmt.Sprintf(" [%s] %s", icon, message), a...)
}

// OutputHuman will print out the provided message if the cli state is
// configured to talk to humans, to switch to json format use --json
func (c *cliState) OutputHuman(format string, a ...interface{}) {
if c.HumanOutput() {
if len(a) == 0 {
fmt.Fprint(os.Stdout, format)
fmt.Fprint(color.Output, format)
} else {
fmt.Fprintf(os.Stdout, format, a...)
fmt.Fprintf(color.Output, format, a...)
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"os"
"strings"

"github.com/fatih/color"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -241,6 +242,8 @@ func initConfig() {
if viper.GetBool("nocolor") {
cli.Log.Info("turning off colors")
cli.JsonF.DisabledColor = true
color.NoColor = true
os.Setenv("NO_COLOR", "true")
}

if viper.GetBool("noninteractive") {
Expand Down

0 comments on commit 41e0785

Please sign in to comment.