feat: logging background colours & vertical overview #439
@ -1,11 +1,14 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,8 +43,21 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"recipe env sample", "app env"}
|
table, err := formatter.CreateTable2()
|
||||||
table := formatter.CreateTable(tableCol)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.
|
||||||
|
Headers("RECIPE ENV SAMPLE", "APP ENV").
|
||||||
|
StyleFunc(func(row, col int) lipgloss.Style {
|
||||||
|
switch {
|
||||||
|
case col == 1:
|
||||||
|
return lipgloss.NewStyle().Padding(0, 1, 0, 1).Align(lipgloss.Center)
|
||||||
|
default:
|
||||||
|
return lipgloss.NewStyle().Padding(0, 1, 0, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
envVars, err := appPkg.CheckEnv(app)
|
envVars, err := appPkg.CheckEnv(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -50,13 +66,15 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
|||||||
|
|
||||||
for _, envVar := range envVars {
|
for _, envVar := range envVars {
|
||||||
if envVar.Present {
|
if envVar.Present {
|
||||||
table.Append([]string{envVar.Name, "✅"})
|
val := []string{envVar.Name, "✅"}
|
||||||
|
table.Row(val...)
|
||||||
} else {
|
} else {
|
||||||
table.Append([]string{envVar.Name, "❌"})
|
val := []string{envVar.Name, "❌"}
|
||||||
|
table.Row(val...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Render()
|
fmt.Println(table)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@ 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"
|
||||||
@ -47,6 +48,8 @@ EXAMPLE:
|
|||||||
abra app deploy foo.example.com 1e83340e`,
|
abra app deploy foo.example.com 1e83340e`,
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
|
var warnMessages []string
|
||||||
|
|
||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(c)
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
@ -115,7 +118,7 @@ EXAMPLE:
|
|||||||
|
|
||||||
if deployMeta.IsDeployed {
|
if deployMeta.IsDeployed {
|
||||||
if internal.Force || internal.Chaos {
|
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 {
|
} else {
|
||||||
log.Fatalf("%s is already deployed", app.Name)
|
log.Fatalf("%s is already deployed", app.Name)
|
||||||
}
|
}
|
||||||
@ -139,13 +142,13 @@ EXAMPLE:
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
version = formatter.SmallSHA(head.String())
|
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"
|
chaosVersion := "false"
|
||||||
if internal.Chaos {
|
if internal.Chaos {
|
||||||
log.Warnf("chaos mode engaged")
|
warnMessages = append(warnMessages, "chaos mode engaged")
|
||||||
|
|
||||||
if isChaosCommit {
|
if isChaosCommit {
|
||||||
chaosVersion = specificVersion
|
chaosVersion = specificVersion
|
||||||
@ -201,14 +204,12 @@ EXAMPLE:
|
|||||||
|
|
||||||
for _, envVar := range envVars {
|
for _, envVar := range envVars {
|
||||||
if !envVar.Present {
|
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 {
|
if !internal.NoDomainChecks {
|
||||||
domainName, ok := app.Env["DOMAIN"]
|
domainName, ok := app.Env["DOMAIN"]
|
||||||
if ok {
|
if ok {
|
||||||
@ -216,10 +217,14 @@ EXAMPLE:
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
} 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)
|
stack.WaitTimeout, err = appPkg.GetTimeoutFromLabel(compose, stackName)
|
||||||
|
@ -239,15 +239,27 @@ can take some time.`,
|
|||||||
|
|
||||||
serverStat := allStats[app.Server]
|
serverStat := allStats[app.Server]
|
||||||
|
|
||||||
tableCol := []string{"recipe", "domain"}
|
headers := []string{"RECIPE", "DOMAIN"}
|
||||||
if status {
|
if status {
|
||||||
tableCol = append(tableCol, []string{"status", "chaos", "version", "upgrade", "autoupdate"}...)
|
headers = append(headers, []string{
|
||||||
|
"STATUS",
|
||||||
|
"CHAOS",
|
||||||
|
"VERSION",
|
||||||
|
"UPGRADE",
|
||||||
|
"AUTOUPDATE"}...,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
table := formatter.CreateTable(tableCol)
|
table, err := formatter.CreateTable2()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for _, appStat := range serverStat.Apps {
|
for _, appStat := range serverStat.Apps {
|
||||||
tableRow := []string{appStat.Recipe, appStat.Domain}
|
row := []string{appStat.Recipe, appStat.Domain}
|
||||||
if status {
|
if status {
|
||||||
chaosStatus := appStat.Chaos
|
chaosStatus := appStat.Chaos
|
||||||
if chaosStatus != "unknown" {
|
if chaosStatus != "unknown" {
|
||||||
@ -259,17 +271,27 @@ can take some time.`,
|
|||||||
chaosStatus = appStat.ChaosVersion
|
chaosStatus = appStat.ChaosVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tableRow = append(tableRow, []string{appStat.Status, chaosStatus, appStat.Version, appStat.Upgrade, appStat.AutoUpdate}...)
|
|
||||||
|
row = append(row, []string{
|
||||||
|
appStat.Status,
|
||||||
|
chaosStatus,
|
||||||
|
appStat.Version,
|
||||||
|
appStat.Upgrade,
|
||||||
|
appStat.AutoUpdate}...,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
table.Append(tableRow)
|
|
||||||
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
if table.NumLines() > 0 {
|
table.Rows(rows...)
|
||||||
table.Render()
|
|
||||||
|
if len(rows) > 0 {
|
||||||
|
fmt.Println(table)
|
||||||
|
|
||||||
if status {
|
if status {
|
||||||
fmt.Println(fmt.Sprintf(
|
fmt.Println(fmt.Sprintf(
|
||||||
"server: %s | total apps: %v | versioned: %v | unversioned: %v | latest: %v | upgrade: %v",
|
"SERVER: %s | TOTAL APPS: %v | VERSIONED: %v | UNVERSIONED: %v | LATEST : %v | UPGRADE: %v",
|
||||||
app.Server,
|
app.Server,
|
||||||
serverStat.AppCount,
|
serverStat.AppCount,
|
||||||
serverStat.VersionCount,
|
serverStat.VersionCount,
|
||||||
@ -278,19 +300,21 @@ can take some time.`,
|
|||||||
serverStat.UpgradeCount,
|
serverStat.UpgradeCount,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(fmt.Sprintf("server: %s | total apps: %v", app.Server, serverStat.AppCount))
|
log.Infof("SERVER: %s TOTAL APPS: %v", app.Server, serverStat.AppCount)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(allStats) > 1 && table.NumLines() > 0 {
|
if len(allStats) > 1 && len(rows) > 0 {
|
||||||
fmt.Println() // newline separator for multiple servers
|
fmt.Println() // newline separator for multiple servers
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alreadySeen[app.Server] = true
|
alreadySeen[app.Server] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allStats) > 1 {
|
if len(allStats) > 1 {
|
||||||
fmt.Println(fmt.Sprintf("total servers: %v | total apps: %v ", totalServersCount, totalAppsCount))
|
totalServers := formatter.BoldStyle.Render("TOTAL SERVERS")
|
||||||
|
totalApps := formatter.BoldStyle.Render("TOTAL APPS")
|
||||||
|
log.Infof("%s: %v | %s: %v ", totalServers, totalServersCount, totalApps, totalAppsCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -9,11 +9,11 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/jsontable"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||||
"coopcloud.tech/abra/pkg/secret"
|
"coopcloud.tech/abra/pkg/secret"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/charmbracelet/lipgloss/table"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
@ -127,7 +127,7 @@ var appNewCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var secrets AppSecrets
|
var secrets AppSecrets
|
||||||
var secretTable *jsontable.JSONTable
|
var secretsTable *table.Table
|
||||||
if internal.Secrets {
|
if internal.Secrets {
|
||||||
sampleEnv, err := recipe.SampleEnv()
|
sampleEnv, err := recipe.SampleEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -158,10 +158,16 @@ var appNewCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretCols := []string{"Name", "Value"}
|
secretsTable, err = formatter.CreateTable2()
|
||||||
secretTable = formatter.CreateTable(secretCols)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"NAME", "VALUE"}
|
||||||
|
secretsTable.Headers(headers...)
|
||||||
|
|
||||||
for name, val := range secrets {
|
for name, val := range secrets {
|
||||||
secretTable.Append([]string{name, val})
|
secretsTable.Row(name, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,14 +175,20 @@ var appNewCommand = cli.Command{
|
|||||||
internal.NewAppServer = "local"
|
internal.NewAppServer = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"server", "recipe", "domain"}
|
table, err := formatter.CreateTable2()
|
||||||
table := formatter.CreateTable(tableCol)
|
if err != nil {
|
||||||
table.Append([]string{internal.NewAppServer, recipe.Name, internal.Domain})
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"SERVER", "RECIPE", "DOMAIN"}
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
|
table.Row(internal.NewAppServer, recipe.Name, internal.Domain)
|
||||||
|
|
||||||
log.Infof("new app '%s' created 🌞", recipe.Name)
|
log.Infof("new app '%s' created 🌞", recipe.Name)
|
||||||
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
table.Render()
|
fmt.Println(table)
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
|
|
||||||
fmt.Println("Configure this app:")
|
fmt.Println("Configure this app:")
|
||||||
@ -190,8 +202,13 @@ var appNewCommand = cli.Command{
|
|||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
fmt.Println("Generated secrets:")
|
fmt.Println("Generated secrets:")
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
secretTable.Render()
|
fmt.Println(secretsTable)
|
||||||
log.Warn("generated secrets are not shown again, please take note of them NOW")
|
|
||||||
|
log.Warnf(
|
||||||
|
"generated secrets %s shown again, please take note of them %s",
|
||||||
|
formatter.BoldStyle.Render("NOT"),
|
||||||
|
formatter.BoldStyle.Render("NOW"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -94,7 +94,7 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var tablerows [][]string
|
var rows [][]string
|
||||||
allContainerStats := make(map[string]map[string]string)
|
allContainerStats := make(map[string]map[string]string)
|
||||||
for _, service := range compose.Services {
|
for _, service := range compose.Services {
|
||||||
filters := filters.NewArgs()
|
filters := filters.NewArgs()
|
||||||
@ -109,8 +109,6 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
var containerStats map[string]string
|
var containerStats map[string]string
|
||||||
if len(containers) == 0 {
|
if len(containers) == 0 {
|
||||||
containerStats = map[string]string{
|
containerStats = map[string]string{
|
||||||
"version": deployedVersion,
|
|
||||||
"chaos": chaosVersion,
|
|
||||||
"service": service.Name,
|
"service": service.Name,
|
||||||
"image": "unknown",
|
"image": "unknown",
|
||||||
"created": "unknown",
|
"created": "unknown",
|
||||||
@ -121,8 +119,6 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
} else {
|
} else {
|
||||||
container := containers[0]
|
container := containers[0]
|
||||||
containerStats = map[string]string{
|
containerStats = map[string]string{
|
||||||
"version": deployedVersion,
|
|
||||||
"chaos": chaosVersion,
|
|
||||||
"service": abraService.ContainerToServiceName(container.Names, app.StackName()),
|
"service": abraService.ContainerToServiceName(container.Names, app.StackName()),
|
||||||
"image": formatter.RemoveSha(container.Image),
|
"image": formatter.RemoveSha(container.Image),
|
||||||
"created": formatter.HumanDuration(container.Created),
|
"created": formatter.HumanDuration(container.Created),
|
||||||
@ -134,9 +130,7 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
|
|
||||||
allContainerStats[containerStats["service"]] = containerStats
|
allContainerStats[containerStats["service"]] = containerStats
|
||||||
|
|
||||||
tablerow := []string{
|
row := []string{
|
||||||
deployedVersion,
|
|
||||||
chaosVersion,
|
|
||||||
containerStats["service"],
|
containerStats["service"],
|
||||||
containerStats["image"],
|
containerStats["image"],
|
||||||
containerStats["created"],
|
containerStats["created"],
|
||||||
@ -145,25 +139,37 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
containerStats["ports"],
|
containerStats["ports"],
|
||||||
}
|
}
|
||||||
|
|
||||||
tablerows = append(tablerows, tablerow)
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
jsonstring, err := json.Marshal(allContainerStats)
|
jsonstring, err := json.Marshal(allContainerStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal("unable to convert to JSON: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(jsonstring))
|
fmt.Println(string(jsonstring))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"version", "chaos", "service", "image", "created", "status", "state", "ports"}
|
table, err := formatter.CreateTable2()
|
||||||
table := formatter.CreateTable(tableCol)
|
if err != nil {
|
||||||
for _, row := range tablerows {
|
log.Fatal(err)
|
||||||
table.Append(row)
|
|
||||||
}
|
}
|
||||||
table.SetAutoMergeCellsByColumnIndex([]int{0, 1})
|
|
||||||
table.Render()
|
headers := []string{
|
||||||
|
"SERVICE",
|
||||||
|
"IMAGE",
|
||||||
|
"CREATED",
|
||||||
|
"STATUS",
|
||||||
|
"STATE",
|
||||||
|
"PORTS",
|
||||||
|
}
|
||||||
|
|
||||||
|
table.
|
||||||
|
Headers(headers...).
|
||||||
|
Rows(rows...)
|
||||||
|
|
||||||
|
fmt.Println(table)
|
||||||
|
|
||||||
|
log.Infof("VERSION: %s CHAOS: %s", deployedVersion, chaosVersion)
|
||||||
}
|
}
|
||||||
|
@ -49,12 +49,14 @@ flag.`,
|
|||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(c)
|
||||||
|
|
||||||
if !internal.Force && !internal.NoInput {
|
if !internal.Force && !internal.NoInput {
|
||||||
|
log.Warnf("ALERTA ALERTA: this will completely remove %s data and config locally and remotely", app.Name)
|
||||||
|
|
||||||
response := false
|
response := false
|
||||||
msg := "ALERTA ALERTA: this will completely remove %s data and configurations locally and remotely, are you sure?"
|
prompt := &survey.Confirm{Message: "are you sure?"}
|
||||||
prompt := &survey.Confirm{Message: fmt.Sprintf(msg, app.Name)}
|
|
||||||
if err := survey.AskOne(prompt, &response); err != nil {
|
if err := survey.AskOne(prompt, &response); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !response {
|
if !response {
|
||||||
log.Fatal("aborting as requested")
|
log.Fatal("aborting as requested")
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,8 @@ EXAMPLE:
|
|||||||
abra app rollback foo.example.com 1.2.3+3.2.1`,
|
abra app rollback foo.example.com 1.2.3+3.2.1`,
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
|
var warnMessages []string
|
||||||
|
|
||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(c)
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
@ -82,7 +84,7 @@ EXAMPLE:
|
|||||||
var availableDowngrades []string
|
var availableDowngrades []string
|
||||||
if deployMeta.Version == "unknown" {
|
if deployMeta.Version == "unknown" {
|
||||||
availableDowngrades = versions
|
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)
|
specificVersion := c.Args().Get(1)
|
||||||
@ -113,7 +115,7 @@ EXAMPLE:
|
|||||||
|
|
||||||
if deployMeta.Version != "unknown" && specificVersion == "" {
|
if deployMeta.Version != "unknown" && specificVersion == "" {
|
||||||
if deployMeta.IsChaos {
|
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 {
|
for _, version := range versions {
|
||||||
@ -203,7 +205,14 @@ EXAMPLE:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(d1): no release notes implemeneted for rolling back
|
// 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)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,18 +115,37 @@ var appSecretGenerateCommand = cli.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"name", "value"}
|
headers := []string{"NAME", "VALUE"}
|
||||||
table := formatter.CreateTable(tableCol)
|
table, err := formatter.CreateTable2()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for name, val := range secretVals {
|
for name, val := range secretVals {
|
||||||
table.Append([]string{name, val})
|
row := []string{name, val}
|
||||||
|
rows = append(rows, row)
|
||||||
|
table.Row(row...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
table.JSONRender()
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
} else {
|
if err != nil {
|
||||||
table.Render()
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Println(out)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
log.Warn("generated secrets are not shown again, please take note of them NOW")
|
|
||||||
|
fmt.Println(table)
|
||||||
|
|
||||||
|
log.Warnf(
|
||||||
|
"generated secrets %s shown again, please take note of them %s",
|
||||||
|
formatter.BoldStyle.Render("NOT"),
|
||||||
|
formatter.BoldStyle.Render("NOW"),
|
||||||
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -345,34 +364,48 @@ var appSecretLsCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"Name", "Version", "Generated Name", "Created On Server"}
|
headers := []string{"NAME", "VERSION", "GENERATED NAME", "CREATED ON SERVER"}
|
||||||
table := formatter.CreateTable(tableCol)
|
table, err := formatter.CreateTable2()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
secStats, err := secret.PollSecretsStatus(cl, app)
|
secStats, err := secret.PollSecretsStatus(cl, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for _, secStat := range secStats {
|
for _, secStat := range secStats {
|
||||||
tableRow := []string{
|
row := []string{
|
||||||
secStat.LocalName,
|
secStat.LocalName,
|
||||||
secStat.Version,
|
secStat.Version,
|
||||||
secStat.RemoteName,
|
secStat.RemoteName,
|
||||||
strconv.FormatBool(secStat.CreatedOnRemote),
|
strconv.FormatBool(secStat.CreatedOnRemote),
|
||||||
}
|
}
|
||||||
table.Append(tableRow)
|
|
||||||
|
rows = append(rows, row)
|
||||||
|
table.Row(row...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if table.NumLines() > 0 {
|
if len(rows) > 0 {
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
table.JSONRender()
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
} else {
|
if err != nil {
|
||||||
table.Render()
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Println(out)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Warnf("no secrets stored for %s", app.Name)
|
fmt.Println(table)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Warnf("no secrets stored for %s", app.Name)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -56,9 +56,15 @@ var appServicesCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"service name", "image"}
|
table, err := formatter.CreateTable2()
|
||||||
table := formatter.CreateTable(tableCol)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"SERVICE (SHORT)", "SERVICE (LONG)", "IMAGE"}
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
var containerNames []string
|
var containerNames []string
|
||||||
for _, containerName := range container.Names {
|
for _, containerName := range container.Names {
|
||||||
@ -69,14 +75,20 @@ var appServicesCommand = cli.Command{
|
|||||||
serviceShortName := service.ContainerToServiceName(container.Names, app.StackName())
|
serviceShortName := service.ContainerToServiceName(container.Names, app.StackName())
|
||||||
serviceLongName := fmt.Sprintf("%s_%s", app.StackName(), serviceShortName)
|
serviceLongName := fmt.Sprintf("%s_%s", app.StackName(), serviceShortName)
|
||||||
|
|
||||||
tableRow := []string{
|
row := []string{
|
||||||
|
serviceShortName,
|
||||||
serviceLongName,
|
serviceLongName,
|
||||||
formatter.RemoveSha(container.Image),
|
formatter.RemoveSha(container.Image),
|
||||||
}
|
}
|
||||||
table.Append(tableRow)
|
|
||||||
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Render()
|
table.Rows(rows...)
|
||||||
|
|
||||||
|
if len(rows) > 0 {
|
||||||
|
fmt.Println(table)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -107,7 +107,7 @@ Passing "-p/--prune" does not remove those volumes.`,
|
|||||||
chaosVersion = deployMeta.ChaosVersion
|
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)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +47,8 @@ EXAMPLE:
|
|||||||
abra app upgrade foo.example.com 1.2.3+3.2.1`,
|
abra app upgrade foo.example.com 1.2.3+3.2.1`,
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
|
var warnMessages []string
|
||||||
|
|
||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(c)
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
@ -82,7 +84,7 @@ EXAMPLE:
|
|||||||
var availableUpgrades []string
|
var availableUpgrades []string
|
||||||
if deployMeta.Version == "unknown" {
|
if deployMeta.Version == "unknown" {
|
||||||
availableUpgrades = versions
|
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)
|
specificVersion := c.Args().Get(1)
|
||||||
@ -114,7 +116,7 @@ EXAMPLE:
|
|||||||
|
|
||||||
if deployMeta.Version != "unknown" && specificVersion == "" {
|
if deployMeta.Version != "unknown" && specificVersion == "" {
|
||||||
if deployMeta.IsChaos {
|
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 {
|
for _, version := range versions {
|
||||||
@ -156,7 +158,7 @@ EXAMPLE:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if internal.Force && chosenUpgrade == "" {
|
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
|
chosenUpgrade = deployMeta.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +232,9 @@ EXAMPLE:
|
|||||||
|
|
||||||
for _, envVar := range envVars {
|
for _, envVar := range envVars {
|
||||||
if !envVar.Present {
|
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
|
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)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ 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"
|
||||||
@ -37,26 +38,35 @@ var appVolumeListCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeList, err := client.GetVolumes(cl, context.Background(), app.Server, filters)
|
volumes, err := client.GetVolumes(cl, context.Background(), app.Server, filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
table := formatter.CreateTable([]string{"name", "created", "mounted"})
|
headers := []string{"name", "created", "mounted"}
|
||||||
var volTable [][]string
|
|
||||||
for _, volume := range volumeList {
|
table, err := formatter.CreateTable2()
|
||||||
volRow := []string{volume.Name, volume.CreatedAt, volume.Mountpoint}
|
if err != nil {
|
||||||
volTable = append(volTable, volRow)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.AppendBulk(volTable)
|
table.Headers(headers...)
|
||||||
|
|
||||||
if table.NumLines() > 0 {
|
var rows [][]string
|
||||||
table.Render()
|
for _, volume := range volumes {
|
||||||
} else {
|
row := []string{volume.Name, volume.CreatedAt, volume.Mountpoint}
|
||||||
log.Warnf("no volumes created for %s", app.Name)
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.Rows(rows...)
|
||||||
|
|
||||||
|
if len(rows) > 0 {
|
||||||
|
fmt.Println(table)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warnf("no volumes created for %s", app.Name)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,9 @@ func newAbraApp(version, commit string) *cli.App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Logger.SetStyles(log.Styles())
|
||||||
charmLog.SetDefault(log.Logger)
|
charmLog.SetDefault(log.Logger)
|
||||||
|
|
||||||
log.Debugf("abra version %s, commit %s", version, commit)
|
log.Debugf("abra version %s, commit %s", version, commit)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -6,17 +6,41 @@ 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"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewVersionOverview shows an upgrade or downgrade overview
|
var borderStyle = lipgloss.NewStyle().
|
||||||
func NewVersionOverview(app appPkg.App, currentVersion, chaosVersion, newVersion, releaseNotes string) error {
|
BorderStyle(lipgloss.ThickBorder()).
|
||||||
tableCol := []string{"server", "recipe", "config", "domain", "version", "chaos", "to deploy"}
|
Padding(0, 1, 0, 1).
|
||||||
table := formatter.CreateTable(tableCol)
|
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"
|
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")
|
||||||
@ -27,22 +51,36 @@ func NewVersionOverview(app appPkg.App, currentVersion, chaosVersion, newVersion
|
|||||||
server = "local"
|
server = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Append([]string{
|
body := strings.Builder{}
|
||||||
server,
|
body.WriteString(
|
||||||
app.Recipe.Name,
|
borderStyle.Render(
|
||||||
deployConfig,
|
lipgloss.JoinVertical(
|
||||||
app.Domain,
|
lipgloss.Center,
|
||||||
currentVersion,
|
headerStyle.Render(fmt.Sprintf("%s OVERVIEW", strings.ToUpper(kind))),
|
||||||
chaosVersion,
|
lipgloss.JoinVertical(
|
||||||
newVersion,
|
lipgloss.Left,
|
||||||
})
|
horizontal(leftStyle.Render("SERVER"), " ", rightStyle.Render(server)),
|
||||||
table.Render()
|
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 != "" {
|
if releaseNotes != "" && newVersion != "" {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Print(releaseNotes)
|
fmt.Print(releaseNotes)
|
||||||
} else {
|
} 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 {
|
if NoInput {
|
||||||
@ -50,16 +88,66 @@ func NewVersionOverview(app appPkg.App, currentVersion, chaosVersion, newVersion
|
|||||||
}
|
}
|
||||||
|
|
||||||
response := false
|
response := false
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Confirm{Message: "proceed?"}
|
||||||
Message: "continue with deployment?",
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, &response); err != nil {
|
if err := survey.AskOne(prompt, &response); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !response {
|
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
|
return nil
|
||||||
@ -118,48 +206,3 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
@ -32,11 +32,25 @@ var recipeLintCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"ref", "rule", "severity", "satisfied", "skipped", "resolve"}
|
headers := []string{
|
||||||
table := formatter.CreateTable(tableCol)
|
"ref",
|
||||||
|
"rule",
|
||||||
|
"severity",
|
||||||
|
"satisfied",
|
||||||
|
"skipped",
|
||||||
|
"resolve",
|
||||||
|
}
|
||||||
|
|
||||||
|
table, err := formatter.CreateTable2()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
hasError := false
|
hasError := false
|
||||||
bar := formatter.CreateProgressbar(-1, "running recipe lint rules...")
|
var rows [][]string
|
||||||
|
var warnMessages []string
|
||||||
for level := range lint.LintRules {
|
for level := range lint.LintRules {
|
||||||
for _, rule := range lint.LintRules[level] {
|
for _, rule := range lint.LintRules[level] {
|
||||||
if internal.OnlyErrors && rule.Level != "error" {
|
if internal.OnlyErrors && rule.Level != "error" {
|
||||||
@ -58,7 +72,7 @@ var recipeLintCommand = cli.Command{
|
|||||||
if !skipped {
|
if !skipped {
|
||||||
ok, err := rule.Function(recipe)
|
ok, err := rule.Function(recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
warnMessages = append(warnMessages, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok && rule.Level == "error" {
|
if !ok && rule.Level == "error" {
|
||||||
@ -78,26 +92,30 @@ var recipeLintCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Append([]string{
|
row := []string{
|
||||||
rule.Ref,
|
rule.Ref,
|
||||||
rule.Description,
|
rule.Description,
|
||||||
rule.Level,
|
rule.Level,
|
||||||
satisfiedOutput,
|
satisfiedOutput,
|
||||||
skippedOutput,
|
skippedOutput,
|
||||||
rule.HowToResolve,
|
rule.HowToResolve,
|
||||||
})
|
}
|
||||||
|
|
||||||
bar.Add(1)
|
rows = append(rows, row)
|
||||||
|
table.Row(row...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if table.NumLines() > 0 {
|
if len(rows) > 0 {
|
||||||
fmt.Println()
|
fmt.Println(table)
|
||||||
table.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasError {
|
for _, warnMsg := range warnMessages {
|
||||||
log.Warn("watch out, some critical errors are present in your recipe config")
|
log.Warn(warnMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasError {
|
||||||
|
log.Warnf("critical errors present in %s config", recipe.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -41,12 +41,27 @@ var recipeListCommand = cli.Command{
|
|||||||
recipes := catl.Flatten()
|
recipes := catl.Flatten()
|
||||||
sort.Sort(recipe.ByRecipeName(recipes))
|
sort.Sort(recipe.ByRecipeName(recipes))
|
||||||
|
|
||||||
tableCol := []string{"name", "category", "status", "healthcheck", "backups", "email", "tests", "SSO"}
|
table, err := formatter.CreateTable2()
|
||||||
table := formatter.CreateTable(tableCol)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
len := 0
|
headers := []string{
|
||||||
|
"name",
|
||||||
|
"category",
|
||||||
|
"status",
|
||||||
|
"healthcheck",
|
||||||
|
"backups",
|
||||||
|
"email",
|
||||||
|
"tests",
|
||||||
|
"SSO",
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for _, recipe := range recipes {
|
for _, recipe := range recipes {
|
||||||
tableRow := []string{
|
row := []string{
|
||||||
recipe.Name,
|
recipe.Name,
|
||||||
recipe.Category,
|
recipe.Category,
|
||||||
strconv.Itoa(recipe.Features.Status),
|
strconv.Itoa(recipe.Features.Status),
|
||||||
@ -59,23 +74,27 @@ var recipeListCommand = cli.Command{
|
|||||||
|
|
||||||
if pattern != "" {
|
if pattern != "" {
|
||||||
if strings.Contains(recipe.Name, pattern) {
|
if strings.Contains(recipe.Name, pattern) {
|
||||||
table.Append(tableRow)
|
table.Row(row...)
|
||||||
len++
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
table.Append(tableRow)
|
table.Row(row...)
|
||||||
len++
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if table.NumLines() > 0 {
|
if len(rows) > 0 {
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
table.SetCaption(false, "")
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
table.JSONRender()
|
if err != nil {
|
||||||
} else {
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
table.SetCaption(true, fmt.Sprintf("total recipes: %v", len))
|
}
|
||||||
table.Render()
|
fmt.Println(out)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(table)
|
||||||
|
log.Infof("total recipes: %v", len(rows))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,6 +36,8 @@ var recipeVersionCommand = cli.Command{
|
|||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
BashComplete: autocomplete.RecipeNameComplete,
|
BashComplete: autocomplete.RecipeNameComplete,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
|
var warnMessages []string
|
||||||
|
|
||||||
recipe := internal.ValidateRecipe(c)
|
recipe := internal.ValidateRecipe(c)
|
||||||
|
|
||||||
catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline)
|
catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline)
|
||||||
@ -46,47 +47,63 @@ var recipeVersionCommand = cli.Command{
|
|||||||
|
|
||||||
recipeMeta, ok := catl[recipe.Name]
|
recipeMeta, ok := catl[recipe.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warn("no published versions in catalogue, trying local recipe repository")
|
warnMessages = append(warnMessages, "retrieved versions from local recipe repository")
|
||||||
|
|
||||||
recipeVersions, err := recipe.GetRecipeVersions()
|
recipeVersions, err := recipe.GetRecipeVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
warnMessages = append(warnMessages, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
recipeMeta = recipePkg.RecipeMeta{Versions: recipeVersions}
|
recipeMeta = recipePkg.RecipeMeta{Versions: recipeVersions}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(recipeMeta.Versions) == 0 {
|
if len(recipeMeta.Versions) == 0 {
|
||||||
log.Fatalf("%s has no catalogue published versions?", recipe.Name)
|
log.Fatalf("%s has no published versions?", recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCols := []string{"version", "service", "image", "tag"}
|
var allRows [][]string
|
||||||
aggregated_table := formatter.CreateTable(tableCols)
|
|
||||||
for i := len(recipeMeta.Versions) - 1; i >= 0; i-- {
|
for i := len(recipeMeta.Versions) - 1; i >= 0; i-- {
|
||||||
table := formatter.CreateTable(tableCols)
|
table, err := formatter.CreateTable2()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers("SERVICE", "NAME", "TAG")
|
||||||
|
|
||||||
for version, meta := range recipeMeta.Versions[i] {
|
for version, meta := range recipeMeta.Versions[i] {
|
||||||
var versions [][]string
|
var rows [][]string
|
||||||
|
|
||||||
for service, serviceMeta := range meta {
|
for service, serviceMeta := range meta {
|
||||||
versions = append(versions, []string{version, service, serviceMeta.Image, serviceMeta.Tag})
|
rows = append(rows, []string{service, serviceMeta.Image, serviceMeta.Tag})
|
||||||
|
allRows = append(allRows, []string{version, service, serviceMeta.Image, serviceMeta.Tag})
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(versions, sortServiceByName(versions))
|
sort.Slice(rows, sortServiceByName(rows))
|
||||||
|
|
||||||
for _, version := range versions {
|
table.Rows(rows...)
|
||||||
table.Append(version)
|
|
||||||
aggregated_table.Append(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !internal.MachineReadable {
|
if !internal.MachineReadable {
|
||||||
table.SetAutoMergeCellsByColumnIndex([]int{0})
|
fmt.Println(table)
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
log.Infof("VERSION: %s", version)
|
||||||
table.Render()
|
|
||||||
fmt.Println()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !internal.MachineReadable {
|
||||||
|
for _, warnMsg := range warnMessages {
|
||||||
|
log.Warn(warnMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
aggregated_table.JSONRender()
|
sort.Slice(allRows, sortServiceByName(allRows))
|
||||||
|
headers := []string{"VERSION", "SERVICE", "NAME", "TAG"}
|
||||||
|
out, err := formatter.ToJSON(headers, allRows)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Println(out)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
@ -29,14 +30,20 @@ var serverListCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableColumns := []string{"name", "host"}
|
table, err := formatter.CreateTable2()
|
||||||
table := formatter.CreateTable(tableColumns)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"NAME", "HOST"}
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
serverNames, err := config.ReadServerNames()
|
serverNames, err := config.ReadServerNames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for _, serverName := range serverNames {
|
for _, serverName := range serverNames {
|
||||||
var row []string
|
var row []string
|
||||||
for _, ctx := range contexts {
|
for _, ctx := range contexts {
|
||||||
@ -57,6 +64,7 @@ var serverListCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
row = []string{serverName, sp.Host}
|
row = []string{serverName, sp.Host}
|
||||||
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,17 +74,22 @@ var serverListCommand = cli.Command{
|
|||||||
} else {
|
} else {
|
||||||
row = []string{serverName, "unknown"}
|
row = []string{serverName, "unknown"}
|
||||||
}
|
}
|
||||||
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Append(row)
|
table.Row(row...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
table.JSONRender()
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Println(out)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Render()
|
fmt.Println(table)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -487,8 +487,11 @@ func newAbraApp(version, commit string) *cli.App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.Before = func(c *cli.Context) error {
|
app.Before = func(c *cli.Context) error {
|
||||||
|
log.Logger.SetStyles(log.Styles())
|
||||||
charmLog.SetDefault(log.Logger)
|
charmLog.SetDefault(log.Logger)
|
||||||
|
|
||||||
log.Debugf("kadabra version %s, commit %s", version, commit)
|
log.Debugf("kadabra version %s, commit %s", version, commit)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
go.mod
7
go.mod
@ -6,6 +6,7 @@ require (
|
|||||||
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb
|
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb
|
||||||
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355
|
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
|
github.com/charmbracelet/lipgloss v0.11.1
|
||||||
github.com/charmbracelet/log v0.4.0
|
github.com/charmbracelet/log v0.4.0
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
github.com/docker/cli v27.0.3+incompatible
|
github.com/docker/cli v27.0.3+incompatible
|
||||||
@ -15,9 +16,9 @@ require (
|
|||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/moby/sys/signal v0.7.0
|
github.com/moby/sys/signal v0.7.0
|
||||||
github.com/moby/term v0.5.0
|
github.com/moby/term v0.5.0
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/schollz/progressbar/v3 v3.14.4
|
github.com/schollz/progressbar/v3 v3.14.4
|
||||||
|
golang.org/x/term v0.22.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gotest.tools/v3 v3.5.1
|
gotest.tools/v3 v3.5.1
|
||||||
)
|
)
|
||||||
@ -32,8 +33,7 @@ require (
|
|||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/charmbracelet/lipgloss v0.11.0 // indirect
|
github.com/charmbracelet/x/ansi v0.1.3 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.1.2 // indirect
|
|
||||||
github.com/cloudflare/circl v1.3.9 // indirect
|
github.com/cloudflare/circl v1.3.9 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
@ -105,7 +105,6 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/term v0.22.0 // indirect
|
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
|
11
go.sum
11
go.sum
@ -135,12 +135,12 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
|
|||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
|
github.com/charmbracelet/lipgloss v0.11.1 h1:a8KgVPHa7kOoP95vm2tQQrjD2AKhbWmfr4uJ2RW6kNk=
|
||||||
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
|
github.com/charmbracelet/lipgloss v0.11.1/go.mod h1:beLlcmkF7MWA+5UrKKIRo/VJ21xGXr7YJ9miWfdMRIU=
|
||||||
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
||||||
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||||
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
|
github.com/charmbracelet/x/ansi v0.1.3 h1:RBh/eleNWML5R524mjUF0yVRePTwqN9tPtV+DPgO5Lw=
|
||||||
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
github.com/charmbracelet/x/ansi v0.1.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||||
@ -621,7 +621,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
|||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
@ -687,8 +686,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
|
|||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
|
||||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/charmbracelet/lipgloss/table"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
// "github.com/olekukonko/tablewriter"
|
"golang.org/x/term"
|
||||||
"coopcloud.tech/abra/pkg/jsontable"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/schollz/progressbar/v3"
|
"github.com/schollz/progressbar/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var BoldStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Underline(true)
|
||||||
|
|
||||||
func ShortenID(str string) string {
|
func ShortenID(str string) string {
|
||||||
return str[:12]
|
return str[:12]
|
||||||
}
|
}
|
||||||
@ -33,12 +40,53 @@ func HumanDuration(timestamp int64) string {
|
|||||||
return units.HumanDuration(now.Sub(date)) + " ago"
|
return units.HumanDuration(now.Sub(date)) + " ago"
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTable prepares a table layout for output.
|
// CreateTable2 prepares a table layout for output.
|
||||||
func CreateTable(columns []string) *jsontable.JSONTable {
|
func CreateTable2() (*table.Table, error) {
|
||||||
table := jsontable.NewJSONTable(os.Stdout)
|
width, _, err := term.GetSize(0)
|
||||||
table.SetAutoWrapText(false)
|
if err != nil {
|
||||||
table.SetHeader(columns)
|
return nil, err
|
||||||
return table
|
}
|
||||||
|
|
||||||
|
if width-10 < 79 {
|
||||||
|
width = 79
|
||||||
|
}
|
||||||
|
|
||||||
|
return table.New().
|
||||||
|
Width(width - 10).
|
||||||
|
Border(lipgloss.ThickBorder()).
|
||||||
|
BorderStyle(
|
||||||
|
lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("63")),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJSON converts a lipgloss.Table to JSON representation. It's not a robust
|
||||||
|
// implementation and mainly caters for our current use case which is basically
|
||||||
|
// a bunch of strings. See https://github.com/charmbracelet/lipgloss/issues/335
|
||||||
|
// for the real thing (hopefully).
|
||||||
|
func ToJSON(headers []string, rows [][]string) (string, error) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
|
||||||
|
buff.Write([]byte("["))
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
payload := make(map[string]string)
|
||||||
|
|
||||||
|
for idx, header := range headers {
|
||||||
|
payload[strings.ToLower(header)] = row[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buff.Write(serialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
buff.Write([]byte("]"))
|
||||||
|
|
||||||
|
return buff.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateProgressbar generates a progress bar
|
// CreateProgressbar generates a progress bar
|
||||||
|
@ -1,211 +0,0 @@
|
|||||||
package jsontable
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A quick-and-dirty proxy/emulator of tablewriter to enable more easy machine readable output
|
|
||||||
// - Does not strictly support types, just quoted or unquoted values
|
|
||||||
// - Does not support nested values.
|
|
||||||
// If a datalabel is set with SetDataLabel(true, "..."), that will be used as the key for teh data of the table,
|
|
||||||
// otherwise if the caption is set with SetCaption(true, "..."), the data label will be set to the default of
|
|
||||||
// "rows", otherwise the table will output as a JSON list.
|
|
||||||
//
|
|
||||||
// Proxys all actions through to the tablewriter except addrow and addbatch, which it does at render time
|
|
||||||
//
|
|
||||||
|
|
||||||
type JSONTable struct {
|
|
||||||
out io.Writer
|
|
||||||
colsize int
|
|
||||||
rows [][]string
|
|
||||||
keys []string
|
|
||||||
quoted []bool // hack to do output typing, quoted vs. unquoted
|
|
||||||
hasDataLabel bool
|
|
||||||
dataLabel string
|
|
||||||
hasCaption bool
|
|
||||||
caption string // the actual caption
|
|
||||||
hasCaptionLabel bool
|
|
||||||
captionLabel string // the key in the dictionary for the caption
|
|
||||||
tbl *tablewriter.Table
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeChar(w io.Writer, c byte) {
|
|
||||||
w.Write([]byte{c})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJSONTable(writer io.Writer) *JSONTable {
|
|
||||||
t := &JSONTable{
|
|
||||||
out: writer,
|
|
||||||
colsize: 0,
|
|
||||||
rows: [][]string{},
|
|
||||||
keys: []string{},
|
|
||||||
quoted: []bool{},
|
|
||||||
hasDataLabel: false,
|
|
||||||
dataLabel: "rows",
|
|
||||||
hasCaption: false,
|
|
||||||
caption: "",
|
|
||||||
hasCaptionLabel: false,
|
|
||||||
captionLabel: "caption",
|
|
||||||
tbl: tablewriter.NewWriter(writer),
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) NumLines() int {
|
|
||||||
// JSON only but reflects a shared state.
|
|
||||||
return len(t.rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetHeader(keys []string) {
|
|
||||||
// Set the keys value which will assign each column to the keys.
|
|
||||||
// Note that we'll ignore values that are beyond the length of the keys list
|
|
||||||
t.colsize = len(keys)
|
|
||||||
t.keys = []string{}
|
|
||||||
for _, k := range keys {
|
|
||||||
t.keys = append(t.keys, k)
|
|
||||||
t.quoted = append(t.quoted, true)
|
|
||||||
}
|
|
||||||
t.tbl.SetHeader(keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetColumnQuoting(quoting []bool) {
|
|
||||||
// Specify which columns are quoted or unquoted in output
|
|
||||||
// JSON only
|
|
||||||
for i := 0; i < t.colsize; i++ {
|
|
||||||
t.quoted[i] = quoting[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) Append(row []string) {
|
|
||||||
// We'll just append whatever to the rows list. If they fix the keys after appending rows, it'll work as
|
|
||||||
// expected.
|
|
||||||
// We should detect if the row is narrower than the key list tho.
|
|
||||||
// JSON only (but we use the rows later when rendering a regular table)
|
|
||||||
t.rows = append(t.rows, row)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) Render() {
|
|
||||||
// Load the table with rows and render.
|
|
||||||
// Proxy only
|
|
||||||
for _, row := range t.rows {
|
|
||||||
t.tbl.Append(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.tbl.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) _JSONRenderInner() {
|
|
||||||
// JSON only
|
|
||||||
// Render the list of dictionaries to the writer.
|
|
||||||
//// inner render loop
|
|
||||||
writeChar(t.out, '[')
|
|
||||||
for rowidx, row := range t.rows {
|
|
||||||
if rowidx != 0 {
|
|
||||||
writeChar(t.out, ',')
|
|
||||||
}
|
|
||||||
writeChar(t.out, '{')
|
|
||||||
for keyidx, key := range t.keys {
|
|
||||||
key := strings.ToLower(key)
|
|
||||||
key = strings.ReplaceAll(key, " ", "-")
|
|
||||||
|
|
||||||
value := "nil"
|
|
||||||
if keyidx < len(row) {
|
|
||||||
value = row[keyidx]
|
|
||||||
}
|
|
||||||
if keyidx != 0 {
|
|
||||||
writeChar(t.out, ',')
|
|
||||||
}
|
|
||||||
if t.quoted[keyidx] {
|
|
||||||
fmt.Fprintf(t.out, "\"%s\":\"%s\"", key, value)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(t.out, "\"%s\":%s", key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeChar(t.out, '}')
|
|
||||||
}
|
|
||||||
writeChar(t.out, ']')
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) JSONRender() {
|
|
||||||
// write JSON table to output
|
|
||||||
// JSON only
|
|
||||||
|
|
||||||
if t.hasDataLabel || t.hasCaption {
|
|
||||||
// dict mode
|
|
||||||
writeChar(t.out, '{')
|
|
||||||
|
|
||||||
if t.hasCaption {
|
|
||||||
fmt.Fprintf(t.out, "\"%s\":\"%s\",", t.captionLabel, t.caption)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(t.out, "\"%s\":", t.dataLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write list
|
|
||||||
t._JSONRenderInner()
|
|
||||||
|
|
||||||
if t.hasDataLabel || t.hasCaption {
|
|
||||||
// dict mode
|
|
||||||
writeChar(t.out, '}')
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetCaption(caption bool, captionText ...string) {
|
|
||||||
t.hasCaption = caption
|
|
||||||
if len(captionText) == 1 {
|
|
||||||
t.caption = captionText[0]
|
|
||||||
}
|
|
||||||
t.tbl.SetCaption(caption, captionText...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetCaptionLabel(captionLabel bool, captionLabelText ...string) {
|
|
||||||
// JSON only
|
|
||||||
t.hasCaptionLabel = captionLabel
|
|
||||||
if len(captionLabelText) == 1 {
|
|
||||||
t.captionLabel = captionLabelText[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetDataLabel(dataLabel bool, dataLabelText ...string) {
|
|
||||||
// JSON only
|
|
||||||
t.hasDataLabel = dataLabel
|
|
||||||
if len(dataLabelText) == 1 {
|
|
||||||
t.dataLabel = dataLabelText[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) AppendBulk(rows [][]string) {
|
|
||||||
// JSON only but reflects shared state
|
|
||||||
for _, row := range rows {
|
|
||||||
t.Append(row)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stuff we should implement but we just proxy for now.
|
|
||||||
func (t *JSONTable) SetAutoMergeCellsByColumnIndex(cols []int) {
|
|
||||||
// FIXME
|
|
||||||
t.tbl.SetAutoMergeCellsByColumnIndex(cols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stuff we should implement but we just proxy for now.
|
|
||||||
func (t *JSONTable) SetAlignment(align int) {
|
|
||||||
// FIXME
|
|
||||||
t.tbl.SetAlignment(align)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetAutoMergeCells(auto bool) {
|
|
||||||
// FIXME
|
|
||||||
t.tbl.SetAutoMergeCells(auto)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub functions
|
|
||||||
func (t *JSONTable) SetAutoWrapText(auto bool) {
|
|
||||||
t.tbl.SetAutoWrapText(auto)
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package jsontable
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TestLine = []string{"1", "2"}
|
|
||||||
var TestGroup = [][]string{{"1", "2", "3"}, {"a", "teohunteohu", "c", "d"}, {"☺", "☹"}}
|
|
||||||
var TestKeys = []string{"key0", "key1", "key2"}
|
|
||||||
|
|
||||||
// test creation
|
|
||||||
func TestNewTable(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
tbl := NewJSONTable(&b)
|
|
||||||
if tbl.NumLines() != 0 {
|
|
||||||
t.Fatalf("Something went weird when making table (should have 0 lines)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// test adding things
|
|
||||||
func TestTableAdd(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
tbl := NewJSONTable(&b)
|
|
||||||
|
|
||||||
tbl.Append(TestLine)
|
|
||||||
if tbl.NumLines() != 1 {
|
|
||||||
t.Fatalf("Appending a line does not result in a length of 1.")
|
|
||||||
}
|
|
||||||
|
|
||||||
tbl.AppendBulk(TestGroup)
|
|
||||||
numlines := tbl.NumLines()
|
|
||||||
if numlines != (len(TestGroup) + 1) {
|
|
||||||
t.Fatalf("Appending two lines does not result in a length of 4 (length is %d).", numlines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// test JSON output is parsable
|
|
||||||
func TestJsonParsable(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
tbl := NewJSONTable(&b)
|
|
||||||
|
|
||||||
tbl.AppendBulk(TestGroup)
|
|
||||||
tbl.SetHeader(TestKeys)
|
|
||||||
|
|
||||||
tbl.JSONRender()
|
|
||||||
|
|
||||||
var son []map[string]interface{}
|
|
||||||
|
|
||||||
err := json.Unmarshal(b.Bytes(), &son)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Did not produce parsable JSON: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// test identical commands to a tablewriter and jsontable produce the same rendered output
|
|
||||||
func TestTableWriter(t *testing.T) {
|
|
||||||
var bjson bytes.Buffer
|
|
||||||
var btable bytes.Buffer
|
|
||||||
|
|
||||||
tbl := NewJSONTable(&bjson)
|
|
||||||
|
|
||||||
tbl.AppendBulk(TestGroup)
|
|
||||||
tbl.SetHeader(TestKeys)
|
|
||||||
tbl.Render()
|
|
||||||
|
|
||||||
wtbl := tablewriter.NewWriter(&btable)
|
|
||||||
|
|
||||||
wtbl.AppendBulk(TestGroup)
|
|
||||||
wtbl.SetHeader(TestKeys)
|
|
||||||
wtbl.Render()
|
|
||||||
|
|
||||||
if bytes.Compare(bjson.Bytes(), btable.Bytes()) != 0 {
|
|
||||||
t.Fatalf("JSON table and TableWriter produce non-identical outputs.\n%s\n%s", bjson.Bytes(), btable.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME test different output formats when captions etc. are added
|
|
@ -3,7 +3,9 @@ package log
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
charmLog "github.com/charmbracelet/log"
|
charmLog "github.com/charmbracelet/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,3 +34,42 @@ var SetLevel = Logger.SetLevel
|
|||||||
var DebugLevel = charmLog.DebugLevel
|
var DebugLevel = charmLog.DebugLevel
|
||||||
var SetOutput = charmLog.SetOutput
|
var SetOutput = charmLog.SetOutput
|
||||||
var SetReportCaller = charmLog.SetReportCaller
|
var SetReportCaller = charmLog.SetReportCaller
|
||||||
|
|
||||||
|
func Styles() *charmLog.Styles {
|
||||||
|
styles := charmLog.DefaultStyles()
|
||||||
|
|
||||||
|
styles.Levels = map[charmLog.Level]lipgloss.Style{
|
||||||
|
charmLog.DebugLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(DebugLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("63")).
|
||||||
|
Foreground(lipgloss.Color("15")),
|
||||||
|
charmLog.InfoLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.InfoLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("86")).
|
||||||
|
Foreground(lipgloss.Color("16")),
|
||||||
|
charmLog.WarnLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.WarnLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("192")).
|
||||||
|
Foreground(lipgloss.Color("16")),
|
||||||
|
charmLog.ErrorLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.ErrorLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("204")).
|
||||||
|
Foreground(lipgloss.Color("15")),
|
||||||
|
charmLog.FatalLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.FatalLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("134")).
|
||||||
|
Foreground(lipgloss.Color("15")),
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles
|
||||||
|
}
|
||||||
|
@ -181,7 +181,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
|
|||||||
|
|
||||||
if err := client.StoreSecret(cl, secret.RemoteName, passwords[0], server); err != nil {
|
if err := client.StoreSecret(cl, secret.RemoteName, passwords[0], server); err != nil {
|
||||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||||
log.Warnf("%s already exists, moving on...", secret.RemoteName)
|
log.Warnf("%s already exists", secret.RemoteName)
|
||||||
ch <- nil
|
ch <- nil
|
||||||
} else {
|
} else {
|
||||||
ch <- err
|
ch <- err
|
||||||
@ -201,7 +201,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
|
|||||||
|
|
||||||
if err := client.StoreSecret(cl, secret.RemoteName, passphrases[0], server); err != nil {
|
if err := client.StoreSecret(cl, secret.RemoteName, passphrases[0], server); err != nil {
|
||||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||||
log.Warnf("%s already exists, moving on...", secret.RemoteName)
|
log.Warnf("%s already exists", secret.RemoteName)
|
||||||
ch <- nil
|
ch <- nil
|
||||||
} else {
|
} else {
|
||||||
ch <- err
|
ch <- err
|
||||||
|
Loading…
x
Reference in New Issue
Block a user