From 44461e21df76694226b62b1d1f1caba207ca7d9e Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 29 Dec 2020 23:55:13 -0500 Subject: [PATCH 1/6] refactor: parallelize scheduler retrieval In many cases, a user may only set a global scheduler, and as such we waste the initial call to get the app scheduler. Parallelizing the fetch increases the speed at which we can check for the scheduler. --- plugins/common/common.go | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/plugins/common/common.go b/plugins/common/common.go index 05fbc8434..6d9c5ae5a 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -11,6 +11,7 @@ import ( "sort" "strconv" "strings" + "sync" "unicode" "github.com/ryanuber/columnize" @@ -64,17 +65,34 @@ func CommandUsage(helpHeader string, helpContent string) { // GetAppScheduler fetches the scheduler for a given application func GetAppScheduler(appName string) string { - if appName == "--global" { - appName = "" - } + appScheduler := "" + globalScheduler := "" - b, _ := PlugnTriggerOutput("config-get", []string{appName, "DOKKU_SCHEDULER"}...) - value := strings.TrimSpace(string(b[:])) - if value != "" { - return value - } + var wg sync.WaitGroup - return GetGlobalScheduler() + if appName != "--global" { + wg.Add(1) + go func() { + defer wg.Done() + b, _ := PlugnTriggerOutput("config-get", []string{appName, "DOKKU_SCHEDULER"}...) + value := strings.TrimSpace(string(b[:])) + if value != "" { + appScheduler = value + } + }() + } + wg.Add(1) + go func() { + defer wg.Done() + globalScheduler = GetGlobalScheduler() + }() + + wg.Wait() + + if appScheduler == "" { + appScheduler = globalScheduler + } + return appScheduler } // GetGlobalScheduler fetchs the global scheduler From 28c9f42a259b810508e81a3ca2d9ea8c1f9d2e10 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 29 Dec 2020 23:56:10 -0500 Subject: [PATCH 2/6] refactor: parallelize report commands This will make reports a bit faster to run, and is safe to do as reports should not change app state. --- plugins/apps/{apps.go => report.go} | 31 +++++-- .../buildpacks/{buildpacks.go => report.go} | 19 ++-- plugins/common/parallel.go | 24 +++++ plugins/network/network.go | 18 ---- plugins/network/report.go | 42 +++++++++ plugins/proxy/proxy.go | 27 ------ plugins/proxy/report.go | 47 ++++++++++ plugins/ps/ps.go | 59 ------------ plugins/ps/report.go | 90 +++++++++++++++++++ 9 files changed, 237 insertions(+), 120 deletions(-) rename plugins/apps/{apps.go => report.go} (63%) rename plugins/buildpacks/{buildpacks.go => report.go} (72%) create mode 100644 plugins/network/report.go create mode 100644 plugins/proxy/report.go create mode 100644 plugins/ps/report.go diff --git a/plugins/apps/apps.go b/plugins/apps/report.go similarity index 63% rename from plugins/apps/apps.go rename to plugins/apps/report.go index 28b0c96ef..ae0a9ba6c 100644 --- a/plugins/apps/apps.go +++ b/plugins/apps/report.go @@ -12,23 +12,36 @@ func ReportSingleApp(appName, infoFlag string) error { return err } + flags := map[string]common.ReportFunc{ + "--app-dir": reportDir, + "--app-deploy-source": reportDeploySource, + "--app-locked": reportLocked, + } + + trimPrefix := false + uppercaseFirstCharacter := true + infoFlags := common.CollectReport(appName, flags) + return common.ReportSingleApp("app", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) +} + +func reportDir(appName string) string { + return common.AppRoot(appName) +} + +func reportDeploySource(appName string) string { deploySource := "" if b, err := common.PlugnTriggerSetup("deploy-source", []string{appName}...).SetInput("").Output(); err != nil { deploySource = strings.TrimSpace(string(b[:])) } + return deploySource +} + +func reportLocked(appName string) string { locked := "false" if appIsLocked(appName) { locked = "true" } - infoFlags := map[string]string{ - "--app-dir": common.AppRoot(appName), - "--app-deploy-source": deploySource, - "--app-locked": locked, - } - - trimPrefix := false - uppercaseFirstCharacter := true - return common.ReportSingleApp("app", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) + return locked } diff --git a/plugins/buildpacks/buildpacks.go b/plugins/buildpacks/report.go similarity index 72% rename from plugins/buildpacks/buildpacks.go rename to plugins/buildpacks/report.go index b8e5e76c2..b1e593a2b 100644 --- a/plugins/buildpacks/buildpacks.go +++ b/plugins/buildpacks/report.go @@ -12,16 +12,21 @@ func ReportSingleApp(appName, infoFlag string) error { return err } - buildpacks, err := common.PropertyListGet("buildpacks", appName, "buildpacks") - if err != nil { - return err - } - - infoFlags := map[string]string{ - "--buildpacks-list": strings.Join(buildpacks, ","), + flags := map[string]common.ReportFunc{ + "--buildpacks-list": reportList, } trimPrefix := false uppercaseFirstCharacter := true + infoFlags := common.CollectReport(appName, flags) return common.ReportSingleApp("buildpacks", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) } + +func reportList(appName string) string { + buildpacks, err := common.PropertyListGet("buildpacks", appName, "buildpacks") + if err != nil { + return "" + } + + return strings.Join(buildpacks, ",") +} diff --git a/plugins/common/parallel.go b/plugins/common/parallel.go index 7ac6e66bc..3258ed56d 100644 --- a/plugins/common/parallel.go +++ b/plugins/common/parallel.go @@ -132,3 +132,27 @@ func createParallelWorkerPool(jobs chan string, results chan parallelCommandResu wg.Wait() close(results) } + +type ReportFunc func(string) string + +func CollectReport(appName string, flags map[string]ReportFunc) map[string]string { + var sm sync.Map + var wg sync.WaitGroup + for flag, fn := range flags { + wg.Add(1) + go func(flag string, fn ReportFunc) { + defer wg.Done() + s := fn(appName) + sm.Store(flag, s) + }(flag, fn) + } + wg.Wait() + + infoFlags := map[string]string{} + sm.Range(func(key interface{}, value interface{}) bool { + infoFlags[key.(string)] = value.(string) + return true + }) + + return infoFlags +} diff --git a/plugins/network/network.go b/plugins/network/network.go index 9f141d53b..e2f36ac47 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -206,21 +206,3 @@ func ClearNetworkConfig(appName string) bool { } return success } - -// ReportSingleApp is an internal function that displays the app report for one or more apps -func ReportSingleApp(appName, infoFlag string) error { - if err := common.VerifyAppName(appName); err != nil { - return err - } - - infoFlags := map[string]string{ - "--network-bind-all-interfaces": common.PropertyGet("network", appName, "bind-all-interfaces"), - "--network-attach-post-create": common.PropertyGet("network", appName, "attach-post-create"), - "--network-attach-post-deploy": common.PropertyGet("network", appName, "attach-post-deploy"), - "--network-web-listeners": strings.Join(GetListeners(appName, "web"), " "), - } - - trimPrefix := false - uppercaseFirstCharacter := true - return common.ReportSingleApp("network", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) -} diff --git a/plugins/network/report.go b/plugins/network/report.go new file mode 100644 index 000000000..a3bccdb02 --- /dev/null +++ b/plugins/network/report.go @@ -0,0 +1,42 @@ +package network + +import ( + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the app report for one or more apps +func ReportSingleApp(appName, infoFlag string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + flags := map[string]common.ReportFunc{ + "--network-bind-all-interfaces": reportBindAllInterfaces, + "--network-attach-post-create": reportAttachPostCreate, + "--network-attach-post-deploy": reportAttachPostDeploy, + "--network-web-listeners": reportWebListeners, + } + + trimPrefix := false + uppercaseFirstCharacter := true + infoFlags := common.CollectReport(appName, flags) + return common.ReportSingleApp("network", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) +} + +func reportBindAllInterfaces(appName string) string { + return common.PropertyGet("network", appName, "bind-all-interfaces") +} + +func reportAttachPostCreate(appName string) string { + return common.PropertyGet("network", appName, "attach-post-create") +} + +func reportAttachPostDeploy(appName string) string { + return common.PropertyGet("network", appName, "attach-post-deploy") +} + +func reportWebListeners(appName string) string { + return strings.Join(GetListeners(appName, "web"), " ") +} diff --git a/plugins/proxy/proxy.go b/plugins/proxy/proxy.go index 3da647658..cec4abce5 100644 --- a/plugins/proxy/proxy.go +++ b/plugins/proxy/proxy.go @@ -43,33 +43,6 @@ func IsAppProxyEnabled(appName string) bool { return proxyEnabled } -// ReportSingleApp is an internal function that displays the app report for one or more apps -func ReportSingleApp(appName string, infoFlag string) error { - if err := common.VerifyAppName(appName); err != nil { - return err - } - - proxyEnabled := "false" - if IsAppProxyEnabled(appName) { - proxyEnabled = "true" - } - - var proxyPortMap []string - for _, portMap := range getProxyPortMap(appName) { - proxyPortMap = append(proxyPortMap, portMap.String()) - } - - infoFlags := map[string]string{ - "--proxy-enabled": proxyEnabled, - "--proxy-type": getAppProxyType(appName), - "--proxy-port-map": strings.Join(proxyPortMap, " "), - } - - trimPrefix := false - uppercaseFirstCharacter := true - return common.ReportSingleApp("proxy", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) -} - func addProxyPorts(appName string, proxyPortMap []PortMap) error { allPortMaps := getProxyPortMap(appName) allPortMaps = append(allPortMaps, proxyPortMap...) diff --git a/plugins/proxy/report.go b/plugins/proxy/report.go new file mode 100644 index 000000000..0791eb273 --- /dev/null +++ b/plugins/proxy/report.go @@ -0,0 +1,47 @@ +package proxy + +import ( + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the app report for one or more apps +func ReportSingleApp(appName string, infoFlag string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + flags := map[string]common.ReportFunc{ + "--proxy-enabled": reportEnabled, + "--proxy-type": reportType, + "--proxy-port-map": reportPortMap, + } + + trimPrefix := false + uppercaseFirstCharacter := true + infoFlags := common.CollectReport(appName, flags) + return common.ReportSingleApp("proxy", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) +} + +func reportEnabled(appName string) string { + proxyEnabled := "false" + if IsAppProxyEnabled(appName) { + proxyEnabled = "true" + } + + return proxyEnabled +} + +func reportType(appName string) string { + return getAppProxyType(appName) +} + +func reportPortMap(appName string) string { + var proxyPortMap []string + for _, portMap := range getProxyPortMap(appName) { + proxyPortMap = append(proxyPortMap, portMap.String()) + } + + return strings.Join(proxyPortMap, " ") +} diff --git a/plugins/ps/ps.go b/plugins/ps/ps.go index a82ca6a1a..84fc40996 100644 --- a/plugins/ps/ps.go +++ b/plugins/ps/ps.go @@ -2,7 +2,6 @@ package ps import ( "fmt" - "strconv" "strings" "github.com/dokku/dokku/plugins/common" @@ -24,64 +23,6 @@ func Rebuild(appName string) error { return common.PlugnTrigger("receive-app", []string{appName}...) } -// ReportSingleApp is an internal function that displays the ps report for one or more apps -func ReportSingleApp(appName, infoFlag string) error { - if err := common.VerifyAppName(appName); err != nil { - return err - } - - policy, _ := getRestartPolicy(appName) - if policy == "" { - policy = DefaultProperties["restart-policy"] - } - - canScale := "false" - if canScaleApp(appName) { - canScale = "true" - } - - deployed := "false" - if common.IsDeployed(appName) { - deployed = "true" - } - - runningState := getRunningState(appName) - - count, err := getProcessCount(appName) - if err != nil { - count = -1 - } - - b, _ := common.PlugnTriggerOutput("config-get", []string{appName, "DOKKU_APP_RESTORE"}...) - restore := strings.TrimSpace(string(b[:])) - if restore == "0" { - restore = "false" - } else { - restore = "true" - } - - infoFlags := map[string]string{ - "--deployed": deployed, - "--processes": strconv.Itoa(count), - "--ps-can-scale": canScale, - "--ps-restart-policy": policy, - "--restore": restore, - "--running": runningState, - } - - scheduler := common.GetAppScheduler(appName) - if scheduler == "docker-local" { - processStatus := getProcessStatus(appName) - for process, value := range processStatus { - infoFlags[fmt.Sprintf("--status-%s", process)] = value - } - } - - trimPrefix := false - uppercaseFirstCharacter := true - return common.ReportSingleApp("ps", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) -} - // Restart restarts the app func Restart(appName string) error { if !common.IsDeployed(appName) { diff --git a/plugins/ps/report.go b/plugins/ps/report.go new file mode 100644 index 000000000..34bd36f47 --- /dev/null +++ b/plugins/ps/report.go @@ -0,0 +1,90 @@ +package ps + +import ( + "fmt" + "strconv" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the ps report for one or more apps +func ReportSingleApp(appName, infoFlag string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + flags := map[string]common.ReportFunc{ + "--deployed": reportDeployed, + "--processes": reportProcesses, + "--ps-can-scale": reportCanScale, + "--ps-restart-policy": reportRestartPolicy, + "--restore": reportRestore, + "--running": reportRunningState, + } + + infoFlags := common.CollectReport(appName, flags) + scheduler := common.GetAppScheduler(appName) + if scheduler == "docker-local" { + processStatus := getProcessStatus(appName) + for process, value := range processStatus { + infoFlags[fmt.Sprintf("--status-%s", process)] = value + } + } + + trimPrefix := false + uppercaseFirstCharacter := true + return common.ReportSingleApp("ps", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) +} + +func reportCanScale(appName string) string { + canScale := "false" + if canScaleApp(appName) { + canScale = "true" + } + + return canScale +} + +func reportDeployed(appName string) string { + deployed := "false" + if common.IsDeployed(appName) { + deployed = "true" + } + + return deployed +} + +func reportProcesses(appName string) string { + count, err := getProcessCount(appName) + if err != nil { + count = -1 + } + + return strconv.Itoa(count) +} + +func reportRestartPolicy(appName string) string { + policy, _ := getRestartPolicy(appName) + if policy == "" { + policy = DefaultProperties["restart-policy"] + } + + return policy +} + +func reportRestore(appName string) string { + b, _ := common.PlugnTriggerOutput("config-get", []string{appName, "DOKKU_APP_RESTORE"}...) + restore := strings.TrimSpace(string(b[:])) + if restore == "0" { + restore = "false" + } else { + restore = "true" + } + + return restore +} + +func reportRunningState(appName string) string { + return getRunningState(appName) +} From 5da5a6fe5ff0440c6531125ddb82b5573fd5afc6 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 30 Dec 2020 00:21:09 -0500 Subject: [PATCH 3/6] refactor: collect process status in parallel This speeds up ps:report for apps that have more than 1 running container. --- plugins/ps/report.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/plugins/ps/report.go b/plugins/ps/report.go index 34bd36f47..42905bfd9 100644 --- a/plugins/ps/report.go +++ b/plugins/ps/report.go @@ -23,17 +23,30 @@ func ReportSingleApp(appName, infoFlag string) error { "--running": reportRunningState, } - infoFlags := common.CollectReport(appName, flags) scheduler := common.GetAppScheduler(appName) if scheduler == "docker-local" { - processStatus := getProcessStatus(appName) - for process, value := range processStatus { - infoFlags[fmt.Sprintf("--status-%s", process)] = value + containerFiles := common.ListFilesWithPrefix(common.AppRoot(appName), "CONTAINER.") + for _, filename := range containerFiles { + // See https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables + containerFilePath := filename + process := strings.TrimPrefix(filename, fmt.Sprintf("%s/CONTAINER.", common.AppRoot(appName))) + + flags[fmt.Sprintf("--status-%s", process)] = func(appName string) string { + containerID := common.ReadFirstLine(containerFilePath) + containerStatus, _ := common.DockerInspect(containerID, "{{ .State.Status }}") + + if containerStatus == "" { + containerStatus = "missing" + } + + return fmt.Sprintf("%s (CID: %s)", containerStatus, containerID[0:11]) + } } } trimPrefix := false uppercaseFirstCharacter := true + infoFlags := common.CollectReport(appName, flags) return common.ReportSingleApp("ps", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) } From 38b96c1e27c712e0dc22b4c8851428877b517e47 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 30 Dec 2020 00:28:59 -0500 Subject: [PATCH 4/6] fix: add comments --- plugins/common/parallel.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/common/parallel.go b/plugins/common/parallel.go index 3258ed56d..b5bdb0f15 100644 --- a/plugins/common/parallel.go +++ b/plugins/common/parallel.go @@ -133,8 +133,12 @@ func createParallelWorkerPool(jobs chan string, results chan parallelCommandResu close(results) } +// ReportFunc is a type that declares functions +// useful for :report subcommands type ReportFunc func(string) string +// CollectReport iterates over a set of report functions +// to collect the :report output in parallel func CollectReport(appName string, flags map[string]ReportFunc) map[string]string { var sm sync.Map var wg sync.WaitGroup From 0e320d28322308e100a53c5bedeb8a45ce3f73d8 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 30 Dec 2020 00:37:43 -0500 Subject: [PATCH 5/6] feat: short-circuit report fetching if only retrieving a single value This speeds up report output when specifying a flag as extra data that isn't used will not be fetched unnecessarily. --- plugins/apps/report.go | 2 +- plugins/buildpacks/report.go | 2 +- plugins/common/parallel.go | 6 +++++- plugins/network/report.go | 2 +- plugins/proxy/report.go | 2 +- plugins/ps/report.go | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/apps/report.go b/plugins/apps/report.go index ae0a9ba6c..d74d9ce51 100644 --- a/plugins/apps/report.go +++ b/plugins/apps/report.go @@ -20,7 +20,7 @@ func ReportSingleApp(appName, infoFlag string) error { trimPrefix := false uppercaseFirstCharacter := true - infoFlags := common.CollectReport(appName, flags) + infoFlags := common.CollectReport(appName, infoFlag, flags) return common.ReportSingleApp("app", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) } diff --git a/plugins/buildpacks/report.go b/plugins/buildpacks/report.go index b1e593a2b..b255095f2 100644 --- a/plugins/buildpacks/report.go +++ b/plugins/buildpacks/report.go @@ -18,7 +18,7 @@ func ReportSingleApp(appName, infoFlag string) error { trimPrefix := false uppercaseFirstCharacter := true - infoFlags := common.CollectReport(appName, flags) + infoFlags := common.CollectReport(appName, infoFlag, flags) return common.ReportSingleApp("buildpacks", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) } diff --git a/plugins/common/parallel.go b/plugins/common/parallel.go index b5bdb0f15..777ad1778 100644 --- a/plugins/common/parallel.go +++ b/plugins/common/parallel.go @@ -139,10 +139,14 @@ type ReportFunc func(string) string // CollectReport iterates over a set of report functions // to collect the :report output in parallel -func CollectReport(appName string, flags map[string]ReportFunc) map[string]string { +func CollectReport(appName string, infoFlag string, flags map[string]ReportFunc) map[string]string { var sm sync.Map var wg sync.WaitGroup for flag, fn := range flags { + if infoFlag != "" && infoFlag != flag { + continue + } + wg.Add(1) go func(flag string, fn ReportFunc) { defer wg.Done() diff --git a/plugins/network/report.go b/plugins/network/report.go index a3bccdb02..ff9fcfdda 100644 --- a/plugins/network/report.go +++ b/plugins/network/report.go @@ -21,7 +21,7 @@ func ReportSingleApp(appName, infoFlag string) error { trimPrefix := false uppercaseFirstCharacter := true - infoFlags := common.CollectReport(appName, flags) + infoFlags := common.CollectReport(appName, infoFlag, flags) return common.ReportSingleApp("network", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) } diff --git a/plugins/proxy/report.go b/plugins/proxy/report.go index 0791eb273..f32365ab1 100644 --- a/plugins/proxy/report.go +++ b/plugins/proxy/report.go @@ -20,7 +20,7 @@ func ReportSingleApp(appName string, infoFlag string) error { trimPrefix := false uppercaseFirstCharacter := true - infoFlags := common.CollectReport(appName, flags) + infoFlags := common.CollectReport(appName, infoFlag, flags) return common.ReportSingleApp("proxy", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) } diff --git a/plugins/ps/report.go b/plugins/ps/report.go index 42905bfd9..c24284ad8 100644 --- a/plugins/ps/report.go +++ b/plugins/ps/report.go @@ -46,7 +46,7 @@ func ReportSingleApp(appName, infoFlag string) error { trimPrefix := false uppercaseFirstCharacter := true - infoFlags := common.CollectReport(appName, flags) + infoFlags := common.CollectReport(appName, infoFlag, flags) return common.ReportSingleApp("ps", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) } From 0176e888af8d50b36386e2a4a0966fd98ed122a8 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 30 Dec 2020 00:38:07 -0500 Subject: [PATCH 6/6] refactor: only add status flags as necessary --- plugins/ps/report.go | 55 +++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/plugins/ps/report.go b/plugins/ps/report.go index c24284ad8..838eaee49 100644 --- a/plugins/ps/report.go +++ b/plugins/ps/report.go @@ -23,25 +23,9 @@ func ReportSingleApp(appName, infoFlag string) error { "--running": reportRunningState, } - scheduler := common.GetAppScheduler(appName) - if scheduler == "docker-local" { - containerFiles := common.ListFilesWithPrefix(common.AppRoot(appName), "CONTAINER.") - for _, filename := range containerFiles { - // See https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables - containerFilePath := filename - process := strings.TrimPrefix(filename, fmt.Sprintf("%s/CONTAINER.", common.AppRoot(appName))) - - flags[fmt.Sprintf("--status-%s", process)] = func(appName string) string { - containerID := common.ReadFirstLine(containerFilePath) - containerStatus, _ := common.DockerInspect(containerID, "{{ .State.Status }}") - - if containerStatus == "" { - containerStatus = "missing" - } - - return fmt.Sprintf("%s (CID: %s)", containerStatus, containerID[0:11]) - } - } + extraFlags := addStatusFlags(appName, infoFlag) + for flag, fn := range extraFlags { + flags[flag] = fn } trimPrefix := false @@ -50,6 +34,39 @@ func ReportSingleApp(appName, infoFlag string) error { return common.ReportSingleApp("ps", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) } +func addStatusFlags(appName string, infoFlag string) map[string]common.ReportFunc { + flags := map[string]common.ReportFunc{} + + if infoFlag != "" && !strings.HasPrefix(infoFlag, "--status-") { + return flags + } + + scheduler := common.GetAppScheduler(appName) + if scheduler != "docker-local" { + return flags + } + + containerFiles := common.ListFilesWithPrefix(common.AppRoot(appName), "CONTAINER.") + for _, filename := range containerFiles { + // See https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables + containerFilePath := filename + process := strings.TrimPrefix(filename, fmt.Sprintf("%s/CONTAINER.", common.AppRoot(appName))) + + flags[fmt.Sprintf("--status-%s", process)] = func(appName string) string { + containerID := common.ReadFirstLine(containerFilePath) + containerStatus, _ := common.DockerInspect(containerID, "{{ .State.Status }}") + + if containerStatus == "" { + containerStatus = "missing" + } + + return fmt.Sprintf("%s (CID: %s)", containerStatus, containerID[0:11]) + } + } + + return flags +} + func reportCanScale(appName string) string { canScale := "false" if canScaleApp(appName) {