Files
dokku/plugins/logs/logs_test.go
Jose Diaz-Gonzalez 52b26a3760 feat: add vector-cron-sink for scheduled cron task output
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.
2026-08-09 01:10:21 -04:00

109 lines
3.0 KiB
Go

package logs
import (
"encoding/base64"
"reflect"
"testing"
)
func TestSinkValueToConfigInputs(t *testing.T) {
sink, err := SinkValueToConfig(SinkValueToConfigInput{
SinkValue: "console://?encoding[codec]=json",
Inputs: []string{"docker-source:myapp"},
})
if err != nil {
t.Fatalf("SinkValueToConfig() error = %v", err)
}
if sink["type"] != "console" {
t.Errorf("type = %v, want console", sink["type"])
}
want := []string{"docker-source:myapp"}
if !reflect.DeepEqual(sink["inputs"], want) {
t.Errorf("inputs = %v, want %v", sink["inputs"], want)
}
}
func TestSinkValueToConfigOmitsEmptyInputs(t *testing.T) {
sink, err := SinkValueToConfig(SinkValueToConfigInput{
SinkValue: "console://?encoding[codec]=json",
})
if err != nil {
t.Fatalf("SinkValueToConfig() error = %v", err)
}
if _, ok := sink["inputs"]; ok {
t.Errorf("inputs should be omitted when no inputs are supplied, got %v", sink["inputs"])
}
}
func TestSinkValueToConfigRejectsSinksOption(t *testing.T) {
_, err := SinkValueToConfig(SinkValueToConfigInput{
SinkValue: "console://?sinks=nope",
})
if err == nil {
t.Fatal("SinkValueToConfig() expected an error for the sinks option")
}
}
func TestSinkValueToConfigSchemeUnderscores(t *testing.T) {
for _, scheme := range []string{"datadog_logs", "aws_cloudwatch_logs"} {
sink, err := SinkValueToConfig(SinkValueToConfigInput{
SinkValue: scheme + "://?api_key=abc123",
})
if err != nil {
t.Fatalf("SinkValueToConfig(%s) error = %v", scheme, err)
}
if sink["type"] != scheme {
t.Errorf("type = %v, want %s", sink["type"], scheme)
}
}
}
func TestSinkValueToConfigBase64Enc(t *testing.T) {
encoded := base64.StdEncoding.EncodeToString([]byte("{{ pod }}"))
sink, err := SinkValueToConfig(SinkValueToConfigInput{
SinkValue: "http://?process=base64enc%3A" + encoded,
})
if err != nil {
t.Fatalf("SinkValueToConfig() error = %v", err)
}
if sink["process"] != "{{ pod }}" {
t.Errorf("process = %v, want {{ pod }}", sink["process"])
}
}
// TestSinkValueToConfigTemplatedFilePath guards the DSN handling that the
// documented cron file sink depends on: a vector template in a query-string
// value must survive url.Parse and the qson decode with its braces and
// interior spaces intact.
func TestSinkValueToConfigTemplatedFilePath(t *testing.T) {
sink, err := SinkValueToConfig(SinkValueToConfigInput{
SinkValue: "file://?path=/var/log/dokku/apps/myapp/cron-{{ dokku_cron_id }}.log&encoding[codec]=text",
Inputs: []string{"docker-cron-remap:myapp"},
})
if err != nil {
t.Fatalf("SinkValueToConfig() error = %v", err)
}
if sink["type"] != "file" {
t.Errorf("type = %v, want file", sink["type"])
}
wantPath := "/var/log/dokku/apps/myapp/cron-{{ dokku_cron_id }}.log"
if sink["path"] != wantPath {
t.Errorf("path = %v, want %v", sink["path"], wantPath)
}
encoding, ok := sink["encoding"].(map[string]interface{})
if !ok {
t.Fatalf("encoding = %v, want a map", sink["encoding"])
}
if encoding["codec"] != "text" {
t.Errorf("encoding.codec = %v, want text", encoding["codec"])
}
}