package app import ( "context" "encoding/json" "fmt" "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/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{ Use: "ps [flags]", Aliases: []string{"p"}, Short: "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.Fatalf("%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 } var rows [][]string allContainerStats := make(map[string]map[string]string) for _, service := range compose.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": "unknown", "created": "unknown", "status": "unknown", "state": "unknown", "ports": "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["image"], dVersion, cVersion, containerStats["status"], } rows = append(rows, row) } if internal.MachineReadable { rendered, err := json.Marshal(allContainerStats) if err != nil { log.Fatal("unable to convert to JSON: %s", err) } fmt.Println(string(rendered)) return } table, err := formatter.CreateTable() if err != nil { log.Fatal(err) } headers := []string{ "SERVICE", "IMAGE", "VERSION", "CHAOS", "STATUS", } table. Headers(headers...). Rows(rows...) if err := formatter.PrintTable(table); err != nil { log.Fatal(err) } } func init() { AppPsCommand.Flags().BoolVarP( &internal.MachineReadable, "machine", "m", false, "print machine-readable output", ) AppPsCommand.Flags().BoolVarP( &internal.Chaos, "chaos", "C", false, "ignore uncommitted recipes changes", ) }