diff --git a/docs/deployment/logs.md b/docs/deployment/logs.md index 7c1a392a8..2e242c689 100644 --- a/docs/deployment/logs.md +++ b/docs/deployment/logs.md @@ -4,8 +4,8 @@ logs [-h] [-t] [-n num] [-q] [-p process] # Display recent log output logs:failed [--all|] # Shows the last failed deploy logs logs:report [] [] # Displays a logs report for one or more apps -logs:set [--global|] # Set or clear a logs property for an app -logs:vector-logs # Tail the logs of the vector container +logs:set [--global|] # Set or clear a logs property for an app +logs:vector-logs [--num num] [--tail] # Display vector log output logs:vector-start # Start the vector logging container logs:vector-stop # Stop the vector logging container ``` @@ -101,7 +101,7 @@ The `vector` container will be stopped and removed from the system. If the conta #### Configuring a log sink -Vector uses the concept of log "sinks" to send logs to a given endpoint. Log sinks may be configured globally or on a per-app basis by specifying a `sink` in DSN form with the `logs:set` command. Specifying a sink value will reload any running vector container. +Vector uses the concept of log "sinks" to send logs to a given endpoint. Log sinks may be configured globally or on a per-app basis by specifying a `vector-sink` in DSN form with the `logs:set` command. Specifying a sink value will reload any running vector container. ```shell # setting the sink value in quotes is encouraged to avoid @@ -112,7 +112,7 @@ dokku logs:set node-js-app sink "console://?encoding[codec]=json" A sink may be removed by setting an empty value, which will also reload the running vector container. ```shell -dokku logs:set node-js-app sink +dokku logs:set node-js-app vector-sink ``` Only one sink may be specified on a per-app basis at a given time. @@ -120,13 +120,13 @@ Only one sink may be specified on a per-app basis at a given time. Log sinks can also be specified globally by specifying the `--global` flag to `logs:set` with no app name specified: ```shell -dokku logs:set --global sink "console://?encoding[codec]=json" +dokku logs:set --global vector-sink "console://?encoding[codec]=json" ``` As with app-specific sink settings, the global value may also be cleared by setting no value. ```shell -dokku logs:set --global sink +dokku logs:set --global vector-sink ``` ##### Log Sink DSN Format diff --git a/plugins/apps/report.go b/plugins/apps/report.go index d74d9ce51..491a45d6c 100644 --- a/plugins/apps/report.go +++ b/plugins/apps/report.go @@ -18,10 +18,15 @@ func ReportSingleApp(appName, infoFlag string) error { "--app-locked": reportLocked, } + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + trimPrefix := false uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("app", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp("app", appName, infoFlag, infoFlags, flagKeys, trimPrefix, uppercaseFirstCharacter) } func reportDir(appName string) string { diff --git a/plugins/apps/src/subcommands/subcommands.go b/plugins/apps/src/subcommands/subcommands.go index 0f146428e..66943d125 100644 --- a/plugins/apps/src/subcommands/subcommands.go +++ b/plugins/apps/src/subcommands/subcommands.go @@ -65,8 +65,8 @@ func main() { err = apps.CommandRename(oldAppName, newAppName, *skipDeploy) case "report": args := flag.NewFlagSet("apps:report", flag.ExitOnError) - osArgs, infoFlag, err := common.ParseReportArgs("apps", os.Args[2:]) - if err == nil { + osArgs, infoFlag, flagErr := common.ParseReportArgs("apps", os.Args[2:]) + if flagErr == nil { args.Parse(osArgs) appName := args.Arg(0) err = apps.CommandReport(appName, infoFlag) diff --git a/plugins/buildpacks/report.go b/plugins/buildpacks/report.go index b255095f2..e67bdd72b 100644 --- a/plugins/buildpacks/report.go +++ b/plugins/buildpacks/report.go @@ -16,10 +16,15 @@ func ReportSingleApp(appName, infoFlag string) error { "--buildpacks-list": reportList, } + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + trimPrefix := false uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("buildpacks", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp("buildpacks", appName, infoFlag, infoFlags, flagKeys, trimPrefix, uppercaseFirstCharacter) } func reportList(appName string) string { diff --git a/plugins/buildpacks/src/subcommands/subcommands.go b/plugins/buildpacks/src/subcommands/subcommands.go index 375b8794e..7bf7a2303 100644 --- a/plugins/buildpacks/src/subcommands/subcommands.go +++ b/plugins/buildpacks/src/subcommands/subcommands.go @@ -44,8 +44,8 @@ func main() { err = buildpacks.CommandRemove(appName, buildpack, *index) case "report": args := flag.NewFlagSet("buildpacks:report", flag.ExitOnError) - osArgs, infoFlag, err := common.ParseReportArgs("buildpacks", os.Args[2:]) - if err == nil { + osArgs, infoFlag, flagErr := common.ParseReportArgs("buildpacks", os.Args[2:]) + if flagErr == nil { args.Parse(osArgs) appName := args.Arg(0) err = buildpacks.CommandReport(appName, infoFlag) diff --git a/plugins/common/common.go b/plugins/common/common.go index 6472b7d9e..7ec38493b 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "os" "path/filepath" - "reflect" "regexp" "sort" "strconv" @@ -324,7 +323,7 @@ func ParseReportArgs(pluginName string, arguments []string) ([]string, string, e } // ReportSingleApp is an internal function that displays a report for an app -func ReportSingleApp(reportType string, appName string, infoFlag string, infoFlags map[string]string, trimPrefix bool, uppercaseFirstCharacter bool) error { +func ReportSingleApp(reportType string, appName string, infoFlag string, infoFlags map[string]string, infoFlagKeys []string, trimPrefix bool, uppercaseFirstCharacter bool) error { flags := []string{} for key := range infoFlags { flags = append(flags, key) @@ -334,7 +333,11 @@ func ReportSingleApp(reportType string, appName string, infoFlag string, infoFla if len(infoFlag) == 0 { LogInfo2Quiet(fmt.Sprintf("%s %v information", appName, reportType)) for _, k := range flags { - v := infoFlags[k] + v, ok := infoFlags[k] + if !ok { + continue + } + prefix := "--" if trimPrefix { prefix = fmt.Sprintf("--%v-", reportType) @@ -353,19 +356,17 @@ func ReportSingleApp(reportType string, appName string, infoFlag string, infoFla for _, k := range flags { if infoFlag == k { - v := infoFlags[k] + v, ok := infoFlags[k] + if !ok { + continue + } fmt.Println(v) return nil } } - keys := reflect.ValueOf(infoFlags).MapKeys() - strkeys := make([]string, len(keys)) - for i := 0; i < len(keys); i++ { - strkeys[i] = keys[i].String() - } - - return fmt.Errorf("Invalid flag passed, valid flags: %s", strings.Join(strkeys, ", ")) + sort.Strings(infoFlagKeys) + return fmt.Errorf("Invalid flag passed, valid flags: %s", strings.Join(infoFlagKeys, ", ")) } // RightPad right-pads the string with pad up to len runes diff --git a/plugins/common/docker.go b/plugins/common/docker.go index 492e754c6..42fe0cb93 100644 --- a/plugins/common/docker.go +++ b/plugins/common/docker.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "strings" + "time" "github.com/codeskyblue/go-sh" ) @@ -45,6 +46,18 @@ func ContainerExists(containerID string) bool { return true } +// ContainerWaitTilReady will wait timeout seconds and then check if a container is running +// returning an error if it is not running at the end of the timeout +func ContainerWaitTilReady(containerID string, timeout time.Duration) error { + time.Sleep(timeout) + + if !ContainerIsRunning(containerID) { + return fmt.Errorf("Container %s is not running", containerID) + } + + return nil +} + // CopyFromImage copies a file from named image to destination func CopyFromImage(appName string, image string, source string, destination string) error { if !VerifyImage(image) { diff --git a/plugins/common/log.go b/plugins/common/log.go index 1a64ca241..6b9e053e2 100644 --- a/plugins/common/log.go +++ b/plugins/common/log.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "strconv" "strings" ) @@ -95,8 +96,15 @@ func LogVerboseQuietContainerLogs(containerID string) { } // LogVerboseQuietContainerLogsTail is the verbose log formatter for container logs with tail mode enabled -func LogVerboseQuietContainerLogsTail(containerID string) { - sc := NewShellCmdWithArgs(DockerBin(), "container", "logs", containerID, "--follow", "--tail", "10") +func LogVerboseQuietContainerLogsTail(containerID string, lines int, tail bool) { + args := []string{"container", "logs", containerID} + if lines > 0 { + args = append(args, "--tail", strconv.Itoa(lines)) + } + if tail { + args = append(args, "--follow") + } + sc := NewShellCmdWithArgs(DockerBin(), args...) stdout, _ := sc.Command.StdoutPipe() sc.Command.Start() @@ -104,7 +112,7 @@ func LogVerboseQuietContainerLogsTail(containerID string) { scanner.Split(bufio.ScanLines) for scanner.Scan() { m := scanner.Text() - fmt.Println(m) + LogVerboseQuiet(m) } sc.Command.Wait() } diff --git a/plugins/logs/functions.go b/plugins/logs/functions.go index 2587d3341..ab616fe26 100644 --- a/plugins/logs/functions.go +++ b/plugins/logs/functions.go @@ -74,7 +74,8 @@ func startVectorContainer(vectorImage string) error { "--volume", "/var/run/docker.sock:/var/run/docker.sock", "--volume", common.MustGetEnv("DOKKU_LOGS_HOST_DIR") + ":/var/logs/dokku/apps", vectorImage, - "--config", "/etc/vector/vector.json", "--watch-config"}, " ")) + "--config", "/etc/vector/vector.json", "--watch-config", "1"}, " ")) + cmd.ShowOutput = false if !cmd.Execute() { return errors.New("Unable to start vector container") @@ -151,11 +152,7 @@ func valueToConfig(appName string, value string) (vectorSink, error) { } func writeVectorConfig() error { - apps, err := common.DokkuApps() - if err != nil { - return err - } - + apps, _ := common.DokkuApps() data := vectorConfig{ Sources: map[string]vectorSource{}, Sinks: map[string]vectorSink{}, diff --git a/plugins/logs/report.go b/plugins/logs/report.go index 1234e6e55..3e8a95aa9 100644 --- a/plugins/logs/report.go +++ b/plugins/logs/report.go @@ -15,10 +15,15 @@ func ReportSingleApp(appName, infoFlag string) error { "--logs-global-vector-sink": reportGlobalVectorSink, } + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + trimPrefix := false uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("logs", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp("logs", appName, infoFlag, infoFlags, flagKeys, trimPrefix, uppercaseFirstCharacter) } func reportVectorSink(appName string) string { diff --git a/plugins/logs/src/commands/commands.go b/plugins/logs/src/commands/commands.go index dd6e72e45..c4fcc3a93 100644 --- a/plugins/logs/src/commands/commands.go +++ b/plugins/logs/src/commands/commands.go @@ -25,8 +25,8 @@ Additional commands:` logs [-h] [-t|--tail] [-n|--num num] [-q|--quiet] [-p|--ps process] , Display recent log output logs:failed [--all|], Shows the last failed deploy logs logs:report [] [], Displays a logs report for one or more apps - logs:set , Set or clear a logs property for an app - logs:vector-logs, Tail the logs of the vector container + logs:set [--global|] , Set or clear a logs property for an app + logs:vector-logs [--num num] [--tail], Display vector log output logs:vector-start, Start the vector logging container logs:vector-stop, Stop the vector logging container ` diff --git a/plugins/logs/src/subcommands/subcommands.go b/plugins/logs/src/subcommands/subcommands.go index b10ce4912..875dc2d67 100644 --- a/plugins/logs/src/subcommands/subcommands.go +++ b/plugins/logs/src/subcommands/subcommands.go @@ -26,8 +26,8 @@ func main() { err = logs.CommandFailed(appName, *allApps) case "report": args := flag.NewFlagSet("logs:report", flag.ExitOnError) - osArgs, infoFlag, err := common.ParseReportArgs("logs", os.Args[2:]) - if err == nil { + osArgs, infoFlag, flagErr := common.ParseReportArgs("logs", os.Args[2:]) + if flagErr == nil { args.Parse(osArgs) appName := args.Arg(0) err = logs.CommandReport(appName, infoFlag) @@ -47,8 +47,10 @@ func main() { err = logs.CommandSet(appName, property, value) case "vector-logs": args := flag.NewFlagSet("logs:vector-logs", flag.ExitOnError) + num := args.Int("num", 100, "the number of lines to display") + tail := args.Bool("tail", false, "continually stream logs") args.Parse(os.Args[2:]) - err = logs.CommandVectorLogs() + err = logs.CommandVectorLogs(*num, *tail) case "vector-start": args := flag.NewFlagSet("logs:vector-start", flag.ExitOnError) vectorImage := args.String("vector-image", logs.VectorImage, "--vector-image: the name of the docker image to run for vector") diff --git a/plugins/logs/subcommands.go b/plugins/logs/subcommands.go index 06b25aaf8..d9c8ce572 100644 --- a/plugins/logs/subcommands.go +++ b/plugins/logs/subcommands.go @@ -5,6 +5,7 @@ import ( "fmt" "path/filepath" "strconv" + "time" "github.com/dokku/dokku/plugins/common" ) @@ -84,7 +85,7 @@ func CommandSet(appName string, property string, value string) error { } // CommandVectorLogs tails the log output for the vector container -func CommandVectorLogs() error { +func CommandVectorLogs(lines int, tail bool) error { if !common.ContainerExists(vectorContainerName) { return errors.New("Vector container does not exist") } @@ -93,8 +94,8 @@ func CommandVectorLogs() error { return errors.New("Vector container is not running") } - common.LogInfo1Quiet("Tailing vector container logs") - common.LogVerboseQuietContainerLogsTail(vectorContainerName) + common.LogInfo1Quiet("Vector container logs") + common.LogVerboseQuietContainerLogsTail(vectorContainerName, lines, tail) return nil } @@ -108,19 +109,28 @@ func CommandVectorStart(vectorImage string) error { return err } - if !common.ContainerExists(vectorContainerName) { - return startVectorContainer(vectorImage) + if common.ContainerExists(vectorContainerName) { + if common.ContainerIsRunning(vectorContainerName) { + common.LogVerbose("Vector container is running") + return nil + } + + common.LogVerbose("Starting vector container") + if !common.ContainerStart(vectorContainerName) { + return errors.New("Unable to start vector container") + } + } else { + if err := startVectorContainer(vectorImage); err != nil { + return err + } } - if common.ContainerIsRunning(vectorContainerName) { - common.LogVerbose("Container already running") - return nil - } - - if !common.ContainerStart(vectorContainerName) { - return errors.New("Unable to start vector container") + common.LogVerbose("Waiting for 10 seconds") + if err := common.ContainerWaitTilReady(vectorContainerName, 10*time.Second); err != nil { + return errors.New("Vector container did not start properly, run logs:vector-logs for more details") } + common.LogVerbose("Vector container is running") return nil } diff --git a/plugins/network/report.go b/plugins/network/report.go index ff9fcfdda..a1cc197b4 100644 --- a/plugins/network/report.go +++ b/plugins/network/report.go @@ -19,10 +19,15 @@ func ReportSingleApp(appName, infoFlag string) error { "--network-web-listeners": reportWebListeners, } + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + trimPrefix := false uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("network", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp("network", appName, infoFlag, infoFlags, flagKeys, trimPrefix, uppercaseFirstCharacter) } func reportBindAllInterfaces(appName string) string { diff --git a/plugins/network/src/subcommands/subcommands.go b/plugins/network/src/subcommands/subcommands.go index b5f1a5cc7..1842692e2 100644 --- a/plugins/network/src/subcommands/subcommands.go +++ b/plugins/network/src/subcommands/subcommands.go @@ -53,8 +53,8 @@ func main() { err = network.CommandRebuildall() case "report": args := flag.NewFlagSet("network:report", flag.ExitOnError) - osArgs, infoFlag, err := common.ParseReportArgs("network", os.Args[2:]) - if err == nil { + osArgs, infoFlag, flagErr := common.ParseReportArgs("network", os.Args[2:]) + if flagErr == nil { args.Parse(osArgs) appName := args.Arg(0) err = network.CommandReport(appName, infoFlag) diff --git a/plugins/proxy/report.go b/plugins/proxy/report.go index f32365ab1..01f181307 100644 --- a/plugins/proxy/report.go +++ b/plugins/proxy/report.go @@ -18,10 +18,15 @@ func ReportSingleApp(appName string, infoFlag string) error { "--proxy-port-map": reportPortMap, } + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + trimPrefix := false uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("proxy", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp("proxy", appName, infoFlag, infoFlags, flagKeys, trimPrefix, uppercaseFirstCharacter) } func reportEnabled(appName string) string { diff --git a/plugins/proxy/src/subcommands/subcommands.go b/plugins/proxy/src/subcommands/subcommands.go index 7765b0375..5df47134c 100644 --- a/plugins/proxy/src/subcommands/subcommands.go +++ b/plugins/proxy/src/subcommands/subcommands.go @@ -64,8 +64,8 @@ func main() { err = proxy.CommandPortsSet(appName, portMaps) case "report": args := flag.NewFlagSet("proxy:report", flag.ExitOnError) - osArgs, infoFlag, err := common.ParseReportArgs("proxy", os.Args[2:]) - if err == nil { + osArgs, infoFlag, flagErr := common.ParseReportArgs("proxy", os.Args[2:]) + if flagErr == nil { args.Parse(osArgs) appName := args.Arg(0) err = proxy.CommandReport(appName, infoFlag) diff --git a/plugins/ps/report.go b/plugins/ps/report.go index 838eaee49..46656697c 100644 --- a/plugins/ps/report.go +++ b/plugins/ps/report.go @@ -28,10 +28,15 @@ func ReportSingleApp(appName, infoFlag string) error { flags[flag] = fn } + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + trimPrefix := false uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("ps", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp("ps", appName, infoFlag, infoFlags, flagKeys, trimPrefix, uppercaseFirstCharacter) } func addStatusFlags(appName string, infoFlag string) map[string]common.ReportFunc { diff --git a/plugins/ps/src/subcommands/subcommands.go b/plugins/ps/src/subcommands/subcommands.go index d19c51173..41aae7d35 100644 --- a/plugins/ps/src/subcommands/subcommands.go +++ b/plugins/ps/src/subcommands/subcommands.go @@ -32,8 +32,8 @@ func main() { err = ps.CommandRebuild(appName, *allApps, *parallelCount) case "report": args := flag.NewFlagSet("ps:report", flag.ExitOnError) - osArgs, infoFlag, err := common.ParseReportArgs("ps", os.Args[2:]) - if err == nil { + osArgs, infoFlag, flagErr := common.ParseReportArgs("ps", os.Args[2:]) + if flagErr == nil { args.Parse(osArgs) appName := args.Arg(0) err = ps.CommandReport(appName, infoFlag) diff --git a/plugins/resource/resource.go b/plugins/resource/resource.go index 7d0ea4c4c..7f09e973f 100644 --- a/plugins/resource/resource.go +++ b/plugins/resource/resource.go @@ -28,15 +28,20 @@ func ReportSingleApp(appName, infoFlag string) error { return nil } - infoFlags := map[string]string{} + flags := map[string]string{} for key, value := range resources { flag := fmt.Sprintf("--resource-%v", key) - infoFlags[flag] = value + flags[flag] = value + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) } trimPrefix := true uppercaseFirstCharacter := false - return common.ReportSingleApp("resource", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp("resource", appName, infoFlag, flags, flagKeys, trimPrefix, uppercaseFirstCharacter) } // GetResourceValue fetches a single value for a given app/process/request/key combination diff --git a/plugins/resource/src/subcommands/subcommands.go b/plugins/resource/src/subcommands/subcommands.go index c0808d06b..8d079e329 100644 --- a/plugins/resource/src/subcommands/subcommands.go +++ b/plugins/resource/src/subcommands/subcommands.go @@ -50,8 +50,8 @@ func main() { err = resource.CommandLimitClear(appName, *processType) case "report": args := flag.NewFlagSet("resource:report", flag.ExitOnError) - osArgs, infoFlag, err := common.ParseReportArgs("resource", os.Args[2:]) - if err == nil { + osArgs, infoFlag, flagErr := common.ParseReportArgs("resource", os.Args[2:]) + if flagErr == nil { args.Parse(osArgs) appName := args.Arg(0) err = resource.CommandReport(appName, infoFlag) diff --git a/tests/unit/logs.bats b/tests/unit/logs.bats new file mode 100644 index 000000000..0e86f5f3a --- /dev/null +++ b/tests/unit/logs.bats @@ -0,0 +1,234 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup +} + +teardown() { + destroy_app + global_teardown +} + +@test "(logs) logs:help" { + run /bin/bash -c "dokku logs:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage log integration for an app" +} + +@test "(logs) logs:report" { + run /bin/bash -c "dokku logs:report" + echo "output: $output" + echo "status: $status" + assert_output_contains "You haven't deployed any applications yet" + + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:report 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$TEST_APP logs information" +} + +@test "(logs) logs:report app" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:report $TEST_APP 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$TEST_APP logs information" + + run /bin/bash -c "dokku logs:report $TEST_APP --invalid-flag 2>&1" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "$TEST_APP logs information" 0 + assert_output_contains "Invalid flag passed, valid flags: --logs-global-vector-sink, --logs-vector-sink" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-vector-sink 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$TEST_APP logs information" 0 + assert_output_contains "Invalid flag passed" 0 + + 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_contains "$TEST_APP logs information" 0 + assert_output_contains "Invalid flag passed" 0 +} + +@test "(logs) logs:set [error]" { + run /bin/bash -c "dokku logs:set 2>&1" + echo "output: $output" + echo "status: $status" + assert_failure + + assert_output_contains "Please specify an app to run the command on" + run /bin/bash -c "dokku logs:set ${TEST_APP}-non-existent" 2>&1 + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "App $TEST_APP-non-existent does not exist" + + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:set $TEST_APP" 2>&1 + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "No property specified" + + run /bin/bash -c "dokku logs:set $TEST_APP invalid" 2>&1 + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid property specified, valid properties include: 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" +} + +@test "(logs) logs:set app" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-vector-sink 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:set $TEST_APP vector-sink" 2>&1 + echo "output: $output" + echo "status: $status" + 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-vector-sink 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:set $TEST_APP vector-sink console://?encoding[codec]=json" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting 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-vector-sink 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "console://?encoding[codec]=json" +} +@test "(logs) logs:set global" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + 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 vector-sink" 2>&1 + echo "output: $output" + echo "status: $status" + 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 vector-sink console://?encoding[codec]=json" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting 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 "console://?encoding[codec]=json" + + run /bin/bash -c "dokku logs:set --global vector-sink" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Unsetting vector-sink" + assert_output_contains "Writing updated vector config to /var/lib/dokku/data/logs/vector.json" +} + +@test "(logs) logs:vector" { + run /bin/bash -c "dokku logs:vector-logs 2>&1" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Vector container does not exist" + + run /bin/bash -c "dokku logs:vector-start 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Vector container is running" + + run /bin/bash -c "dokku logs:vector-logs 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Vector container logs" + + run /bin/bash -c "dokku logs:vector-logs --num 10 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Vector container logs" + assert_output_contains "vector:" 10 + assert_line_count 11 + + run /bin/bash -c "dokku logs:vector-logs --num 5 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Vector container logs" + assert_output_contains "vector:" 5 + assert_line_count 6 + + run /bin/bash -c "dokku logs:vector-stop 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "StoppingĀ and removing vector container" +} diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index cebb96fce..353772f39 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -163,6 +163,14 @@ assert_line() { fi } +# ShellCheck doesn't know about $lines from Bats +# shellcheck disable=SC2154 +assert_line_count() { + declare EXPECTED="$1" + local num_lines="${#lines[@]}" + assert_equal "$EXPECTED" "$num_lines" +} + refute_line() { if [[ "$1" -ge 0 ]] 2>/dev/null; then local num_lines="${#lines[@]}"