mirror of
https://github.com/dokku/dokku.git
synced 2026-08-29 01:59:07 +02:00
fix: retire cron containers past their active deadline
Cron containers were never reaped once they exceeded their active deadline, so a hung cron task ran indefinitely instead of being retired after 24 hours as documented. `cron:run` now also accepts a `--ttl-seconds` argument, matching the one `dokku run` already takes.
This commit is contained in:
@@ -2903,8 +2903,8 @@ DOKKU_SCHEDULER="$1"; APP="$2"; CONTAINER="$3"; TAIL="$4"; PRETTY_PRINT="$5"; NU
|
||||
> The scheduler plugin trigger apis are under development and may change
|
||||
> between minor releases until the 1.0 release.
|
||||
|
||||
- Description: Allows you to retire containers started by the `run` command
|
||||
- Invoked by: `dokku run:retire`
|
||||
- Description: Allows you to retire containers started by the `run` and `cron:run` commands that have exceeded their active deadline
|
||||
- Invoked by: `dokku run:retire`, `dokku ps:retire`
|
||||
- Arguments: `$DOKKU_SCHEDULER $APP`
|
||||
- Example:
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
> New as of 0.23.0
|
||||
|
||||
```
|
||||
cron:list <app> [--format json|stdout] # List scheduled cron tasks for an app
|
||||
cron:report [<app>] [<flag>] # Display report about an app
|
||||
cron:resume <app> <cron_id> # Resume a cron task
|
||||
cron:run <app> <cron_id> [--detach] # Run a cron task on the fly
|
||||
cron:set [--global|<app>] <key> <value> # Set or clear a cron property for an app
|
||||
cron:suspend <app> <cron_id> # Suspend a cron task
|
||||
cron:list <app> [--format json|stdout] # List scheduled cron tasks for an app
|
||||
cron:report [<app>] [<flag>] # Display report about an app
|
||||
cron:resume <app> <cron_id> # Resume a cron task
|
||||
cron:run <app> <cron_id> [--detach] [--ttl-seconds SECONDS] # Run a cron task on the fly
|
||||
cron:set [--global|<app>] <key> <value> # Set or clear a cron property for an app
|
||||
cron:suspend <app> <cron_id> # Suspend a cron task
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -43,7 +43,7 @@ A cron task takes the following properties:
|
||||
|
||||
Zero or more cron tasks can be specified per app. Cron tasks are validated after the build artifact is created but before the app is deployed, and the cron schedule is updated during the post-deploy phase.
|
||||
|
||||
Cron tasks can run for a maximum of 24 hours via the docker-local scheduler, after which they are reaped from the system.
|
||||
Cron tasks can run for a maximum of 24 hours, after which they are reaped from the system. The `docker-local` scheduler reaps expired tasks via the `dokku ps:retire` pass that runs every 5 minutes, so a task may overrun its deadline by up to 5 minutes. The `k3s` scheduler enforces the deadline directly through the job's `activeDeadlineSeconds`.
|
||||
|
||||
See the [app.json location documentation](/docs/advanced-usage/deployment-tasks.md#changing-the-appjson-location) for more information on where to place your `app.json` file.
|
||||
|
||||
@@ -190,6 +190,14 @@ By default, the task is run in an attached container - as supported by the sched
|
||||
dokku cron:run node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 --detach
|
||||
```
|
||||
|
||||
An on-the-fly invocation runs for a maximum of 24 hours (86400 seconds), the same deadline scheduled invocations receive. A different deadline can be requested with the `--ttl-seconds` argument:
|
||||
|
||||
```shell
|
||||
dokku cron:run node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 --detach --ttl-seconds 600
|
||||
```
|
||||
|
||||
The value applies only to that invocation - tasks started by the schedule keep the 24 hour default.
|
||||
|
||||
All one-off cron executions have their containers terminated after invocation.
|
||||
|
||||
#### Displaying reports
|
||||
|
||||
@@ -42,6 +42,23 @@ var (
|
||||
|
||||
const MaintenancePropertyPrefix = "maintenance."
|
||||
|
||||
// DefaultTTLSeconds is how long a cron task may run before it is reaped. The
|
||||
// docker-local scheduler stamps this onto the container as the
|
||||
// com.dokku.active-deadline-seconds label, while the k3s scheduler renders it
|
||||
// as the CronJob's activeDeadlineSeconds.
|
||||
const DefaultTTLSeconds int64 = 86400
|
||||
|
||||
// validateTTLSeconds returns an error if the requested task lifetime is not a
|
||||
// positive number of seconds. A zero or negative deadline would either expire
|
||||
// the task the instant it starts or never expire it at all.
|
||||
func validateTTLSeconds(ttlSeconds int64) error {
|
||||
if ttlSeconds <= 0 {
|
||||
return fmt.Errorf("--ttl-seconds must be a positive integer")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CronTask is a struct that represents a cron task
|
||||
type CronTask struct {
|
||||
// ID is a unique identifier for the cron task
|
||||
|
||||
@@ -123,3 +123,32 @@ func TestDokkuRunCommandAppTaskIgnoresLogFile(t *testing.T) {
|
||||
t.Errorf("DokkuRunCommand() interpolated a redirect into an app task line: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateTTLSeconds pins that a cron task deadline must be a positive
|
||||
// number of seconds. A zero or negative value would either expire the task the
|
||||
// instant it starts or leave it running forever, and the docker-local retire
|
||||
// pass only reaps containers whose deadline has actually elapsed.
|
||||
func TestValidateTTLSeconds(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
ttlSeconds int64
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "default", ttlSeconds: DefaultTTLSeconds},
|
||||
{name: "positive override", ttlSeconds: 1},
|
||||
{name: "zero", ttlSeconds: 0, wantErr: true},
|
||||
{name: "negative", ttlSeconds: -1, wantErr: true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := validateTTLSeconds(tc.ttlSeconds)
|
||||
if tc.wantErr && err == nil {
|
||||
t.Errorf("validateTTLSeconds(%d) = nil, want an error", tc.ttlSeconds)
|
||||
}
|
||||
if !tc.wantErr && err != nil {
|
||||
t.Errorf("validateTTLSeconds(%d) = %v, want nil", tc.ttlSeconds, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ Additional commands:`
|
||||
cron:list <app> [--format json|stdout], List scheduled cron tasks for an app
|
||||
cron:report [<app>] [<flag>], Display report about an app
|
||||
cron:resume <app> <cron_id>, Resume a cron task
|
||||
cron:run <app> <cron_id> [--detach], Run a cron task on the fly
|
||||
cron:run <app> <cron_id> [--detach] [--ttl-seconds SECONDS], Run a cron task on the fly
|
||||
cron:set [--global|<app>] <key> <value>, Set or clear a cron property for an app
|
||||
cron:suspend <app> <cron_id>, Suspend a cron task`
|
||||
)
|
||||
|
||||
@@ -49,10 +49,11 @@ func main() {
|
||||
case "run":
|
||||
args := flag.NewFlagSet("cron:run", flag.ExitOnError)
|
||||
detached := args.Bool("detach", false, "--detach: run the container in a detached mode")
|
||||
ttlSeconds := args.Int64("ttl-seconds", cron.DefaultTTLSeconds, "--ttl-seconds: number of seconds the task may run before it is reaped")
|
||||
args.Parse(os.Args[2:])
|
||||
appName := args.Arg(0)
|
||||
cronID := args.Arg(1)
|
||||
err = cron.CommandRun(appName, cronID, *detached)
|
||||
err = cron.CommandRun(appName, cronID, *detached, *ttlSeconds)
|
||||
case "set":
|
||||
args := flag.NewFlagSet("cron:set", flag.ExitOnError)
|
||||
global := args.Bool("global", false, "--global: set a global property")
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
@@ -97,11 +98,15 @@ func CommandResume(appName string, cronID string) error {
|
||||
}
|
||||
|
||||
// CommandRun executes a cron task on the fly
|
||||
func CommandRun(appName string, cronID string, detached bool) error {
|
||||
func CommandRun(appName string, cronID string, detached bool, ttlSeconds int64) error {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateTTLSeconds(ttlSeconds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tasks, err := FetchCronTasks(FetchCronTasksInput{AppName: appName})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -139,7 +144,7 @@ func CommandRun(appName string, cronID string, detached bool) error {
|
||||
os.Setenv("DOKKU_CONCURRENCY_POLICY", concurrencyPolicy)
|
||||
os.Setenv("DOKKU_CRON_ID", cronID)
|
||||
os.Setenv("DOKKU_RM_CONTAINER", "1")
|
||||
os.Setenv("DOKKU_RUN_TTL_SECONDS", "86400")
|
||||
os.Setenv("DOKKU_RUN_TTL_SECONDS", strconv.FormatInt(ttlSeconds, 10))
|
||||
scheduler := common.GetAppScheduler(appName)
|
||||
args := append([]string{scheduler, appName, "0", "--"}, fields...)
|
||||
_, err = common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
|
||||
@@ -145,7 +145,7 @@ func CommandRetire(appName string) error {
|
||||
return fmt.Errorf("Error retiring containers: %w", err)
|
||||
}
|
||||
|
||||
common.LogInfo1("Retiring expired run containers")
|
||||
common.LogInfo1("Retiring expired run and cron containers")
|
||||
_, err = common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Trigger: "scheduler-run-retire",
|
||||
StreamStdio: true,
|
||||
|
||||
@@ -7,7 +7,7 @@ source "$PLUGIN_AVAILABLE_PATH/scheduler-docker-local/internal-functions"
|
||||
|
||||
fn-scheduler-docker-local-run-retire-container() {
|
||||
declare desc="stop a container"
|
||||
declare CONTAINER_ID="$1"
|
||||
declare CONTAINER_ID="$1" APP="$2"
|
||||
|
||||
local DOKKU_DOCKER_STOP_TIMEOUT="$(plugn trigger ps-get-property "$APP" stop-timeout-seconds || true)"
|
||||
|
||||
@@ -18,34 +18,45 @@ fn-scheduler-docker-local-run-retire-container() {
|
||||
"$DOCKER_BIN" container kill "$CONTAINER_ID" &>/dev/null || true
|
||||
"$DOCKER_BIN" container rm "$CONTAINER_ID" &>/dev/null || true
|
||||
|
||||
if "$DOCKER_BIN" container inspect "$CONTAINER_ID" &>/dev/null; then
|
||||
dokku_log_warn "Unable to retire container ${CONTAINER_ID}"
|
||||
return 1
|
||||
local CONTAINER_STATUS
|
||||
if ! CONTAINER_STATUS="$("$DOCKER_BIN" container inspect "$CONTAINER_ID" --format '{{ .State.Status }}' 2>/dev/null)"; then
|
||||
return
|
||||
fi
|
||||
|
||||
# a container started with --rm is removed by the daemon once it exits, and
|
||||
# stays inspectable in the removing state until that completes
|
||||
if [[ "$CONTAINER_STATUS" == "removing" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
dokku_log_warn "Unable to retire container ${CONTAINER_ID}"
|
||||
return 1
|
||||
}
|
||||
|
||||
fn-scheduler-docker-local-run-retire() {
|
||||
declare desc="stop all run containers that have exceeded their active deadline"
|
||||
declare APP="$1"
|
||||
declare desc="stop all containers of a given type that have exceeded their active deadline"
|
||||
declare CONTAINER_TYPE="$1" APP="$2"
|
||||
local containers=""
|
||||
|
||||
# find all run containers for the specified app
|
||||
container_type_filter="label=com.dokku.container-type=run"
|
||||
# docker ands repeated label filters, so each container type requires its own pass
|
||||
declare -a filters=(--filter "label=com.dokku.container-type=$CONTAINER_TYPE")
|
||||
if [[ -n "$APP" ]]; then
|
||||
app_filter="label=com.dokku.app-name=$APP"
|
||||
containers="$("$DOCKER_BIN" container ls --filter "$container_type_filter" --filter "$app_filter" --format '{{.ID}} {{.CreatedAt}}')"
|
||||
else
|
||||
containers="$("$DOCKER_BIN" container ls --filter "$container_type_filter" --format '{{.ID}} {{.CreatedAt}}')"
|
||||
filters+=(--filter "label=com.dokku.app-name=$APP")
|
||||
fi
|
||||
|
||||
containers="$("$DOCKER_BIN" container ls "${filters[@]}" --format '{{.ID}} {{.CreatedAt}}')"
|
||||
|
||||
if [[ -z "$containers" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# iterate over all containers, ignoring the timezone in the last two columns
|
||||
echo "$containers" | awk '{NF-=2} 1' | while read -r container_id container_date container_time; do
|
||||
# get the cutoff time in seconds from the container's `com.dokku.active-deadline-seconds` label
|
||||
active_deadline_seconds="$("$DOCKER_BIN" container inspect "$container_id" --format '{{ index .Config.Labels "com.dokku.active-deadline-seconds" }}')"
|
||||
# get the cutoff time in seconds from the container's `com.dokku.active-deadline-seconds` label,
|
||||
# along with the owning app so the app's stop timeout is respected during the global pass
|
||||
container_labels="$("$DOCKER_BIN" container inspect "$container_id" --format '{{ index .Config.Labels "com.dokku.active-deadline-seconds" }}|{{ index .Config.Labels "com.dokku.app-name" }}')"
|
||||
active_deadline_seconds="${container_labels%%|*}"
|
||||
container_app="${container_labels##*|}"
|
||||
if [[ -z "$active_deadline_seconds" ]]; then
|
||||
continue
|
||||
fi
|
||||
@@ -59,28 +70,36 @@ fn-scheduler-docker-local-run-retire() {
|
||||
# if the container start time is before the cutoff time, stop the container
|
||||
if [[ "$start_time" -lt "$cutoff_time" ]]; then
|
||||
dokku_log_verbose_quiet "Retiring container ${container_id}"
|
||||
fn-scheduler-docker-local-run-retire-container "$container_id"
|
||||
fn-scheduler-docker-local-run-retire-container "$container_id" "$container_app"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
trigger-scheduler-docker-local-scheduler-run-retire() {
|
||||
declare desc="retires all run containers for an app that have exceeded their active deadline"
|
||||
declare desc="retires all run and cron containers for an app that have exceeded their active deadline"
|
||||
declare trigger="scheduler-run-retire"
|
||||
declare DOKKU_SCHEDULER="$1" APP="$2"
|
||||
declare -a CONTAINER_TYPES=(run cron)
|
||||
local exit_code=0
|
||||
|
||||
if [[ -z "$DOKKU_SCHEDULER" ]]; then
|
||||
dokku_log_info1_quiet "Retiring all run containers"
|
||||
fn-scheduler-docker-local-run-retire
|
||||
return "$?"
|
||||
dokku_log_info1_quiet "Retiring all run and cron containers"
|
||||
for container_type in "${CONTAINER_TYPES[@]}"; do
|
||||
fn-scheduler-docker-local-run-retire "$container_type" || exit_code=1
|
||||
done
|
||||
return "$exit_code"
|
||||
fi
|
||||
|
||||
if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
dokku_log_info1_quiet "Retiring run containers for app ${APP}"
|
||||
fn-scheduler-docker-local-run-retire "$APP"
|
||||
dokku_log_info1_quiet "Retiring run and cron containers for app ${APP}"
|
||||
for container_type in "${CONTAINER_TYPES[@]}"; do
|
||||
fn-scheduler-docker-local-run-retire "$container_type" "$APP" || exit_code=1
|
||||
done
|
||||
|
||||
return "$exit_code"
|
||||
}
|
||||
|
||||
trigger-scheduler-docker-local-scheduler-run-retire "$@"
|
||||
|
||||
@@ -577,12 +577,13 @@ func BuildAppChart(ctx context.Context, appName, imageTag string, opts BuildOpti
|
||||
Args: words,
|
||||
Annotations: annotations,
|
||||
Cron: ProcessCron{
|
||||
ID: cronTask.ID,
|
||||
Hash: cronIDLabelValue(cronTask.ID),
|
||||
Schedule: cronTask.Schedule,
|
||||
Suffix: suffix,
|
||||
Suspend: cronTask.Maintenance,
|
||||
ConcurrencyPolicy: ProcessCronConcurrencyPolicy(concurrencyPolicy),
|
||||
ID: cronTask.ID,
|
||||
Hash: cronIDLabelValue(cronTask.ID),
|
||||
Schedule: cronTask.Schedule,
|
||||
Suffix: suffix,
|
||||
Suspend: cronTask.Maintenance,
|
||||
ConcurrencyPolicy: ProcessCronConcurrencyPolicy(concurrencyPolicy),
|
||||
ActiveDeadlineSeconds: cron.DefaultTTLSeconds,
|
||||
},
|
||||
Labels: labels,
|
||||
ProcessType: ProcessType_Cron,
|
||||
|
||||
@@ -3,6 +3,7 @@ package scheduler_k3s
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -148,6 +149,61 @@ func TestCronJobTemplateRendersLongCronID(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestCronJobTemplateRendersActiveDeadlineSeconds asserts that the job's
|
||||
// activeDeadlineSeconds comes from the chart values rather than a literal
|
||||
// baked into the template, so the deadline cannot drift from the value the
|
||||
// docker-local scheduler stamps onto its cron containers. A release rendered
|
||||
// from values that predate the field must still fall back to 24 hours.
|
||||
func TestCronJobTemplateRendersActiveDeadlineSeconds(t *testing.T) {
|
||||
baseValues := map[string]interface{}{
|
||||
"id": "abcde",
|
||||
"hash": "0123456789abcdef0123456789abcdef01234567",
|
||||
"schedule": "5 5 5 5 5",
|
||||
"suffix": "abcde",
|
||||
"suspend": false,
|
||||
"concurrency_policy": "Allow",
|
||||
}
|
||||
|
||||
t.Run("from values", func(t *testing.T) {
|
||||
values := maps.Clone(baseValues)
|
||||
values["active_deadline_seconds"] = 600
|
||||
if got := renderedActiveDeadlineSeconds(t, renderCronJobTemplate(t, values)); got != 600 {
|
||||
t.Errorf("activeDeadlineSeconds = %d, want 600", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("defaults when absent", func(t *testing.T) {
|
||||
if got := renderedActiveDeadlineSeconds(t, renderCronJobTemplate(t, maps.Clone(baseValues))); got != 86400 {
|
||||
t.Errorf("activeDeadlineSeconds = %d, want 86400", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// renderedActiveDeadlineSeconds pulls jobTemplate.spec.activeDeadlineSeconds
|
||||
// out of a rendered cron-job manifest.
|
||||
func renderedActiveDeadlineSeconds(t *testing.T, manifest string) int {
|
||||
t.Helper()
|
||||
|
||||
var doc map[string]interface{}
|
||||
if err := yaml.NewDecoder(strings.NewReader(manifest)).Decode(&doc); err != nil {
|
||||
t.Fatalf("yaml decode failed (would also fail in Kubernetes API): %v\nrendered:\n%s", err, manifest)
|
||||
}
|
||||
|
||||
spec, _ := doc["spec"].(map[string]interface{})
|
||||
jobTemplate, _ := spec["jobTemplate"].(map[string]interface{})
|
||||
jobSpec, ok := jobTemplate["spec"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("jobTemplate.spec missing from rendered manifest:\n%s", manifest)
|
||||
}
|
||||
|
||||
deadline, ok := jobSpec["activeDeadlineSeconds"].(int)
|
||||
if !ok {
|
||||
t.Fatalf("activeDeadlineSeconds = %v (type %T); want an integer", jobSpec["activeDeadlineSeconds"], jobSpec["activeDeadlineSeconds"])
|
||||
}
|
||||
|
||||
return deadline
|
||||
}
|
||||
|
||||
// renderCronJobTemplate renders templates/chart/cron-job.yaml with the
|
||||
// supplied cron-process values and returns the YAML manifest as a string.
|
||||
// The chart is materialised in a tempdir alongside the shared _helpers.tpl
|
||||
|
||||
@@ -306,12 +306,13 @@ const (
|
||||
)
|
||||
|
||||
type ProcessCron struct {
|
||||
ID string `yaml:"id"`
|
||||
Hash string `yaml:"hash"`
|
||||
Schedule string `yaml:"schedule"`
|
||||
Suffix string `yaml:"suffix"`
|
||||
Suspend bool `yaml:"suspend"`
|
||||
ConcurrencyPolicy ProcessCronConcurrencyPolicy `yaml:"concurrency_policy"`
|
||||
ID string `yaml:"id"`
|
||||
Hash string `yaml:"hash"`
|
||||
Schedule string `yaml:"schedule"`
|
||||
Suffix string `yaml:"suffix"`
|
||||
Suspend bool `yaml:"suspend"`
|
||||
ConcurrencyPolicy ProcessCronConcurrencyPolicy `yaml:"concurrency_policy"`
|
||||
ActiveDeadlineSeconds int64 `yaml:"active_deadline_seconds"`
|
||||
}
|
||||
|
||||
type ProcessCronConcurrencyPolicy string
|
||||
|
||||
@@ -52,7 +52,7 @@ spec:
|
||||
backoffLimit: 0
|
||||
podReplacementPolicy: Failed
|
||||
ttlSecondsAfterFinished: 60
|
||||
activeDeadlineSeconds: 86400
|
||||
activeDeadlineSeconds: {{ $config.cron.active_deadline_seconds | default 86400 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
|
||||
1
tests.mk
1
tests.mk
@@ -181,6 +181,7 @@ go-tests:
|
||||
@$(MAKE) go-test-plugin PLUGIN_NAME=certs
|
||||
@$(MAKE) go-test-plugin PLUGIN_NAME=common
|
||||
@$(MAKE) go-test-plugin PLUGIN_NAME=config
|
||||
@$(MAKE) go-test-plugin PLUGIN_NAME=cron
|
||||
@$(MAKE) go-test-plugin PLUGIN_NAME=docker-options
|
||||
@$(MAKE) go-test-plugin PLUGIN_NAME=logs
|
||||
@$(MAKE) go-test-plugin PLUGIN_NAME=network
|
||||
|
||||
263
tests/unit/cron-1.bats
Normal file
263
tests/unit/cron-1.bats
Normal file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
create_app
|
||||
mkdir -p /var/lib/dokku/plugins/available/cron-entries
|
||||
mkdir -p /var/lib/dokku/plugins/enabled/cron-entries
|
||||
cat >"/var/lib/dokku/plugins/available/cron-entries/plugin.toml" <<EOF
|
||||
[plugin]
|
||||
description = "dokku test cron-entries plugin"
|
||||
version = "0.0.1"
|
||||
[plugin.config]
|
||||
EOF
|
||||
cp /var/lib/dokku/plugins/available/cron-entries/plugin.toml /var/lib/dokku/plugins/enabled/cron-entries/plugin.toml
|
||||
}
|
||||
|
||||
teardown() {
|
||||
rm -rf /var/lib/dokku/plugins/available/cron-entries /var/lib/dokku/plugins/enabled/cron-entries
|
||||
# restore the default scheduler before destroy: a k3s-scheduled app cannot be
|
||||
# torn down cleanly without a cluster, and would otherwise leak into the next
|
||||
# test's cleanup_apps
|
||||
dokku scheduler:set "$TEST_APP" selected docker-local 2>/dev/null || true
|
||||
destroy_app
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@test "(cron) cron:help" {
|
||||
run /bin/bash -c "dokku cron"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "Manage scheduled cron tasks"
|
||||
help_output="$output"
|
||||
|
||||
run /bin/bash -c "dokku cron:help"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "Manage scheduled cron tasks"
|
||||
assert_output "$help_output"
|
||||
}
|
||||
|
||||
@test "(cron) installed" {
|
||||
run /bin/bash -c "test -f /etc/sudoers.d/dokku-cron"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) scheduler-uses-host-cron" {
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-uses-host-cron docker-local"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-uses-host-cron k3s"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "false"
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-uses-host-cron null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "false"
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-uses-host-cron bogus-scheduler"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output ""
|
||||
}
|
||||
|
||||
@test "(cron) invalid [missing-keys]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) invalid [schedule]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid_schedule
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) invalid [seconds]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid_schedule_seconds
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) create [single-verbose]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_single
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $cron_id"
|
||||
assert_output_contains "python3 task.py schedule" 0
|
||||
}
|
||||
|
||||
@test "(cron) create [single-short]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_short
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $cron_id"
|
||||
assert_output_contains "python3 task.py daily" 0
|
||||
}
|
||||
|
||||
@test "(cron) k3s-scheduled app skips host crontab" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_single
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $cron_id"
|
||||
|
||||
# switch the app to a scheduler that manages its own cron backend
|
||||
run /bin/bash -c "dokku scheduler:set $TEST_APP selected k3s"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
# regenerating the host crontab must now skip the k3s app's tasks, leaving
|
||||
# no host-cron tasks and therefore no crontab file
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) crontab format [no raw command leakage]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_single
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $cron_id"
|
||||
# User command must never appear verbatim in the crontab - the crontab
|
||||
# only references the cron ID, and cron:run resolves the command at run
|
||||
# time and exec's it inside the container.
|
||||
assert_output_contains "python3 task.py schedule" 0
|
||||
}
|
||||
|
||||
template_cron_file_invalid() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting invalid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"key": "value"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_invalid_schedule() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting invalid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py",
|
||||
"schedule": "@nonstandard"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_invalid_schedule_seconds() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting invalid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py",
|
||||
"schedule": "0 5 * * * *"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_valid_single() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py schedule",
|
||||
"schedule": "5 5 5 5 5"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_valid_short() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py daily",
|
||||
"schedule": "@daily"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
411
tests/unit/cron-2.bats
Normal file
411
tests/unit/cron-2.bats
Normal file
@@ -0,0 +1,411 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
create_app
|
||||
mkdir -p /var/lib/dokku/plugins/available/cron-entries
|
||||
mkdir -p /var/lib/dokku/plugins/enabled/cron-entries
|
||||
cat >"/var/lib/dokku/plugins/available/cron-entries/plugin.toml" <<EOF
|
||||
[plugin]
|
||||
description = "dokku test cron-entries plugin"
|
||||
version = "0.0.1"
|
||||
[plugin.config]
|
||||
EOF
|
||||
cp /var/lib/dokku/plugins/available/cron-entries/plugin.toml /var/lib/dokku/plugins/enabled/cron-entries/plugin.toml
|
||||
}
|
||||
|
||||
teardown() {
|
||||
rm -rf /var/lib/dokku/plugins/available/cron-entries /var/lib/dokku/plugins/enabled/cron-entries
|
||||
# restore the default scheduler before destroy: a k3s-scheduled app cannot be
|
||||
# torn down cleanly without a cluster, and would otherwise leak into the next
|
||||
# test's cleanup_apps
|
||||
dokku scheduler:set "$TEST_APP" selected docker-local 2>/dev/null || true
|
||||
destroy_app
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@test "(cron:report) --global --format json" {
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -e ."
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-maintenance\")'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "false"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-maintenance\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"global-maintenance\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-maintenance\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"computed-maintenance\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-mailfrom\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"global-mailfrom\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-mailfrom\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"computed-mailfrom\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-mailto\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"global-mailto\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-mailto\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"computed-mailto\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "(cron:set) --global mailto" {
|
||||
run /bin/bash -c "dokku cron:set --global mailto admin@example.com"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailto\"'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "admin@example.com"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailto\"'"
|
||||
assert_success
|
||||
assert_output "admin@example.com"
|
||||
|
||||
run /bin/bash -c "dokku cron:set --global mailto"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailto\"'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output ""
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailto\"'"
|
||||
assert_success
|
||||
assert_output ""
|
||||
}
|
||||
|
||||
@test "(cron:set) --global mailfrom" {
|
||||
run /bin/bash -c "dokku cron:set --global mailfrom dokku@example.com"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailfrom\"'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "dokku@example.com"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailfrom\"'"
|
||||
assert_success
|
||||
assert_output "dokku@example.com"
|
||||
|
||||
run /bin/bash -c "dokku cron:set --global mailfrom"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailfrom\"'"
|
||||
assert_success
|
||||
assert_output ""
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailfrom\"'"
|
||||
assert_success
|
||||
assert_output ""
|
||||
}
|
||||
|
||||
@test "(cron:set) --global maintenance" {
|
||||
run /bin/bash -c "dokku cron:set --global maintenance true"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-maintenance\"'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:set --global maintenance"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
}
|
||||
|
||||
@test "(cron:report) maintenance raw vs computed vs global" {
|
||||
run /bin/bash -c "dokku cron:set --global maintenance"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-maintenance\"'"
|
||||
assert_success
|
||||
assert_output ""
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-global-maintenance\"'"
|
||||
assert_success
|
||||
assert_output ""
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-computed-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "false"
|
||||
|
||||
run /bin/bash -c "dokku cron:set --global maintenance true"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-global-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-computed-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:set $TEST_APP maintenance true"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-global-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
# cron computed-maintenance returns "true" if either per-app or global is
|
||||
# "true" (cron's logic at plugins/cron/report.go::reportComputedMaintenance
|
||||
# is OR-not-override: any "true" wins). This is intentional for maintenance
|
||||
# mode - a per-app "false" does NOT override a global "true".
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-computed-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:set $TEST_APP maintenance"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:set --global maintenance"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "(cron:set) --global rejects task maintenance properties" {
|
||||
run /bin/bash -c "dokku cron:set --global maintenance.fakeid true"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
assert_output_contains "Task maintenance properties cannot be set globally"
|
||||
}
|
||||
|
||||
@test "(cron) create [multiple]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_multiple
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
first_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
second_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[1].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $first_id"
|
||||
assert_output_contains "dokku cron:run $TEST_APP $second_id"
|
||||
assert_output_contains "python3 task.py first" 0
|
||||
assert_output_contains "python3 task.py second" 0
|
||||
}
|
||||
|
||||
@test "(cron) cron:list --format json" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:list $TEST_APP --format json"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_exists
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
run /bin/bash -c "echo $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_exists
|
||||
}
|
||||
|
||||
@test "(cron) cron:run" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
run /bin/bash -c "echo $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_exists
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "['task.py', 'schedule']"
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[1].id')"
|
||||
run /bin/bash -c "echo $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_exists
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "['task.py', 'schedule', 'now']"
|
||||
}
|
||||
|
||||
@test "(cron) cron:suspend cron:resume" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_multiple
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
first_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
second_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[1].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $first_id"
|
||||
assert_output_contains "dokku cron:run $TEST_APP $second_id"
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --cron-maintenance-$first_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
|
||||
run /bin/bash -c "dokku cron:suspend $TEST_APP $first_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --cron-maintenance-$first_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $first_id" 0
|
||||
assert_output_contains "dokku cron:run $TEST_APP $second_id"
|
||||
|
||||
run /bin/bash -c "dokku cron:resume $TEST_APP $first_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --cron-maintenance-$first_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $first_id"
|
||||
assert_output_contains "dokku cron:run $TEST_APP $second_id"
|
||||
}
|
||||
|
||||
template_cron_file_valid() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py schedule",
|
||||
"schedule": "5 5 5 5 5"
|
||||
},
|
||||
{
|
||||
"command": "python3 task.py schedule now",
|
||||
"schedule": "6 5 5 5 5"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_valid_multiple() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py first",
|
||||
"schedule": "5 5 5 5 5"
|
||||
},
|
||||
{
|
||||
"command": "python3 task.py second",
|
||||
"schedule": "@daily"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
310
tests/unit/cron-3.bats
Normal file
310
tests/unit/cron-3.bats
Normal file
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
create_app
|
||||
mkdir -p /var/lib/dokku/plugins/available/cron-entries
|
||||
mkdir -p /var/lib/dokku/plugins/enabled/cron-entries
|
||||
cat >"/var/lib/dokku/plugins/available/cron-entries/plugin.toml" <<EOF
|
||||
[plugin]
|
||||
description = "dokku test cron-entries plugin"
|
||||
version = "0.0.1"
|
||||
[plugin.config]
|
||||
EOF
|
||||
cp /var/lib/dokku/plugins/available/cron-entries/plugin.toml /var/lib/dokku/plugins/enabled/cron-entries/plugin.toml
|
||||
}
|
||||
|
||||
teardown() {
|
||||
rm -rf /var/lib/dokku/plugins/available/cron-entries /var/lib/dokku/plugins/enabled/cron-entries
|
||||
# restore the default scheduler before destroy: a k3s-scheduled app cannot be
|
||||
# torn down cleanly without a cluster, and would otherwise leak into the next
|
||||
# test's cleanup_apps
|
||||
dokku scheduler:set "$TEST_APP" selected docker-local 2>/dev/null || true
|
||||
destroy_app
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@test "(cron) create [empty]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) injected 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
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:list --global --format json"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output '[{"id":"5cruaotm4yzzpnjlsdunblj8qyjp","command":"/bin/true","global":true,"schedule":"@daily","concurrency_policy":"","app-in-maintenance":false,"task-in-maintenance":false,"maintenance":false}]'
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "@daily /bin/true"
|
||||
|
||||
# log file
|
||||
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
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "@daily /bin/true &>> /var/log/dokku/log.log"
|
||||
|
||||
# specify matching scheduler
|
||||
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
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "@daily /bin/true"
|
||||
|
||||
# specify non-matching scheduler
|
||||
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
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) invalid [command]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_injection
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) cron:run concurrency_policy forbid" {
|
||||
run deploy_app dockerfile dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_concurrency_forbid
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
run /bin/bash -c "echo cron $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_exists
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id --detach"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "docker ps --filter "label=com.dokku.cron-id=$cron_id" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.concurrency-policy\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "forbid"
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "currently has a cron lock in place for $cron_id"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) container labels regression" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_long_running
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id --detach"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.cron-id\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "$cron_id"
|
||||
|
||||
run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.concurrency-policy\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "allow"
|
||||
|
||||
run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.container-type\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "cron"
|
||||
|
||||
run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.app-name\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "$TEST_APP"
|
||||
|
||||
run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.active-deadline-seconds\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "86400"
|
||||
}
|
||||
|
||||
@test "(cron:run) retires containers past their active deadline" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_long_running
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id --detach --ttl-seconds 1"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
expiring_container="$output"
|
||||
|
||||
run /bin/bash -c "docker container inspect $expiring_container --format '{{ index .Config.Labels \"com.dokku.active-deadline-seconds\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "1"
|
||||
|
||||
run /bin/bash -c "docker container inspect $expiring_container --format '{{ index .Config.Labels \"com.dokku.container-type\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "cron"
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id --detach --ttl-seconds 300"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
surviving_container="$output"
|
||||
|
||||
run /bin/bash -c "sleep 2"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ps:retire"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "docker container ls --filter \"label=com.dokku.container-type=cron\" --format '{{ .Names }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "$expiring_container"
|
||||
assert_output_contains "$surviving_container"
|
||||
}
|
||||
|
||||
@test "(cron:run) --ttl-seconds must be a positive integer" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_long_running
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id --detach --ttl-seconds 0"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
assert_output_contains "--ttl-seconds must be a positive integer"
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id --detach --ttl-seconds -1"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
assert_output_contains "--ttl-seconds must be a positive integer"
|
||||
}
|
||||
|
||||
template_cron_file_injection() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting injection-attempt cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "echo CRON_OK; echo hi > /tmp/appjson-injection-test.txt",
|
||||
"schedule": "* * * * *"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_concurrency_forbid() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "sleep 30",
|
||||
"schedule": "0 0 * * *",
|
||||
"concurrency_policy": "forbid"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_long_running() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting long-running cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "sleep 30",
|
||||
"schedule": "0 0 * * *"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
@@ -1,858 +0,0 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
create_app
|
||||
mkdir -p /var/lib/dokku/plugins/available/cron-entries
|
||||
mkdir -p /var/lib/dokku/plugins/enabled/cron-entries
|
||||
cat >"/var/lib/dokku/plugins/available/cron-entries/plugin.toml" <<EOF
|
||||
[plugin]
|
||||
description = "dokku test cron-entries plugin"
|
||||
version = "0.0.1"
|
||||
[plugin.config]
|
||||
EOF
|
||||
cp /var/lib/dokku/plugins/available/cron-entries/plugin.toml /var/lib/dokku/plugins/enabled/cron-entries/plugin.toml
|
||||
}
|
||||
|
||||
teardown() {
|
||||
rm -rf /var/lib/dokku/plugins/available/cron-entries /var/lib/dokku/plugins/enabled/cron-entries
|
||||
# restore the default scheduler before destroy: a k3s-scheduled app cannot be
|
||||
# torn down cleanly without a cluster, and would otherwise leak into the next
|
||||
# test's cleanup_apps
|
||||
dokku scheduler:set "$TEST_APP" selected docker-local 2>/dev/null || true
|
||||
destroy_app
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@test "(cron) cron:help" {
|
||||
run /bin/bash -c "dokku cron"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "Manage scheduled cron tasks"
|
||||
help_output="$output"
|
||||
|
||||
run /bin/bash -c "dokku cron:help"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "Manage scheduled cron tasks"
|
||||
assert_output "$help_output"
|
||||
}
|
||||
|
||||
@test "(cron) installed" {
|
||||
run /bin/bash -c "test -f /etc/sudoers.d/dokku-cron"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) scheduler-uses-host-cron" {
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-uses-host-cron docker-local"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-uses-host-cron k3s"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "false"
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-uses-host-cron null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "false"
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-uses-host-cron bogus-scheduler"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output ""
|
||||
}
|
||||
|
||||
@test "(cron) invalid [missing-keys]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) invalid [schedule]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid_schedule
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) invalid [seconds]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid_schedule_seconds
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) create [empty]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) create [single-verbose]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_single
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $cron_id"
|
||||
assert_output_contains "python3 task.py schedule" 0
|
||||
}
|
||||
|
||||
@test "(cron) create [single-short]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_short
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $cron_id"
|
||||
assert_output_contains "python3 task.py daily" 0
|
||||
}
|
||||
|
||||
@test "(cron:report) --global --format json" {
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -e ."
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-maintenance\")'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "false"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-maintenance\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"global-maintenance\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-maintenance\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"computed-maintenance\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-mailfrom\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"global-mailfrom\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-mailfrom\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"computed-mailfrom\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-mailto\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"global-mailto\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-mailto\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"computed-mailto\")'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "(cron:set) --global mailto" {
|
||||
run /bin/bash -c "dokku cron:set --global mailto admin@example.com"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailto\"'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "admin@example.com"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailto\"'"
|
||||
assert_success
|
||||
assert_output "admin@example.com"
|
||||
|
||||
run /bin/bash -c "dokku cron:set --global mailto"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailto\"'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output ""
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailto\"'"
|
||||
assert_success
|
||||
assert_output ""
|
||||
}
|
||||
|
||||
@test "(cron:set) --global mailfrom" {
|
||||
run /bin/bash -c "dokku cron:set --global mailfrom dokku@example.com"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailfrom\"'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "dokku@example.com"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailfrom\"'"
|
||||
assert_success
|
||||
assert_output "dokku@example.com"
|
||||
|
||||
run /bin/bash -c "dokku cron:set --global mailfrom"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailfrom\"'"
|
||||
assert_success
|
||||
assert_output ""
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailfrom\"'"
|
||||
assert_success
|
||||
assert_output ""
|
||||
}
|
||||
|
||||
@test "(cron:set) --global maintenance" {
|
||||
run /bin/bash -c "dokku cron:set --global maintenance true"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
|
||||
run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-maintenance\"'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:set --global maintenance"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "unknown flag"
|
||||
}
|
||||
|
||||
@test "(cron:report) maintenance raw vs computed vs global" {
|
||||
run /bin/bash -c "dokku cron:set --global maintenance"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-maintenance\"'"
|
||||
assert_success
|
||||
assert_output ""
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-global-maintenance\"'"
|
||||
assert_success
|
||||
assert_output ""
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-computed-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "false"
|
||||
|
||||
run /bin/bash -c "dokku cron:set --global maintenance true"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-global-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-computed-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:set $TEST_APP maintenance true"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-global-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
# cron computed-maintenance returns "true" if either per-app or global is
|
||||
# "true" (cron's logic at plugins/cron/report.go::reportComputedMaintenance
|
||||
# is OR-not-override: any "true" wins). This is intentional for maintenance
|
||||
# mode - a per-app "false" does NOT override a global "true".
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-computed-maintenance\"'"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "dokku cron:set $TEST_APP maintenance"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:set --global maintenance"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "(cron:set) --global rejects task maintenance properties" {
|
||||
run /bin/bash -c "dokku cron:set --global maintenance.fakeid true"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
assert_output_contains "Task maintenance properties cannot be set globally"
|
||||
}
|
||||
|
||||
@test "(cron) create [multiple]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_multiple
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
first_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
second_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[1].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $first_id"
|
||||
assert_output_contains "dokku cron:run $TEST_APP $second_id"
|
||||
assert_output_contains "python3 task.py first" 0
|
||||
assert_output_contains "python3 task.py second" 0
|
||||
}
|
||||
|
||||
@test "(cron) injected 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
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:list --global --format json"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output '[{"id":"5cruaotm4yzzpnjlsdunblj8qyjp","command":"/bin/true","global":true,"schedule":"@daily","concurrency_policy":"","app-in-maintenance":false,"task-in-maintenance":false,"maintenance":false}]'
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "@daily /bin/true"
|
||||
|
||||
# log file
|
||||
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
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "@daily /bin/true &>> /var/log/dokku/log.log"
|
||||
|
||||
# specify matching scheduler
|
||||
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
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "@daily /bin/true"
|
||||
|
||||
# specify non-matching scheduler
|
||||
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
|
||||
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) k3s-scheduled app skips host crontab" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_single
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $cron_id"
|
||||
|
||||
# switch the app to a scheduler that manages its own cron backend
|
||||
run /bin/bash -c "dokku scheduler:set $TEST_APP selected k3s"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
# regenerating the host crontab must now skip the k3s app's tasks, leaving
|
||||
# no host-cron tasks and therefore no crontab file
|
||||
run /bin/bash -c "dokku plugin:trigger scheduler-cron-write"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) cron:list --format json" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:list $TEST_APP --format json"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_exists
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
run /bin/bash -c "echo $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_exists
|
||||
}
|
||||
|
||||
@test "(cron) cron:run" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
run /bin/bash -c "echo $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_exists
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "['task.py', 'schedule']"
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[1].id')"
|
||||
run /bin/bash -c "echo $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_exists
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "['task.py', 'schedule', 'now']"
|
||||
}
|
||||
|
||||
@test "(cron) cron:run concurrency_policy forbid" {
|
||||
run deploy_app dockerfile dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_concurrency_forbid
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
run /bin/bash -c "echo cron $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_exists
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id --detach"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "docker ps --filter "label=com.dokku.cron-id=$cron_id" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.concurrency-policy\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "forbid"
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "currently has a cron lock in place for $cron_id"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) cron:suspend cron:resume" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_multiple
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
first_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
second_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[1].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $first_id"
|
||||
assert_output_contains "dokku cron:run $TEST_APP $second_id"
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --cron-maintenance-$first_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
|
||||
run /bin/bash -c "dokku cron:suspend $TEST_APP $first_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --cron-maintenance-$first_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "true"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $first_id" 0
|
||||
assert_output_contains "dokku cron:run $TEST_APP $second_id"
|
||||
|
||||
run /bin/bash -c "dokku cron:resume $TEST_APP $first_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku cron:report $TEST_APP --cron-maintenance-$first_id"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $first_id"
|
||||
assert_output_contains "dokku cron:run $TEST_APP $second_id"
|
||||
}
|
||||
|
||||
@test "(cron) invalid [command]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_injection
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(cron) crontab format [no raw command leakage]" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_single
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "cat /var/spool/cron/crontabs/dokku"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "dokku cron:run $TEST_APP $cron_id"
|
||||
# User command must never appear verbatim in the crontab - the crontab
|
||||
# only references the cron ID, and cron:run resolves the command at run
|
||||
# time and exec's it inside the container.
|
||||
assert_output_contains "python3 task.py schedule" 0
|
||||
}
|
||||
|
||||
@test "(cron) container labels regression" {
|
||||
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_long_running
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')"
|
||||
|
||||
run /bin/bash -c "dokku cron:run $TEST_APP $cron_id --detach"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.cron-id\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "$cron_id"
|
||||
|
||||
run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.concurrency-policy\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "allow"
|
||||
|
||||
run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.container-type\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "cron"
|
||||
|
||||
run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.app-name\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "$TEST_APP"
|
||||
|
||||
run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.active-deadline-seconds\" }}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "86400"
|
||||
}
|
||||
|
||||
template_cron_file_long_running() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting long-running cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "sleep 30",
|
||||
"schedule": "0 0 * * *"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_injection() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting injection-attempt cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "echo CRON_OK; echo hi > /tmp/appjson-injection-test.txt",
|
||||
"schedule": "* * * * *"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_invalid() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting invalid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"key": "value"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_invalid_schedule() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting invalid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py",
|
||||
"schedule": "@nonstandard"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_invalid_schedule_seconds() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting invalid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py",
|
||||
"schedule": "0 5 * * * *"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_valid() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py schedule",
|
||||
"schedule": "5 5 5 5 5"
|
||||
},
|
||||
{
|
||||
"command": "python3 task.py schedule now",
|
||||
"schedule": "6 5 5 5 5"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_valid_single() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py schedule",
|
||||
"schedule": "5 5 5 5 5"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
template_cron_file_valid_short() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py daily",
|
||||
"schedule": "@daily"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_valid_multiple() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "python3 task.py first",
|
||||
"schedule": "5 5 5 5 5"
|
||||
},
|
||||
{
|
||||
"command": "python3 task.py second",
|
||||
"schedule": "@daily"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
template_cron_file_concurrency_forbid() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json"
|
||||
cat <<EOF >"$APP_REPO_DIR/app.json"
|
||||
{
|
||||
"cron": [
|
||||
{
|
||||
"command": "sleep 30",
|
||||
"schedule": "0 0 * * *",
|
||||
"concurrency_policy": "forbid"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
Reference in New Issue
Block a user