refactor: execute one-off commands with DOKKU_APP_SHELL as the initial command
This commit changes the launched pod to use the configured DOKKU_APP_SHELL - default /bin/bash - to launch processes. Without this, if a non-interactive process was executed and did not immediately exit, it was possible for that process to launch twice - once when the pod started, and once when entering the pod. Ideally we know whether the process is interactive or not, but this isn't always possible when running under a tty (non-tty == non-interactive). As such, this also removes the ability to launch one-off containers that do not have a configured shell in the container, but this is better than attempting to run commands twice.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package appjson
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func constructScript(command string, shell string, isHerokuishImage bool, isCnbImage bool, dockerfileEntrypoint string) []string {
|
||||
@@ -99,7 +97,7 @@ func getPhaseScript(appName string, phase string) (string, error) {
|
||||
}
|
||||
|
||||
// getReleaseCommand extracts the release command from a given app's procfile
|
||||
func getReleaseCommand(appName string, image string) string {
|
||||
func getReleaseCommand(appName string) string {
|
||||
processType := "release"
|
||||
port := "5000"
|
||||
results, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
@@ -109,40 +107,6 @@ func getReleaseCommand(appName string, image string) string {
|
||||
return results.StdoutContents()
|
||||
}
|
||||
|
||||
func getDokkuAppShell(appName string) string {
|
||||
shell := "/bin/bash"
|
||||
globalShell := ""
|
||||
appShell := ""
|
||||
|
||||
ctx := context.Background()
|
||||
errs, ctx := errgroup.WithContext(ctx)
|
||||
errs.Go(func() error {
|
||||
results, _ := common.CallPlugnTriggerWithContext(ctx, common.PlugnTriggerInput{
|
||||
Trigger: "config-get-global",
|
||||
Args: []string{"DOKKU_APP_SHELL"},
|
||||
})
|
||||
globalShell = results.StdoutContents()
|
||||
return nil
|
||||
})
|
||||
errs.Go(func() error {
|
||||
results, _ := common.CallPlugnTriggerWithContext(ctx, common.PlugnTriggerInput{
|
||||
Trigger: "config-get",
|
||||
Args: []string{appName, "DOKKU_APP_SHELL"},
|
||||
})
|
||||
appShell = results.StdoutContents()
|
||||
return nil
|
||||
})
|
||||
|
||||
errs.Wait()
|
||||
if appShell != "" {
|
||||
shell = appShell
|
||||
} else if globalShell != "" {
|
||||
shell = globalShell
|
||||
}
|
||||
|
||||
return shell
|
||||
}
|
||||
|
||||
func hasAppJSON(appName string) bool {
|
||||
appJSONPath := getAppJSONPath(appName)
|
||||
if common.FileExists(fmt.Sprintf("%s.%s.missing", appJSONPath, os.Getenv("DOKKU_PID"))) {
|
||||
@@ -156,7 +120,7 @@ func hasAppJSON(appName string) bool {
|
||||
return common.FileExists(appJSONPath)
|
||||
}
|
||||
|
||||
func cleanupDeploymentContainer(appName string, containerID string, phase string) error {
|
||||
func cleanupDeploymentContainer(containerID string, phase string) error {
|
||||
if phase != "predeploy" {
|
||||
os.Setenv("DOKKU_SKIP_IMAGE_RETIRE", "true")
|
||||
}
|
||||
@@ -177,7 +141,7 @@ func executeScript(appName string, image string, imageTag string, phase string)
|
||||
command := ""
|
||||
phaseSource := ""
|
||||
if phase == "release" {
|
||||
command = getReleaseCommand(appName, image)
|
||||
command = getReleaseCommand(appName)
|
||||
phaseSource = "Procfile"
|
||||
} else {
|
||||
var err error
|
||||
@@ -207,7 +171,7 @@ func executeScript(appName string, image string, imageTag string, phase string)
|
||||
dockerfileCommand, _ = getCommandFromImage(image)
|
||||
}
|
||||
|
||||
dokkuAppShell := getDokkuAppShell(appName)
|
||||
dokkuAppShell := common.GetDokkuAppShell(appName)
|
||||
script := constructScript(command, dokkuAppShell, isHerokuishImage, isCnbImage, dockerfileEntrypoint)
|
||||
|
||||
imageSourceType := "dockerfile"
|
||||
@@ -304,7 +268,7 @@ func executeScript(appName string, image string, imageTag string, phase string)
|
||||
return fmt.Errorf("Failed to create %s execution container: %s", phase, err.Error())
|
||||
}
|
||||
|
||||
defer cleanupDeploymentContainer(appName, containerID, phase)
|
||||
defer cleanupDeploymentContainer(containerID, phase)
|
||||
|
||||
if !waitForExecution(containerID) {
|
||||
common.LogInfo2Quiet(fmt.Sprintf("Start of %s %s task (%s) output", appName, phaseName, containerID[0:9]))
|
||||
@@ -455,7 +419,7 @@ func createdContainerID(appName string, dockerArgs []string, image string, comma
|
||||
return containerID, err
|
||||
}
|
||||
|
||||
func setScale(appName string, image string) error {
|
||||
func setScale(appName string) error {
|
||||
appJSON, err := GetAppJSON(appName)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -226,7 +226,7 @@ func TriggerPostReleaseBuilder(builderType string, appName string, image string)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setScale(appName, image); err != nil {
|
||||
if err := setScale(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +285,41 @@ func GetRunningImageTag(appName string, imageTag string) (string, error) {
|
||||
return imageTag, nil
|
||||
}
|
||||
|
||||
// GetDokkuAppShell returns the shell for a given app
|
||||
func GetDokkuAppShell(appName string) string {
|
||||
shell := "/bin/bash"
|
||||
globalShell := ""
|
||||
appShell := ""
|
||||
|
||||
ctx := context.Background()
|
||||
errs, ctx := errgroup.WithContext(ctx)
|
||||
errs.Go(func() error {
|
||||
results, _ := CallPlugnTriggerWithContext(ctx, PlugnTriggerInput{
|
||||
Trigger: "config-get-global",
|
||||
Args: []string{"DOKKU_APP_SHELL"},
|
||||
})
|
||||
globalShell = results.StdoutContents()
|
||||
return nil
|
||||
})
|
||||
errs.Go(func() error {
|
||||
results, _ := CallPlugnTriggerWithContext(ctx, PlugnTriggerInput{
|
||||
Trigger: "config-get",
|
||||
Args: []string{appName, "DOKKU_APP_SHELL"},
|
||||
})
|
||||
appShell = results.StdoutContents()
|
||||
return nil
|
||||
})
|
||||
|
||||
errs.Wait()
|
||||
if appShell != "" {
|
||||
shell = appShell
|
||||
} else if globalShell != "" {
|
||||
shell = globalShell
|
||||
}
|
||||
|
||||
return shell
|
||||
}
|
||||
|
||||
// DokkuApps returns a list of all local apps
|
||||
func DokkuApps() ([]string, error) {
|
||||
apps, err := UnfilteredDokkuApps()
|
||||
|
||||
@@ -788,24 +788,7 @@ func TriggerSchedulerEnter(scheduler string, appName string, processType string,
|
||||
|
||||
command := args
|
||||
if len(args) == 0 {
|
||||
command = []string{"/bin/bash"}
|
||||
results, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Trigger: "config-get-global",
|
||||
Args: []string{"DOKKU_APP_SHELL"},
|
||||
})
|
||||
globalShell := results.StdoutContents()
|
||||
if err == nil && globalShell != "" {
|
||||
command = []string{globalShell}
|
||||
}
|
||||
|
||||
results, err = common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Trigger: "config-get",
|
||||
Args: []string{appName, "DOKKU_APP_SHELL"},
|
||||
})
|
||||
appShell := results.StdoutContents()
|
||||
if err == nil && appShell != "" {
|
||||
command = []string{appShell}
|
||||
}
|
||||
command = []string{common.GetDokkuAppShell(appName)}
|
||||
}
|
||||
|
||||
entrypoint := ""
|
||||
@@ -1071,25 +1054,9 @@ func TriggerSchedulerRun(scheduler string, appName string, envCount int, args []
|
||||
|
||||
// todo: do something with docker args
|
||||
command := args
|
||||
commandShell := common.GetDokkuAppShell(appName)
|
||||
if len(args) == 0 {
|
||||
command = []string{"/bin/bash"}
|
||||
results, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Trigger: "config-get-global",
|
||||
Args: []string{"DOKKU_APP_SHELL"},
|
||||
})
|
||||
globalShell := results.StdoutContents()
|
||||
if err == nil && globalShell != "" {
|
||||
command = []string{globalShell}
|
||||
}
|
||||
|
||||
results, err = common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Trigger: "config-get",
|
||||
Args: []string{appName, "DOKKU_APP_SHELL"},
|
||||
})
|
||||
appShell := results.StdoutContents()
|
||||
if err == nil && appShell != "" {
|
||||
command = []string{appShell}
|
||||
}
|
||||
command = []string{commandShell}
|
||||
} else if len(args) == 1 {
|
||||
resp, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Trigger: "procfile-get-command",
|
||||
@@ -1135,7 +1102,7 @@ func TriggerSchedulerRun(scheduler string, appName string, envCount int, args []
|
||||
workingDir := common.GetWorkingDir(appName, image)
|
||||
job, err := templateKubernetesJob(Job{
|
||||
AppName: appName,
|
||||
Command: command,
|
||||
Command: []string{commandShell},
|
||||
DeploymentID: deploymentID,
|
||||
Entrypoint: entrypoint,
|
||||
Env: extraEnv,
|
||||
|
||||
Reference in New Issue
Block a user