diff --git a/cli/app/deploy.go b/cli/app/deploy.go index d589c694..60cf765d 100644 --- a/cli/app/deploy.go +++ b/cli/app/deploy.go @@ -2,6 +2,7 @@ package app import ( "context" + "fmt" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" @@ -47,6 +48,8 @@ EXAMPLE: abra app deploy foo.example.com 1e83340e`, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { + var warnMessages []string + app := internal.ValidateApp(c) stackName := app.StackName() @@ -115,7 +118,7 @@ EXAMPLE: if deployMeta.IsDeployed { if internal.Force || internal.Chaos { - log.Warnf("%s is already deployed but continuing (--force/--chaos)", app.Name) + warnMessages = append(warnMessages, fmt.Sprintf("%s is already deployed", app.Name)) } else { log.Fatalf("%s is already deployed", app.Name) } @@ -139,13 +142,13 @@ EXAMPLE: log.Fatal(err) } version = formatter.SmallSHA(head.String()) - log.Warn("no versions detected, using latest commit") + warnMessages = append(warnMessages, fmt.Sprintf("no versions detected, using latest commit")) } } chaosVersion := "false" if internal.Chaos { - log.Warnf("chaos mode engaged") + warnMessages = append(warnMessages, "chaos mode engaged") if isChaosCommit { chaosVersion = specificVersion @@ -201,14 +204,12 @@ EXAMPLE: for _, envVar := range envVars { if !envVar.Present { - log.Warnf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain) + warnMessages = append(warnMessages, + fmt.Sprintf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain), + ) } } - if err := internal.DeployOverview(app, version, chaosVersion, "continue with deployment?"); err != nil { - log.Fatal(err) - } - if !internal.NoDomainChecks { domainName, ok := app.Env["DOMAIN"] if ok { @@ -216,10 +217,14 @@ EXAMPLE: log.Fatal(err) } } else { - log.Warn("skipping domain checks as no DOMAIN=... configured for app") + warnMessages = append(warnMessages, "skipping domain checks as no DOMAIN=... configured for app") } } else { - log.Warn("skipping domain checks as requested") + warnMessages = append(warnMessages, "skipping domain checks as requested") + } + + if err := internal.DeployOverview(app, warnMessages, version, chaosVersion); err != nil { + log.Fatal(err) } stack.WaitTimeout, err = appPkg.GetTimeoutFromLabel(compose, stackName) diff --git a/cli/app/rollback.go b/cli/app/rollback.go index 79a35359..29b2710e 100644 --- a/cli/app/rollback.go +++ b/cli/app/rollback.go @@ -47,6 +47,8 @@ EXAMPLE: abra app rollback foo.example.com 1.2.3+3.2.1`, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { + var warnMessages []string + app := internal.ValidateApp(c) stackName := app.StackName() @@ -82,7 +84,7 @@ EXAMPLE: var availableDowngrades []string if deployMeta.Version == "unknown" { availableDowngrades = versions - log.Warnf("failed to determine deployed version of %s", app.Name) + warnMessages = append(warnMessages, fmt.Sprintf("failed to determine deployed version of %s", app.Name)) } specificVersion := c.Args().Get(1) @@ -113,7 +115,7 @@ EXAMPLE: if deployMeta.Version != "unknown" && specificVersion == "" { if deployMeta.IsChaos { - log.Warn("attempting to rollback a chaos deployment") + warnMessages = append(warnMessages, fmt.Sprintf("attempting to rollback a chaos deployment")) } for _, version := range versions { @@ -203,7 +205,14 @@ EXAMPLE: } // NOTE(d1): no release notes implemeneted for rolling back - if err := internal.NewVersionOverview(app, deployMeta.Version, chaosVersion, chosenDowngrade, ""); err != nil { + if err := internal.NewVersionOverview( + app, + warnMessages, + "rollback", + deployMeta.Version, + chaosVersion, + chosenDowngrade, + ""); err != nil { log.Fatal(err) } diff --git a/cli/app/undeploy.go b/cli/app/undeploy.go index 5e63d62c..3adffd06 100644 --- a/cli/app/undeploy.go +++ b/cli/app/undeploy.go @@ -107,7 +107,7 @@ Passing "-p/--prune" does not remove those volumes.`, chaosVersion = deployMeta.ChaosVersion } - if err := internal.DeployOverview(app, deployMeta.Version, chaosVersion, "continue with undeploy?"); err != nil { + if err := internal.DeployOverview(app, []string{}, deployMeta.Version, chaosVersion); err != nil { log.Fatal(err) } diff --git a/cli/app/upgrade.go b/cli/app/upgrade.go index 26366aef..38849856 100644 --- a/cli/app/upgrade.go +++ b/cli/app/upgrade.go @@ -47,6 +47,8 @@ EXAMPLE: abra app upgrade foo.example.com 1.2.3+3.2.1`, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { + var warnMessages []string + app := internal.ValidateApp(c) stackName := app.StackName() @@ -82,7 +84,7 @@ EXAMPLE: var availableUpgrades []string if deployMeta.Version == "unknown" { availableUpgrades = versions - log.Warnf("failed to determine deployed version of %s", app.Name) + warnMessages = append(warnMessages, fmt.Sprintf("failed to determine deployed version of %s", app.Name)) } specificVersion := c.Args().Get(1) @@ -114,7 +116,7 @@ EXAMPLE: if deployMeta.Version != "unknown" && specificVersion == "" { if deployMeta.IsChaos { - log.Warn("attempting to upgrade a chaos deployment") + warnMessages = append(warnMessages, fmt.Sprintf("attempting to upgrade a chaos deployment")) } for _, version := range versions { @@ -156,7 +158,7 @@ EXAMPLE: } if internal.Force && chosenUpgrade == "" { - log.Warnf("%s is already upgraded to latest but continuing (--force)", app.Name) + warnMessages = append(warnMessages, fmt.Sprintf("%s is already upgraded to latest", app.Name)) chosenUpgrade = deployMeta.Version } @@ -230,7 +232,9 @@ EXAMPLE: for _, envVar := range envVars { if !envVar.Present { - log.Warnf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain) + warnMessages = append(warnMessages, + fmt.Sprintf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain), + ) } } @@ -245,7 +249,14 @@ EXAMPLE: chaosVersion = deployMeta.ChaosVersion } - if err := internal.NewVersionOverview(app, deployMeta.Version, chaosVersion, chosenUpgrade, releaseNotes); err != nil { + if err := internal.NewVersionOverview( + app, + warnMessages, + "upgrade", + deployMeta.Version, + chaosVersion, + chosenUpgrade, + releaseNotes); err != nil { log.Fatal(err) } diff --git a/cli/internal/deploy.go b/cli/internal/deploy.go index 3147d66c..5f761a65 100644 --- a/cli/internal/deploy.go +++ b/cli/internal/deploy.go @@ -6,17 +6,41 @@ import ( "strings" appPkg "coopcloud.tech/abra/pkg/app" - "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/log" "github.com/AlecAivazis/survey/v2" + "github.com/charmbracelet/lipgloss" dockerClient "github.com/docker/docker/client" ) -// NewVersionOverview shows an upgrade or downgrade overview -func NewVersionOverview(app appPkg.App, currentVersion, chaosVersion, newVersion, releaseNotes string) error { - tableCol := []string{"server", "recipe", "config", "domain", "version", "chaos", "to deploy"} - table := formatter.CreateTable(tableCol) +var borderStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.ThickBorder()). + Padding(0, 1, 0, 1). + MaxWidth(79). + BorderForeground(lipgloss.Color("63")) +var headerStyle = lipgloss.NewStyle(). + Underline(true). + Bold(true) + +var leftStyle = lipgloss.NewStyle(). + Bold(true) + +var rightStyle = lipgloss.NewStyle() + +// horizontal is a JoinHorizontal helper function. +func horizontal(left, mid, right string) string { + return lipgloss.JoinHorizontal(lipgloss.Left, left, mid, right) +} + +// NewVersionOverview shows an upgrade or downgrade overview +func NewVersionOverview( + app appPkg.App, + warnMessages []string, + kind, + currentVersion, + chaosVersion, + newVersion, + releaseNotes string) error { deployConfig := "compose.yml" if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n") @@ -27,22 +51,36 @@ func NewVersionOverview(app appPkg.App, currentVersion, chaosVersion, newVersion server = "local" } - table.Append([]string{ - server, - app.Recipe.Name, - deployConfig, - app.Domain, - currentVersion, - chaosVersion, - newVersion, - }) - table.Render() + body := strings.Builder{} + body.WriteString( + borderStyle.Render( + lipgloss.JoinVertical( + lipgloss.Center, + headerStyle.Render(fmt.Sprintf("%s OVERVIEW", strings.ToUpper(kind))), + lipgloss.JoinVertical( + lipgloss.Left, + horizontal(leftStyle.Render("SERVER"), " ", rightStyle.Render(server)), + horizontal(leftStyle.Render("DOMAIN"), " ", rightStyle.Render(app.Domain)), + horizontal(leftStyle.Render("RECIPE"), " ", rightStyle.Render(app.Recipe.Name)), + horizontal(leftStyle.Render("CONFIG"), " ", rightStyle.Render(deployConfig)), + horizontal(leftStyle.Render("VERSION"), " ", rightStyle.Render(currentVersion)), + horizontal(leftStyle.Render("CHAOS"), " ", rightStyle.Render(chaosVersion)), + horizontal(leftStyle.Render("DEPLOY"), " ", rightStyle.Padding(0).Render(newVersion)), + ), + ), + ), + ) + fmt.Println(body.String()) if releaseNotes != "" && newVersion != "" { fmt.Println() fmt.Print(releaseNotes) } else { - log.Warnf("no release notes available for %s", newVersion) + warnMessages = append(warnMessages, fmt.Sprintf("no release notes available for %s", newVersion)) + } + + for _, msg := range warnMessages { + log.Warn(msg) } if NoInput { @@ -50,16 +88,66 @@ func NewVersionOverview(app appPkg.App, currentVersion, chaosVersion, newVersion } response := false - prompt := &survey.Confirm{ - Message: "continue with deployment?", - } - + prompt := &survey.Confirm{Message: "proceed?"} if err := survey.AskOne(prompt, &response); err != nil { return err } if !response { - log.Fatal("exiting as requested") + log.Fatal("deployment cancelled") + } + + return nil +} + +// DeployOverview shows a deployment overview +func DeployOverview(app appPkg.App, warnMessages []string, version, chaosVersion string) error { + deployConfig := "compose.yml" + if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { + deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n") + } + + server := app.Server + if app.Server == "default" { + server = "local" + } + + body := strings.Builder{} + body.WriteString( + borderStyle.Render( + lipgloss.JoinVertical( + lipgloss.Center, + headerStyle.Render("DEPLOY OVERVIEW"), + lipgloss.JoinVertical( + lipgloss.Left, + horizontal(leftStyle.Render("SERVER"), " ", rightStyle.Render(server)), + horizontal(leftStyle.Render("DOMAIN"), " ", rightStyle.Render(app.Domain)), + horizontal(leftStyle.Render("RECIPE"), " ", rightStyle.Render(app.Recipe.Name)), + horizontal(leftStyle.Render("CONFIG"), " ", rightStyle.Render(deployConfig)), + horizontal(leftStyle.Render("VERSION"), " ", rightStyle.Render(version)), + horizontal(leftStyle.Render("CHAOS"), " ", rightStyle.Padding(0).Render(chaosVersion)), + ), + ), + ), + ) + fmt.Println(body.String()) + + for _, msg := range warnMessages { + log.Warn(msg) + } + + if NoInput { + return nil + } + + response := false + prompt := &survey.Confirm{Message: "proceed?"} + if err := survey.AskOne(prompt, &response); err != nil { + return err + } + + if !response { + log.Fatal("deployment cancelled") } return nil @@ -118,48 +206,3 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error { } return nil } - -// DeployOverview shows a deployment overview -func DeployOverview(app appPkg.App, version, chaosVersion, message string) error { - tableCol := []string{"server", "recipe", "config", "domain", "version", "chaos"} - table := formatter.CreateTable(tableCol) - - deployConfig := "compose.yml" - if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { - deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n") - } - - server := app.Server - if app.Server == "default" { - server = "local" - } - - table.Append([]string{ - server, - app.Recipe.Name, - deployConfig, - app.Domain, - version, - chaosVersion, - }) - table.Render() - - if NoInput { - return nil - } - - response := false - prompt := &survey.Confirm{ - Message: message, - } - - if err := survey.AskOne(prompt, &response); err != nil { - return err - } - - if !response { - log.Fatal("exiting as requested") - } - - return nil -}