package ps import ( "fmt" "strings" "github.com/dokku/dokku/plugins/common" ) // RunInSerial is the default value for whether to run a command in parallel or not // and defaults to -1 (false) const RunInSerial = 0 var ( // DefaultProperties is a map of all valid ps properties with corresponding default property values DefaultProperties = map[string]string{ "restart-policy": "on-failure:10", "procfile-path": "", } // GlobalProperties is a map of all valid global ps properties GlobalProperties = map[string]bool{ "procfile-path": true, } ) // RetireLockFailed wraps error to distinguish between a normal error // and an error where the retire lock could not be fetched type RetireLockFailed struct { Err *error } // ExitCode returns an exit code to use in case this error bubbles // up into an os.Exit() call func (err *RetireLockFailed) ExitCode() int { return 137 } // Error returns a standard non-existent app error func (err *RetireLockFailed) Error() string { if err.Err != nil { e := *err.Err return fmt.Sprintf("Failed to acquire ps:retire lock: %s", e.Error()) } return fmt.Sprintf("Failed to acquire ps:retire lock") } // Formation contains scaling information for a given process type type Formation struct { ProcessType string `json:"process_type"` Quantity int `json:"quantity"` } // FormationSlice contains a slice of Formations that can be sorted type FormationSlice []*Formation func (d FormationSlice) Len() int { return len(d) } func (d FormationSlice) Swap(i, j int) { d[i], d[j] = d[j], d[i] } func (d FormationSlice) Less(i, j int) bool { return d[i].ProcessType < d[j].ProcessType } // Rebuild rebuilds app from base image func Rebuild(appName string) error { return common.PlugnTrigger("receive-app", []string{appName}...) } // 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 } imageTag, err := common.GetRunningImageTag(appName, "") if err != nil { return err } if imageTag == "" { common.LogWarn("No deployed-image-tag property saved, falling back to full release-and-deploy") return common.PlugnTrigger("release-and-deploy", []string{appName}...) } return common.PlugnTrigger("deploy", []string{appName, imageTag}...) } // RestartProcess restarts a process type within an app func RestartProcess(appName string, processName string) error { if !common.IsDeployed(appName) { common.LogWarn(fmt.Sprintf("App %s has not been deployed", appName)) return nil } imageTag, err := common.GetRunningImageTag(appName, "") if err != nil { return err } if imageTag == "" { common.LogWarn("No deployed-image-tag property saved, falling back to full release-and-deploy") return common.PlugnTrigger("release-and-deploy", []string{appName}...) } return common.PlugnTrigger("deploy", []string{appName, imageTag, processName}...) } // Restore ensures an app that should be running is running on boot func Restore(appName string) error { scheduler := common.GetAppScheduler(appName) if err := common.PlugnTrigger("scheduler-pre-restore", []string{scheduler, appName}...); err != nil { return fmt.Errorf("Error running scheduler-pre-restore: %s", err) } common.LogInfo1("Clearing potentially invalid proxy configuration") if err := common.PlugnTrigger("proxy-clear-config", []string{appName}...); err != nil { common.LogWarn(fmt.Sprintf("Error clearing proxy config: %s", err)) } if !common.IsDeployed(appName) { common.LogWarn(fmt.Sprintf("App %s has not been deployed", appName)) return nil } b, _ := common.PlugnTriggerOutput("config-get", []string{appName, "DOKKU_APP_RESTORE"}...) restore := strings.TrimSpace(string(b[:])) if restore == "0" { common.LogWarn(fmt.Sprintf("Skipping ps:restore for %s as DOKKU_APP_RESTORE=%s", appName, restore)) return nil } common.LogInfo1("Starting app") return Start(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") } else if runningState == "false" { common.LogWarn("App has been detected as not running, 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 }