diff --git a/plugins/apps/apps.go b/plugins/apps/report.go similarity index 62% rename from plugins/apps/apps.go rename to plugins/apps/report.go index 28b0c96ef..d74d9ce51 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, infoFlag, 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 71% rename from plugins/buildpacks/buildpacks.go rename to plugins/buildpacks/report.go index b8e5e76c2..b255095f2 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, infoFlag, 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/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 diff --git a/plugins/common/parallel.go b/plugins/common/parallel.go index 7ac6e66bc..777ad1778 100644 --- a/plugins/common/parallel.go +++ b/plugins/common/parallel.go @@ -132,3 +132,35 @@ func createParallelWorkerPool(jobs chan string, results chan parallelCommandResu wg.Wait() 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, 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() + 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..ff9fcfdda --- /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, infoFlag, 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..f32365ab1 --- /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, infoFlag, 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..838eaee49 --- /dev/null +++ b/plugins/ps/report.go @@ -0,0 +1,120 @@ +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, + } + + extraFlags := addStatusFlags(appName, infoFlag) + for flag, fn := range extraFlags { + flags[flag] = fn + } + + trimPrefix := false + uppercaseFirstCharacter := true + infoFlags := common.CollectReport(appName, infoFlag, flags) + 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) { + 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) +}