package app import ( "context" "encoding/json" "fmt" "sort" "strings" "coopcloud.tech/abra/cli/internal" appPkg "coopcloud.tech/abra/pkg/app" "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/i18n" "coopcloud.tech/abra/pkg/log" abraService "coopcloud.tech/abra/pkg/service" stack "coopcloud.tech/abra/pkg/upstream/stack" dockerFormatter "github.com/docker/cli/cli/command/formatter" containerTypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" dockerClient "github.com/docker/docker/client" "github.com/spf13/cobra" ) var AppPsCommand = &cobra.Command{ // translators: `app ps` command Use: i18n.G("ps [flags]"), Aliases: []string{i18n.G("p")}, Short: i18n.G("Check app deployment status"), Args: cobra.ExactArgs(1), ValidArgsFunction: func( cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return autocomplete.AppNameComplete() }, Run: func(cmd *cobra.Command, args []string) { app := internal.ValidateApp(args) if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil { log.Fatal(err) } cl, err := client.New(app.Server) if err != nil { log.Fatal(err) } deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName()) if err != nil { log.Fatal(err) } if !deployMeta.IsDeployed { log.Fatal(i18n.G("%s is not deployed?", app.Name)) } chaosVersion := config.CHAOS_DEFAULT statuses, err := appPkg.GetAppStatuses([]appPkg.App{app}, true) if statusMeta, ok := statuses[app.StackName()]; ok { if isChaos, exists := statusMeta["chaos"]; exists && isChaos == "true" { if cVersion, exists := statusMeta["chaosVersion"]; exists { chaosVersion = cVersion if strings.HasSuffix(chaosVersion, config.DIRTY_DEFAULT) { chaosVersion = formatter.BoldDirtyDefault(chaosVersion) } } } } showPSOutput(app, cl, deployMeta.Version, chaosVersion) }, } // showPSOutput renders ps output. func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chaosVersion string) { composeFiles, err := app.Recipe.GetComposeFiles(app.Env) if err != nil { log.Fatal(err) return } deployOpts := stack.Deploy{ Composefiles: composeFiles, Namespace: app.StackName(), Prune: false, ResolveImage: stack.ResolveImageAlways, } compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env) if err != nil { log.Fatal(err) return } services := compose.Services sort.Slice(services, func(i, j int) bool { return services[i].Name < services[j].Name }) var rows [][]string allContainerStats := make(map[string]map[string]string) for _, service := range services { filters := filters.NewArgs() filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name)) containers, err := cl.ContainerList(context.Background(), containerTypes.ListOptions{Filters: filters}) if err != nil { log.Fatal(err) return } var containerStats map[string]string if len(containers) == 0 { containerStats = map[string]string{ "version": deployedVersion, "chaos": chaosVersion, "service": service.Name, "image": i18n.G("unknown"), "created": i18n.G("unknown"), "status": i18n.G("unknown"), "state": i18n.G("unknown"), "ports": i18n.G("unknown"), } } else { container := containers[0] containerStats = map[string]string{ "version": deployedVersion, "chaos": chaosVersion, "service": abraService.ContainerToServiceName(container.Names, app.StackName()), "image": formatter.RemoveSha(container.Image), "created": formatter.HumanDuration(container.Created), "status": container.Status, "state": container.State, "ports": dockerFormatter.DisplayablePorts(container.Ports), } } allContainerStats[containerStats["service"]] = containerStats // NOTE(d1): don't clobber these variables for --machine output dVersion := deployedVersion cVersion := chaosVersion if containerStats["service"] != "app" { // NOTE(d1): don't repeat info which only relevant for the "app" service dVersion = "" cVersion = "" } row := []string{ containerStats["service"], containerStats["status"], containerStats["image"], dVersion, cVersion, } rows = append(rows, row) } if internal.MachineReadable { rendered, err := json.Marshal(allContainerStats) if err != nil { log.Fatal(i18n.G("unable to convert to JSON: %s", err)) } fmt.Println(string(rendered)) return } table, err := formatter.CreateTable() if err != nil { log.Fatal(err) } headers := []string{ i18n.G("SERVICE"), i18n.G("STATUS"), i18n.G("IMAGE"), i18n.G("VERSION"), i18n.G("CHAOS"), } table. Headers(headers...). Rows(rows...) if err := formatter.PrintTable(table); err != nil { log.Fatal(err) } } func init() { AppPsCommand.Flags().BoolVarP( &internal.MachineReadable, i18n.G("machine"), i18n.G("m"), false, i18n.G("print machine-readable output"), ) AppPsCommand.Flags().BoolVarP( &internal.Chaos, i18n.G("chaos"), i18n.G("C"), false, i18n.G("ignore uncommitted recipes changes"), ) }