refactor: implement most of ps plugin in golang

This commit is contained in:
Jose Diaz-Gonzalez
2020-11-01 15:53:53 -05:00
parent c72bbf41ea
commit e7ee7a1781
48 changed files with 1227 additions and 676 deletions

View File

@@ -5,3 +5,15 @@
- Underscores are no longer valid characters in app names. Please rename applications before upgrading. - Underscores are no longer valid characters in app names. Please rename applications before upgrading.
- Process type names specified in Procfile may no longer use characters not valid in DNS Label Names ([RFC 1123](https://tools.ietf.org/html/rfc1123)). - Process type names specified in Procfile may no longer use characters not valid in DNS Label Names ([RFC 1123](https://tools.ietf.org/html/rfc1123)).
- The minimum Docker version is now 17.05.0. - The minimum Docker version is now 17.05.0.
## Removals
The `ps` command has had a few removals as a result of a rewrite to Golang:
- The `ps:set-restart-policy` command has been removed in favor the `ps:set` command.
- The `ps:restart-policy` command has been removed in favor of the `ps:report` command.
- The `ps:rebuildall` command has been removed in favor of calling the `ps:rebuild` with the `--all` flag instead of an app name.
- The `ps:restartall` command has been removed in favor of calling the `ps:restart` with the `--all` flag instead of an app name.
- The `ps:stopall` command has been removed in favor of calling the `ps:stop` with the `--all` flag instead of an app name.
- The `ps:startall` command has been removed in favor of calling the `ps:start` with the `--all` flag instead of an app name.
- The `DOKKU_PROCFILE` file is no longer located in the git directory for a given app on the server. It's location is currently not exposed.

View File

@@ -8,10 +8,9 @@ ps:rebuild <app> # Rebuild an app from source
ps:rebuildall # Rebuild all apps from source ps:rebuildall # Rebuild all apps from source
ps:report [<app>] [<flag>] # Displays a process report for one or more apps ps:report [<app>] [<flag>] # Displays a process report for one or more apps
ps:restart <app> # Restart app container(s) ps:restart <app> # Restart app container(s)
ps:restart-policy <app> # Shows the restart-policy for an app
ps:restartall # Restart all deployed app containers ps:restartall # Restart all deployed app containers
ps:scale <app> <proc>=<count> [<proc>=<count>] # Get/Set how many instances of a given process to run ps:scale <app> <proc>=<count> [<proc>=<count>] # Get/Set how many instances of a given process to run
ps:set-restart-policy <app> <policy> # Sets app restart-policy ps:set <app> <key> <value> # Set or clear a ps property for an app
ps:start <app> # Start app container(s) ps:start <app> # Start app container(s)
ps:startall # Start all deployed app containers ps:startall # Start all deployed app containers
ps:stop <app> # Stop app container(s) ps:stop <app> # Stop app container(s)
@@ -133,68 +132,29 @@ dokku ps:startall
> New as of 0.7.0 > New as of 0.7.0
By default, Dokku will automatically restart containers that exit with a non-zero status up to 10 times via the [on-failure Docker restart policy](https://docs.docker.com/engine/reference/run/#restart-policies-restart). By default, Dokku will automatically restart containers that exit with a non-zero status up to 10 times via the [on-failure Docker restart policy](https://docs.docker.com/engine/reference/run/#restart-policies---restart).
### Showing the current restart policy
The `ps:restart-policy` command will show the currently configured restart policy for an application. The default policy is `on-failure:10`
```shell
dokku ps:restart-policy node-js-app
```
```
=====> node-js-app restart-policy:
on-failure:10
```
### Setting the restart policy ### Setting the restart policy
You can configure this via the `ps:set-restart-policy` command: > A change in the restart policy must be followed by a `ps:rebuild` call.
You can configure this via the `ps:set` command:
```shell ```shell
# always restart an exited container # always restart an exited container
dokku ps:set-restart-policy node-js-app always dokku ps:set node-js-app restart-policy always
```
```
-----> Setting restart policy: always
```
```shell
# never restart an exited container # never restart an exited container
dokku ps:set-restart-policy node-js-app no dokku ps:set node-js-app restart-policy no
```
```
-----> Setting restart policy: no
```
```shell
# only restart it on Docker restart if it was not manually stopped # only restart it on Docker restart if it was not manually stopped
dokku ps:set-restart-policy node-js-app unless-stopped dokku ps:set node-js-app restart-policy unless-stopped
```
```
-----> Setting restart policy: unless-stopped
```
```shell
# restart only on non-zero exit status # restart only on non-zero exit status
dokku ps:set-restart-policy node-js-app on-failure dokku ps:set node-js-app restart-policy on-failure
```
```
-----> Setting restart policy: on-failure
```
```shell
# restart only on non-zero exit status up to 20 times # restart only on non-zero exit status up to 20 times
dokku ps:set-restart-policy node-js-app on-failure:20 dokku ps:set node-js-app restart-policy on-failure:20
```
```
-----> Setting restart policy: on-failure:20
``` ```
Restart policies have no bearing on server reboot, and Dokku will always attempt to restart your applications at that point unless they were manually stopped. Restart policies have no bearing on server reboot, and Dokku will always attempt to restart your applications at that point unless they were manually stopped.

View File

@@ -9,6 +9,7 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"sort" "sort"
"strconv"
"strings" "strings"
"unicode" "unicode"
@@ -379,6 +380,16 @@ func ToBool(s string) bool {
return s == "true" return s == "true"
} }
// ToInt returns an int value for a given string
func ToInt(s string, defaultValue int) int {
i, err := strconv.Atoi(s)
if err != nil {
return defaultValue
}
return i
}
// UcFirst uppercases the first character in a string // UcFirst uppercases the first character in a string
func UcFirst(str string) string { func UcFirst(str string) string {
for i, v := range str { for i, v := range str {

View File

@@ -4,13 +4,39 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"os" "os"
"path/filepath"
"sort" "sort"
"strings" "strings"
"github.com/dokku/dokku/plugins/common"
) )
func SetDockerOptionForPhases(appName string, phases []string, name string, value string) error {
for _, phase := range phases {
if err := touchPhaseFile(appName, phase); err != nil {
return err
}
options, err := GetDockerOptionsForPhase(appName, phase)
if err != nil {
return err
}
newOptions := []string{}
for _, option := range options {
if strings.HasPrefix(option, fmt.Sprintf("--%s=", name)) {
continue
}
newOptions = append(newOptions, option)
}
newOptions = append(newOptions, fmt.Sprintf("--%s=%s", name, value))
sort.Strings(newOptions)
if err = writeDockerOptionsForPhase(appName, phase, newOptions); err != nil {
return err
}
}
return nil
}
// AddDockerOptionToPhases adds docker option to specified phases // AddDockerOptionToPhases adds docker option to specified phases
func AddDockerOptionToPhases(appName string, phases []string, option string) error { func AddDockerOptionToPhases(appName string, phases []string, option string) error {
for _, phase := range phases { for _, phase := range phases {
@@ -58,29 +84,3 @@ func GetDockerOptionsForPhase(appName string, phase string) ([]string, error) {
return options, nil return options, nil
} }
func getPhaseFilePath(appName string, phase string) string {
return filepath.Join(common.MustGetEnv("DOKKU_ROOT"), appName, "DOCKER_OPTIONS_"+strings.ToUpper(phase))
}
func touchPhaseFile(appName string, phase string) error {
phaseFilePath := getPhaseFilePath(appName, phase)
_, err := os.Stat(phaseFilePath)
if !os.IsNotExist(err) {
return nil
}
file, err := os.Create(phaseFilePath)
if err != nil {
return fmt.Errorf("Unable to create docker options phase file %s.%s: %s", appName, phase, err.Error())
}
defer file.Close()
return nil
}
func writeDockerOptionsForPhase(appName string, phase string, options []string) error {
phaseFilePath := getPhaseFilePath(appName, phase)
return common.WriteSliceToFile(phaseFilePath, options)
}

View File

@@ -0,0 +1,36 @@
package dockeroptions
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/dokku/dokku/plugins/common"
)
func getPhaseFilePath(appName string, phase string) string {
return filepath.Join(common.AppRoot(appName), "DOCKER_OPTIONS_"+strings.ToUpper(phase))
}
func touchPhaseFile(appName string, phase string) error {
phaseFilePath := getPhaseFilePath(appName, phase)
_, err := os.Stat(phaseFilePath)
if !os.IsNotExist(err) {
return nil
}
file, err := os.Create(phaseFilePath)
if err != nil {
return fmt.Errorf("Unable to create docker options phase file %s.%s: %s", appName, phase, err.Error())
}
defer file.Close()
return nil
}
func writeDockerOptionsForPhase(appName string, phase string, options []string) error {
phaseFilePath := getPhaseFilePath(appName, phase)
return common.WriteSliceToFile(phaseFilePath, options)
}

11
plugins/ps/.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
/commands
/subcommands/*
/triggers/*
/triggers
/proxy-*
/install
/post-*
/report
/pre-*
/procfile-*
/app-restart

6
plugins/ps/Makefile Normal file
View File

@@ -0,0 +1,6 @@
SUBCOMMANDS = subcommands/inspect subcommands/rebuild subcommands/rebuildall subcommands/report subcommands/restart subcommands/restart-policy subcommands/restartall subcommands/restore subcommands/retire subcommands/scale subcommands/set subcommands/start subcommands/startall subcommands/stop subcommands/stopall
TRIGGERS = triggers/app-restart triggers/install triggers/post-app-clone triggers/post-app-rename triggers/post-create triggers/post-delete triggers/post-stop triggers/pre-deploy triggers/procfile-extract triggers/procfile-get-command triggers/procfile-remove triggers/report
BUILD = commands subcommands triggers
PLUGIN_NAME = ps
include ../../common.mk

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
trigger-ps-app-restart() {
declare desc="ps app-restart plugin trigger"
declare trigger="app-restart"
declare APP="$1"
ps_restart "$APP"
}
trigger-ps-app-restart "$@"

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env bash
[[ " help ps:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
source "$PLUGIN_AVAILABLE_PATH/ps/help-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
case "$1" in
help | ps:help)
cmd-ps-help "$@"
;;
*)
exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
;;
esac

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
trigger-ps-core-post-deploy() {
declare desc="ps core-post-deploy plugin trigger"
declare trigger="core-post-deploy"
declare APP="$1"
remove_procfile "$APP"
DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" DOKKU_APP_RESTORE=1
}
trigger-ps-core-post-deploy "$@"

287
plugins/ps/functions.go Normal file
View File

@@ -0,0 +1,287 @@
package ps
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/dokku/dokku/plugins/common"
dockeroptions "github.com/dokku/dokku/plugins/docker-options"
)
func canScaleApp(appName string) bool {
return !common.FileExists(getScalefilePath(appName))
}
func extractProcfile(appName, image string, procfilePath string) error {
if err := removeProcfile(appName); err != nil {
return err
}
destination := getProcfilePath(appName)
common.CopyFromImage(appName, image, "Procfile", destination)
if !common.FileExists(destination) {
common.LogInfo1Quiet("No Procfile found in app image")
return nil
}
common.LogInfo1Quiet("App Procfile file found")
checkCmd := common.NewShellCmd(strings.Join([]string{
"procfile-util",
"check",
"--procfile",
destination,
}, " "))
var stderr bytes.Buffer
checkCmd.ShowOutput = false
checkCmd.Command.Stderr = &stderr
_, err := checkCmd.Output()
if err != nil {
return fmt.Errorf(strings.TrimSpace(stderr.String()))
}
return nil
}
func extractOrGenerateScalefile(appName string, image string) error {
destination := getScalefilePath(appName)
extracted := getScalefileExtractedPath(appName)
if err := common.CopyFromImage(appName, image, "DOKKU_SCALE", destination); err != nil {
os.Remove(extracted)
os.Remove(destination)
} else if err := common.CopyFile(destination, extracted); err != nil {
return err
}
if common.FileExists(destination) {
return nil
}
common.LogInfo1Quiet("DOKKU_SCALE file not found in app image. Generating one based on Procfile...")
if err := generateScalefile(appName, destination); err != nil {
common.LogDebug(fmt.Sprintf("Error: %s", err.Error()))
return err
}
return nil
}
func generateScalefile(appName string, destination string) error {
procfilePath := getProcfilePath(appName)
content := []string{"web=1"}
if !common.FileExists(procfilePath) {
return common.WriteSliceToFile(destination, content)
}
lines, err := common.FileToSlice(procfilePath)
if err != nil {
return common.WriteSliceToFile(destination, content)
}
content = []string{}
for _, line := range lines {
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.Split(line, ":")
count := 0
if parts[0] == "web" {
count = 1
}
content = append(content, fmt.Sprintf("%s=%d", parts[0], count))
}
return common.WriteSliceToFile(destination, content)
}
func getProcfileCommand(procfilePath string, processType string, port int) (string, error) {
shellCmd := common.NewShellCmd(strings.Join([]string{
"procfile-util",
"show",
"--procfile",
procfilePath,
"--process-type",
processType,
"--default-port",
strconv.Itoa(port),
}, " "))
var stderr bytes.Buffer
shellCmd.ShowOutput = false
shellCmd.Command.Stderr = &stderr
b, err := shellCmd.Output()
if err != nil {
return "", fmt.Errorf(strings.TrimSpace(stderr.String()))
}
return strings.TrimSpace(string(b[:])), nil
}
func getProcfilePath(appName string) string {
directory := filepath.Join(common.MustGetEnv("DOKKU_LIB_ROOT"), "data", "ps", appName)
return filepath.Join(directory, "Procfile")
}
func getRestartPolicy(appName string) (string, error) {
options, err := dockeroptions.GetDockerOptionsForPhase(appName, "deploy")
if err != nil {
return "", err
}
for _, option := range options {
if strings.HasPrefix(option, "--restart=") {
return strings.TrimPrefix(option, "--restart="), nil
}
}
return "", nil
}
func getRunningState(appName string) string {
scheduler := common.GetAppScheduler(appName)
b, _ := common.PlugnTriggerOutput("scheduler-app-status", []string{scheduler, appName}...)
return strings.Split(strings.TrimSpace(string(b[:])), " ")[1]
}
func getScalefilePath(appName string) string {
return filepath.Join(common.AppRoot(appName), "DOKKU_SCALE")
}
func getScalefileExtractedPath(appName string) string {
return filepath.Join(common.AppRoot(appName), "DOKKU_SCALE.extracted")
}
func isValidRestartPolicy(policy string) bool {
if policy == "" {
return false
}
validRestartPolicies := map[string]bool{
"no": true,
"always": true,
"unless-stopped": true,
"on-failure": true,
}
if _, ok := validRestartPolicies[policy]; ok {
return true
}
return strings.HasPrefix(policy, "on-failure:")
}
func processesInProcfile(procfilePath string) (map[string]bool, error) {
processes := map[string]bool{}
shellCmd := common.NewShellCmd(strings.Join([]string{
"procfile-util",
"list",
"--procfile",
procfilePath,
}, " "))
var stderr bytes.Buffer
shellCmd.ShowOutput = false
shellCmd.Command.Stderr = &stderr
b, err := shellCmd.Output()
if err != nil {
return processes, fmt.Errorf(strings.TrimSpace(stderr.String()))
}
for _, s := range strings.Split(strings.TrimSpace(string(b[:])), "\n") {
processes[s] = true
}
return processes, nil
}
func removeProcfile(appName string) error {
procfile := getProcfilePath(appName)
if !common.FileExists(procfile) {
return nil
}
return os.Remove(procfile)
}
func updateScalefile(appName string, processTuples []string) error {
procfilePath := getProcfilePath(appName)
scalefilePath := getScalefilePath(appName)
lines, err := common.FileToSlice(scalefilePath)
if err != nil {
return err
}
processTypes, err := processesInProcfile(procfilePath)
if err != nil {
return err
}
newLines := []string{}
for processType := range processTypes {
found := false
for _, line := range lines {
if strings.HasPrefix(line, fmt.Sprintf("%s=", processType)) {
newLines = append(newLines, line)
found = true
break
}
}
if !found {
newLines = append(newLines, fmt.Sprintf("%s=0", processType))
}
}
scale := []string{}
for _, processTuple := range processTuples {
s := strings.Split(processTuple, "=")
if len(s) == 1 {
return fmt.Errorf("Missing count for process type %s", processTuple)
}
processType := s[0]
count, err := strconv.Atoi(s[1])
if err != nil {
return fmt.Errorf("Invalid count for process type %s", s[0])
}
if _, ok := processTypes[processType]; !ok {
if count != 0 && len(processTypes) == 0 {
return fmt.Errorf("%s is not a valid process name to scale up", processType)
}
}
scale = append(scale, fmt.Sprintf("%s=%d", processType, count))
}
for _, line := range newLines {
s := strings.Split(line, "=")
processType := s[0]
found := false
for _, s := range scale {
if strings.HasPrefix(s, fmt.Sprintf("%s=", processType)) {
found = true
break
}
}
if !found {
scale = append(scale, line)
}
}
if err := common.WriteSliceToFile(scalefilePath, scale); err != nil {
return err
}
return err
}

15
plugins/ps/go.mod Normal file
View File

@@ -0,0 +1,15 @@
module github.com/dokku/dokku/plugins/ps
go 1.14
require (
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000
github.com/dokku/dokku/plugins/docker-options v0.0.0-00010101000000-000000000000
github.com/ryanuber/columnize v1.1.2-0.20190319233515-9e6335e58db3
)
replace github.com/dokku/dokku/plugins/common => ../common
replace github.com/dokku/dokku/plugins/config => ../config
replace github.com/dokku/dokku/plugins/docker-options => ../docker-options

10
plugins/ps/go.sum Normal file
View File

@@ -0,0 +1,10 @@
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 h1:HHUr4P/aKh4quafGxDT9LDasjGdlGkzLbfmmrlng3kA=
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27/go.mod h1:VQx0hjo2oUeQkQUET7wRwradO6f+fN5jzXgB/zROxxE=
github.com/dokku/dokku v0.21.4 h1:WG0hyPzNqKqoQXuI7H/N+cWX9B7mAxTW6Yop40BjCQk=
github.com/joho/godotenv v1.2.0 h1:vGTvz69FzUFp+X4/bAkb0j5BoLC+9bpqTWY8mjhA9pc=
github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/ryanuber/columnize v1.1.2-0.20190319233515-9e6335e58db3 h1:utdYOikI1XjNtTFGCwSM6OmFJblU4ld4gACoJsbadJg=
github.com/ryanuber/columnize v1.1.2-0.20190319233515-9e6335e58db3/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-ps-help() {
declare desc="help command"
declare CMD="$1"
local plugin_name="ps"
local plugin_description="Manage container processes"
if [[ "$CMD" == "$plugin_name:help" ]]; then
echo -e "Usage: dokku $plugin_name[:COMMAND]"
echo ''
echo "$plugin_description"
echo ''
echo 'Additional commands:'
fn-help-content | sort | column -c2 -t -s,
elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then
fn-help-content
else
cat <<help_desc
$plugin_name, $plugin_description
help_desc
fi
}
fn-help-content() {
declare desc="return help content"
cat <<help_content
ps:inspect <app>, Displays a sanitized version of docker inspect for an app
ps:scale <app> <proc>=<count> [<proc>=<count>...], Get/Set how many instances of a given process to run
ps:start <app>, Start app container(s)
ps:startall, Starts all apps via command line
ps:stop <app>, Stop app container(s)
ps:stopall, Stop all app container(s)
ps:rebuild <app>, Rebuild an app from source
ps:rebuildall, Rebuild all apps from source
ps:report [<app>] [<flag>], Displays a process report for one or more apps
ps:restart <app>, Restart app container(s)
ps:restartall, Restart all deployed app containers
ps:restore, Start previously running apps e.g. after reboot
ps:restart-policy <app>, Shows the restart-policy for an app
ps:set-restart-policy <app> <policy>, Sets app restart-policy
help_content
}

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/docker-options/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
trigger-ps-install() {
declare desc="set the default restart policy for all applications if there is not one already set"
declare trigger="install"
local APPS="$(dokku_apps)"
local APP
mkdir -p "${DOKKU_LIB_ROOT}/data/ps"
chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/ps"
for APP in $APPS; do
local RESTART_POLICIES=$(get_restart_policies "$(get_phase_file_path "deploy")")
if [[ -z "$RESTART_POLICIES" ]]; then
local passed_phases=(deploy)
add_passed_docker_option passed_phases[@] "--restart=on-failure:10"
fi
done
}
trigger-ps-install "$@"

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
trigger-ps-post-app-clone() {
declare desc="ps post-app-clone plugin trigger"
declare trigger="post-app-clone"
declare OLD_APP="$1" NEW_APP="$2"
if [[ "$SKIP_REBUILD" != "true" ]]; then
ps_rebuild "$NEW_APP"
fi
}
trigger-ps-post-app-clone "$@"

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
trigger-ps-post-app-rename() {
declare desc="ps post-app-rename plugin trigger"
declare trigger="post-app-rename"
declare OLD_APP="$1" NEW_APP="$2"
if [[ "$SKIP_REBUILD" != "true" ]]; then
ps_rebuild "$NEW_APP"
fi
}
trigger-ps-post-app-rename "$@"

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/docker-options/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
trigger-ps-post-create() {
declare desc="ps post-create plugin trigger"
declare trigger="post-create"
local APP="$1"
local passed_phases=(deploy)
add_passed_docker_option passed_phases[@] "--restart=on-failure:10"
mkdir -p "${DOKKU_LIB_ROOT}/data/ps/${APP}"
chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/ps/${APP}"
}
trigger-ps-post-create "$@"

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
trigger-ps-post-extract() {
declare desc="ps post-extract plugin trigger"
declare trigger="post-extract"
declare APP="$1" TMP_WORK_DIR="$2" REV="$3"
if [[ ! -f "$TMP_WORK_DIR/Procfile" ]]; then
return
fi
pushd "$TMP_WORK_DIR" >/dev/null
for process_type in $(procfile-util list); do
if echo "$process_type" | grep -vqE '^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'; then
dokku_log_fail "Invalid process type name ($process_type)"
fi
done
popd >/dev/null
}
trigger-ps-post-extract "$@"

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
trigger-ps-post-stop() {
declare desc="ps post-stop plugin trigger"
declare trigger="post-stop"
declare APP="$1"
DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" DOKKU_APP_RESTORE=0
}
trigger-ps-post-stop "$@"

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
trigger-ps-pre-deploy() {
declare desc="ps pre-deploy plugin trigger"
declare trigger="pre-deploy"
declare APP="$1" IMAGE_TAG="$2"
extract_procfile "$APP" "$IMAGE_TAG"
generate_scale_file "$APP" "$IMAGE_TAG"
}
trigger-ps-pre-deploy "$@"

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
trigger-ps-procfile-extract() {
declare desc="ps procfile-extract plugin trigger"
declare trigger="procfile-extract"
declare desc="extract procfile from a specified docker image"
declare APP="$1" IMAGE="$2" FORCE_EXTRACT="$3" PROCFILE_PATH="$4"
if [[ -z "$PROCFILE_PATH" ]]; then
PROCFILE_PATH="$DOKKU_LIB_ROOT/data/ps/$APP/Procfile.extracted"
mkdir -p "${DOKKU_LIB_ROOT}/data/ps/${APP}"
chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/ps/${APP}"
fi
if [[ "$FORCE_EXTRACT" == "true" ]]; then
plugn trigger procfile-remove "$APP" "$PROCFILE_PATH"
fi
fn-procfile-extract "$IMAGE" "$PROCFILE_PATH"
}
trigger-ps-procfile-extract "$@"

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
trigger-ps-procfile-get-command() {
declare desc="ps procfile-get-command plugin trigger"
declare trigger="procfile-get-command"
declare desc="extract command from an app procfile"
declare APP="$1" PROCESS_TYPE="$2" PORT="$3" PROCFILE_PATH="$4"
if [[ -z "$PROCFILE_PATH" ]]; then
PROCFILE_PATH="$DOKKU_LIB_ROOT/data/ps/$APP/Procfile.extracted"
fi
fn-procfile-get-command "$PROCFILE_PATH" "$PROCESS_TYPE" "$PORT"
}
trigger-ps-procfile-get-command "$@"

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
trigger-ps-procfile-remove() {
declare desc="ps procfile-remove plugin trigger"
declare trigger="procfile-remove"
declare desc="remove procfile for a given app"
declare APP="$1" PROCFILE_PATH="$2"
if [[ -z "$PROCFILE_PATH" ]]; then
PROCFILE_PATH="$DOKKU_LIB_ROOT/data/ps/$APP/Procfile.extracted"
fi
if [[ ! -f "$PROCFILE_PATH" ]]; then
return
fi
rm -f "$PROCFILE_PATH" 2>/dev/null
}
trigger-ps-procfile-remove "$@"

148
plugins/ps/ps.go Normal file
View File

@@ -0,0 +1,148 @@
package ps
import (
"fmt"
"github.com/dokku/dokku/plugins/common"
)
const RUN_IN_SERIAL = -1
type ParallelCommand func(string) error
var (
// DefaultProperties is a map of all valid ps properties with corresponding default property values
DefaultProperties = map[string]string{
"restart-policy": "on-failure:10",
}
)
func RunCommandAgainstAllApps(command ParallelCommand, commandName string, runInSerial bool, parallelCount int) error {
if runInSerial && parallelCount != RUN_IN_SERIAL {
common.LogWarn("Ignoring --parallel value and running in serial mode")
}
if runInSerial {
return RunCommandAgainstAllAppsSerially(command, commandName)
}
return RunCommandAgainstAllAppsInParallel(command, commandName, parallelCount)
}
// TODO: implement me
func RunCommandAgainstAllAppsInParallel(command ParallelCommand, commandName string, parallelCount int) error {
return nil
}
func RunCommandAgainstAllAppsSerially(command ParallelCommand, commandName string) error {
apps, err := common.DokkuApps()
if err != nil {
common.LogWarn(err.Error())
return nil
}
errorCount := 0
for _, appName := range apps {
if err = command(appName); err != nil {
errorCount += 1
}
}
if errorCount > 0 {
return fmt.Errorf("%s command returned %d errors", commandName, errorCount)
}
return nil
}
// Rebuild rebuilds app from base image
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
// TODO: implement me
func ReportSingleApp(appName, infoFlag string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
policy, _ := getRestartPolicy(appName)
if policy == "" {
policy = DefaultProperties["restart-policy"]
}
infoFlags := map[string]string{
"--ps-restart-policy": policy,
}
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) {
common.LogWarn(fmt.Sprintf("App %s has not been deployed", appName))
return nil
}
return common.PlugnTrigger("release-and-deploy", []string{appName}...)
}
// Start starts the app
func Start(appName string) error {
imageTag, _ := common.GetRunningImageTag(appName)
if !common.IsDeployed(appName) {
common.LogWarn(fmt.Sprintf("App %s has not been deployed", appName))
return nil
}
if err := common.PlugnTrigger("pre-start", []string{appName}...); err != nil {
return fmt.Errorf("Failure in pre-start hook: %s", err)
}
runningState := getRunningState(appName)
if runningState == "mixed" {
common.LogWarn("App is running in mixed mode, releasing")
}
if runningState != "true" {
if err := common.PlugnTrigger("release-and-deploy", []string{appName, imageTag}...); err != nil {
return err
}
} else {
common.LogWarn(fmt.Sprintf("App %s already running", appName))
}
if err := common.PlugnTrigger("proxy-build-config", []string{appName}...); err != nil {
return err
}
return nil
}
// Stop stops the app
func Stop(appName string) error {
if !common.IsDeployed(appName) {
common.LogWarn(fmt.Sprintf("App %s has not been deployed", appName))
return nil
}
common.LogQuiet(fmt.Sprintf("Stopping %s", appName))
scheduler := common.GetAppScheduler(appName)
if err := common.PlugnTrigger("scheduler-stop", []string{scheduler, appName}...); err != nil {
return err
}
if err := common.PlugnTrigger("post-stop", []string{appName}...); err != nil {
return err
}
return nil
}

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-ps-report-single "$@"

View File

@@ -0,0 +1,62 @@
package main
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"github.com/dokku/dokku/plugins/common"
)
const (
helpHeader = `Usage: dokku ps[:COMMAND]
Manage app processes
Additional commands:`
helpContent = `
ps:inspect <app>, Displays a sanitized version of docker inspect for an app
ps:scale <app> <proc>=<count> [<proc>=<count>...], Get/Set how many instances of a given process to run
ps:start [--parallel count] [--serial] [--all|<app>], Start an app
ps:stop [--parallel count] [--serial] [--all|<app>], Stop an app
ps:rebuild [--parallel count] [--serial] [--all|<app>], Rebuilds an app from source
ps:report [<app>] [<flag>], Displays a process report for one or more apps
ps:restart [--parallel count] [--serial] [--all|<app>], Restart an app
ps:restore, Start previously running apps e.g. after reboot
`
)
func main() {
flag.Usage = usage
flag.Parse()
cmd := flag.Arg(0)
switch cmd {
case "ps", "ps:help":
usage()
case "help":
command := common.NewShellCmd(fmt.Sprintf("ps -o command= %d", os.Getppid()))
command.ShowOutput = false
output, err := command.Output()
if err == nil && strings.Contains(string(output), "--all") {
fmt.Println(helpContent)
} else {
fmt.Print("\n ps, Manage app processes\n")
}
default:
dokkuNotImplementExitCode, err := strconv.Atoi(os.Getenv("DOKKU_NOT_IMPLEMENTED_EXIT"))
if err != nil {
fmt.Println("failed to retrieve DOKKU_NOT_IMPLEMENTED_EXIT environment variable")
dokkuNotImplementExitCode = 10
}
os.Exit(dokkuNotImplementExitCode)
}
}
func usage() {
common.CommandUsage(helpHeader, helpContent)
}

View File

@@ -0,0 +1,92 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/ps"
)
// main entrypoint to all subcommands
func main() {
parts := strings.Split(os.Args[0], "/")
subcommand := parts[len(parts)-1]
var err error
switch subcommand {
case "inspect":
args := flag.NewFlagSet("ps:inspect", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
err = ps.CommandInspect(appName)
case "rebuild":
args := flag.NewFlagSet("ps:rebuild", flag.ExitOnError)
allApps := args.Bool("all", false, "--all: restart all apps")
parallelCount := args.Int("parallel", ps.RUN_IN_SERIAL, "--parallel: number of apps to restart in parallel, -1 to match cpu count")
runInSerial := args.Bool("serial", false, "--serial: restart apps serially")
args.Parse(os.Args[2:])
appName := args.Arg(0)
err = ps.CommandRebuild(appName, *allApps, *runInSerial, *parallelCount)
case "report":
args := flag.NewFlagSet("ps:report", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
infoFlag := args.Arg(1)
err = ps.CommandReport(appName, infoFlag)
case "restart":
args := flag.NewFlagSet("ps:restart", flag.ExitOnError)
allApps := args.Bool("all", false, "--all: restart all apps")
parallelCount := args.Int("parallel", ps.RUN_IN_SERIAL, "--parallel: number of apps to restart in parallel, -1 to match cpu count")
runInSerial := args.Bool("serial", false, "--serial: restart apps serially")
args.Parse(os.Args[2:])
appName := args.Arg(0)
err = ps.CommandRestart(appName, *allApps, *runInSerial, *parallelCount)
case "restore":
args := flag.NewFlagSet("ps:restore", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
err = ps.CommandRestore(appName)
case "retire":
args := flag.NewFlagSet("ps:retire", flag.ExitOnError)
args.Parse(os.Args[2:])
err = ps.CommandRetire()
case "scale":
args := flag.NewFlagSet("ps:scale", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
_, processTuples := common.ShiftString(args.Args())
err = ps.CommandScale(appName, processTuples)
case "set":
args := flag.NewFlagSet("ps:set", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
property := args.Arg(1)
value := args.Arg(2)
err = ps.CommandSet(appName, property, value)
case "start":
args := flag.NewFlagSet("ps:start", flag.ExitOnError)
allApps := args.Bool("all", false, "--all: restart all apps")
parallelCount := args.Int("parallel", ps.RUN_IN_SERIAL, "--parallel: number of apps to restart in parallel, -1 to match cpu count")
runInSerial := args.Bool("serial", false, "--serial: restart apps serially")
args.Parse(os.Args[2:])
appName := args.Arg(0)
err = ps.CommandStart(appName, *allApps, *runInSerial, *parallelCount)
case "stop":
args := flag.NewFlagSet("ps:stop", flag.ExitOnError)
allApps := args.Bool("all", false, "--all: restart all apps")
parallelCount := args.Int("parallel", ps.RUN_IN_SERIAL, "--parallel: number of apps to restart in parallel, -1 to match cpu count")
runInSerial := args.Bool("serial", false, "--serial: restart apps serially")
args.Parse(os.Args[2:])
appName := args.Arg(0)
err = ps.CommandStop(appName, *allApps, *runInSerial, *parallelCount)
default:
common.LogFail(fmt.Sprintf("Invalid plugin subcommand call: %s", subcommand))
}
if err != nil {
common.LogFail(err.Error())
}
}

View File

@@ -0,0 +1,80 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/ps"
)
// main entrypoint to all triggers
func main() {
parts := strings.Split(os.Args[0], "/")
trigger := parts[len(parts)-1]
flag.Parse()
var err error
switch trigger {
case "app-restart":
appName := flag.Arg(0)
err = ps.TriggerAppRestart(appName)
case "core-post-deploy":
appName := flag.Arg(0)
err = ps.TriggerCorePostDeploy(appName)
case "install":
err = ps.TriggerInstall()
case "post-app-clone":
oldAppName := flag.Arg(0)
newAppName := flag.Arg(1)
err = ps.TriggerPostAppClone(oldAppName, newAppName)
case "post-app-rename":
oldAppName := flag.Arg(0)
newAppName := flag.Arg(1)
err = ps.TriggerPostAppRename(oldAppName, newAppName)
case "post-create":
appName := flag.Arg(0)
err = ps.TriggerPostCreate(appName)
case "post-delete":
appName := flag.Arg(0)
err = ps.TriggerPostDelete(appName)
case "post-extract":
appName := flag.Arg(0)
tmpWorkDir := flag.Arg(1)
err = ps.TriggerPostExtract(appName, tmpWorkDir)
case "post-stop":
appName := flag.Arg(0)
err = ps.TriggerPostStop(appName)
case "pre-deploy":
appName := flag.Arg(0)
imageTag := flag.Arg(1)
err = ps.TriggerPreDeploy(appName, imageTag)
case "procfile-extract":
appName := flag.Arg(0)
image := flag.Arg(1)
forceExtract := common.ToBool(flag.Arg(2))
procfilePath := flag.Arg(3)
err = ps.TriggerProcfileExtract(appName, image, forceExtract, procfilePath)
case "procfile-get-command":
appName := flag.Arg(0)
processType := flag.Arg(1)
port := common.ToInt(flag.Arg(2), 5000)
procfilePath := flag.Arg(3)
err = ps.TriggerProcfileGetCommand(appName, processType, port, procfilePath)
case "procfile-remove":
appName := flag.Arg(0)
procfilePath := flag.Arg(1)
err = ps.TriggerProcfileRemove(appName, procfilePath)
case "report":
appName := flag.Arg(0)
err = ps.ReportSingleApp(appName, "")
default:
common.LogFail(fmt.Sprintf("Invalid plugin trigger call: %s", trigger))
}
if err != nil {
common.LogFail(err.Error())
}
}

215
plugins/ps/subcommands.go Normal file
View File

@@ -0,0 +1,215 @@
package ps
import (
"errors"
"fmt"
"os"
"sort"
"strings"
dockeroptions "github.com/dokku/dokku/plugins/docker-options"
"github.com/dokku/dokku/plugins/common"
"github.com/ryanuber/columnize"
)
func CommandInspect(appName string) error {
if appName == "" {
return errors.New("Please specify an app to run the command on")
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
scheduler := common.GetAppScheduler(appName)
return common.PlugnTrigger("scheduler-inspect", []string{scheduler, appName}...)
}
func CommandRebuild(appName string, allApps bool, runInSerial bool, parallelCount int) error {
if allApps {
return RunCommandAgainstAllApps(Rebuild, "rebuild", runInSerial, parallelCount)
}
if appName == "" {
return errors.New("Please specify an app to run the command on")
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
return Rebuild(appName)
}
// CommandReport displays a ps report for one or more apps
func CommandReport(appName string, infoFlag string) error {
if strings.HasPrefix(appName, "--") {
infoFlag = appName
appName = ""
}
if len(appName) == 0 {
apps, err := common.DokkuApps()
if err != nil {
return err
}
for _, appName := range apps {
if err := ReportSingleApp(appName, infoFlag); err != nil {
return err
}
}
return nil
}
return ReportSingleApp(appName, infoFlag)
}
func CommandRestart(appName string, allApps bool, runInSerial bool, parallelCount int) error {
if allApps {
return RunCommandAgainstAllApps(Restart, "restart", runInSerial, parallelCount)
}
if appName == "" {
return errors.New("Please specify an app to run the command on")
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
return Restart(appName)
}
// TODO: implement me
func CommandRestore(appName string) error {
return nil
}
// TODO: implement me
func CommandRetire() error {
return nil
}
// TODO: implement me
func CommandScale(appName string, processTuples []string) error {
if appName == "" {
return errors.New("Please specify an app to run the command on")
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
procfilePath := getProcfilePath(appName)
scalefilePath := getScalefilePath(appName)
if !common.FileExists(procfilePath) {
image := common.GetAppImageRepo(appName)
extractProcfile(appName, image, procfilePath)
}
if !common.FileExists(scalefilePath) {
err := common.SuppressOutput(func() error {
return generateScalefile(appName, scalefilePath)
})
if err != nil {
return err
}
} else if common.FileExists(procfilePath) {
if err := updateScalefile(appName, []string{}); err != nil {
return err
}
}
if len(processTuples) == 0 {
lines, err := common.FileToSlice(scalefilePath)
if err != nil {
return err
}
common.LogInfo1Quiet(fmt.Sprintf("Scaling for %s", appName))
config := columnize.DefaultConfig()
config.Delim = "="
config.Glue = ": "
config.Prefix = " "
config.Empty = ""
content := []string{}
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
content = append(content, "proctype=qty","--------=---")
}
sort.Strings(lines)
for _, line := range lines {
content = append(content, line)
}
for _, line := range content {
s := strings.Split(line, "=")
common.Log(fmt.Sprintf("%s %s", common.RightPad(fmt.Sprintf("%s:", s[0]), 5, " "), s[1]))
}
} else {
if !canScaleApp(appName) {
return fmt.Errorf("App %s contains DOKKU_SCALE file and cannot be manually scaled", appName)
}
if err := updateScalefile(appName, processTuples); err != nil {
return err
}
imageTag, err := common.GetRunningImageTag(appName)
if err != nil {
return err
}
return common.PlugnTrigger("release-and-deploy", []string{appName, imageTag}...)
}
return nil
}
// TODO: implement me
func CommandSet(appName string, property string, value string) error {
if property == "restart-policy" {
if !isValidRestartPolicy(value) {
return errors.New("Invalid restart-policy specified")
}
common.LogInfo2Quiet(fmt.Sprintf("Setting %s to %s", property, value))
return dockeroptions.SetDockerOptionForPhases(appName, []string{"deploy"}, "restart", value)
}
common.CommandPropertySet("ps", appName, property, value, DefaultProperties)
return nil
}
func CommandStart(appName string, allApps bool, runInSerial bool, parallelCount int) error {
if allApps {
return RunCommandAgainstAllApps(Start, "start", runInSerial, parallelCount)
}
if appName == "" {
return errors.New("Please specify an app to run the command on")
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
return Start(appName)
}
func CommandStop(appName string, allApps bool, runInSerial bool, parallelCount int) error {
if allApps {
return RunCommandAgainstAllApps(Stop, "stop", runInSerial, parallelCount)
}
if appName == "" {
return errors.New("Please specify an app to run the command on")
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
return Stop(appName)
}

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_AVAILABLE_PATH/ps/help-functions"
cmd-ps-help "ps:help"

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-ps-inspect "$@"

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
cmd-ps-rebuild() {
declare desc="rebuilds an app from source"
declare cmd="ps:rebuild"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1"
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
if (is_deployed "$APP"); then
ps_rebuild "$APP"
else
dokku_log_warn "App $APP has not been deployed"
fi
}
cmd-ps-rebuild "$@"

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions"
cmd-ps-rebuildall() {
declare desc="rebuilds all apps from source"
declare cmd="ps:rebuildall"
[[ "$1" == "$cmd" ]] && shift 1
fn-ps-parallel-cmd "rebuild"
}
cmd-ps-rebuildall "$@"

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-ps-report "$@"

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
cmd-ps-restart() {
declare desc="restarts app via command line"
declare cmd="ps:restart"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1"
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
plugn trigger app-restart "$APP"
}
cmd-ps-restart "$@"

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/docker-options/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
cmd-ps-restart-policy() {
declare desc="shows the restart-policy for an app"
declare cmd="ps:restart-policy"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1"
local passed_phases="deploy"
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
verify_app_name "$APP"
dokku_log_info2_quiet "$APP restart-policy:"
get_restart_policies "$(get_phase_file_path "$passed_phases")"
}
cmd-ps-restart-policy "$@"

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions"
cmd-ps-restartall() {
declare desc="restarts all apps via command line"
declare cmd="ps:restartall"
[[ "$1" == "$cmd" ]] && shift 1
fn-ps-parallel-cmd "restart"
}
cmd-ps-restartall "$@"

View File

@@ -1,40 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions"
cmd-ps-restore() {
declare desc="starts all apps with DOKKU_APP_RESTORE not set to 0 via command line"
declare cmd="ps:restore"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1"
local DOKKU_SCHEDULER
DOKKU_SCHEDULER=$(get_app_scheduler "$APP")
plugn trigger pre-restore "$DOKKU_SCHEDULER" "$APP"
if [[ -n "$APP" ]]; then
plugn trigger proxy-clear-config "$APP"
if ! (is_deployed "$APP"); then
dokku_log_warn "App $APP has not been deployed"
return
fi
DOKKU_APP_RESTORE="$(config_get "$APP" DOKKU_APP_RESTORE || true)"
if [[ $DOKKU_APP_RESTORE != 0 ]]; then
ps_restore "$APP" || dokku_log_warn "dokku ps:restore ${APP} failed"
fi
else
for APP in $(dokku_apps); do
plugn trigger proxy-clear-config "$APP"
done
fn-ps-parallel-cmd "restore"
fi
}
cmd-ps-restore "$@"

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
cmd-ps-retire() {
declare desc="ensures old containers are retired"
declare cmd="ps:retire"
[[ "$1" == "$cmd" ]] && shift 1
local LOCK_FILE="${DOKKU_LIB_ROOT}/data/ps/retire"
local DOKKU_SCHEDULER=$(config_get "--global" DOKKU_SCHEDULER || echo "docker-local")
acquire_advisory_lock "$LOCK_FILE" "exclusive" "" "Failed to acquire ps:retire lock"
plugn trigger scheduler-retire "$DOKKU_SCHEDULER"
release_advisory_lock "$LOCK_FILE"
}
cmd-ps-retire "$@"

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
cmd-ps-scale() {
declare desc="sets app scaling config via command line"
declare cmd="ps:scale"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1"
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
ps_scale "$@"
}
cmd-ps-scale "$@"

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/docker-options/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
cmd-ps-set-restart-policy() {
declare desc="sets app restart-policy"
declare cmd="ps:set-restart-policy"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1" RESTART_POLICY="$2"
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
verify_app_name "$APP"
local RE_RESTART_POLICY="^(no|unless-stopped|always|on-failure(:[1-9][0-9]*)?)$"
if [[ ! "$RESTART_POLICY" =~ $RE_RESTART_POLICY ]]; then
dokku_log_fail "Please specify an valid restart policy matching the following regex: ${RE_RESTART_POLICY}"
fi
local RESTART_POLICIES=$(get_raw_restart_policies "$(get_phase_file_path "deploy")")
local passed_phases=(deploy)
for policy in $RESTART_POLICIES; do
remove_passed_docker_option passed_phases[@] "$policy"
done
dokku_log_info1_quiet "Setting restart policy: ${RESTART_POLICY}"
local passed_phases=(deploy)
add_passed_docker_option passed_phases[@] "--restart=${RESTART_POLICY}"
}
cmd-ps-set-restart-policy "$@"

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
cmd-ps-start() {
declare desc="starts app via command line"
declare cmd="ps:start"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1"
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
ps_start "$APP"
}
cmd-ps-start "$@"

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions"
cmd-ps-startall() {
declare desc="starts all apps via command line"
declare cmd="ps:startall"
[[ "$1" == "$cmd" ]] && shift 1
fn-ps-parallel-cmd "start"
}
cmd-ps-startall "$@"

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/functions"
cmd-ps-stop() {
declare desc="stops app via command line"
declare cmd="ps:stop"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1"
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
ps_stop "$APP"
}
cmd-ps-stop "$@"

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions"
cmd-ps-stopall() {
declare desc="stops all apps via command line"
declare cmd="ps:stopall"
[[ "$1" == "$cmd" ]] && shift 1
fn-ps-parallel-cmd "stop"
}
cmd-ps-stopall "$@"

193
plugins/ps/triggers.go Normal file
View File

@@ -0,0 +1,193 @@
package ps
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
dockeroptions "github.com/dokku/dokku/plugins/docker-options"
sh "github.com/codeskyblue/go-sh"
)
func TriggerAppRestart(appName string) error {
return Restart(appName)
}
func TriggerCorePostDeploy(appName string) error {
if err := removeProcfile(appName); err != nil {
return err
}
entries := map[string]string{
"DOKKU_APP_RESTORE": "1",
}
return common.SuppressOutput(func() error {
return config.SetMany(appName, entries, false)
})
}
func TriggerInstall() error {
directory := filepath.Join(common.MustGetEnv("DOKKU_LIB_ROOT"), "data", "ps")
if err := os.MkdirAll(directory, 0755); err != nil {
return err
}
if err := common.SetPermissions(directory, 0755); err != nil {
return err
}
apps, err := common.DokkuApps()
if err != nil {
return nil
}
for _, appName := range apps {
policies, err := getRestartPolicy(appName)
if err != nil {
return err
}
if len(policies) != 0 {
continue
}
if err := dockeroptions.AddDockerOptionToPhases(appName, []string{"deploy"}, "--restart=on-failure:10"); err != nil {
common.LogWarn(err.Error())
}
}
return nil
}
func TriggerPostAppClone(oldAppName string, newAppName string) error {
if os.Getenv("SKIP_REBUILD") == "true" {
return nil
}
return Rebuild(newAppName)
}
func TriggerPostAppRename(oldAppName string, newAppName string) error {
if os.Getenv("SKIP_REBUILD") == "true" {
return nil
}
return Rebuild(newAppName)
}
func TriggerPostCreate(appName string) error {
if err := dockeroptions.AddDockerOptionToPhases(appName, []string{"deploy"}, "--restart=on-failure:10"); err != nil {
return err
}
directory := filepath.Join(common.MustGetEnv("DOKKU_LIB_ROOT"), "data", "ps", appName)
if err := os.MkdirAll(directory, 0755); err != nil {
return err
}
if err := common.SetPermissions(directory, 0755); err != nil {
return err
}
return nil
}
// TriggerPostDelete destroys the ps properties for a given app container
func TriggerPostDelete(appName string) error {
return common.PropertyDestroy("ps", appName)
}
func TriggerPostExtract(appName string, tempWorkDir string) error {
procfile := filepath.Join(tempWorkDir, "Procfile")
if !common.FileExists(procfile) {
return nil
}
b, err := sh.Command("procfile-util", "check", "-P", procfile).CombinedOutput()
if err != nil {
return fmt.Errorf(strings.TrimSpace(string(b[:])))
}
return nil
}
func TriggerPostStop(appName string) error {
entries := map[string]string{
"DOKKU_APP_RESTORE": "0",
}
return common.SuppressOutput(func() error {
return config.SetMany(appName, entries, false)
})
}
func TriggerPreDeploy(appName string, imageTag string) error {
image := common.GetAppImageRepo(appName)
removeProcfile(appName)
procfilePath := getProcfilePath(appName)
if err := extractProcfile(appName, image, procfilePath); err != nil {
return err
}
if err := extractOrGenerateScalefile(appName, imageTag); err != nil {
return err
}
return nil
}
func TriggerProcfileExtract(appName string, image string, forceExtract bool, procfilePath string) error {
if procfilePath == "" {
directory := filepath.Join(common.MustGetEnv("DOKKU_LIB_ROOT"), "data", "ps", appName)
if err := os.MkdirAll(directory, 0755); err != nil {
return err
}
if err := common.SetPermissions(directory, 0755); err != nil {
return err
}
procfilePath = getProcfilePath(appName)
}
if forceExtract {
if err := common.PlugnTrigger("procfile-remove", []string{appName, procfilePath}...); err != nil {
return err
}
}
return extractProcfile(appName, image, procfilePath)
}
func TriggerProcfileGetCommand(appName string, processType string, port int, procfilePath string) error {
if procfilePath == "" {
procfilePath = getProcfilePath(appName)
}
command, err := getProcfileCommand(procfilePath, processType, port)
if err != nil {
return err
}
if command != "" {
fmt.Printf("%s\n", command)
}
return nil
}
func TriggerProcfileRemove(appName string, procfilePath string) error {
if procfilePath == "" {
procfilePath = getProcfilePath(appName)
}
if !common.FileExists(procfilePath) {
return nil
}
os.Remove(procfilePath)
return nil
}

View File

@@ -16,13 +16,13 @@ teardown() {
run /bin/bash -c "dokku ps" run /bin/bash -c "dokku ps"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_output_contains "Manage container processes" assert_output_contains "Manage app processes"
help_output="$output" help_output="$output"
run /bin/bash -c "dokku ps:help" run /bin/bash -c "dokku ps:help"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_output_contains "Manage container processes" assert_output_contains "Manage app processes"
assert_output "$help_output" assert_output "$help_output"
} }
@@ -64,7 +64,7 @@ EOF
run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" run /bin/bash -c "dokku --quiet ps:scale $TEST_APP"
output=$(echo "$output" | tr -s " ") output=$(echo "$output" | tr -s " ")
echo "output: ($output)" echo "output: ($output)"
assert_output $'cron: 0 \ncustom: 0 \nrelease: 0 \nweb: 1 \nworker: 0 ' assert_output $'cron: 0\ncustom: 0\nrelease: 0\nweb: 1\nworker: 0'
pushd $TMP pushd $TMP
echo scaletest: sleep infinity >> Procfile echo scaletest: sleep infinity >> Procfile
@@ -74,28 +74,27 @@ EOF
run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" run /bin/bash -c "dokku --quiet ps:scale $TEST_APP"
output=$(echo "$output" | tr -s " ") output=$(echo "$output" | tr -s " ")
echo "output: ($output)" echo "output: ($output)"
assert_output $'cron: 0 \ncustom: 0 \nrelease: 0 \nweb: 1 \nworker: 0 \nscaletest: 0 ' assert_output $'cron: 0\ncustom: 0\nrelease: 0\nscaletest: 0\nweb: 1\nworker: 0'
popd popd
rm -rf "$TMP" rm -rf "$TMP"
} }
@test "(ps:restart-policy) default policy" { @test "(ps:restart-policy) default policy" {
run /bin/bash -c "dokku --quiet ps:restart-policy $TEST_APP" run /bin/bash -c "dokku --quiet ps:report $TEST_APP --ps-restart-policy"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_output "on-failure:10" assert_output "on-failure:10"
} }
@test "(ps:restart-policy) ps:set restart-policy" {
@test "(ps:restart-policy) ps:set-restart-policy, ps:restart-policy" {
for policy in no unless-stopped always on-failure on-failure:20; do for policy in no unless-stopped always on-failure on-failure:20; do
run /bin/bash -c "dokku ps:set-restart-policy $TEST_APP $policy" run /bin/bash -c "dokku ps:set $TEST_APP restart-policy $policy"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_success assert_success
run /bin/bash -c "dokku --quiet ps:restart-policy $TEST_APP" run /bin/bash -c "dokku --quiet ps:report $TEST_APP --ps-restart-policy"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_output "$policy" assert_output "$policy"
@@ -104,12 +103,12 @@ EOF
@test "(ps:restart-policy) deployed policy" { @test "(ps:restart-policy) deployed policy" {
test_restart_policy="on-failure:20" test_restart_policy="on-failure:20"
run /bin/bash -c "dokku ps:set-restart-policy $TEST_APP $test_restart_policy" run /bin/bash -c "dokku ps:set $TEST_APP restart-policy $test_restart_policy"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_success assert_success
run /bin/bash -c "dokku --quiet ps:restart-policy $TEST_APP" run /bin/bash -c "dokku --quiet ps:report $TEST_APP --ps-restart-policy"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_output "$test_restart_policy" assert_output "$test_restart_policy"