package app import ( "encoding/json" "fmt" "sort" "strconv" "strings" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/tagcmp" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var status bool var statusFlag = &cli.BoolFlag{ Name: "status, S", Usage: "Show app deployment status", Destination: &status, } var recipeFilter string var recipeFlag = &cli.StringFlag{ Name: "recipe, r", Value: "", Usage: "Show apps of a specific recipe", Destination: &recipeFilter, } var listAppServer string var 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 := config.LoadAppFiles(listAppServer) if err != nil { logrus.Fatal(err) } apps, err := config.GetApps(appFiles, recipeFilter) if err != nil { logrus.Fatal(err) } sort.Sort(config.ByServerAndRecipe(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 { alreadySeen[app.Server] = true } } statuses, err = config.GetAppStatuses(apps, internal.MachineReadable) if err != nil { logrus.Fatal(err) } catl, err = recipe.ReadRecipeCatalogue(internal.Offline) 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{} if recipeFilter == "" { // count server, no filtering totalServersCount++ } } if app.Recipe == 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 := recipe.GetRecipeCatalogueVersions(app.Recipe, 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 { newUpdates = internal.ReverseStringList(newUpdates) appStats.Upgrade = strings.Join(newUpdates, "\n") stats.UpgradeCount++ } } appStats.Server = app.Server appStats.Recipe = app.Recipe 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 { logrus.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] tableCol := []string{"recipe", "domain"} if status { tableCol = append(tableCol, []string{"status", "chaos", "version", "upgrade", "autoupdate"}...) } table := formatter.CreateTable(tableCol) for _, appStat := range serverStat.Apps { tableRow := []string{appStat.Recipe, appStat.Domain} if status { chaosStatus := appStat.Chaos if chaosStatus != "unknown" { chaosEnabled, err := strconv.ParseBool(chaosStatus) if err != nil { logrus.Fatal(err) } if chaosEnabled && appStat.ChaosVersion != "unknown" { chaosStatus = appStat.ChaosVersion } } tableRow = append(tableRow, []string{appStat.Status, chaosStatus, appStat.Version, appStat.Upgrade, appStat.AutoUpdate}...) } table.Append(tableRow) } if table.NumLines() > 0 { table.Render() 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 { fmt.Println(fmt.Sprintf("server: %s | total apps: %v", app.Server, serverStat.AppCount)) } } if len(allStats) > 1 && table.NumLines() > 0 { fmt.Println() // newline separator for multiple servers } alreadySeen[app.Server] = true } if len(allStats) > 1 { fmt.Println(fmt.Sprintf("total servers: %v | total apps: %v ", totalServersCount, totalAppsCount)) } return nil }, }