mirror of
https://github.com/dokku/dokku.git
synced 2026-08-29 10:08:53 +02:00
Scheduled cron task output previously reached only the `dokku` user's cron mail, and could not be redirected because `app.json` rejects bare shell operators in a cron `command`. Setting `vector-cron-sink` on an app or globally routes that output to a dedicated sink instead, on both the `docker-local` and `k3s` schedulers, which keeps log destinations under operator control rather than in a deployed repository. Cron events carry `dokku_app` and `dokku_cron_id` fields so a sink can give each task its own destination. This also fixes a `k3s` bug where configuring a global `vector-sink` silently removed the vector prometheus exporter sink.
126 lines
3.3 KiB
Go
126 lines
3.3 KiB
Go
package cron
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestDokkuRunCommandAppTaskDispatchesViaCronRun(t *testing.T) {
|
|
task := CronTask{
|
|
App: "myapp",
|
|
ID: "abc123",
|
|
Command: "echo CRON_OK; echo hi > /tmp/appjson-test.txt",
|
|
Schedule: "* * * * *",
|
|
ConcurrencyPolicy: "allow",
|
|
}
|
|
|
|
got := task.DokkuRunCommand()
|
|
want := "dokku cron:run myapp abc123"
|
|
if got != want {
|
|
t.Errorf("DokkuRunCommand() = %q, want %q", got, want)
|
|
}
|
|
|
|
if strings.Contains(got, task.Command) {
|
|
t.Errorf("DokkuRunCommand() leaked user command into crontab line: %q", got)
|
|
}
|
|
if strings.ContainsAny(got, ";>|&`$") {
|
|
t.Errorf("DokkuRunCommand() contains shell metacharacters: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestDokkuRunCommandPlainCommandStillUsesCronRun(t *testing.T) {
|
|
task := CronTask{
|
|
App: "myapp",
|
|
ID: "abc123",
|
|
Command: "npm run send-email",
|
|
Schedule: "@daily",
|
|
ConcurrencyPolicy: "forbid",
|
|
}
|
|
|
|
got := task.DokkuRunCommand()
|
|
want := "dokku cron:run myapp abc123"
|
|
if got != want {
|
|
t.Errorf("DokkuRunCommand() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDokkuRunCommandAltCommandUnchanged(t *testing.T) {
|
|
task := CronTask{
|
|
ID: "abc123",
|
|
AltCommand: "/usr/bin/some-internal-task --flag",
|
|
}
|
|
|
|
got := task.DokkuRunCommand()
|
|
want := "/usr/bin/some-internal-task --flag"
|
|
if got != want {
|
|
t.Errorf("DokkuRunCommand() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestValidateCronCommandAcceptsValidCommands(t *testing.T) {
|
|
cases := []string{
|
|
"python3 task.py schedule",
|
|
"npm run send-email",
|
|
"sh -c 'echo CRON_OK; echo hi > /tmp/x.txt'",
|
|
`node -e 'console.log(1)'`,
|
|
"true",
|
|
}
|
|
for _, cmd := range cases {
|
|
if err := ValidateCronCommand(cmd); err != nil {
|
|
t.Errorf("ValidateCronCommand(%q) returned error: %v", cmd, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateCronCommandRejectsShellOperators(t *testing.T) {
|
|
cases := []string{
|
|
"echo CRON_OK; echo hi > /tmp/x.txt",
|
|
"cmd1 && cmd2",
|
|
"cmd | other",
|
|
"cmd > file",
|
|
"cmd $(other)",
|
|
}
|
|
for _, cmd := range cases {
|
|
if err := ValidateCronCommand(cmd); err == nil {
|
|
t.Errorf("ValidateCronCommand(%q) accepted a command containing a shell operator", cmd)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDokkuRunCommandAltCommandWithLogFile(t *testing.T) {
|
|
task := CronTask{
|
|
ID: "abc123",
|
|
AltCommand: "/usr/bin/some-internal-task",
|
|
LogFile: "/var/log/dokku/internal-task.log",
|
|
}
|
|
|
|
got := task.DokkuRunCommand()
|
|
want := "/usr/bin/some-internal-task &>> /var/log/dokku/internal-task.log"
|
|
if got != want {
|
|
t.Errorf("DokkuRunCommand() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
// TestDokkuRunCommandAppTaskIgnoresLogFile pins that LogFile is honored only
|
|
// for internally injected tasks from the cron-entries trigger. App tasks never
|
|
// interpolate a path into the crontab line - their output is shipped by the
|
|
// vector integration instead.
|
|
func TestDokkuRunCommandAppTaskIgnoresLogFile(t *testing.T) {
|
|
task := CronTask{
|
|
App: "myapp",
|
|
ID: "abc123",
|
|
Command: "npm run send-email",
|
|
LogFile: "/var/log/dokku/should-not-appear.log",
|
|
}
|
|
|
|
got := task.DokkuRunCommand()
|
|
want := "dokku cron:run myapp abc123"
|
|
if got != want {
|
|
t.Errorf("DokkuRunCommand() = %q, want %q", got, want)
|
|
}
|
|
|
|
if strings.Contains(got, ">>") {
|
|
t.Errorf("DokkuRunCommand() interpolated a redirect into an app task line: %q", got)
|
|
}
|
|
}
|