abra/cli/app/list.go
cellarspoon 4c186678b8
fix: clone https url by default
Catalogue package had to be merged into the recipe package due to too
many circular import errors. Also, use https url for cloning, assume
folks don't have ssh setup by default (the whole reason for the
refactor).
2021-12-27 16:45:56 +01:00

250 lines
5.9 KiB
Go

package app
import (
"fmt"
"sort"
"strings"
abraFormatter "coopcloud.tech/abra/cli/formatter"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/ssh"
"coopcloud.tech/tagcmp"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
var status bool
var statusFlag = &cli.BoolFlag{
Name: "status",
Aliases: []string{"S"},
Value: false,
Usage: "Show app deployment status",
Destination: &status,
}
var appType string
var typeFlag = &cli.StringFlag{
Name: "type",
Aliases: []string{"t"},
Value: "",
Usage: "Show apps of a specific type",
Destination: &appType,
}
var listAppServer string
var listAppServerFlag = &cli.StringFlag{
Name: "server",
Aliases: []string{"s"},
Value: "",
Usage: "Show apps of a specific server",
Destination: &listAppServer,
}
type appStatus struct {
server string
recipe string
appName string
domain string
status string
version string
upgrade string
}
type serverStatus struct {
apps []appStatus
appCount int
versionCount int
unversionedCount int
latestCount int
upgradeCount int
}
var appListCommand = &cli.Command{
Name: "list",
Usage: "List all managed apps",
Description: `
This command looks at your local file system listing of apps and servers (e.g.
in ~/.abra/) to generate a report of all your apps.
By passing the "--status/-S" flag, you can query all your servers for the
actual live deployment status. Depending on how many servers you manage, this
can take some time.
`,
Aliases: []string{"ls"},
Flags: []cli.Flag{
statusFlag,
listAppServerFlag,
typeFlag,
},
Action: func(c *cli.Context) error {
appFiles, err := config.LoadAppFiles(listAppServer)
if err != nil {
logrus.Fatal(err)
}
apps, err := config.GetApps(appFiles)
if err != nil {
logrus.Fatal(err)
}
sort.Sort(config.ByServerAndType(apps))
statuses := make(map[string]map[string]string)
var catl recipe.RecipeCatalogue
if status {
alreadySeen := make(map[string]bool)
for _, app := range apps {
if _, ok := alreadySeen[app.Server]; !ok {
if err := ssh.EnsureHostKey(app.Server); err != nil {
logrus.Fatal(fmt.Sprintf(internal.SSHFailMsg, app.Server))
}
alreadySeen[app.Server] = true
}
}
statuses, err = config.GetAppStatuses(appFiles)
if err != nil {
logrus.Fatal(err)
}
var err error
catl, err = recipe.ReadRecipeCatalogue()
if err != nil {
logrus.Fatal(err)
}
}
var totalServersCount int
var totalAppsCount int
allStats := make(map[string]serverStatus)
for _, app := range apps {
var stats serverStatus
var ok bool
if stats, ok = allStats[app.Server]; !ok {
stats = serverStatus{}
totalServersCount++
}
if app.Type == appType || appType == "" {
appStats := appStatus{}
stats.appCount++
totalAppsCount++
if status {
stackName := app.StackName()
status := "unknown"
version := "unknown"
if statusMeta, ok := statuses[stackName]; ok {
if currentVersion, exists := statusMeta["version"]; exists {
version = currentVersion
}
if statusMeta["status"] != "" {
status = statusMeta["status"]
}
stats.versionCount++
} else {
stats.unversionedCount++
}
appStats.status = status
appStats.version = version
var newUpdates []string
if version != "unknown" {
updates, err := recipe.GetRecipeCatalogueVersions(app.Type, catl)
if err != nil {
logrus.Fatal(err)
}
parsedVersion, err := tagcmp.Parse(version)
if err != nil {
logrus.Fatal(err)
}
for _, update := range updates {
parsedUpdate, err := tagcmp.Parse(update)
if err != nil {
logrus.Fatal(err)
}
if update != version && parsedUpdate.IsGreaterThan(parsedVersion) {
newUpdates = append(newUpdates, update)
}
}
}
if len(newUpdates) == 0 {
if version == "unknown" {
appStats.upgrade = "unknown"
} else {
appStats.upgrade = "latest"
stats.latestCount++
}
} else {
// FIXME: jeezus golang why do you not have a list reverse function
for i, j := 0, len(newUpdates)-1; i < j; i, j = i+1, j-1 {
newUpdates[i], newUpdates[j] = newUpdates[j], newUpdates[i]
}
appStats.upgrade = strings.Join(newUpdates, "\n")
stats.upgradeCount++
}
}
appStats.server = app.Server
appStats.recipe = app.Type
appStats.appName = app.StackName()
appStats.domain = app.Domain
stats.apps = append(stats.apps, appStats)
}
allStats[app.Server] = stats
}
for serverName, serverStat := range allStats {
tableCol := []string{"recipe", "app name", "domain"}
if status {
tableCol = append(tableCol, []string{"status", "version", "upgrade"}...)
}
table := abraFormatter.CreateTable(tableCol)
for _, appStat := range serverStat.apps {
tableRow := []string{appStat.recipe, appStat.appName, appStat.domain}
if status {
tableRow = append(tableRow, []string{appStat.status, appStat.version, appStat.upgrade}...)
}
table.Append(tableRow)
}
table.Render()
if status {
fmt.Println(fmt.Sprintf(
"server: %s | total apps: %v | versioned: %v | unversioned: %v | latest: %v | upgrade: %v",
serverName,
serverStat.appCount,
serverStat.versionCount,
serverStat.unversionedCount,
serverStat.latestCount,
serverStat.upgradeCount,
))
} else {
fmt.Println(fmt.Sprintf("server: %s | total apps: %v", serverName, serverStat.appCount))
}
if len(allStats) > 1 {
fmt.Println() // newline separator for multiple servers
}
}
if len(allStats) > 1 {
fmt.Println(fmt.Sprintf("total servers: %v | total apps: %v ", totalServersCount, totalAppsCount))
}
return nil
},
}