This is almost certainly the correct default for Dokku. While it's a BC break and might cause an increase in memory usage, the api is mostly internal and therefore this is safe to use.
320 lines
8.5 KiB
Go
320 lines
8.5 KiB
Go
package ps
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/dokku/dokku/plugins/common"
|
|
"github.com/dokku/dokku/plugins/config"
|
|
dockeroptions "github.com/dokku/dokku/plugins/docker-options"
|
|
)
|
|
|
|
// TriggerAppRestart restarts an app
|
|
func TriggerAppRestart(appName string) error {
|
|
return Restart(appName)
|
|
}
|
|
|
|
// TriggerCorePostDeploy sets a property to
|
|
// allow the app to be restored on boot
|
|
func TriggerCorePostDeploy(appName string) error {
|
|
existingProcfile := getProcfilePath(appName)
|
|
processSpecificProcfile := fmt.Sprintf("%s.%s", existingProcfile, os.Getenv("DOKKU_PID"))
|
|
if common.FileExists(processSpecificProcfile) {
|
|
if err := os.Rename(processSpecificProcfile, existingProcfile); err != nil {
|
|
return err
|
|
}
|
|
} else if common.FileExists(fmt.Sprintf("%s.missing", processSpecificProcfile)) {
|
|
if err := os.Remove(fmt.Sprintf("%s.missing", processSpecificProcfile)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if common.FileExists(existingProcfile) {
|
|
if err := os.Remove(existingProcfile); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := common.PropertyDelete("ps", appName, "scale.old"); err != nil {
|
|
return err
|
|
}
|
|
|
|
entries := map[string]string{
|
|
"DOKKU_APP_RESTORE": "1",
|
|
}
|
|
|
|
return common.SuppressOutput(func() error {
|
|
return config.SetMany(appName, entries, false)
|
|
})
|
|
}
|
|
|
|
// TriggerCorePostExtract ensures that the main Procfile is the one specified by procfile-path
|
|
func TriggerCorePostExtract(appName string, sourceWorkDir string) error {
|
|
procfilePath := strings.Trim(reportComputedProcfilePath(appName), "/")
|
|
if procfilePath == "" {
|
|
procfilePath = "Procfile"
|
|
}
|
|
|
|
existingProcfile := getProcfilePath(appName)
|
|
files, err := filepath.Glob(fmt.Sprintf("%s.*", existingProcfile))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, f := range files {
|
|
if err := os.Remove(f); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
processSpecificProcfile := fmt.Sprintf("%s.%s", existingProcfile, os.Getenv("DOKKU_PID"))
|
|
b, _ := common.PlugnTriggerOutput("git-get-property", []string{appName, "source-image"}...)
|
|
appSourceImage := strings.TrimSpace(string(b[:]))
|
|
|
|
repoDefaultProcfilePath := path.Join(sourceWorkDir, "Procfile")
|
|
if appSourceImage == "" {
|
|
repoProcfilePath := path.Join(sourceWorkDir, procfilePath)
|
|
if !common.FileExists(repoProcfilePath) {
|
|
if procfilePath != "Procfile" && common.FileExists(repoDefaultProcfilePath) {
|
|
if err := os.Remove(repoDefaultProcfilePath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return common.TouchFile(fmt.Sprintf("%s.missing", processSpecificProcfile))
|
|
}
|
|
|
|
if err := common.Copy(repoProcfilePath, processSpecificProcfile); err != nil {
|
|
return fmt.Errorf("Unable to extract Procfile: %v", err.Error())
|
|
}
|
|
|
|
if procfilePath != "Procfile" {
|
|
if err := common.Copy(repoProcfilePath, repoDefaultProcfilePath); err != nil {
|
|
return fmt.Errorf("Unable to move Procfile into place: %v", err.Error())
|
|
}
|
|
}
|
|
} else {
|
|
if err := common.CopyFromImage(appName, appSourceImage, procfilePath, processSpecificProcfile); err != nil {
|
|
return common.TouchFile(fmt.Sprintf("%s.missing", processSpecificProcfile))
|
|
}
|
|
}
|
|
|
|
result, err := common.CallExecCommand(common.ExecCommandInput{
|
|
Command: "procfile-util",
|
|
Args: []string{"check", "-P", processSpecificProcfile},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf(result.StderrContents())
|
|
}
|
|
if result.ExitCode != 0 {
|
|
return fmt.Errorf("Invalid Procfile: %s", result.StderrContents())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TriggerInstall initializes app restart policies
|
|
func TriggerInstall() error {
|
|
if err := common.PropertySetup("ps"); err != nil {
|
|
return fmt.Errorf("Unable to install the ps plugin: %s", err.Error())
|
|
}
|
|
|
|
if err := common.SetupAppData("ps"); err != nil {
|
|
return err
|
|
}
|
|
|
|
apps, err := common.UnfilteredDokkuApps()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
for _, appName := range apps {
|
|
if err := common.CreateAppDataDirectory("ps", appName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|
|
|
|
for _, appName := range apps {
|
|
dokkuScaleFile := filepath.Join(common.AppRoot(appName), "DOKKU_SCALE")
|
|
if common.FileExists(dokkuScaleFile) {
|
|
os.Remove(dokkuScaleFile)
|
|
}
|
|
|
|
dokkuScaleExtracted := filepath.Join(common.AppRoot(appName), "DOKKU_SCALE.extracted")
|
|
if common.FileExists(dokkuScaleExtracted) {
|
|
os.Remove(dokkuScaleExtracted)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TriggerPostAppClone rebuilds the new app
|
|
func TriggerPostAppClone(oldAppName string, newAppName string) error {
|
|
if os.Getenv("SKIP_REBUILD") == "true" {
|
|
return nil
|
|
}
|
|
|
|
return Rebuild(newAppName)
|
|
}
|
|
|
|
// TriggerPostAppCloneSetup creates new ps files
|
|
func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error {
|
|
err := common.PropertyClone("ps", oldAppName, newAppName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return common.CloneAppData("ps", oldAppName, newAppName)
|
|
}
|
|
|
|
// TriggerPostAppRename rebuilds the renamed app
|
|
func TriggerPostAppRename(oldAppName string, newAppName string) error {
|
|
if err := common.MigrateAppDataDirectory("ps", oldAppName, newAppName); err != nil {
|
|
return err
|
|
}
|
|
|
|
if os.Getenv("SKIP_REBUILD") == "true" {
|
|
return nil
|
|
}
|
|
|
|
return Rebuild(newAppName)
|
|
}
|
|
|
|
// TriggerPostAppRenameSetup renames ps files
|
|
func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error {
|
|
if err := common.PropertyClone("ps", oldAppName, newAppName); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := common.PropertyDestroy("ps", oldAppName); err != nil {
|
|
return err
|
|
}
|
|
|
|
return common.CloneAppData("ps", oldAppName, newAppName)
|
|
}
|
|
|
|
// TriggerPostCreate ensures apps have a default restart policy
|
|
// and scale value for web
|
|
func TriggerPostCreate(appName string) error {
|
|
if err := dockeroptions.AddDockerOptionToPhases(appName, []string{"deploy"}, "--restart=on-failure:10"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := common.CreateAppDataDirectory("ps", appName); err != nil {
|
|
return err
|
|
}
|
|
|
|
formations := FormationSlice{
|
|
&Formation{
|
|
ProcessType: "web",
|
|
Quantity: 1,
|
|
},
|
|
}
|
|
return updateScale(appName, false, formations)
|
|
}
|
|
|
|
// TriggerPostDelete destroys the ps properties for a given app container
|
|
func TriggerPostDelete(appName string) error {
|
|
dataErr := common.RemoveAppDataDirectory("ps", appName)
|
|
propertyErr := common.PropertyDestroy("ps", appName)
|
|
|
|
if dataErr != nil {
|
|
return dataErr
|
|
}
|
|
|
|
return propertyErr
|
|
}
|
|
|
|
// TriggerPostStop sets the restore property to false
|
|
func TriggerPostStop(appName string) error {
|
|
entries := map[string]string{
|
|
"DOKKU_APP_RESTORE": "0",
|
|
}
|
|
|
|
return common.SuppressOutput(func() error {
|
|
return config.SetMany(appName, entries, false)
|
|
})
|
|
}
|
|
|
|
// TriggerPreDeploy ensures an app has an up to date scale parameters
|
|
func TriggerPreDeploy(appName string, imageTag string) error {
|
|
if err := updateScale(appName, false, FormationSlice{}); err != nil {
|
|
common.LogDebug(fmt.Sprintf("Error generating scale file: %s", err.Error()))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TriggerProcfileExists checks if a procfile exists
|
|
func TriggerProcfileExists(appName string) error {
|
|
if hasProcfile(appName) {
|
|
return nil
|
|
}
|
|
|
|
return errors.New("Procfile does not exist")
|
|
}
|
|
|
|
// TriggerProcfileGetCommand fetches a command from the procfile
|
|
func TriggerProcfileGetCommand(appName string, processType string, port int) error {
|
|
if !hasProcfile(appName) {
|
|
return nil
|
|
}
|
|
|
|
command, err := getProcfileCommand(getProcessSpecificProcfilePath(appName), processType, port)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if command != "" {
|
|
fmt.Printf("%s\n", command)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TriggerPsCanScale sets whether or not a user can scale an app with ps:scale
|
|
func TriggerPsCanScale(appName string, canScale bool) error {
|
|
return common.PropertyWrite("ps", appName, "can-scale", strconv.FormatBool(canScale))
|
|
}
|
|
|
|
// TriggerPsCurrentScale prints out the current scale contents (process-type=quantity) delimited by newlines
|
|
func TriggerPsCurrentScale(appName string) error {
|
|
formations, err := getFormations(appName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lines := []string{}
|
|
for _, formation := range formations {
|
|
lines = append(lines, fmt.Sprintf("%s=%d", formation.ProcessType, formation.Quantity))
|
|
}
|
|
|
|
fmt.Print(strings.Join(lines, "\n"))
|
|
|
|
return nil
|
|
}
|
|
|
|
// TriggerPsSetScale configures the scale parameters for a given app
|
|
func TriggerPsSetScale(appName string, skipDeploy bool, clearExisting bool, processTuples []string) error {
|
|
return scaleSet(appName, skipDeploy, clearExisting, processTuples)
|
|
}
|