Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): colorize CDK commands #859

Merged
merged 2 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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