forked from toolshed/abra
		
	
		
			
				
	
	
		
			338 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package app
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"coopcloud.tech/abra/cli/internal"
 | |
| 	appPkg "coopcloud.tech/abra/pkg/app"
 | |
| 	"coopcloud.tech/abra/pkg/autocomplete"
 | |
| 	"coopcloud.tech/abra/pkg/formatter"
 | |
| 	"coopcloud.tech/abra/pkg/i18n"
 | |
| 	"coopcloud.tech/abra/pkg/log"
 | |
| 	"coopcloud.tech/tagcmp"
 | |
| 	"github.com/spf13/cobra"
 | |
| )
 | |
| 
 | |
| 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"`
 | |
| }
 | |
| 
 | |
| // translators: `abra app list` aliases. use a comma separated list of aliases with
 | |
| // no spaces in between
 | |
| var appListAliases = i18n.G("ls")
 | |
| 
 | |
| var AppListCommand = &cobra.Command{
 | |
| 	// translators: `app list` command
 | |
| 	Use:     i18n.G("list [flags]"),
 | |
| 	Aliases: strings.Split(appListAliases, ","),
 | |
| 	// translators: Short description for `app list` command
 | |
| 	Short: i18n.G("List all managed apps"),
 | |
| 	Long: i18n.G(`Generate a report of all managed apps.
 | |
| 
 | |
| Use "--status/-S" flag to query all servers for the live deployment status.`),
 | |
| 	Example: i18n.G(`  # list apps of all servers without live status
 | |
|   abra app ls
 | |
| 
 | |
|   # list apps of a specific server with live status
 | |
|   abra app ls -s 1312.net -S
 | |
| 
 | |
|   # list apps of all servers which match a specific recipe
 | |
|   abra app ls -r gitea`),
 | |
| 	Args: cobra.NoArgs,
 | |
| 	Run: func(cmd *cobra.Command, args []string) {
 | |
| 		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 := i18n.G("unknown")
 | |
| 					version := i18n.G("unknown")
 | |
| 					chaos := i18n.G("unknown")
 | |
| 					chaosVersion := i18n.G("unknown")
 | |
| 					autoUpdate := i18n.G("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" && chaos == "false" {
 | |
| 						if err := app.Recipe.EnsureExists(); err != nil {
 | |
| 							log.Fatal(i18n.G("unable to clone %s: %s", app.Name, err))
 | |
| 						}
 | |
| 
 | |
| 						updates, err := app.Recipe.Tags()
 | |
| 						if err != nil {
 | |
| 							log.Fatal(i18n.G("unable to retrieve tags for %s: %s", app.Name, 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 = i18n.G("unknown")
 | |
| 						} else {
 | |
| 							appStats.Upgrade = i18n.G("latest")
 | |
| 							stats.LatestCount++
 | |
| 						}
 | |
| 					} else {
 | |
| 						newUpdates = internal.SortVersionsDesc(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
 | |
| 		}
 | |
| 
 | |
| 		alreadySeen := make(map[string]bool)
 | |
| 		for _, app := range apps {
 | |
| 			if _, ok := alreadySeen[app.Server]; ok {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			serverStat := allStats[app.Server]
 | |
| 
 | |
| 			headers := []string{i18n.G("RECIPE"), i18n.G("DOMAIN"), i18n.G("SERVER")}
 | |
| 			if status {
 | |
| 				headers = append(headers, []string{
 | |
| 					i18n.G("STATUS"),
 | |
| 					i18n.G("CHAOS"),
 | |
| 					i18n.G("VERSION"),
 | |
| 					i18n.G("UPGRADE"),
 | |
| 					i18n.G("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, appStat.Server}
 | |
| 				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 {
 | |
| 				if err := formatter.PrintTable(table); err != nil {
 | |
| 					log.Fatal(err)
 | |
| 				}
 | |
| 
 | |
| 				if len(allStats) > 1 && len(rows) > 0 {
 | |
| 					fmt.Println() // newline separator for multiple servers
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			alreadySeen[app.Server] = true
 | |
| 		}
 | |
| 	},
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	status        bool
 | |
| 	recipeFilter  string
 | |
| 	listAppServer string
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	AppListCommand.Flags().BoolVarP(
 | |
| 		&status,
 | |
| 		i18n.G("status"),
 | |
| 		i18n.G("S"),
 | |
| 		false,
 | |
| 		i18n.G("show app deployment status"),
 | |
| 	)
 | |
| 
 | |
| 	AppListCommand.Flags().StringVarP(
 | |
| 		&recipeFilter,
 | |
| 		i18n.G("recipe"),
 | |
| 		i18n.G("r"),
 | |
| 		"",
 | |
| 		i18n.G("show apps of a specific recipe"),
 | |
| 	)
 | |
| 
 | |
| 	AppListCommand.RegisterFlagCompletionFunc(
 | |
| 		i18n.G("recipe"),
 | |
| 		func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 | |
| 			return autocomplete.RecipeNameComplete()
 | |
| 		},
 | |
| 	)
 | |
| 
 | |
| 	AppListCommand.Flags().BoolVarP(
 | |
| 		&internal.MachineReadable,
 | |
| 		i18n.G("machine"),
 | |
| 		i18n.G("m"),
 | |
| 		false,
 | |
| 		i18n.G("print machine-readable output"),
 | |
| 	)
 | |
| 
 | |
| 	AppListCommand.Flags().StringVarP(
 | |
| 		&listAppServer,
 | |
| 		i18n.G("server"),
 | |
| 		i18n.G("s"),
 | |
| 		"",
 | |
| 		i18n.G("show apps of a specific server"),
 | |
| 	)
 | |
| 
 | |
| 	AppListCommand.RegisterFlagCompletionFunc(
 | |
| 		i18n.G("server"),
 | |
| 		func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 | |
| 			return autocomplete.ServerNameComplete()
 | |
| 		},
 | |
| 	)
 | |
| }
 |