refactor!: vertical render & UI/UX fixes
All checks were successful
continuous-integration/drone/push Build is passing

See coop-cloud/abra#454
This commit is contained in:
decentral1se 2024-12-28 15:30:43 +01:00 committed by decentral1se
parent b6573720ec
commit 97959ef5da
17 changed files with 352 additions and 184 deletions

View File

@ -46,7 +46,10 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
} }
table. table.
Headers("RECIPE ENV SAMPLE", "APP ENV"). Headers(
fmt.Sprintf("%s .env.sample", app.Recipe.Name),
fmt.Sprintf("%s.env", app.Name),
).
StyleFunc(func(row, col int) lipgloss.Style { StyleFunc(func(row, col int) lipgloss.Style {
switch { switch {
case col == 1: case col == 1:
@ -71,7 +74,9 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
} }
} }
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Fatal(err)
}
}, },
} }

View File

@ -110,19 +110,19 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
// is because we need to deal with GetComposeFiles under the hood and these // is because we need to deal with GetComposeFiles under the hood and these
// files change from version to version which therefore affects which // files change from version to version which therefore affects which
// secrets might be generated // secrets might be generated
version := deployMeta.Version toDeployVersion := deployMeta.Version
if specificVersion != "" { if specificVersion != "" {
version = specificVersion toDeployVersion = specificVersion
log.Debugf("choosing %s as version to deploy", version) log.Debugf("choosing %s as version to deploy", toDeployVersion)
var err error var err error
isChaosCommit, err = app.Recipe.EnsureVersion(version) isChaosCommit, err = app.Recipe.EnsureVersion(toDeployVersion)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if isChaosCommit { if isChaosCommit {
log.Debugf("assuming '%s' is a chaos commit", version) log.Debugf("assuming '%s' is a chaos commit", toDeployVersion)
internal.Chaos = true internal.Chaos = true
} }
} }
@ -138,12 +138,8 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
} }
} }
if deployMeta.IsDeployed { if deployMeta.IsDeployed && !(internal.Force || internal.Chaos) {
if internal.Force || internal.Chaos { log.Fatalf("%s is already deployed", app.Name)
warnMessages = append(warnMessages, fmt.Sprintf("%s is already deployed", app.Name))
} else {
log.Fatalf("%s is already deployed", app.Name)
}
} }
if !internal.Chaos && specificVersion == "" { if !internal.Chaos && specificVersion == "" {
@ -153,9 +149,9 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
} }
if len(versions) > 0 && !internal.Chaos { if len(versions) > 0 && !internal.Chaos {
version = versions[len(versions)-1] toDeployVersion = versions[len(versions)-1]
log.Debugf("choosing %s as version to deploy", version) log.Debugf("choosing %s as version to deploy", toDeployVersion)
if _, err := app.Recipe.EnsureVersion(version); err != nil { if _, err := app.Recipe.EnsureVersion(toDeployVersion); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} else { } else {
@ -163,25 +159,22 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
version = formatter.SmallSHA(head.String()) toDeployVersion = formatter.SmallSHA(head.String())
warnMessages = append(warnMessages, fmt.Sprintf("no versions detected, using latest commit"))
} }
} }
chaosVersion := config.CHAOS_DEFAULT toDeployChaosVersion := config.CHAOS_DEFAULT
if internal.Chaos { if internal.Chaos {
warnMessages = append(warnMessages, "chaos mode engaged")
if isChaosCommit { if isChaosCommit {
chaosVersion = specificVersion toDeployChaosVersion = specificVersion
versionLabelLocal, err := app.Recipe.GetVersionLabelLocal() versionLabelLocal, err := app.Recipe.GetVersionLabelLocal()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
version = versionLabelLocal toDeployVersion = versionLabelLocal
} else { } else {
var err error var err error
chaosVersion, err = app.Recipe.ChaosVersion() toDeployChaosVersion, err = app.Recipe.ChaosVersion()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -216,7 +209,7 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
appPkg.ExposeAllEnv(stackName, compose, app.Env) appPkg.ExposeAllEnv(stackName, compose, app.Env)
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name) appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos) appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
appPkg.SetChaosVersionLabel(compose, stackName, chaosVersion) appPkg.SetChaosVersionLabel(compose, stackName, toDeployChaosVersion)
appPkg.SetUpdateLabel(compose, stackName, app.Env) appPkg.SetUpdateLabel(compose, stackName, app.Env)
envVars, err := appPkg.CheckEnv(app) envVars, err := appPkg.CheckEnv(app)
@ -239,13 +232,24 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
log.Fatal(err) log.Fatal(err)
} }
} else { } else {
warnMessages = append(warnMessages, "skipping domain checks as no DOMAIN=... configured for app") log.Debug("skipping domain checks as no DOMAIN=... configured for app")
} }
} else { } else {
warnMessages = append(warnMessages, "skipping domain checks as requested") log.Debug("skipping domain checks as requested")
} }
if err := internal.DeployOverview(app, warnMessages, version, chaosVersion); err != nil { deployedVersion := "N/A"
if deployMeta.IsDeployed {
deployedVersion = deployMeta.Version
}
if err := internal.DeployOverview(
app,
warnMessages,
deployedVersion,
deployMeta.ChaosVersion,
toDeployVersion,
toDeployChaosVersion); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -267,9 +271,9 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
} }
} }
app.Recipe.Version = version app.Recipe.Version = toDeployVersion
if chaosVersion != config.CHAOS_DEFAULT { if toDeployChaosVersion != config.CHAOS_DEFAULT {
app.Recipe.Version = chaosVersion app.Recipe.Version = toDeployChaosVersion
} }
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version) log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil { if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {

View File

@ -208,7 +208,7 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
serverStat := allStats[app.Server] serverStat := allStats[app.Server]
headers := []string{"RECIPE", "DOMAIN"} headers := []string{"RECIPE", "DOMAIN", "SERVER"}
if status { if status {
headers = append(headers, []string{ headers = append(headers, []string{
"STATUS", "STATUS",
@ -228,7 +228,7 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
var rows [][]string var rows [][]string
for _, appStat := range serverStat.Apps { for _, appStat := range serverStat.Apps {
row := []string{appStat.Recipe, appStat.Domain} row := []string{appStat.Recipe, appStat.Domain, appStat.Server}
if status { if status {
chaosStatus := appStat.Chaos chaosStatus := appStat.Chaos
if chaosStatus != "unknown" { if chaosStatus != "unknown" {
@ -256,20 +256,8 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
table.Rows(rows...) table.Rows(rows...)
if len(rows) > 0 { if len(rows) > 0 {
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Fatal(err)
if status {
fmt.Println(fmt.Sprintf(
"SERVER: %s | TOTAL APPS: %v | VERSIONED: %v | UNVERSIONED: %v | LATEST : %v | UPGRADE: %v",
app.Server,
serverStat.AppCount,
serverStat.VersionCount,
serverStat.UnversionedCount,
serverStat.LatestCount,
serverStat.UpgradeCount,
))
} else {
log.Infof("SERVER: %s TOTAL APPS: %v", app.Server, serverStat.AppCount)
} }
if len(allStats) > 1 && len(rows) > 0 { if len(allStats) > 1 && len(rows) > 0 {
@ -279,12 +267,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
alreadySeen[app.Server] = true alreadySeen[app.Server] = true
} }
if len(allStats) > 1 {
totalServers := formatter.BoldStyle.Render("TOTAL SERVERS")
totalApps := formatter.BoldStyle.Render("TOTAL APPS")
log.Infof("%s: %v | %s: %v ", totalServers, totalServersCount, totalApps, totalAppsCount)
}
}, },
} }

View File

@ -64,7 +64,6 @@ var AppNewCommand = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
recipe := internal.ValidateRecipe(args, cmd.Name()) recipe := internal.ValidateRecipe(args, cmd.Name())
var recipeVersion string
if !internal.Chaos { if !internal.Chaos {
if err := recipe.EnsureIsClean(); err != nil { if err := recipe.EnsureIsClean(); err != nil {
log.Fatal(err) log.Fatal(err)
@ -74,41 +73,47 @@ var AppNewCommand = &cobra.Command{
log.Fatal(err) log.Fatal(err)
} }
} }
}
if len(args) == 2 { var recipeVersion string
recipeVersion = args[1] if len(args) == 2 {
} recipeVersion = args[1]
}
if recipeVersion == "" { var recipeVersions recipePkg.RecipeVersions
recipeVersions, err := recipe.GetRecipeVersions() if recipeVersion == "" {
if err != nil { var err error
log.Fatal(err) recipeVersions, err = recipe.GetRecipeVersions()
} if err != nil {
log.Fatal(err)
if len(recipeVersions) > 0 {
latest := recipeVersions[len(recipeVersions)-1]
for tag := range latest {
recipeVersion = tag
}
if _, err := recipe.EnsureVersion(recipeVersion); err != nil {
log.Fatal(err)
}
} else {
if err := recipe.EnsureLatest(); err != nil {
log.Fatal(err)
}
}
} else {
if _, err := recipe.EnsureVersion(recipeVersion); err != nil {
log.Fatal(err)
}
} }
} }
if internal.Chaos && recipeVersion == "" { if len(recipeVersions) > 0 {
latest := recipeVersions[len(recipeVersions)-1]
for tag := range latest {
recipeVersion = tag
}
if _, err := recipe.EnsureVersion(recipeVersion); err != nil {
log.Fatal(err)
}
} else {
if err := recipe.EnsureLatest(); err != nil {
log.Fatal(err)
}
}
if !internal.Chaos && recipeVersion != "" {
if _, err := recipe.EnsureVersion(recipeVersion); err != nil {
log.Fatal(err)
}
}
chaosVersion := config.CHAOS_DEFAULT
if internal.Chaos {
var err error var err error
recipeVersion, err = recipe.ChaosVersion() chaosVersion, err = recipe.ChaosVersion()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -187,37 +192,20 @@ var AppNewCommand = &cobra.Command{
newAppServer = "local" newAppServer = "local"
} }
table, err := formatter.CreateTable() log.Infof("%s created successfully (version: %s, chaos: %s)", appDomain, recipeVersion, chaosVersion)
if err != nil {
log.Fatal(err)
}
headers := []string{"SERVER", "DOMAIN", "RECIPE", "VERSION"}
table.Headers(headers...)
table.Row(newAppServer, appDomain, recipe.Name, recipeVersion)
log.Infof("new app '%s' created 🌞", recipe.Name)
fmt.Println("")
fmt.Println(table)
fmt.Println("")
fmt.Println("Configure this app:")
fmt.Println(fmt.Sprintf("\n abra app config %s", appDomain))
fmt.Println("")
fmt.Println("Deploy this app:")
fmt.Println(fmt.Sprintf("\n abra app deploy %s", appDomain))
if len(appSecrets) > 0 { if len(appSecrets) > 0 {
fmt.Println("") rows := [][]string{}
fmt.Println("Generated secrets:") for k, v := range appSecrets {
fmt.Println("") rows = append(rows, []string{k, v})
fmt.Println(secretsTable) }
overview := formatter.CreateOverview("SECRETS OVERVIEW", rows)
fmt.Println(overview)
log.Warnf( log.Warnf(
"generated secrets %s shown again, please take note of them %s", "secrets are %s shown again, please save them %s",
formatter.BoldStyle.Render("NOT"), formatter.BoldStyle.Render("NOT"),
formatter.BoldStyle.Render("NOW"), formatter.BoldStyle.Render("NOW"),
) )

View File

@ -128,24 +128,35 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
allContainerStats[containerStats["service"]] = containerStats allContainerStats[containerStats["service"]] = containerStats
// NOTE(d1): don't clobber these variables for --machine output
dVersion := deployedVersion
cVersion := chaosVersion
if containerStats["service"] != "app" {
// NOTE(d1): don't repeat info which only relevant for the "app" service
dVersion = ""
cVersion = ""
}
row := []string{ row := []string{
containerStats["service"], containerStats["service"],
containerStats["image"], containerStats["image"],
containerStats["created"], dVersion,
cVersion,
containerStats["status"], containerStats["status"],
containerStats["state"],
containerStats["ports"],
} }
rows = append(rows, row) rows = append(rows, row)
} }
if internal.MachineReadable { if internal.MachineReadable {
jsonstring, err := json.Marshal(allContainerStats) rendered, err := json.Marshal(allContainerStats)
if err != nil { if err != nil {
log.Fatal("unable to convert to JSON: %s", err) log.Fatal("unable to convert to JSON: %s", err)
} }
fmt.Println(string(jsonstring))
fmt.Println(string(rendered))
return return
} }
@ -157,19 +168,18 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
headers := []string{ headers := []string{
"SERVICE", "SERVICE",
"IMAGE", "IMAGE",
"CREATED", "VERSION",
"CHAOS",
"STATUS", "STATUS",
"STATE",
"PORTS",
} }
table. table.
Headers(headers...). Headers(headers...).
Rows(rows...) Rows(rows...)
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Fatal(err)
log.Infof("VERSION: %s CHAOS: %s", deployedVersion, chaosVersion) }
} }
func init() { func init() {

View File

@ -127,7 +127,9 @@ var AppSecretGenerateCommand = &cobra.Command{
return return
} }
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Fatal(err)
}
log.Warnf( log.Warnf(
"generated secrets %s shown again, please take note of them %s", "generated secrets %s shown again, please take note of them %s",
@ -394,7 +396,10 @@ var AppSecretLsCommand = &cobra.Command{
return return
} }
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Fatal(err)
}
return return
} }

View File

@ -63,7 +63,7 @@ var AppServicesCommand = &cobra.Command{
log.Fatal(err) log.Fatal(err)
} }
headers := []string{"SERVICE (SHORT)", "SERVICE (LONG)", "IMAGE"} headers := []string{"SERVICE (SHORT)", "SERVICE (LONG)"}
table.Headers(headers...) table.Headers(headers...)
var rows [][]string var rows [][]string
@ -80,7 +80,6 @@ var AppServicesCommand = &cobra.Command{
row := []string{ row := []string{
serviceShortName, serviceShortName,
serviceLongName, serviceLongName,
formatter.RemoveSha(container.Image),
} }
rows = append(rows, row) rows = append(rows, row)
@ -89,7 +88,9 @@ var AppServicesCommand = &cobra.Command{
table.Rows(rows...) table.Rows(rows...)
if len(rows) > 0 { if len(rows) > 0 {
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Fatal(err)
}
} }
}, },
} }

View File

@ -59,7 +59,10 @@ Passing "--prune/-p" does not remove those volumes.`,
chaosVersion = deployMeta.ChaosVersion chaosVersion = deployMeta.ChaosVersion
} }
if err := internal.DeployOverview(app, []string{}, deployMeta.Version, chaosVersion); err != nil { if err := internal.UndeployOverview(
app,
deployMeta.Version,
chaosVersion); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -2,7 +2,6 @@ package app
import ( import (
"context" "context"
"fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
@ -43,7 +42,7 @@ var AppVolumeListCommand = &cobra.Command{
log.Fatal(err) log.Fatal(err)
} }
headers := []string{"name", "created", "mounted"} headers := []string{"NAME", "ON SERVER"}
table, err := formatter.CreateTable() table, err := formatter.CreateTable()
if err != nil { if err != nil {
@ -54,14 +53,16 @@ var AppVolumeListCommand = &cobra.Command{
var rows [][]string var rows [][]string
for _, volume := range volumes { for _, volume := range volumes {
row := []string{volume.Name, volume.CreatedAt, volume.Mountpoint} row := []string{volume.Name, volume.Mountpoint}
rows = append(rows, row) rows = append(rows, row)
} }
table.Rows(rows...) table.Rows(rows...)
if len(rows) > 0 { if len(rows) > 0 {
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Fatal(err)
}
return return
} }

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
appPkg "coopcloud.tech/abra/pkg/app" appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
@ -20,7 +21,8 @@ var borderStyle = lipgloss.NewStyle().
var headerStyle = lipgloss.NewStyle(). var headerStyle = lipgloss.NewStyle().
Underline(true). Underline(true).
Bold(true) Bold(true).
PaddingBottom(1)
var leftStyle = lipgloss.NewStyle(). var leftStyle = lipgloss.NewStyle().
Bold(true) Bold(true)
@ -51,6 +53,11 @@ func NewVersionOverview(
server = "local" server = "local"
} }
domain := app.Domain
if domain == "" {
domain = "N/A"
}
body := strings.Builder{} body := strings.Builder{}
body.WriteString( body.WriteString(
borderStyle.Render( borderStyle.Render(
@ -60,7 +67,7 @@ func NewVersionOverview(
lipgloss.JoinVertical( lipgloss.JoinVertical(
lipgloss.Left, lipgloss.Left,
horizontal(leftStyle.Render("SERVER"), " ", rightStyle.Render(server)), horizontal(leftStyle.Render("SERVER"), " ", rightStyle.Render(server)),
horizontal(leftStyle.Render("DOMAIN"), " ", rightStyle.Render(app.Domain)), horizontal(leftStyle.Render("DOMAIN"), " ", rightStyle.Render(domain)),
horizontal(leftStyle.Render("RECIPE"), " ", rightStyle.Render(app.Recipe.Name)), horizontal(leftStyle.Render("RECIPE"), " ", rightStyle.Render(app.Recipe.Name)),
horizontal(leftStyle.Render("CONFIG"), " ", rightStyle.Render(deployConfig)), horizontal(leftStyle.Render("CONFIG"), " ", rightStyle.Render(deployConfig)),
horizontal(leftStyle.Render("VERSION"), " ", rightStyle.Render(currentVersion)), horizontal(leftStyle.Render("VERSION"), " ", rightStyle.Render(currentVersion)),
@ -101,7 +108,13 @@ func NewVersionOverview(
} }
// DeployOverview shows a deployment overview // DeployOverview shows a deployment overview
func DeployOverview(app appPkg.App, warnMessages []string, version, chaosVersion string) error { func DeployOverview(
app appPkg.App,
warnMessages []string,
deployedVersion string,
deployedChaosVersion string,
toDeployVersion,
toDeployChaosVersion string) error {
deployConfig := "compose.yml" deployConfig := "compose.yml"
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n") deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
@ -112,25 +125,25 @@ func DeployOverview(app appPkg.App, warnMessages []string, version, chaosVersion
server = "local" server = "local"
} }
body := strings.Builder{} domain := app.Domain
body.WriteString( if domain == "" {
borderStyle.Render( domain = "N/A"
lipgloss.JoinVertical( }
lipgloss.Center,
headerStyle.Render("DEPLOY OVERVIEW"), rows := [][]string{
lipgloss.JoinVertical( []string{"APP", domain},
lipgloss.Left, []string{"RECIPE", app.Recipe.Name},
horizontal(leftStyle.Render("SERVER"), " ", rightStyle.Render(server)), []string{"SERVER", server},
horizontal(leftStyle.Render("DOMAIN"), " ", rightStyle.Render(app.Domain)), []string{"DEPLOYED", deployedVersion},
horizontal(leftStyle.Render("RECIPE"), " ", rightStyle.Render(app.Recipe.Name)), []string{"CURRENT CHAOS ", deployedChaosVersion},
horizontal(leftStyle.Render("CONFIG"), " ", rightStyle.Render(deployConfig)), []string{"TO DEPLOY", toDeployVersion},
horizontal(leftStyle.Render("VERSION"), " ", rightStyle.Render(version)), []string{"NEW CHAOS", toDeployChaosVersion},
horizontal(leftStyle.Render("CHAOS"), " ", rightStyle.Padding(0).Render(chaosVersion)), []string{"CONFIG", deployConfig},
), }
),
), overview := formatter.CreateOverview("DEPLOY OVERVIEW", rows)
)
fmt.Println(body.String()) fmt.Println(overview)
for _, msg := range warnMessages { for _, msg := range warnMessages {
log.Warn(msg) log.Warn(msg)
@ -153,6 +166,56 @@ func DeployOverview(app appPkg.App, warnMessages []string, version, chaosVersion
return nil return nil
} }
// UndeployOverview shows an undeployment overview
func UndeployOverview(
app appPkg.App,
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"
}
domain := app.Domain
if domain == "" {
domain = "N/A"
}
rows := [][]string{
[]string{"APP", domain},
[]string{"RECIPE", app.Recipe.Name},
[]string{"SERVER", server},
[]string{"DEPLOYED", version},
[]string{"CHAOS", chaosVersion},
[]string{"CONFIG", deployConfig},
}
overview := formatter.CreateOverview("UNDEPLOY OVERVIEW", rows)
fmt.Println(overview)
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("undeploy cancelled")
}
return nil
}
// PostCmds parses a string of commands and executes them inside of the respective services // PostCmds parses a string of commands and executes them inside of the respective services
// the commands string must have the following format: // the commands string must have the following format:
// "<service> <command> <arguments>|<service> <command> <arguments>|... " // "<service> <command> <arguments>|<service> <command> <arguments>|... "

View File

@ -1,8 +1,6 @@
package recipe package recipe
import ( import (
"fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
@ -104,7 +102,9 @@ var RecipeLintCommand = &cobra.Command{
} }
if len(rows) > 0 { if len(rows) > 0 {
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Fatal(err)
}
for _, warnMsg := range warnMessages { for _, warnMsg := range warnMessages {
log.Warn(warnMsg) log.Warn(warnMsg)

View File

@ -79,8 +79,9 @@ var RecipeListCommand = &cobra.Command{
return return
} }
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Infof("total recipes: %v", len(rows)) log.Fatal(err)
}
} }
}, },
} }

View File

@ -55,15 +55,32 @@ var RecipeVersionCommand = &cobra.Command{
log.Fatal(err) log.Fatal(err)
} }
table.Headers("SERVICE", "NAME", "TAG") table.Headers("SERVICE", "IMAGE", "TAG", "VERSION")
for version, meta := range recipeMeta.Versions[i] { for version, meta := range recipeMeta.Versions[i] {
var allRows [][]string var allRows [][]string
var rows [][]string var rows [][]string
for service, serviceMeta := range meta { for service, serviceMeta := range meta {
rows = append(rows, []string{service, serviceMeta.Image, serviceMeta.Tag}) recipeVersion := version
allRows = append(allRows, []string{version, service, serviceMeta.Image, serviceMeta.Tag}) if service != "app" {
recipeVersion = ""
}
rows = append(rows, []string{
service,
serviceMeta.Image,
serviceMeta.Tag,
recipeVersion,
})
allRows = append(allRows, []string{
version,
service,
serviceMeta.Image,
serviceMeta.Tag,
recipeVersion,
})
} }
sort.Slice(rows, sortServiceByName(rows)) sort.Slice(rows, sortServiceByName(rows))
@ -71,8 +88,9 @@ var RecipeVersionCommand = &cobra.Command{
table.Rows(rows...) table.Rows(rows...)
if !internal.MachineReadable { if !internal.MachineReadable {
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Infof("VERSION: %s", version) log.Fatal(err)
}
fmt.Println() fmt.Println()
continue continue
} }
@ -100,11 +118,7 @@ var RecipeVersionCommand = &cobra.Command{
func sortServiceByName(versions [][]string) func(i, j int) bool { func sortServiceByName(versions [][]string) func(i, j int) bool {
return func(i, j int) bool { return func(i, j int) bool {
// NOTE(d1): corresponds to the `tableCol` definition below return versions[i][0] < versions[j][0]
if versions[i][1] == "app" {
return true
}
return versions[i][1] < versions[j][1]
} }
} }

View File

@ -86,7 +86,9 @@ var ServerListCommand = &cobra.Command{
return return
} }
fmt.Println(table) if err := formatter.PrintTable(table); err != nil {
log.Fatal(err)
}
}, },
} }

View File

@ -615,7 +615,7 @@ func (a App) WriteRecipeVersion(version string, dryRun bool) error {
} }
if !skipped { if !skipped {
log.Infof("version %s saved to %s.env", version, a.Domain) log.Debugf("version %s saved to %s.env", version, a.Domain)
} else { } else {
log.Debugf("skipping version %s write as already exists in %s.env", version, a.Domain) log.Debugf("skipping version %s write as already exists in %s.env", version, a.Domain)
} }

View File

@ -43,33 +43,122 @@ func HumanDuration(timestamp int64) string {
// CreateTable prepares a table layout for output. // CreateTable prepares a table layout for output.
func CreateTable() (*table.Table, error) { func CreateTable() (*table.Table, error) {
var (
renderer = lipgloss.NewRenderer(os.Stdout)
headerStyle = renderer.NewStyle().Bold(true).Align(lipgloss.Center)
cellStyle = renderer.NewStyle().Padding(0, 1)
borderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
)
table := table.New(). table := table.New().
Border(lipgloss.ThickBorder()). Border(lipgloss.ThickBorder()).
BorderStyle( BorderStyle(borderStyle).
lipgloss.NewStyle(). StyleFunc(func(row, col int) lipgloss.Style {
Foreground(lipgloss.Color("63")), var style lipgloss.Style
)
switch {
case row == table.HeaderRow:
return headerStyle
default:
style = cellStyle
}
return style
})
return table, nil
}
func PrintTable(t *table.Table) error {
if isAbraCI, ok := os.LookupEnv("ABRA_CI"); ok && isAbraCI == "1" { if isAbraCI, ok := os.LookupEnv("ABRA_CI"); ok && isAbraCI == "1" {
// NOTE(d1): no width limits for CI testing since we test against outputs // NOTE(d1): no width limits for CI testing since we test against outputs
log.Debug("detected ABRA_CI=1") log.Debug("detected ABRA_CI=1")
return table, nil fmt.Println(t)
return nil
} }
tWidth, _ := lipgloss.Size(t.String())
width, _, err := term.GetSize(0) width, _, err := term.GetSize(0)
if err != nil { if err != nil {
return nil, err return err
} }
if width-10 < 79 { if tWidth > width {
// NOTE(d1): maintain standard minimum width t.Width(width - 10)
table.Width(79)
} else {
// NOTE(d1): tests show that this produces stable border drawing
table.Width(width - 10)
} }
return table, nil fmt.Println(t)
return nil
}
// horizontal is a JoinHorizontal helper function.
func horizontal(left, mid, right string) string {
return lipgloss.JoinHorizontal(lipgloss.Right, left, mid, right)
}
func CreateOverview(header string, rows [][]string) string {
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).
PaddingBottom(1)
var leftStyle = lipgloss.NewStyle().
Bold(true)
var rightStyle = lipgloss.NewStyle()
var longest int
for _, row := range rows {
if len(row[0]) > longest {
longest = len(row[0])
}
}
var renderedRows []string
for _, row := range rows {
if len(row) > 2 {
panic("CreateOverview: only accepts rows of len == 2")
}
lenOffset := 4
if len(row[0]) < longest {
lenOffset += longest - len(row[0])
}
offset := ""
for range lenOffset {
offset = offset + " "
}
renderedRows = append(
renderedRows,
horizontal(leftStyle.Render(row[0]), offset, rightStyle.Render(row[1])),
)
}
body := strings.Builder{}
body.WriteString(
borderStyle.Render(
lipgloss.JoinVertical(
lipgloss.Center,
headerStyle.Render(header),
lipgloss.JoinVertical(
lipgloss.Left,
renderedRows...,
),
),
),
)
return body.String()
} }
// ToJSON converts a lipgloss.Table to JSON representation. It's not a robust // ToJSON converts a lipgloss.Table to JSON representation. It's not a robust

View File

@ -247,7 +247,7 @@ func (r Recipe) ChaosVersion() (string, error) {
} }
if !isClean { if !isClean {
version = fmt.Sprintf("%s + unstaged changes", version) version = fmt.Sprintf("%s+U", version)
} }
return version, nil return version, nil