package app import ( "fmt" "sort" "strings" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/ssh" "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 appType string var typeFlag = &cli.StringFlag{ Name: "type, t", Value: "", Usage: "Show apps of a specific type", Destination: &appType, } 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 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", Aliases: []string{"ls"}, 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. `, Flags: []cli.Flag{ internal.DebugFlag, internal.NoInputFlag, statusFlag, listAppServerFlag, typeFlag, }, 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) 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{} if appType == "" { // count server, no filtering totalServersCount++ } } if app.Type == appType || appType == "" { if appType != "" { // only count server if matches filter totalServersCount++ } appStats := appStatus{} stats.appCount++ totalAppsCount++ if status { status := "unknown" version := "unknown" if statusMeta, ok := statuses[app.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 { newUpdates = internal.ReverseStringList(newUpdates) appStats.upgrade = strings.Join(newUpdates, "\n") stats.upgradeCount++ } } appStats.server = app.Server appStats.recipe = app.Type appStats.appName = app.Name appStats.domain = app.Domain stats.apps = append(stats.apps, appStats) } allStats[app.Server] = stats } alreadySeen := make(map[string]bool) for _, app := range apps { if _, ok := alreadySeen[app.Server]; ok { continue } serverStat := allStats[app.Server] tableCol := []string{"recipe", "domain", "app name"} if status { tableCol = append(tableCol, []string{"status", "version", "upgrade"}...) } table := formatter.CreateTable(tableCol) for _, appStat := range serverStat.apps { tableRow := []string{appStat.recipe, appStat.domain, appStat.appName} if status { tableRow = append(tableRow, []string{appStat.status, appStat.version, appStat.upgrade}...) } 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 }, }