diff --git a/plugins/logs/.gitignore b/plugins/logs/.gitignore index 291036bd4..ce3e77e5c 100644 --- a/plugins/logs/.gitignore +++ b/plugins/logs/.gitignore @@ -2,6 +2,7 @@ /subcommands/* /triggers/* /triggers +/docker-args-process-deploy /install /post-* /report diff --git a/plugins/logs/Makefile b/plugins/logs/Makefile index bcd96e90f..213c56394 100644 --- a/plugins/logs/Makefile +++ b/plugins/logs/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/failed subcommands/report subcommands/set subcommands/vector-logs subcommands/vector-start subcommands/vector-stop -TRIGGERS = triggers/install triggers/post-delete triggers/report +TRIGGERS = triggers/docker-args-process-deploy triggers/install triggers/post-delete triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = logs diff --git a/plugins/logs/logs.go b/plugins/logs/logs.go index 7af0000af..c735f18d4 100644 --- a/plugins/logs/logs.go +++ b/plugins/logs/logs.go @@ -6,14 +6,18 @@ import ( "github.com/dokku/dokku/plugins/common" ) +const MaxSize = "10m" + var ( // DefaultProperties is a map of all valid ps properties with corresponding default property values DefaultProperties = map[string]string{ + "max-size": "", "vector-sink": "", } // GlobalProperties is a map of all valid global logs properties GlobalProperties = map[string]bool{ + "max-size": true, "vector-sink": true, } ) diff --git a/plugins/logs/report.go b/plugins/logs/report.go index 3e8a95aa9..c88ac5047 100644 --- a/plugins/logs/report.go +++ b/plugins/logs/report.go @@ -11,8 +11,11 @@ func ReportSingleApp(appName, infoFlag string) error { } flags := map[string]common.ReportFunc{ - "--logs-vector-sink": reportVectorSink, + "--logs-computed-max-size": reportComputedMaxSize, + "--logs-global-max-size": reportGlobalMaxSize, "--logs-global-vector-sink": reportGlobalVectorSink, + "--logs-max-size": reportMaxSize, + "--logs-vector-sink": reportVectorSink, } flagKeys := []string{} @@ -26,10 +29,27 @@ func ReportSingleApp(appName, infoFlag string) error { return common.ReportSingleApp("logs", appName, infoFlag, infoFlags, flagKeys, trimPrefix, uppercaseFirstCharacter) } -func reportVectorSink(appName string) string { - return common.PropertyGet("logs", appName, "vector-sink") +func reportComputedMaxSize(appName string) string { + value := reportMaxSize(appName) + if value == "" { + value = reportGlobalMaxSize(appName) + } + + return value +} + +func reportGlobalMaxSize(appName string) string { + return common.PropertyGetDefault("logs", "--global", "max-size", MaxSize) } func reportGlobalVectorSink(appName string) string { return common.PropertyGet("logs", "--global", "vector-sink") } + +func reportMaxSize(appName string) string { + return common.PropertyGet("logs", appName, "max-size") +} + +func reportVectorSink(appName string) string { + return common.PropertyGet("logs", appName, "vector-sink") +} diff --git a/plugins/logs/set.go b/plugins/logs/set.go new file mode 100644 index 000000000..5878958f9 --- /dev/null +++ b/plugins/logs/set.go @@ -0,0 +1,58 @@ +package logs + +import ( + "errors" + "fmt" + "strconv" +) + +func validateSetValue(appName string, key string, value string) error { + if key == "max-size" { + return validateMaxSize(appName, value) + } + + if key == "vector-sink" { + return validateVectorSink(appName, value) + } + + return nil +} + +func validateMaxSize(appName string, value string) error { + if value == "" { + return nil + } + + if value == "unlimited" { + return nil + } + + last := value[len(value)-1:] + if last != "k" && last != "m" && last != "g" { + return errors.New("Invalid max-size unit measure, value must end in any of [k, m, g]") + } + + if len(value) < 2 { + return errors.New("Invalid max-size value, must be a number followed by a unit of measure [k, m, d]") + } + + number := value[:len(value)-1] + if _, err := strconv.Atoi(number); err != nil { + return fmt.Errorf("Invalid max-size value, unable to convert number to int: %s", err.Error()) + } + + return nil +} + +func validateVectorSink(appName string, value string) error { + if value == "" { + return nil + } + + _, err := valueToConfig(appName, value) + if err != nil { + return err + } + + return nil +} diff --git a/plugins/logs/src/subcommands/subcommands.go b/plugins/logs/src/subcommands/subcommands.go index 875dc2d67..f40ec834b 100644 --- a/plugins/logs/src/subcommands/subcommands.go +++ b/plugins/logs/src/subcommands/subcommands.go @@ -35,6 +35,7 @@ func main() { case "set": args := flag.NewFlagSet("logs:set", flag.ExitOnError) global := args.Bool("global", false, "--global: set a global property") + valueOverride := args.Bool("1", false, "-1: negative value (for max-size)") args.Parse(os.Args[2:]) appName := args.Arg(0) property := args.Arg(1) @@ -44,6 +45,11 @@ func main() { property = args.Arg(0) value = args.Arg(1) } + + if *valueOverride { + value = "-1" + } + err = logs.CommandSet(appName, property, value) case "vector-logs": args := flag.NewFlagSet("logs:vector-logs", flag.ExitOnError) diff --git a/plugins/logs/src/triggers/triggers.go b/plugins/logs/src/triggers/triggers.go index d09fa5c80..0fd62af87 100644 --- a/plugins/logs/src/triggers/triggers.go +++ b/plugins/logs/src/triggers/triggers.go @@ -18,6 +18,9 @@ func main() { var err error switch trigger { + case "docker-args-process-deploy": + appName := flag.Arg(0) + err = logs.TriggerDockerArgsProcessDeploy(appName) case "install": err = logs.TriggerInstall() case "post-delete": diff --git a/plugins/logs/subcommands.go b/plugins/logs/subcommands.go index d9c8ce572..e8a9b4a25 100644 --- a/plugins/logs/subcommands.go +++ b/plugins/logs/subcommands.go @@ -69,11 +69,8 @@ func CommandReport(appName string, infoFlag string) error { // CommandSet sets or clears a logs property for an app func CommandSet(appName string, property string, value string) error { - if property == "vector-sink" && value != "" { - _, err := valueToConfig(appName, value) - if err != nil { - return err - } + if err := validateSetValue(appName, property, value); err != nil { + return err } common.CommandPropertySet("logs", appName, property, value, DefaultProperties, GlobalProperties) diff --git a/plugins/logs/triggers.go b/plugins/logs/triggers.go index a43c9c884..3058ce9d1 100644 --- a/plugins/logs/triggers.go +++ b/plugins/logs/triggers.go @@ -2,12 +2,33 @@ package logs import ( "fmt" + "io/ioutil" "os" "path/filepath" "github.com/dokku/dokku/plugins/common" ) +// TriggerDockerArgsProcessDeploy outputs the logs plugin docker options for an app +func TriggerDockerArgsProcessDeploy(appName string) error { + stdin, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return err + } + + maxSize := common.PropertyGet("logs", appName, "max-size") + if maxSize == "" { + maxSize = common.PropertyGetDefault("logs", "--global", "max-size", MaxSize) + } + + if maxSize != "unlimited" { + fmt.Printf(" --log-opt max-size=%s ", maxSize) + } + + fmt.Print(string(stdin)) + return nil +} + // TriggerInstall initializes app restart policies func TriggerInstall() error { if err := common.PropertySetup("logs"); err != nil { diff --git a/plugins/scheduler-docker-local/install b/plugins/scheduler-docker-local/install index 78d883b5e..780159608 100755 --- a/plugins/scheduler-docker-local/install +++ b/plugins/scheduler-docker-local/install @@ -8,7 +8,6 @@ trigger-scheduler-docker-local-install() { declare desc="scheduler-docker-local install plugin trigger" declare trigger="install" local DOKKU_PATH - DOKKU_LOGROTATE_FILE=/etc/logrotate.d/docker mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-docker-local" chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/scheduler-docker-local" @@ -17,20 +16,6 @@ trigger-scheduler-docker-local-install() { DOKKU_PATH="$(which dokku)" - if [[ ! -f "$DOKKU_LOGROTATE_FILE" ]]; then - cat >"$DOKKU_LOGROTATE_FILE" </dev/null) =~ -\.mount ]]; then cat </etc/systemd/system/dokku-retire.service [Unit] diff --git a/tests/unit/logs.bats b/tests/unit/logs.bats index 0e86f5f3a..3b2dc9d8c 100644 --- a/tests/unit/logs.bats +++ b/tests/unit/logs.bats @@ -98,13 +98,13 @@ teardown() { echo "output: $output" echo "status: $status" assert_failure - assert_output_contains "Invalid property specified, valid properties include: vector-sink" + assert_output_contains "Invalid property specified, valid properties include: max-size, vector-sink" run /bin/bash -c "dokku logs:set $TEST_APP invalid value" 2>&1 echo "output: $output" echo "status: $status" assert_failure - assert_output_contains "Invalid property specified, valid properties include: vector-sink" + assert_output_contains "Invalid property specified, valid properties include: max-size, vector-sink" } @test "(logs) logs:set app" { @@ -144,7 +144,50 @@ teardown() { echo "status: $status" assert_success assert_output "console://?encoding[codec]=json" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:set $TEST_APP max-size" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Unsetting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:set $TEST_APP max-size 20m" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "20m" + + run /bin/bash -c "dokku logs:set $TEST_APP max-size unlimited" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "unlimited" } + @test "(logs) logs:set global" { run create_app echo "output: $output" @@ -189,6 +232,54 @@ teardown() { assert_success assert_output_contains "Unsetting vector-sink" assert_output_contains "Writing updated vector config to /var/lib/dokku/data/logs/vector.json" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-global-vector-sink 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:set --global max-size" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Unsetting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-global-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "10m" + + run /bin/bash -c "dokku logs:set --global max-size 20m" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-global-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "20m" + + run /bin/bash -c "dokku logs:set --global max-size unlimited" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-global-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "unlimited" + + run /bin/bash -c "dokku logs:set --global max-size" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Unsetting max-size" } @test "(logs) logs:vector" {