Merge pull request #7579 from dokku/6872-stop-all

Stop all app containers when calling ps:stop
This commit is contained in:
Jose Diaz-Gonzalez
2025-06-23 04:41:25 -04:00
committed by GitHub
20 changed files with 247 additions and 66 deletions

View File

@@ -98,7 +98,7 @@ At this time, the following dokku commands are used to implement a complete sche
- `apps:clone`: handles app cloning - `apps:clone`: handles app cloning
- triggers: post-app-clone-setup - triggers: post-app-clone-setup
- `cron`: generates cron tasks for the app - `cron`: generates cron tasks for the app
- triggers: cron-write - triggers: scheduler-cron-write
- `deploy`: deploys app proceses and checks the status of a deploy - `deploy`: deploys app proceses and checks the status of a deploy
- triggers: scheduler-app-status, scheduler-deploy, scheduler-is-deployed, scheduler-logs-failed - triggers: scheduler-app-status, scheduler-deploy, scheduler-is-deployed, scheduler-logs-failed
- `enter`: enters a running container - `enter`: enters a running container
@@ -112,7 +112,9 @@ At this time, the following dokku commands are used to implement a complete sche
- `run:logs`: fetches running container logs - `run:logs`: fetches running container logs
- triggers: scheduler-run-logs - triggers: scheduler-run-logs
- `ps:stop`: stops app processes - `ps:stop`: stops app processes
- triggers: scheduler-stop - triggers: scheduler-stop, scheduler-cron-write
- `ps:start`: starts app processes
- triggers: scheduler-deploy, scheduler-cron-write
- `ps:inspect`: outputs inspect output for processes in an app - `ps:inspect`: outputs inspect output for processes in an app
- triggers: scheduler-inspect - triggers: scheduler-inspect

View File

@@ -119,6 +119,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO # TODO
``` ```
### `app-json-is-valid`
- Description: Checks to see if the provided app.json file is valid
- Invoked by: Appjson extraction during deployment
- Arguments: `$APP $APP_JSON_PATH`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `app-maybe-create` ### `app-maybe-create`
- Description: Creates an app (gated by whether this is globally enabled or not) - Description: Creates an app (gated by whether this is globally enabled or not)
@@ -583,22 +598,6 @@ DOKKU_SCHEDULER="$1"
# TODO # TODO
``` ```
### `cron-write`
- Description: Force triggers writing out cron entries
- Invoked by:
- Arguments:
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
# TODO
```
### `dependencies` ### `dependencies`
- Description: Used to install system-level dependencies. - Description: Used to install system-level dependencies.
@@ -2445,6 +2444,26 @@ DOKKU_SCHEDULER="$1"; APP="$2";
# TODO # TODO
``` ```
### `scheduler-cron-write`
> [!WARNING]
> The scheduler plugin trigger apis are under development and may change
> between minor releases until the 1.0 release.
- Description: Force triggers writing out cron entries. Arguments are optional.
- Invoked by: `ps:start`, `ps:stop`, `cron:set`
- Arguments: `$DOKKU_SCHEDULER $APP`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
# TODO
```
### `scheduler-deploy` ### `scheduler-deploy`
> [!WARNING] > [!WARNING]

View File

@@ -200,21 +200,9 @@ func GetAppJSON(appName string) (AppJSON, error) {
return AppJSON{}, nil return AppJSON{}, nil
} }
b, err := os.ReadFile(getProcessSpecificAppJSONPath(appName)) appJSONPath := getProcessSpecificAppJSONPath(appName)
if err != nil {
return AppJSON{}, fmt.Errorf("Cannot read app.json file: %v", err)
}
if strings.TrimSpace(string(b)) == "" { return ReadAppJSON(appJSONPath)
return AppJSON{}, nil
}
var appJSON AppJSON
if err = json.Unmarshal(b, &appJSON); err != nil {
return AppJSON{}, fmt.Errorf("Cannot parse app.json: %v", err)
}
return appJSON, nil
} }
func GetAutoscalingConfig(appName string, processType string, replicas int) (FormationAutoscaling, bool, error) { func GetAutoscalingConfig(appName string, processType string, replicas int) (FormationAutoscaling, bool, error) {
@@ -256,3 +244,22 @@ func GetAutoscalingConfig(appName string, processType string, replicas int) (For
return autoscaling, true, nil return autoscaling, true, nil
} }
// ReadAppJSON reads an app.json file from a given path
func ReadAppJSON(path string) (AppJSON, error) {
b, err := os.ReadFile(path)
if err != nil {
return AppJSON{}, fmt.Errorf("Cannot read app.json file: %v", err)
}
if strings.TrimSpace(string(b)) == "" {
return AppJSON{}, nil
}
var appJSON AppJSON
if err = json.Unmarshal(b, &appJSON); err != nil {
return AppJSON{}, fmt.Errorf("Cannot parse app.json: %v", err)
}
return appJSON, nil
}

View File

@@ -1,6 +1,7 @@
package appjson package appjson
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path" "path"
@@ -140,6 +141,22 @@ func TriggerCorePostExtract(appName string, sourceWorkDir string) error {
} }
} }
if common.FileExists(processSpecificAppJSON) {
result, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "app-json-is-valid",
Args: []string{appName, processSpecificAppJSON},
StreamStdout: true,
StreamStderr: true,
})
if err != nil {
if result.StderrContents() != "" {
return errors.New(result.StderrContents())
}
return err
}
}
// TODO: add validation to app.json file by ensuring it can be deserialized // TODO: add validation to app.json file by ensuring it can be deserialized
return nil return nil
} }

View File

@@ -1,5 +1,5 @@
SUBCOMMANDS = subcommands/list subcommands/report subcommands/run subcommands/set SUBCOMMANDS = subcommands/list subcommands/report subcommands/run subcommands/set
TRIGGERS = triggers/cron-get-property triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/scheduler-stop TRIGGERS = triggers/app-json-is-valid triggers/cron-get-property triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/scheduler-stop
BUILD = commands subcommands triggers BUILD = commands subcommands triggers
PLUGIN_NAME = cron PLUGIN_NAME = cron

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
appjson "github.com/dokku/dokku/plugins/app-json" appjson "github.com/dokku/dokku/plugins/app-json"
"github.com/dokku/dokku/plugins/common"
"github.com/multiformats/go-base36" "github.com/multiformats/go-base36"
cronparser "github.com/robfig/cron/v3" cronparser "github.com/robfig/cron/v3"
@@ -61,25 +62,55 @@ func (t TemplateCommand) CronCommand() string {
return fmt.Sprintf("dokku run --cron-id %s %s %s", t.ID, t.App, t.Command) return fmt.Sprintf("dokku run --cron-id %s %s %s", t.ID, t.App, t.Command)
} }
// FetchCronEntriesInput is the input for the FetchCronEntries function
type FetchCronEntriesInput struct {
AppName string
AppJSON *appjson.AppJSON
WarnToFailure bool
}
// FetchCronEntries returns a list of cron commands for a given app // FetchCronEntries returns a list of cron commands for a given app
func FetchCronEntries(appName string) ([]TemplateCommand, error) { func FetchCronEntries(input FetchCronEntriesInput) ([]TemplateCommand, error) {
appName := input.AppName
commands := []TemplateCommand{} commands := []TemplateCommand{}
isMaintenance := reportComputedMaintenance(appName) == "true" isMaintenance := reportComputedMaintenance(appName) == "true"
appJSON, err := appjson.GetAppJSON(appName) if input.AppJSON == nil {
if err != nil { appJSON, err := appjson.GetAppJSON(appName)
return commands, fmt.Errorf("Unable to fetch app.json for app %s: %s", appName, err.Error()) if err != nil {
return commands, fmt.Errorf("Unable to fetch app.json for app %s: %s", appName, err.Error())
}
input.AppJSON = &appJSON
} }
if appJSON.Cron == nil { if input.AppJSON.Cron == nil {
return commands, nil return commands, nil
} }
for _, c := range appJSON.Cron { for i, c := range input.AppJSON.Cron {
if c.Command == "" {
if input.WarnToFailure {
return commands, fmt.Errorf("Missing cron command for app %s (index %d)", appName, i)
}
common.LogWarn(fmt.Sprintf("Missing cron command for app %s (index %d)", appName, i))
continue
}
if c.Schedule == "" {
if input.WarnToFailure {
return commands, fmt.Errorf("Missing cron schedule for app %s (index %d)", appName, i)
}
common.LogWarn(fmt.Sprintf("Missing cron schedule for app %s (index %d)", appName, i))
continue
}
parser := cronparser.NewParser(cronparser.Minute | cronparser.Hour | cronparser.Dom | cronparser.Month | cronparser.Dow | cronparser.Descriptor) parser := cronparser.NewParser(cronparser.Minute | cronparser.Hour | cronparser.Dom | cronparser.Month | cronparser.Dow | cronparser.Descriptor)
_, err := parser.Parse(c.Schedule) _, err := parser.Parse(c.Schedule)
if err != nil { if err != nil {
return commands, fmt.Errorf("Invalid cron schedule %s: %s", c.Schedule, err.Error()) return commands, fmt.Errorf("Invalid cron schedule for app %s (schedule %s): %s", appName, c.Schedule, err.Error())
} }
commands = append(commands, TemplateCommand{ commands = append(commands, TemplateCommand{

View File

@@ -41,7 +41,7 @@ func reportMailto(_ string) string {
} }
func reportTasks(appName string) string { func reportTasks(appName string) string {
c, _ := FetchCronEntries(appName) c, _ := FetchCronEntries(FetchCronEntriesInput{AppName: appName})
return strconv.Itoa(len(c)) return strconv.Itoa(len(c))
} }

View File

@@ -19,6 +19,10 @@ func main() {
var err error var err error
switch trigger { switch trigger {
case "app-json-is-valid":
appName := flag.Arg(0)
appJSONPath := flag.Arg(1)
err = cron.TriggerAppJSONIsValid(appName, appJSONPath)
case "cron-get-property": case "cron-get-property":
appName := flag.Arg(0) appName := flag.Arg(0)
property := flag.Arg(1) property := flag.Arg(1)

View File

@@ -26,7 +26,7 @@ func CommandList(appName string, format string) error {
return fmt.Errorf("Invalid format specified, supported formats: json, stdout") return fmt.Errorf("Invalid format specified, supported formats: json, stdout")
} }
entries, err := FetchCronEntries(appName) entries, err := FetchCronEntries(FetchCronEntriesInput{AppName: appName})
if err != nil { if err != nil {
return err return err
} }
@@ -79,7 +79,7 @@ func CommandRun(appName string, cronID string, detached bool) error {
return err return err
} }
entries, err := FetchCronEntries(appName) entries, err := FetchCronEntries(FetchCronEntriesInput{AppName: appName})
if err != nil { if err != nil {
return err return err
} }
@@ -130,8 +130,10 @@ func CommandSet(appName string, property string, value string) error {
} }
common.CommandPropertySet("cron", appName, property, value, DefaultProperties, GlobalProperties) common.CommandPropertySet("cron", appName, property, value, DefaultProperties, GlobalProperties)
scheduler := common.GetAppScheduler(appName)
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{ _, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "cron-write", Trigger: "scheduler-cron-write",
Args: []string{scheduler, appName},
StreamStdio: true, StreamStdio: true,
}) })
return err return err

View File

@@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
appjson "github.com/dokku/dokku/plugins/app-json"
"github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/common"
) )
@@ -23,6 +24,29 @@ func TriggerCronGetProperty(appName string, key string) error {
return nil return nil
} }
// TriggerAppJSONIsValid validates the cron entries for a given app
func TriggerAppJSONIsValid(appName string, appJSONPath string) error {
if !common.FileExists(appJSONPath) {
return nil
}
appJSON, err := appjson.ReadAppJSON(appJSONPath)
if err != nil {
return err
}
_, err = FetchCronEntries(FetchCronEntriesInput{
AppName: appName,
AppJSON: &appJSON,
WarnToFailure: true,
})
if err != nil {
return err
}
return nil
}
// TriggerInstall runs the install step for the cron plugin // TriggerInstall runs the install step for the cron plugin
func TriggerInstall() error { func TriggerInstall() error {
if err := common.PropertySetup("cron"); err != nil { if err := common.PropertySetup("cron"); err != nil {
@@ -57,7 +81,21 @@ func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error {
// TriggerPostDelete destroys the cron property for a given app container // TriggerPostDelete destroys the cron property for a given app container
func TriggerPostDelete(appName string) error { func TriggerPostDelete(appName string) error {
return common.PropertyDestroy("cron", appName) scheduler := common.GetAppScheduler(appName)
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "scheduler-cron-write",
Args: []string{scheduler, appName},
StreamStdio: true,
})
if err != nil {
return err
}
if err := common.PropertyDestroy("cron", appName); err != nil {
return err
}
return nil
} }
// TriggerSchedulerStop stops the scheduler for a given app container // TriggerSchedulerStop stops the scheduler for a given app container

View File

@@ -1,4 +1,4 @@
TRIGGERS = triggers/cron-write TRIGGERS = triggers/scheduler-cron-write
BUILD = triggers BUILD = triggers
PLUGIN_NAME = scheduler-docker-local PLUGIN_NAME = scheduler-docker-local

View File

@@ -52,10 +52,11 @@ func generateCronEntries() ([]cron.TemplateCommand, error) {
return nil return nil
} }
c, err := cron.FetchCronEntries(appName) c, err := cron.FetchCronEntries(cron.FetchCronEntriesInput{AppName: appName})
if err != nil { if err != nil {
results <- []cron.TemplateCommand{} results <- []cron.TemplateCommand{}
return err common.LogWarn(err.Error())
return nil
} }
results <- c results <- c
@@ -115,7 +116,12 @@ func generateCronEntries() ([]cron.TemplateCommand, error) {
return commands, nil return commands, nil
} }
func writeCronEntries() error { func writeCronEntries(scheduler string) error {
// allow empty scheduler, which means all apps (used by letsencrypt)
if scheduler != "docker-local" && scheduler != "" {
return nil
}
commands, err := generateCronEntries() commands, err := generateCronEntries()
if err != nil { if err != nil {
return err return err

View File

@@ -14,8 +14,6 @@ trigger-scheduler-docker-local-post-delete() {
# remove all application containers # remove all application containers
"$DOCKER_BIN" container ls --filter "label=com.dokku.app-name=${APP}" -q | xargs -n1 -I {} "$DOCKER_BIN" container rm --force {} &>/dev/null || true "$DOCKER_BIN" container ls --filter "label=com.dokku.app-name=${APP}" -q | xargs -n1 -I {} "$DOCKER_BIN" container rm --force {} &>/dev/null || true
plugn trigger cron-write
} }
trigger-scheduler-docker-local-post-delete "$@" trigger-scheduler-docker-local-post-delete "$@"

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -eo pipefail set -eo pipefail
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
[[ $DOKKU_TRACE ]] && set -x [[ $DOKKU_TRACE ]] && set -x
trigger-scheduler-docker-local-post-deploy() { trigger-scheduler-docker-local-post-deploy() {
@@ -7,7 +8,8 @@ trigger-scheduler-docker-local-post-deploy() {
declare trigger="post-deploy" declare trigger="post-deploy"
declare APP="$1" declare APP="$1"
plugn trigger cron-write local DOKKU_SCHEDULER="$(get_app_scheduler "$APP")"
plugn trigger scheduler-cron-write "$DOKKU_SCHEDULER" "$APP"
} }
trigger-scheduler-docker-local-post-deploy "$@" trigger-scheduler-docker-local-post-deploy "$@"

View File

@@ -4,6 +4,12 @@ set -eo pipefail
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions"
fn-stop-container() {
declare APP="$1" CID="$2"
"$DOCKER_BIN" container update --restart=no "$CID" &>/dev/null || true
"$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG "$CID" &>/dev/null || true
}
trigger-scheduler-docker-local-scheduler-stop() { trigger-scheduler-docker-local-scheduler-stop() {
declare desc="scheduler-docker-local scheduler-stop plugin trigger" declare desc="scheduler-docker-local scheduler-stop plugin trigger"
declare trigger="scheduler-stop" declare trigger="scheduler-stop"
@@ -19,14 +25,22 @@ trigger-scheduler-docker-local-scheduler-stop() {
[[ -n "$DOKKU_DOCKER_STOP_TIMEOUT" ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}" [[ -n "$DOKKU_DOCKER_STOP_TIMEOUT" ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}"
if [[ -n "$DOKKU_APP_RUNNING_CONTAINER_IDS" ]]; then if [[ -n "$DOKKU_APP_RUNNING_CONTAINER_IDS" ]]; then
# Disable the container restart policy for CID in $DOKKU_APP_RUNNING_CONTAINER_IDS; do
"$DOCKER_BIN" container update --restart=no $DOKKU_APP_RUNNING_CONTAINER_IDS &>/dev/null || true fn-stop-container "$APP" "$CID"
done
fi
"$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG $DOKKU_APP_RUNNING_CONTAINER_IDS >/dev/null || true # Stop all other containers related to the app
# fetch all containers with the com.dokku.app-name label
local DOKKU_APP_CIDS="$(docker container ls -q -f label=com.dokku.app-name="$APP")"
if [[ -n "$DOKKU_APP_CIDS" ]]; then
for CID in $DOKKU_APP_CIDS; do
fn-stop-container "$APP" "$CID"
done
fi fi
if [[ "$REMOVE_CONTAINERS" == "true" ]]; then if [[ "$REMOVE_CONTAINERS" == "true" ]]; then
local DOKKU_APP_CIDS=$(get_app_container_ids "$APP") local DOKKU_APP_CIDS="$(docker ps -q -f label=com.dokku.app-name="$APP")"
if [[ -n "$DOKKU_APP_CIDS" ]]; then if [[ -n "$DOKKU_APP_CIDS" ]]; then
for CID in $DOKKU_APP_CIDS; do for CID in $DOKKU_APP_CIDS; do
@@ -36,6 +50,8 @@ trigger-scheduler-docker-local-scheduler-stop() {
"$DOCKER_BIN" container rm --force $DOKKU_APP_CIDS &>/dev/null || true "$DOCKER_BIN" container rm --force $DOKKU_APP_CIDS &>/dev/null || true
fi fi
fi fi
plugn trigger scheduler-cron-write "$DOKKU_SCHEDULER" "$APP"
} }
trigger-scheduler-docker-local-scheduler-stop "$@" trigger-scheduler-docker-local-scheduler-stop "$@"

View File

@@ -18,8 +18,9 @@ func main() {
var err error var err error
switch trigger { switch trigger {
case "cron-write": case "scheduler-cron-write":
err = schedulerdockerlocal.TriggerCronWrite() scheduler := flag.Arg(0)
err = schedulerdockerlocal.TriggerSchedulerCronWrite(scheduler)
default: default:
err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) err = fmt.Errorf("Invalid plugin trigger call: %s", trigger)
} }

View File

@@ -1,6 +1,6 @@
package schedulerdockerlocal package schedulerdockerlocal
// TriggerCronWrite force updates the cron file for all apps // TriggerSchedulerCronWrite force updates the cron file for all apps
func TriggerCronWrite() error { func TriggerSchedulerCronWrite(scheduler string) error {
return writeCronEntries() return writeCronEntries(scheduler)
} }

View File

@@ -940,3 +940,32 @@ func streamLogsFromRequest(ctx context.Context, request rest.ResponseWrapper, ou
} }
} }
} }
// SuspendCronJobsInput contains all the information needed to suspend a Kubernetes cron job
type SuspendCronJobsInput struct {
// LabelSelector is the Kubernetes label selector
LabelSelector string
// Namespace is the Kubernetes namespace
Namespace string
}
// SuspendCronJobs suspends a Kubernetes cron job
func (k KubernetesClient) SuspendCronJobs(ctx context.Context, input SuspendCronJobsInput) error {
cronJobs, err := k.Client.BatchV1().CronJobs(input.Namespace).List(ctx, metav1.ListOptions{
LabelSelector: input.LabelSelector,
})
if err != nil {
return err
}
for _, cronJob := range cronJobs.Items {
cronJob.Spec.Suspend = ptr.To(true)
_, err := k.Client.BatchV1().CronJobs(input.Namespace).Update(ctx, &cronJob, metav1.UpdateOptions{})
if err != nil {
return err
}
}
return nil
}

View File

@@ -266,7 +266,7 @@ func TriggerSchedulerDeploy(scheduler string, appName string, imageTag string) e
workingDir := common.GetWorkingDir(appName, image) workingDir := common.GetWorkingDir(appName, image)
allCronEntries, err := cron.FetchCronEntries(appName) allCronEntries, err := cron.FetchCronEntries(cron.FetchCronEntriesInput{AppName: appName})
if err != nil { if err != nil {
return fmt.Errorf("Error fetching cron entries: %w", err) return fmt.Errorf("Error fetching cron entries: %w", err)
} }
@@ -1499,5 +1499,14 @@ func TriggerSchedulerStop(scheduler string, appName string) error {
} }
} }
// get all cronjobs for the app
err = clientset.SuspendCronJobs(ctx, SuspendCronJobsInput{
Namespace: namespace,
LabelSelector: fmt.Sprintf("app.kubernetes.io/part-of=%s", appName),
})
if err != nil {
return fmt.Errorf("Error suspending cron jobs: %w", err)
}
return nil return nil
} }

View File

@@ -121,7 +121,7 @@ teardown() {
echo "echo '@daily;/bin/true'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries echo "echo '@daily;/bin/true'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries
chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries
run /bin/bash -c "dokku plugin:trigger cron-write" run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_success assert_success
@@ -136,7 +136,7 @@ teardown() {
echo "echo '@daily;/bin/true;/var/log/dokku/log.log'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries echo "echo '@daily;/bin/true;/var/log/dokku/log.log'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries
chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries
run /bin/bash -c "dokku plugin:trigger cron-write" run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_success assert_success
@@ -151,7 +151,7 @@ teardown() {
echo "[[ \$1 == 'docker-local' ]] && echo '@daily;/bin/true'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries echo "[[ \$1 == 'docker-local' ]] && echo '@daily;/bin/true'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries
chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries
run /bin/bash -c "dokku plugin:trigger cron-write" run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_success assert_success
@@ -166,7 +166,7 @@ teardown() {
echo "[[ \$1 == 'kubernetes' ]] && echo '@daily;/bin/true'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries echo "[[ \$1 == 'kubernetes' ]] && echo '@daily;/bin/true'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries
chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries
run /bin/bash -c "dokku plugin:trigger cron-write" run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
echo "output: $output" echo "output: $output"
echo "status: $status" echo "status: $status"
assert_success assert_success