feat: implement the scheduler plugin

This also performs a one-time migration of the DOKKU_SCHEDULER values the scheduler plugin properties.

Closes #4739
This commit is contained in:
Jose Diaz-Gonzalez
2021-10-09 17:44:35 -04:00
parent 8984b748c5
commit 3c6396fbca
23 changed files with 510 additions and 24 deletions

View File

@@ -101,7 +101,7 @@ func GetAppScheduler(appName string) string {
}
func getAppScheduler(appName string) string {
b, _ := PlugnTriggerOutput("config-get", []string{appName, "DOKKU_SCHEDULER"}...)
b, _ := PlugnTriggerOutput("scheduler-detect", []string{appName}...)
value := strings.TrimSpace(string(b[:]))
if value != "" {
return value
@@ -111,7 +111,7 @@ func getAppScheduler(appName string) string {
// GetGlobalScheduler fetchs the global scheduler
func GetGlobalScheduler() string {
b, _ := PlugnTriggerOutput("config-get-global", []string{"DOKKU_SCHEDULER"}...)
b, _ := PlugnTriggerOutput("scheduler-detect", []string{"--global"}...)
value := strings.TrimSpace(string(b[:]))
if value != "" {
return value

View File

@@ -324,22 +324,8 @@ get_app_image_name() {
get_app_scheduler() {
declare desc="fetch the scheduler for a given application"
declare APP="$1"
local DOKKU_APP_SCHEDULER DOKKU_GLOBAL_SCHEDULER DOKKU_SCHEDULER
if [[ "$APP" == "--global" ]]; then
APP=""
fi
source "$PLUGIN_AVAILABLE_PATH/config/functions"
[[ -n "$APP" ]] && DOKKU_APP_SCHEDULER="$(config_get "$APP" DOKKU_SCHEDULER || true)"
DOKKU_GLOBAL_SCHEDULER="$(config_get --global DOKKU_SCHEDULER || true)"
DOKKU_SCHEDULER=${DOKKU_APP_SCHEDULER:="$DOKKU_GLOBAL_SCHEDULER"}
if [[ -z "$DOKKU_SCHEDULER" ]]; then
DOKKU_SCHEDULER="docker-local"
fi
echo "$DOKKU_SCHEDULER"
"$PLUGIN_CORE_AVAILABLE_PATH/common/common" --quiet scheduler-detect "$APP"
}
get_running_image_tag() {
@@ -690,7 +676,8 @@ release_and_deploy() {
dokku_release "$APP" "$IMAGE_SOURCE_TYPE" "$IMAGE_TAG"
if [[ "$DOKKU_SKIP_DEPLOY" != "true" ]]; then
dokku_log_info1 "Deploying $APP..."
local DOKKU_SCHEDULER=$(get_app_scheduler "$APP")
dokku_log_info1 "Deploying $APP via the $DOKKU_SCHEDULER scheduler..."
cmd-deploy "$APP" "$IMAGE_TAG"
dokku_log_info2 "Application deployed:"
get_app_urls urls "$APP" | sed "s/^/ /"

View File

@@ -32,6 +32,12 @@ func main() {
if !common.IsDeployed(appName) {
err = fmt.Errorf("App %v not deployed", appName)
}
case "scheduler-detect":
appName := flag.Arg(1)
if *global {
appName = "--global"
}
fmt.Print(common.GetAppScheduler(appName))
default:
err = fmt.Errorf("Invalid common command call: %v", cmd)
}

9
plugins/scheduler/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
/commands
/subcommands/*
/triggers/*
/triggers
/network-*
/install
/post-*
/report
/scheduler-detect

View File

@@ -0,0 +1,6 @@
SUBCOMMANDS = subcommands/report subcommands/set
TRIGGERS = triggers/scheduler-detect triggers/install triggers/post-delete triggers/report
BUILD = commands subcommands triggers
PLUGIN_NAME = scheduler
include ../../common.mk

14
plugins/scheduler/go.mod Normal file
View File

@@ -0,0 +1,14 @@
module github.com/dokku/dokku/plugins/scheduler
go 1.15
require (
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0
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/spf13/pflag v1.0.5 // indirect
)
replace github.com/dokku/dokku/plugins/common => ../common
replace github.com/dokku/dokku/plugins/config => ../config

12
plugins/scheduler/go.sum Normal file
View File

@@ -0,0 +1,12 @@
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/joho/godotenv v1.2.0 h1:vGTvz69FzUFp+X4/bAkb0j5BoLC+9bpqTWY8mjhA9pc=
github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
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=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@@ -0,0 +1,4 @@
[plugin]
description = "dokku core scheduler plugin"
version = "0.25.6"
[plugin.config]

View File

@@ -0,0 +1,45 @@
package scheduler
import (
"github.com/dokku/dokku/plugins/common"
)
// ReportSingleApp is an internal function that displays the scheduler report for one or more apps
func ReportSingleApp(appName string, format string, infoFlag string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
flags := map[string]common.ReportFunc{
"--scheduler-computed-selected": reportComputedSelected,
"--scheduler-global-selected": reportGlobalSelected,
"--scheduler-selected": reportSelected,
}
flagKeys := []string{}
for flagKey := range flags {
flagKeys = append(flagKeys, flagKey)
}
trimPrefix := false
uppercaseFirstCharacter := true
infoFlags := common.CollectReport(appName, infoFlag, flags)
return common.ReportSingleApp("scheduler", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter)
}
func reportComputedSelected(appName string) string {
value := reportSelected(appName)
if value == "" {
value = reportGlobalSelected(appName)
}
return value
}
func reportGlobalSelected(appName string) string {
return common.PropertyGetDefault("scheduler", "--global", "selected", "docker-local")
}
func reportSelected(appName string) string {
return common.PropertyGet("scheduler", appName, "selected")
}

View File

@@ -0,0 +1,13 @@
package scheduler
var (
// DefaultProperties is a map of all valid network properties with corresponding default property values
DefaultProperties = map[string]string{
"selected": "docker-local",
}
// GlobalProperties is a map of all valid global network properties
GlobalProperties = map[string]bool{
"selected": true,
}
)

View File

@@ -0,0 +1,56 @@
package main
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"github.com/dokku/dokku/plugins/common"
)
const (
helpHeader = `Usage: dokku scheduler[:COMMAND]
Manage scheduler settings for an app
Additional commands:`
helpContent = `
scheduler:report [<app>] [<flag>], Displays a scheduler report for one or more apps
scheduler:set <app> <property> (<value>), Set or clear a scheduler property for an app
`
)
func main() {
flag.Usage = usage
flag.Parse()
cmd := flag.Arg(0)
switch cmd {
case "scheduler", "scheduler: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 scheduler, Manage scheduler settings for an app\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,50 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/scheduler"
flag "github.com/spf13/pflag"
)
// main entrypoint to all subcommands
func main() {
parts := strings.Split(os.Args[0], "/")
subcommand := parts[len(parts)-1]
var err error
switch subcommand {
case "report":
args := flag.NewFlagSet("scheduler:report", flag.ExitOnError)
format := args.String("format", "stdout", "format: [ stdout | json ]")
osArgs, infoFlag, flagErr := common.ParseReportArgs("scheduler", os.Args[2:])
if flagErr == nil {
args.Parse(osArgs)
appName := args.Arg(0)
err = scheduler.CommandReport(appName, *format, infoFlag)
}
case "set":
args := flag.NewFlagSet("scheduler:set", flag.ExitOnError)
global := args.Bool("global", false, "--global: set a global property")
args.Parse(os.Args[2:])
appName := args.Arg(0)
property := args.Arg(1)
value := args.Arg(2)
if *global {
appName = "--global"
property = args.Arg(0)
value = args.Arg(1)
}
err = scheduler.CommandSet(appName, property, value)
default:
err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,39 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/scheduler"
)
// 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 "scheduler-detect":
appName := flag.Arg(0)
err = scheduler.TriggerSchedulerDetect(appName)
case "install":
err = scheduler.TriggerInstall()
case "post-delete":
appName := flag.Arg(0)
err = scheduler.TriggerPostDelete(appName)
case "report":
appName := flag.Arg(0)
err = scheduler.ReportSingleApp(appName, "", "")
default:
err = fmt.Errorf("Invalid plugin trigger call: %s", trigger)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,29 @@
package scheduler
import (
"github.com/dokku/dokku/plugins/common"
)
// CommandReport displays a scheduler report for one or more apps
func CommandReport(appName string, format string, infoFlag string) error {
if len(appName) == 0 {
apps, err := common.DokkuApps()
if err != nil {
return err
}
for _, appName := range apps {
if err := ReportSingleApp(appName, format, infoFlag); err != nil {
return err
}
}
return nil
}
return ReportSingleApp(appName, format, infoFlag)
}
// CommandSet set or clear a scheduler property for an app
func CommandSet(appName string, property string, value string) error {
common.CommandPropertySet("scheduler", appName, property, value, DefaultProperties, GlobalProperties)
return nil
}

View File

@@ -0,0 +1,71 @@
package scheduler
import (
"fmt"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
)
// TriggerSchedulerDetect outputs a manually selected scheduler for the app
func TriggerSchedulerDetect(appName string) error {
if scheduler := common.PropertyGet("scheduler", appName, "selected"); scheduler != "" {
fmt.Println(scheduler)
return nil
}
if scheduler := common.PropertyGet("scheduler", "--global", "selected"); scheduler != "" {
fmt.Println(scheduler)
return nil
}
fmt.Println("docker-local")
return nil
}
// TriggerInstall runs the install step for the scheduler plugin
func TriggerInstall() error {
if err := common.PropertySetup("scheduler"); err != nil {
return fmt.Errorf("Unable to install the scheduler plugin: %s", err.Error())
}
apps, err := common.DokkuApps()
if err != nil {
return nil
}
globalScheduler := config.GetWithDefault("--scheduler", "DOKKU_SCHEDULER", "")
if globalScheduler != "" {
common.LogVerboseQuiet(fmt.Sprintf("Setting scheduler property 'selected' to %v", globalScheduler))
if err := common.PropertyWrite("scheduler", "--global", "selected", globalScheduler); err != nil {
return err
}
if err := config.UnsetMany("--global", []string{"DOKKU_SCHEDULER"}, false); err != nil {
common.LogWarn(err.Error())
}
}
for _, appName := range apps {
scheduler := config.GetWithDefault(appName, "DOKKU_SCHEDULER", "")
if scheduler == "" {
continue
}
common.LogVerboseQuiet(fmt.Sprintf("Setting scheduler property 'selected' to %v", scheduler))
if err := common.PropertyWrite("scheduler", appName, "selected", scheduler); err != nil {
return err
}
if err := config.UnsetMany(appName, []string{"DOKKU_SCHEDULER"}, false); err != nil {
common.LogWarn(err.Error())
}
}
return nil
}
// TriggerPostDelete destroys the scheduler property for a given app container
func TriggerPostDelete(appName string) error {
return common.PropertyDestroy("scheduler", appName)
}