diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index 991b91af0..202c74a9d 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -821,6 +821,20 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `post-container-create` + +- Description: This trigger should be used to do stuff to containers after they are created but before they are started. They are explicitely for commands that may involve network traffic, and _not_ for commands that are self-contained, such as chown or tar. +- Invoked by: `dokku run`, `dokku ps:rebuild`, `dokku deploy` +- Arguments "app|service" "$CONTAINER_ID" "$APP|$SERVICE" "$PHASE" + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `post-create` - Description: Can be used to run commands after an app is created. diff --git a/plugins/app-json/internal-functions b/plugins/app-json/internal-functions index 0be8030f5..c3abb5625 100755 --- a/plugins/app-json/internal-functions +++ b/plugins/app-json/internal-functions @@ -98,8 +98,13 @@ execute_script() { DOKKU_APP_SHELL="$(config_get "$APP" DOKKU_APP_SHELL || echo "$DOKKU_APP_SHELL")" [[ -z "$DOKKU_APP_SHELL" ]] && DOKKU_APP_SHELL="/bin/bash" - id=$("$DOCKER_BIN" container run "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --env DOKKU_TRACE="$DOKKU_TRACE" --label=dokku_phase_script="${PHASE_SCRIPT_KEY}" -d -v "$CACHE_HOST_DIR:/cache" "${ARG_ARRAY[@]}" "$IMAGE" "$DOKKU_APP_SHELL" -c "$COMMAND") - if test "$("$DOCKER_BIN" container wait "$id")" -ne 0; then + local DOKKU_CONTAINER_EXIT_CODE=0 + id=$("$DOCKER_BIN" container create "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --env DOKKU_TRACE="$DOKKU_TRACE" --label=dokku_phase_script="${PHASE_SCRIPT_KEY}" -v "$CACHE_HOST_DIR:/cache" "${ARG_ARRAY[@]}" "$IMAGE" "$DOKKU_APP_SHELL" -c "$COMMAND") + plugn trigger post-container-create "app" "$id" "$APP" "$PHASE_SCRIPT_KEY" + "$DOCKER_BIN" container start "$id" >/dev/null || DOKKU_CONTAINER_EXIT_CODE=$? + DOKKU_CONTAINER_EXIT_CODE="$("$DOCKER_BIN" container wait "$id" 2>/dev/null || echo "$DOKKU_CONTAINER_EXIT_CODE")" + + if [[ "$DOKKU_CONTAINER_EXIT_CODE" -ne 0 ]]; then dokku_container_log_verbose_quiet "$id" dokku_log_fail "execution of '$SCRIPT_CMD' failed!" fi diff --git a/plugins/builder-herokuish/builder-build b/plugins/builder-herokuish/builder-build index a81b5fd68..2627d74c5 100755 --- a/plugins/builder-herokuish/builder-build +++ b/plugins/builder-herokuish/builder-build @@ -44,10 +44,15 @@ trigger-builder-build-builder-herokuish() { declare -a ARG_ARRAY eval "ARG_ARRAY=($DOCKER_ARGS)" - # shellcheck disable=SC2086 - CID=$("$DOCKER_BIN" container run "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS -d -v $DOKKU_APP_HOST_CACHE_DIR:/cache -e CACHE_PATH=/cache "${ARG_ARRAY[@]}" $IMAGE /build) + + local DOKKU_CONTAINER_EXIT_CODE=0 + local CID=$("$DOCKER_BIN" container create "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS -v $DOKKU_APP_HOST_CACHE_DIR:/cache -e CACHE_PATH=/cache "${ARG_ARRAY[@]}" $IMAGE /build) + plugn trigger post-container-create "app" "$CID" "$APP" "build" + "$DOCKER_BIN" container start "$CID" >/dev/null || DOKKU_CONTAINER_EXIT_CODE=$? "$DOCKER_BIN" container attach "$CID" - if test "$("$DOCKER_BIN" container wait "$CID")" -ne 0; then + DOKKU_CONTAINER_EXIT_CODE="$("$DOCKER_BIN" container wait "$CID" 2>/dev/null || echo "$DOKKU_CONTAINER_EXIT_CODE")" + + if [[ "$DOKKU_CONTAINER_EXIT_CODE" -ne 0 ]]; then dokku_log_warn "Failure during app build" return 1 fi diff --git a/plugins/common/common.go b/plugins/common/common.go index 3ad404df2..f83190afa 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" "regexp" "strings" "unicode" @@ -115,6 +116,61 @@ func GetAppImageRepo(appName string) string { return strings.Join([]string{"dokku", appName}, "/") } +// GetAppContainerIDs returns a list of docker container ids for given app and optional container_type +func GetAppContainerIDs(appName string, containerType string) ([]string, error) { + var containerIDs []string + if err := VerifyAppName(appName); err != nil { + return containerIDs, err + } + + dokkuRoot := MustGetEnv("DOKKU_ROOT") + appRoot := fmt.Sprintf("%v/%v", dokkuRoot, appName) + containerFilePath := fmt.Sprintf("%v/CONTAINER", appRoot) + _, err := os.Stat(containerFilePath) + if !os.IsNotExist(err) { + containerIDs = append(containerIDs, ReadFirstLine(containerFilePath)) + } + + containerPattern := fmt.Sprintf("%v/CONTAINER.*", appRoot) + if containerType != "" { + containerPattern = fmt.Sprintf("%v/CONTAINER.%v.*", appRoot, containerType) + if strings.Contains(".", containerType) { + containerPattern = fmt.Sprintf("%v/CONTAINER.%v", appRoot, containerType) + } + } + + files, _ := filepath.Glob(containerPattern) + for _, containerFile := range files { + containerIDs = append(containerIDs, ReadFirstLine(containerFile)) + } + + return containerIDs, nil +} + +// GetAppRunningContainerIDs return a list of running docker container ids for given app and optional container_type +func GetAppRunningContainerIDs(appName string, containerType string) ([]string, error) { + var runningContainerIDs []string + if err := VerifyAppName(appName); err != nil { + return runningContainerIDs, err + } + + if !IsDeployed(appName) { + LogFail(fmt.Sprintf("App %v has not been deployed", appName)) + } + + containerIDs, err := GetAppContainerIDs(appName, containerType) + if err != nil { + return runningContainerIDs, nil + } + for _, containerID := range containerIDs { + if ContainerIsRunning(containerID) { + runningContainerIDs = append(runningContainerIDs, containerID) + } + } + + return runningContainerIDs, nil +} + // ContainerIsRunning checks to see if a container is running func ContainerIsRunning(containerID string) bool { b, err := DockerInspect(containerID, "'{{.State.Running}}'") diff --git a/plugins/network/.gitignore b/plugins/network/.gitignore index 6385eaf1e..dee017e8f 100644 --- a/plugins/network/.gitignore +++ b/plugins/network/.gitignore @@ -1,10 +1,9 @@ /commands +/core-post-deploy /subcommands/* /triggers/* /triggers /network-* /install -/post-app-clone-setup -/post-create -/post-delete +/post-* /report diff --git a/plugins/network/Makefile b/plugins/network/Makefile index 7dd901ccf..fe23e2813 100644 --- a/plugins/network/Makefile +++ b/plugins/network/Makefile @@ -1,5 +1,5 @@ -SUBCOMMANDS = subcommands/rebuild subcommands/rebuildall subcommands/report subcommands/set -TRIGGERS = triggers/install triggers/network-build-config triggers/network-compute-ports triggers/network-config-exists triggers/network-get-ipaddr triggers/network-get-listeners triggers/network-get-port triggers/network-get-property triggers/network-write-ipaddr triggers/network-write-port triggers/post-app-clone-setup triggers/post-create triggers/post-delete triggers/report +SUBCOMMANDS = subcommands/create subcommands/destroy subcommands/exists subcommands/info subcommands/list subcommands/rebuild subcommands/rebuildall subcommands/report subcommands/set +TRIGGERS = triggers/core-post-deploy triggers/install triggers/network-build-config triggers/network-compute-ports triggers/network-config-exists triggers/network-get-ipaddr triggers/network-get-listeners triggers/network-get-port triggers/network-get-property triggers/network-write-ipaddr triggers/network-write-port triggers/post-app-clone-setup triggers/post-container-create triggers/post-create triggers/post-delete triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = network diff --git a/plugins/network/network.go b/plugins/network/network.go index d5b671fd1..5aa80cda3 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -1,6 +1,8 @@ package network import ( + "bytes" + "errors" "fmt" "io/ioutil" "os" @@ -19,9 +21,55 @@ var ( // DefaultProperties is a map of all valid network properties with corresponding default property values DefaultProperties = map[string]string{ "bind-all-interfaces": "false", + "attach-post-create": "", + "attach-post-deploy": "", + "tld": "", } ) +// AttachAppToNetwork attaches a container to a network +func AttachAppToNetwork(containerID string, networkName string, appName string, phase string, processType string) { + cmdParts := []string{ + common.DockerBin(), + "network", + "connect", + } + + if phase == "deploy" { + property := "tld" + defaultValue := GetDefaultValue(property) + tld := common.PropertyGetDefault("network", appName, property, defaultValue) + + networkAlias := fmt.Sprintf("%v.%v", appName, processType) + if tld != "" { + networkAlias = fmt.Sprintf("%v.%v", networkAlias, tld) + } + + cmdParts = append(cmdParts, "--alias") + cmdParts = append(cmdParts, networkAlias) + + hostname, err := common.DockerInspect(containerID, "{{ .Config.Hostname }}") + if err != nil { + common.LogWarn(err.Error()) + } else { + cmdParts = append(cmdParts, "--alias") + cmdParts = append(cmdParts, fmt.Sprintf("%v.%v", hostname, networkAlias)) + } + } + + cmdParts = append(cmdParts, networkName) + cmdParts = append(cmdParts, containerID) + attachCmd := common.NewShellCmd(strings.Join(cmdParts, " ")) + var stderr bytes.Buffer + attachCmd.ShowOutput = false + attachCmd.Command.Stderr = &stderr + _, err := attachCmd.Output() + if err != nil { + err = errors.New(strings.TrimSpace(stderr.String())) + common.LogFail(fmt.Sprintf("Unable to attach container to network: %v", err.Error())) + } +} + // BuildConfig builds network config files func BuildConfig(appName string) { if err := common.VerifyAppName(appName); err != nil { @@ -186,6 +234,21 @@ func HasNetworkConfig(appName string) bool { return common.FileExists(ipfile) && common.FileExists(portfile) } +// ListNetworks returns a list of docker networks +func ListNetworks() ([]string, error) { + b, err := sh.Command(common.DockerBin(), "network", "list", "--format", "{{ .Name }}").Output() + output := strings.TrimSpace(string(b[:])) + + networks := []string{} + if err != nil { + common.LogVerboseQuiet(output) + return networks, err + } + + networks = strings.Split(output, "\n") + return networks, nil +} + // PostAppCloneSetup removes old IP and PORT files for a newly cloned app func PostAppCloneSetup(appName string) bool { dokkuRoot := common.MustGetEnv("DOKKU_ROOT") @@ -217,6 +280,8 @@ func ReportSingleApp(appName, infoFlag string) { infoFlags := map[string]string{ "--network-bind-all-interfaces": common.PropertyGet("network", appName, "bind-all-interfaces"), + "--network-attach-post-create": common.PropertyGet("network", appName, "attach-post-create"), + "--network-attach-post-deploy": common.PropertyGet("network", appName, "attach-post-deploy"), "--network-listeners": strings.Join(GetListeners(appName), " "), } diff --git a/plugins/network/src/subcommands/subcommands.go b/plugins/network/src/subcommands/subcommands.go index 75e88ba89..b83d6aa5a 100644 --- a/plugins/network/src/subcommands/subcommands.go +++ b/plugins/network/src/subcommands/subcommands.go @@ -17,6 +17,38 @@ func main() { flag.Parse() switch subcommand { + case "create": + networkName := flag.Arg(1) + err := network.CommandCreate(networkName) + if err != nil { + common.LogFail(err.Error()) + } + case "destroy": + networkName := flag.Arg(1) + forceDestroy := false + if flag.Arg(2) == "force" { + forceDestroy = true + } + err := network.CommandDestroy(networkName, forceDestroy) + if err != nil { + common.LogFail(err.Error()) + } + case "exists": + networkName := flag.Arg(1) + err := network.CommandExists(networkName) + if err != nil { + common.LogFail(err.Error()) + } + case "info": + err := network.CommandInfo() + if err != nil { + common.LogFail(err.Error()) + } + case "list": + err := network.CommandList() + if err != nil { + common.LogFail(err.Error()) + } case "rebuild": appName := flag.Arg(1) network.BuildConfig(appName) diff --git a/plugins/network/src/triggers/triggers.go b/plugins/network/src/triggers/triggers.go index 0f34ca578..24bdd96d0 100644 --- a/plugins/network/src/triggers/triggers.go +++ b/plugins/network/src/triggers/triggers.go @@ -63,12 +63,22 @@ func main() { case "post-app-clone-setup": appName := flag.Arg(1) network.TriggerPostAppCloneSetup(appName) + case "post-container-create": + containerType := flag.Arg(0) + containerID := flag.Arg(1) + appName := flag.Arg(2) + phase := flag.Arg(3) + processType := flag.Arg(4) + network.TriggerPostContainerCreate(containerType, containerID, appName, phase, processType) case "post-create": appName := flag.Arg(0) network.TriggerPostCreate(appName) case "post-delete": appName := flag.Arg(0) network.TriggerPostDelete(appName) + case "core-post-deploy": + appName := flag.Arg(0) + network.TriggerCorePostDeploy(appName) case "report": appName := flag.Arg(0) network.ReportSingleApp(appName, "") diff --git a/plugins/network/subcommands.go b/plugins/network/subcommands.go index 3e5cdc7d2..d0ad8c270 100644 --- a/plugins/network/subcommands.go +++ b/plugins/network/subcommands.go @@ -1,11 +1,132 @@ package network import ( + "bytes" + "errors" + "fmt" + "os" "strings" "github.com/dokku/dokku/plugins/common" ) +// CommandCreate is an alias for "docker network create" +func CommandCreate(networkName string) error { + common.LogInfo1Quiet(fmt.Sprintf("Creating network %v", networkName)) + createCmd := common.NewShellCmd(strings.Join([]string{ + common.DockerBin(), + "network", + "create", + "--attachable", + "--label", + "com.dokku.network=true", + "--label", + fmt.Sprintf("com.dokku.network-name=%v", networkName), + networkName, + }, " ")) + var stderr bytes.Buffer + createCmd.ShowOutput = false + createCmd.Command.Stderr = &stderr + _, err := createCmd.Output() + + if err != nil { + err = errors.New(strings.TrimSpace(stderr.String())) + } + + return err +} + +// CommandDestroy is an alias for "docker network rm" +func CommandDestroy(networkName string, forceDestroy bool) error { + if os.Getenv("DOKKU_APPS_FORCE_DELETE") == "1" { + forceDestroy = true + } + + if !forceDestroy { + common.LogWarn("WARNING: Potentially Destructive Action") + common.LogWarn(fmt.Sprintf("This command will destroy network %v.", networkName)) + common.LogWarn(fmt.Sprintf("To proceed, type \"%v\"", networkName)) + fmt.Print("> ") + var response string + _, err := fmt.Scanln(&response) + if err != nil { + return err + } + + if response != networkName { + common.LogStderr("Confirmation did not match test. Aborted.") + os.Exit(1) + return nil + } + } + + common.LogInfo1Quiet(fmt.Sprintf("Destroying network %v", networkName)) + destroyCmd := common.NewShellCmd(strings.Join([]string{ + common.DockerBin(), + "network", + "rm", + networkName, + }, " ")) + var stderr bytes.Buffer + destroyCmd.ShowOutput = false + destroyCmd.Command.Stderr = &stderr + _, err := destroyCmd.Output() + + if err != nil { + err = errors.New(strings.TrimSpace(stderr.String())) + } + + return err +} + +// CommandExists checks if a network exists +func CommandExists(networkName string) error { + exists := false + if networkName == "" { + return errors.New("No network name specified") + } + + networks, err := ListNetworks() + if err != nil { + return err + } + + for _, n := range networks { + if networkName == n { + exists = true + break + } + } + + if exists { + common.LogInfo1Quiet("Network exists") + } else { + common.LogFail("Network does not exist") + } + + return nil +} + +// CommandInfo is an alias for "docker network inspect" +func CommandInfo() error { + return nil +} + +// CommandList is an alias for "docker network ls" +func CommandList() error { + networks, err := ListNetworks() + if err != nil { + return err + } + + common.LogInfo2Quiet("Networks") + for _, networkName := range networks { + fmt.Println(networkName) + } + + return nil +} + // CommandRebuildall rebuilds network settings for all apps func CommandRebuildall() { apps, err := common.DokkuApps() @@ -44,5 +165,18 @@ func CommandSet(appName string, property string, value string) { value = "false" } + attachProperites := map[string]bool{ + "attach-post-create": true, + "attach-post-deploy": true, + } + + invalidNetworks := map[string]bool{ + "host": true, + "bridge": true, + } + if attachProperites[property] && invalidNetworks[value] { + common.LogFail("Invalid network name specified for attach") + } + common.CommandPropertySet("network", appName, property, value, DefaultProperties) } diff --git a/plugins/network/triggers.go b/plugins/network/triggers.go index 22f8f3956..86693a98c 100644 --- a/plugins/network/triggers.go +++ b/plugins/network/triggers.go @@ -161,6 +161,22 @@ func TriggerPostAppCloneSetup(appName string) { } } +// TriggerPostContainerCreate associates the container with a specified network +func TriggerPostContainerCreate(containerType string, containerID string, appName string, phase string, processType string) { + if containerType != "app" { + return + } + + property := "attach-post-create" + defaultValue := GetDefaultValue(property) + networkName := common.PropertyGetDefault("network", appName, property, defaultValue) + if networkName == "" { + return + } + + AttachAppToNetwork(containerID, networkName, appName, phase, processType) +} + // TriggerPostCreate sets bind-all-interfaces to false by default func TriggerPostCreate(appName string) { err := common.PropertyWrite("network", appName, "bind-all-interfaces", "false") @@ -176,3 +192,27 @@ func TriggerPostDelete(appName string) { common.LogFail(err.Error()) } } + +// TriggerCorePostDeploy associates the container with a specified network +func TriggerCorePostDeploy(appName string) { + property := "attach-post-deploy" + defaultValue := GetDefaultValue(property) + networkName := common.PropertyGetDefault("network", appName, property, defaultValue) + if networkName == "" { + return + } + + common.LogInfo1Quiet(fmt.Sprintf("Associating app with network %s", networkName)) + containerIDs, err := common.GetAppRunningContainerIDs(appName, "") + if err != nil { + common.LogFail(err.Error()) + } + + for _, containerID := range containerIDs { + processType, err := common.DockerInspect(containerID, "{{ index .Config.Labels \"com.dokku.process-type\"}}") + if err != nil { + common.LogFail(err.Error()) + } + AttachAppToNetwork(containerID, networkName, appName, "deploy", processType) + } +} diff --git a/plugins/scheduler-docker-local/scheduler-deploy b/plugins/scheduler-docker-local/scheduler-deploy index 9d7a46aa1..6e3b6557d 100755 --- a/plugins/scheduler-docker-local/scheduler-deploy +++ b/plugins/scheduler-docker-local/scheduler-deploy @@ -98,18 +98,21 @@ scheduler-docker-local-scheduler-deploy() { START_CMD=$(fn-scheduler-docker-local-extract-start-cmd "$APP" "$PROC_TYPE" "$START_CMD" "$DOKKU_HEROKUISH" "$DOKKU_PORT") if [[ "$DOKKU_NETWORK_BIND_ALL" == "false" ]]; then # shellcheck disable=SC2086 - cid=$("$DOCKER_BIN" container run --label=com.dokku.container-type=deploy --label=com.dokku.process-type=$PROC_TYPE "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --init --detach --env PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD) + cid=$("$DOCKER_BIN" container create --label=com.dokku.container-type=deploy --label=com.dokku.process-type=$PROC_TYPE "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --init --env PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD) else # shellcheck disable=SC2086 - cid=$("$DOCKER_BIN" container run --label=com.dokku.container-type=deploy --label=com.dokku.process-type=$PROC_TYPE "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --init --detach $DOKKU_DOCKER_PORT_ARGS --env PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD) + cid=$("$DOCKER_BIN" container create --label=com.dokku.container-type=deploy --label=com.dokku.process-type=$PROC_TYPE "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --init $DOKKU_DOCKER_PORT_ARGS --env PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD) fi else START_CMD=$(fn-scheduler-docker-local-extract-start-cmd "$APP" "$PROC_TYPE" "$START_CMD" "$DOKKU_HEROKUISH") # shellcheck disable=SC2086 - cid=$("$DOCKER_BIN" container run --label=com.dokku.container-type=deploy --label=com.dokku.process-type=$PROC_TYPE "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --init --detach "${ARG_ARRAY[@]}" $IMAGE $START_CMD) + cid=$("$DOCKER_BIN" container create --label=com.dokku.container-type=deploy --label=com.dokku.process-type=$PROC_TYPE "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --init "${ARG_ARRAY[@]}" $IMAGE $START_CMD) fi + plugn trigger post-container-create "app" "$cid" "$APP" "deploy" "$PROC_TYPE" + "$DOCKER_BIN" container start "$cid" >/dev/null || true + ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$cid") port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$cid") diff --git a/plugins/scheduler-docker-local/scheduler-run b/plugins/scheduler-docker-local/scheduler-run index a91c518ab..cc3f347f7 100755 --- a/plugins/scheduler-docker-local/scheduler-run +++ b/plugins/scheduler-docker-local/scheduler-run @@ -76,6 +76,7 @@ scheduler-docker-local-scheduler-run() { # shellcheck disable=SC2086 CONTAINER_ID="$("$DOCKER_BIN" container create --label=com.dokku.container-type=run "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS "${DOCKER_ARGS_ARRAY[@]}" $IMAGE $EXEC_CMD "$@")" + plugn trigger post-container-create "app" "$CONTAINER_ID" "$APP" "run" declare -a DOCKER_START_ARGS_ARRAY if [[ "$DOKKU_DETACH_CONTAINER" != "1" ]]; then @@ -84,17 +85,17 @@ scheduler-docker-local-scheduler-run() { has_tty && DOCKER_START_ARGS_ARRAY+=" --interactive" fi - local EARLY_EXIT_CODE=0 EXIT_CODE=0 + local EXIT_CODE=0 DOKKU_CONTAINER_EXIT_CODE=0 if [[ "$DOKKU_DETACH_CONTAINER" == "1" ]]; then - "$DOCKER_BIN" container start ${DOCKER_START_ARGS_ARRAY[@]} "$CONTAINER_ID" >/dev/null || CONTAINER_EXIT_CODE=$? + "$DOCKER_BIN" container start ${DOCKER_START_ARGS_ARRAY[@]} "$CONTAINER_ID" >/dev/null || DOKKU_CONTAINER_EXIT_CODE=$? echo "$CONTAINER_ID" else - "$DOCKER_BIN" container start ${DOCKER_START_ARGS_ARRAY[@]} "$CONTAINER_ID" || CONTAINER_EXIT_CODE=$? - EXIT_CODE="$("$DOCKER_BIN" container wait "$CONTAINER_ID" 2>/dev/null || echo "$CONTAINER_EXIT_CODE")" - [[ -z "$EXIT_CODE" ]] && EXIT_CODE=0 + "$DOCKER_BIN" container start ${DOCKER_START_ARGS_ARRAY[@]} "$CONTAINER_ID" || EXIT_CODE=$? + DOKKU_CONTAINER_EXIT_CODE="$("$DOCKER_BIN" container wait "$CONTAINER_ID" 2>/dev/null || echo "$EXIT_CODE")" + [[ -z "$DOKKU_CONTAINER_EXIT_CODE" ]] && DOKKU_CONTAINER_EXIT_CODE=0 fi - plugn trigger scheduler-post-run "$DOKKU_SCHEDULER" "$APP" "$CONTAINER_ID" "$EXIT_CODE" - return "$EXIT_CODE" + plugn trigger scheduler-post-run "$DOKKU_SCHEDULER" "$APP" "$CONTAINER_ID" "$DOKKU_CONTAINER_EXIT_CODE" + return "$DOKKU_CONTAINER_EXIT_CODE" } scheduler-docker-local-scheduler-run "$@"