package app import ( "encoding/json" "fmt" "sort" "strconv" "strings" "coopcloud.tech/abra/cli/internal" appPkg "coopcloud.tech/abra/pkg/app" "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/log" "coopcloud.tech/tagcmp" "github.com/urfave/cli" ) var ( status bool statusFlag = &cli.BoolFlag{ Name: "status, S", Usage: "Show app deployment status", Destination: &status, } ) var ( recipeFilter string recipeFlag = &cli.StringFlag{ Name: "recipe, r", Value: "", Usage: "Show apps of a specific recipe", Destination: &recipeFilter, } ) var ( listAppServer string listAppServerFlag = &cli.StringFlag{ Name: "server, s", Value: "", Usage: "Show apps of a specific server", Destination: &listAppServer, } ) type appStatus struct { Server string `json:"server"` Recipe string `json:"recipe"` AppName string `json:"appName"` Domain string `json:"domain"` Status string `json:"status"` Chaos string `json:"chaos"` ChaosVersion string `json:"chaosVersion"` AutoUpdate string `json:"autoUpdate"` Version string `json:"version"` Upgrade string `json:"upgrade"` } type serverStatus struct { Apps []appStatus `json:"apps"` AppCount int `json:"appCount"` VersionCount int `json:"versionCount"` UnversionedCount int `json:"unversionedCount"` LatestCount int `json:"latestCount"` UpgradeCount int `json:"upgradeCount"` } var appListCommand = cli.Command{ Name: "list", Aliases: []string{"ls"}, Usage: "List all managed apps", Description: ` Read the local file system listing of apps and servers (e.g. ~/.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.`, Flags: []cli.Flag{ internal.DebugFlag, internal.MachineReadableFlag, statusFlag, listAppServerFlag, recipeFlag, internal.OfflineFlag, }, Before: internal.SubCommandBefore, Action: func(c *cli.Context) error { appFiles, err := appPkg.LoadAppFiles(listAppServer) if err != nil { log.Fatal(err) } apps, err := appPkg.GetApps(appFiles, recipeFilter) if err != nil { log.Fatal(err) } sort.Sort(appPkg.ByServerAndRecipe(apps)) statuses := make(map[string]map[string]string) if status { alreadySeen := make(map[string]bool) for _, app := range apps { if _, ok := alreadySeen[app.Server]; !ok { alreadySeen[app.Server] = true } } statuses, err = appPkg.GetAppStatuses(apps, internal.MachineReadable) if err != nil { log.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{} if recipeFilter == "" { // count server, no filtering totalServersCount++ } } if app.Recipe.Name == recipeFilter || recipeFilter == "" { if recipeFilter != "" { // only count server if matches filter totalServersCount++ } appStats := appStatus{} stats.AppCount++ totalAppsCount++ if status { status := "unknown" version := "unknown" chaos := "unknown" chaosVersion := "unknown" autoUpdate := "unknown" if statusMeta, ok := statuses[app.StackName()]; ok { if currentVersion, exists := statusMeta["version"]; exists { if currentVersion != "" { version = currentVersion } } if chaosDeploy, exists := statusMeta["chaos"]; exists { chaos = chaosDeploy } if chaosDeployVersion, exists := statusMeta["chaosVersion"]; exists { chaosVersion = chaosDeployVersion } if autoUpdateState, exists := statusMeta["autoUpdate"]; exists { autoUpdate = autoUpdateState } if statusMeta["status"] != "" { status = statusMeta["status"] } stats.VersionCount++ } else { stats.UnversionedCount++ } appStats.Status = status appStats.Chaos = chaos appStats.ChaosVersion = chaosVersion appStats.Version = version appStats.AutoUpdate = autoUpdate var newUpdates []string if version != "unknown" { updates, err := app.Recipe.Tags() if err != nil { log.Fatal(err) } parsedVersion, err := tagcmp.Parse(version) if err != nil { log.Fatal(err) } for _, update := range updates { parsedUpdate, err := tagcmp.Parse(update) if err != nil { log.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 { newUpdates = internal.ReverseStringList(newUpdates) appStats.Upgrade = strings.Join(newUpdates, "\n") stats.UpgradeCount++ } } appStats.Server = app.Server appStats.Recipe = app.Recipe.Name appStats.AppName = app.Name appStats.Domain = app.Domain stats.Apps = append(stats.Apps, appStats) } allStats[app.Server] = stats } if internal.MachineReadable { jsonstring, err := json.Marshal(allStats) if err != nil { log.Fatal(err) } else { fmt.Println(string(jsonstring)) } return nil } alreadySeen := make(map[string]bool) for _, app := range apps { if _, ok := alreadySeen[app.Server]; ok { continue } serverStat := allStats[app.Server] headers := []string{"RECIPE", "DOMAIN"} if status { headers = append(headers, []string{ "STATUS", "CHAOS", "VERSION", "UPGRADE", "AUTOUPDATE"}..., ) } table, err := formatter.CreateTable() if err != nil { log.Fatal(err) } table.Headers(headers...) var rows [][]string for _, appStat := range serverStat.Apps { row := []string{appStat.Recipe, appStat.Domain} if status { chaosStatus := appStat.Chaos if chaosStatus != "unknown" { chaosEnabled, err := strconv.ParseBool(chaosStatus) if err != nil { log.Fatal(err) } if chaosEnabled && appStat.ChaosVersion != "unknown" { chaosStatus = appStat.ChaosVersion } } row = append(row, []string{ appStat.Status, chaosStatus, appStat.Version, appStat.Upgrade, appStat.AutoUpdate}..., ) } rows = append(rows, row) } table.Rows(rows...) if len(rows) > 0 { fmt.Println(table) 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 { fmt.Println() // newline separator for multiple servers } } 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) } return nil }, }