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.
- 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.
## 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:report [<app>] [<flag>] # Displays a process report for one or more apps
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: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:startall # Start all deployed app containers
ps:stop <app> # Stop app container(s)
@@ -133,68 +132,29 @@ dokku ps:startall
> 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).
### 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
```
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).
### 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
# 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
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
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
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
dokku ps:set-restart-policy node-js-app on-failure:20
```
```
-----> Setting restart policy: on-failure:20
dokku ps:set node-js-app 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.

View File

@@ -9,6 +9,7 @@ import (
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"unicode"
@@ -379,6 +380,16 @@ func ToBool(s string) bool {
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
func UcFirst(str string) string {
for i, v := range str {

View File

@@ -4,13 +4,39 @@ import (
"bufio"
"fmt"
"os"
"path/filepath"
"sort"
"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
func AddDockerOptionToPhases(appName string, phases []string, option string) error {
for _, phase := range phases {
@@ -58,29 +84,3 @@ func GetDockerOptionsForPhase(appName string, phase string) ([]string, error) {
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"
echo "output: $output"
echo "status: $status"
assert_output_contains "Manage container processes"
assert_output_contains "Manage app processes"
help_output="$output"
run /bin/bash -c "dokku ps:help"
echo "output: $output"
echo "status: $status"
assert_output_contains "Manage container processes"
assert_output_contains "Manage app processes"
assert_output "$help_output"
}
@@ -64,7 +64,7 @@ EOF
run /bin/bash -c "dokku --quiet ps:scale $TEST_APP"
output=$(echo "$output" | tr -s " ")
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
echo scaletest: sleep infinity >> Procfile
@@ -74,28 +74,27 @@ EOF
run /bin/bash -c "dokku --quiet ps:scale $TEST_APP"
output=$(echo "$output" | tr -s " ")
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
rm -rf "$TMP"
}
@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 "status: $status"
assert_output "on-failure:10"
}
@test "(ps:restart-policy) ps:set-restart-policy, ps:restart-policy" {
@test "(ps:restart-policy) ps:set restart-policy" {
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 "status: $status"
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 "status: $status"
assert_output "$policy"
@@ -104,12 +103,12 @@ EOF
@test "(ps:restart-policy) deployed policy" {
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 "status: $status"
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 "status: $status"
assert_output "$test_restart_policy"