From 502cedf1e147bfb1f959791743b0b37cda4b5527 Mon Sep 17 00:00:00 2001 From: oleiade Date: Tue, 20 Aug 2024 18:22:38 +0200 Subject: [PATCH 1/9] Implement the --local-execution mode for k6 cloud run --- cmd/cloud.go | 2 +- cmd/cloud_run.go | 144 ++++++++++++++++++++++++++++---- cmd/tests/cmd_cloud_run_test.go | 53 +++++++++++- 3 files changed, 183 insertions(+), 16 deletions(-) diff --git a/cmd/cloud.go b/cmd/cloud.go index 9f529bd7064..834f881b0ef 100644 --- a/cmd/cloud.go +++ b/cmd/cloud.go @@ -382,7 +382,7 @@ To run tests in the cloud, users are now invited to migrate to the "k6 cloud run } // Register `k6 cloud` subcommands - cloudCmd.AddCommand(getCmdCloudRun(gs)) + cloudCmd.AddCommand(getCmdCloudRun(c)) cloudCmd.AddCommand(getCmdCloudLogin(gs)) cloudCmd.Flags().SortFlags = false diff --git a/cmd/cloud_run.go b/cmd/cloud_run.go index 2dd81033e27..40f20b4776d 100644 --- a/cmd/cloud_run.go +++ b/cmd/cloud_run.go @@ -1,21 +1,62 @@ package cmd import ( + "fmt" + "github.com/spf13/cobra" - "go.k6.io/k6/cmd/state" + "github.com/spf13/pflag" + "go.k6.io/k6/execution" + "go.k6.io/k6/execution/local" ) const cloudRunCommandName string = "run" -func getCmdCloudRun(gs *state.GlobalState) *cobra.Command { - deprecatedCloudCmd := &cmdCloud{ - gs: gs, - showCloudLogs: true, - exitOnRunning: false, - uploadOnly: false, +type cmdCloudRun struct { + // localExecution stores the state of the --local-execution flag. + localExecution bool + + // linger stores the state of the --linger flag. + linger bool + + // noUsageReport stores the state of the --no-usage-report flag. + noUsageReport bool + + // runCmd holds an instance of the k6 run command that we store + // in order to be able to call its run method to support + // the --local-execution flag mode. + runCmd *cmdRun + + // deprecatedCloudCmd holds an instance of the k6 cloud command that we store + // in order to be able to call its run method to support the cloud execution + // feature, and to have access to its flagSet if necessary. + deprecatedCloudCmd *cmdCloud +} + +func getCmdCloudRun(cloudCmd *cmdCloud) *cobra.Command { + // We instantiate the run command here to be able to call its run method + // when the --local-execution flag is set. + runCmd := &cmdRun{ + gs: cloudCmd.gs, + + // We override the loadConfiguredTest func to use the local execution + // configuration which enforces the use of the cloud output among other + // side effects. + loadConfiguredTest: func(cmd *cobra.Command, args []string) ( + *loadedAndConfiguredTest, + execution.Controller, + error, + ) { + test, err := loadAndConfigureLocalTest(cloudCmd.gs, cmd, args, getCloudRunLocalExecutionConfig) + return test, local.NewController(), err + }, } - exampleText := getExampleText(gs, ` + cloudRunCmd := &cmdCloudRun{ + deprecatedCloudCmd: cloudCmd, + runCmd: runCmd, + } + + exampleText := getExampleText(cloudCmd.gs, ` # Run a test script in Grafana Cloud k6 $ {{.}} cloud run script.js @@ -25,7 +66,7 @@ func getCmdCloudRun(gs *state.GlobalState) *cobra.Command { # Read a test script or archive from stdin and run it in Grafana Cloud k6 $ {{.}} cloud run - < script.js`[1:]) - cloudRunCmd := &cobra.Command{ + thisCmd := &cobra.Command{ Use: cloudRunCommandName, Short: "Run a test in Grafana Cloud k6", Long: `Run a test in Grafana Cloud k6. @@ -38,12 +79,87 @@ Use the "k6 cloud login" command to authenticate.`, "the k6 cloud run command expects a single argument consisting in either a path to a script or "+ "archive file, or the \"-\" symbol indicating the script or archive should be read from stdin", ), - PreRunE: deprecatedCloudCmd.preRun, - RunE: deprecatedCloudCmd.run, + PreRunE: cloudRunCmd.preRun, + RunE: cloudRunCmd.run, + } + + thisCmd.Flags().SortFlags = false + thisCmd.Flags().AddFlagSet(cloudRunCmd.flagSet()) + thisCmd.Flags().AddFlagSet(cloudCmd.flagSet()) + + return thisCmd +} + +func (c *cmdCloudRun) preRun(cmd *cobra.Command, args []string) error { + if c.localExecution { + return nil + } + + if c.linger { + return fmt.Errorf("the --linger flag can only be used in conjunction with the --local-execution flag") + } + + if c.noUsageReport { + return fmt.Errorf("the --no-usage-report can only be used in conjunction with the --local-execution flag") + } + + return c.deprecatedCloudCmd.preRun(cmd, args) +} + +func (c *cmdCloudRun) run(cmd *cobra.Command, args []string) error { + if c.localExecution { + if cmd.Flags().Changed("exit-on-running") { + return fmt.Errorf("the --local-execution flag is not compatible with the --exit-on-running flag") + } + + if cmd.Flags().Changed("show-logs") { + return fmt.Errorf("the --local-execution flag is not compatible with the --show-logs flag") + } + + return c.runCmd.run(cmd, args) + } + + // When executing in the cloud, we enforce the usage report to be deactivated. + c.noUsageReport = true + + return c.deprecatedCloudCmd.run(cmd, args) +} + +func (c *cmdCloudRun) flagSet() *pflag.FlagSet { + flags := pflag.NewFlagSet("", pflag.ContinueOnError) + flags.SortFlags = false + + flags.BoolVar(&c.localExecution, "local-execution", c.localExecution, + "executes the test locally instead of in the cloud") + flags.BoolVar( + &c.linger, + "linger", + c.linger, + "only when using the local-execution mode, keeps the API server alive past the test end", + ) + flags.BoolVar( + &c.noUsageReport, + "no-usage-report", + c.noUsageReport, + "only when using the local-execution mode, don't send anonymous stats to the developers", + ) + + return flags +} + +func getCloudRunLocalExecutionConfig(flags *pflag.FlagSet) (Config, error) { + opts, err := getOptions(flags) + if err != nil { + return Config{}, err } - cloudRunCmd.Flags().SortFlags = false - cloudRunCmd.Flags().AddFlagSet(deprecatedCloudCmd.flagSet()) + // When running locally, we force the output to be cloud. + out := []string{"cloud"} - return cloudRunCmd + return Config{ + Options: opts, + Out: out, + Linger: getNullBool(flags, "linger"), + NoUsageReport: getNullBool(flags, "no-usage-report"), + }, nil } diff --git a/cmd/tests/cmd_cloud_run_test.go b/cmd/tests/cmd_cloud_run_test.go index b16ec5b1a1c..0765f1c1e13 100644 --- a/cmd/tests/cmd_cloud_run_test.go +++ b/cmd/tests/cmd_cloud_run_test.go @@ -1,6 +1,11 @@ package tests -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.k6.io/k6/cmd" +) func TestK6CloudRun(t *testing.T) { t.Parallel() @@ -10,3 +15,49 @@ func TestK6CloudRun(t *testing.T) { func setupK6CloudRunCmd(cliFlags []string) []string { return append([]string{"k6", "cloud", "run"}, append(cliFlags, "test.js")...) } + +func TestCloudRunCommandIncompatibleFlags(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + cliArgs []string + wantStderrContains string + }{ + { + name: "using --linger should be incompatible with k6 cloud run", + cliArgs: []string{"--linger"}, + wantStderrContains: "the --linger flag can only be used in conjunction with the --local-execution flag", + }, + { + name: "using --no-usage-report linger should be incompatible with k6 cloud run", + cliArgs: []string{"--no-usage-report"}, + wantStderrContains: "the --no-usage-report can only be used in conjunction with the --local-execution flag", + }, + { + name: "using --exit-on-running should be incompatible with k6 cloud run --local-execution", + cliArgs: []string{"--local-execution", "--exit-on-running"}, + wantStderrContains: "the --local-execution flag is not compatible with the --exit-on-running flag", + }, + { + name: "using --show-logs should be incompatible with k6 cloud run --local-execution", + cliArgs: []string{"--local-execution", "--show-logs"}, + wantStderrContains: "the --local-execution flag is not compatible with the --show-logs flag", + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ts := getSimpleCloudTestState(t, nil, setupK6CloudRunCmd, tc.cliArgs, nil, nil) + ts.ExpectedExitCode = -1 + cmd.ExecuteWithGlobalState(ts.GlobalState) + + stderr := ts.Stderr.String() + assert.Contains(t, stderr, tc.wantStderrContains) + }) + } +} From 0390e6569d4e4b06f6fe8a53d920a43019e909fb Mon Sep 17 00:00:00 2001 From: oleiade Date: Thu, 29 Aug 2024 09:53:17 +0200 Subject: [PATCH 2/9] Explicit the handling of the --no-usage-report option This commits aligns the handling of the --no-usage-report option in the context of the k6 cloud run command with the following agreed upon behavior: * k6 cloud run --no-usage-report should ignore the flag, and make sure we don't send the report. * k6 cloud run --local-execution --no-usage-report should take the flag in consideration, and act accordingly. --- cmd/cloud_run.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cmd/cloud_run.go b/cmd/cloud_run.go index 40f20b4776d..c21fca771e9 100644 --- a/cmd/cloud_run.go +++ b/cmd/cloud_run.go @@ -92,6 +92,14 @@ Use the "k6 cloud login" command to authenticate.`, func (c *cmdCloudRun) preRun(cmd *cobra.Command, args []string) error { if c.localExecution { + if cmd.Flags().Changed("exit-on-running") { + return fmt.Errorf("the --local-execution flag is not compatible with the --exit-on-running flag") + } + + if cmd.Flags().Changed("show-logs") { + return fmt.Errorf("the --local-execution flag is not compatible with the --show-logs flag") + } + return nil } @@ -99,27 +107,19 @@ func (c *cmdCloudRun) preRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("the --linger flag can only be used in conjunction with the --local-execution flag") } - if c.noUsageReport { - return fmt.Errorf("the --no-usage-report can only be used in conjunction with the --local-execution flag") - } - return c.deprecatedCloudCmd.preRun(cmd, args) } func (c *cmdCloudRun) run(cmd *cobra.Command, args []string) error { if c.localExecution { - if cmd.Flags().Changed("exit-on-running") { - return fmt.Errorf("the --local-execution flag is not compatible with the --exit-on-running flag") - } - - if cmd.Flags().Changed("show-logs") { - return fmt.Errorf("the --local-execution flag is not compatible with the --show-logs flag") - } - + // Note that when running the k6 cloud run command with the --local-execution + // flag, we handle the no-usage-report flag here as we would do in the k6 run + // command. return c.runCmd.run(cmd, args) } - // When executing in the cloud, we enforce the usage report to be deactivated. + // When running the k6 cloud run command and executing in the cloud however, we + // explicitly disable the usage report. c.noUsageReport = true return c.deprecatedCloudCmd.run(cmd, args) From 61d64064d946bdbbf1add88b741706332c447c03 Mon Sep 17 00:00:00 2001 From: oleiade Date: Thu, 29 Aug 2024 11:31:10 +0200 Subject: [PATCH 3/9] Fix tests --- cmd/tests/cmd_cloud_run_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/tests/cmd_cloud_run_test.go b/cmd/tests/cmd_cloud_run_test.go index 0765f1c1e13..ccade725071 100644 --- a/cmd/tests/cmd_cloud_run_test.go +++ b/cmd/tests/cmd_cloud_run_test.go @@ -29,11 +29,6 @@ func TestCloudRunCommandIncompatibleFlags(t *testing.T) { cliArgs: []string{"--linger"}, wantStderrContains: "the --linger flag can only be used in conjunction with the --local-execution flag", }, - { - name: "using --no-usage-report linger should be incompatible with k6 cloud run", - cliArgs: []string{"--no-usage-report"}, - wantStderrContains: "the --no-usage-report can only be used in conjunction with the --local-execution flag", - }, { name: "using --exit-on-running should be incompatible with k6 cloud run --local-execution", cliArgs: []string{"--local-execution", "--exit-on-running"}, From e806632b7e3877ec946d588896dac411b248b1eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Crevon?= Date: Fri, 30 Aug 2024 15:16:20 +0200 Subject: [PATCH 4/9] Update cmd/cloud_run.go Co-authored-by: Ivan <2103732+codebien@users.noreply.github.com> --- cmd/cloud_run.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/cloud_run.go b/cmd/cloud_run.go index c21fca771e9..2657e56e0ca 100644 --- a/cmd/cloud_run.go +++ b/cmd/cloud_run.go @@ -118,8 +118,7 @@ func (c *cmdCloudRun) run(cmd *cobra.Command, args []string) error { return c.runCmd.run(cmd, args) } - // When running the k6 cloud run command and executing in the cloud however, we - // explicitly disable the usage report. + // When running the `k6 cloud run` command explicitly disable the usage report. c.noUsageReport = true return c.deprecatedCloudCmd.run(cmd, args) From 428e77db0268388126a40d1cb1368eeed893196c Mon Sep 17 00:00:00 2001 From: oleiade Date: Wed, 4 Sep 2024 16:23:33 +0200 Subject: [PATCH 5/9] Apply pull request review suggestions --- cmd/cloud_run.go | 19 ++++++++++++++++--- cmd/tests/cmd_cloud_run_test.go | 4 +++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/cmd/cloud_run.go b/cmd/cloud_run.go index 2657e56e0ca..8770da215a2 100644 --- a/cmd/cloud_run.go +++ b/cmd/cloud_run.go @@ -3,6 +3,10 @@ package cmd import ( "fmt" + "go.k6.io/k6/errext/exitcodes" + + "go.k6.io/k6/errext" + "github.com/spf13/cobra" "github.com/spf13/pflag" "go.k6.io/k6/execution" @@ -93,18 +97,27 @@ Use the "k6 cloud login" command to authenticate.`, func (c *cmdCloudRun) preRun(cmd *cobra.Command, args []string) error { if c.localExecution { if cmd.Flags().Changed("exit-on-running") { - return fmt.Errorf("the --local-execution flag is not compatible with the --exit-on-running flag") + return errext.WithExitCodeIfNone( + fmt.Errorf("the --local-execution flag is not compatible with the --exit-on-running flag"), + exitcodes.InvalidConfig, + ) } if cmd.Flags().Changed("show-logs") { - return fmt.Errorf("the --local-execution flag is not compatible with the --show-logs flag") + return errext.WithExitCodeIfNone( + fmt.Errorf("the --local-execution flag is not compatible with the --show-logs flag"), + exitcodes.InvalidConfig, + ) } return nil } if c.linger { - return fmt.Errorf("the --linger flag can only be used in conjunction with the --local-execution flag") + return errext.WithExitCodeIfNone( + fmt.Errorf("the --linger flag can only be used in conjunction with the --local-execution flag"), + exitcodes.InvalidConfig, + ) } return c.deprecatedCloudCmd.preRun(cmd, args) diff --git a/cmd/tests/cmd_cloud_run_test.go b/cmd/tests/cmd_cloud_run_test.go index ccade725071..aad3f7eb4c2 100644 --- a/cmd/tests/cmd_cloud_run_test.go +++ b/cmd/tests/cmd_cloud_run_test.go @@ -3,6 +3,8 @@ package tests import ( "testing" + "go.k6.io/k6/errext/exitcodes" + "github.com/stretchr/testify/assert" "go.k6.io/k6/cmd" ) @@ -48,7 +50,7 @@ func TestCloudRunCommandIncompatibleFlags(t *testing.T) { t.Parallel() ts := getSimpleCloudTestState(t, nil, setupK6CloudRunCmd, tc.cliArgs, nil, nil) - ts.ExpectedExitCode = -1 + ts.ExpectedExitCode = int(exitcodes.InvalidConfig) cmd.ExecuteWithGlobalState(ts.GlobalState) stderr := ts.Stderr.String() From 3b72b3a2f631cb175f2c565ada2de932520819af Mon Sep 17 00:00:00 2001 From: oleiade Date: Wed, 4 Sep 2024 16:25:39 +0200 Subject: [PATCH 6/9] Apply pull request review suggestions --- cmd/cloud_run.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/cloud_run.go b/cmd/cloud_run.go index 8770da215a2..b8c967bc41e 100644 --- a/cmd/cloud_run.go +++ b/cmd/cloud_run.go @@ -125,9 +125,6 @@ func (c *cmdCloudRun) preRun(cmd *cobra.Command, args []string) error { func (c *cmdCloudRun) run(cmd *cobra.Command, args []string) error { if c.localExecution { - // Note that when running the k6 cloud run command with the --local-execution - // flag, we handle the no-usage-report flag here as we would do in the k6 run - // command. return c.runCmd.run(cmd, args) } From e4dec3fb666cae79224117cd43b86ea75fc38676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Crevon?= Date: Tue, 10 Sep 2024 10:14:56 +0200 Subject: [PATCH 7/9] Update cmd/cloud_run.go Co-authored-by: Oleg Bespalov --- cmd/cloud_run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cloud_run.go b/cmd/cloud_run.go index b8c967bc41e..90cea8a4520 100644 --- a/cmd/cloud_run.go +++ b/cmd/cloud_run.go @@ -150,7 +150,7 @@ func (c *cmdCloudRun) flagSet() *pflag.FlagSet { &c.noUsageReport, "no-usage-report", c.noUsageReport, - "only when using the local-execution mode, don't send anonymous stats to the developers", + "only when using the local-execution mode, don't send anonymous usage stats (https://grafana.com/docs/k6/latest/set-up/usage-collection/)", ) return flags From e47a2b19d11bdddaa78545a32047b60fc56ae06d Mon Sep 17 00:00:00 2001 From: oleiade Date: Tue, 10 Sep 2024 10:31:11 +0200 Subject: [PATCH 8/9] Fix linting errors --- cmd/cloud_run.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/cloud_run.go b/cmd/cloud_run.go index 90cea8a4520..e787fd3b7fe 100644 --- a/cmd/cloud_run.go +++ b/cmd/cloud_run.go @@ -150,7 +150,8 @@ func (c *cmdCloudRun) flagSet() *pflag.FlagSet { &c.noUsageReport, "no-usage-report", c.noUsageReport, - "only when using the local-execution mode, don't send anonymous usage stats (https://grafana.com/docs/k6/latest/set-up/usage-collection/)", + "only when using the local-execution mode, don't send anonymous usage "+ + "stats (https://grafana.com/docs/k6/latest/set-up/usage-collection/)", ) return flags From 534850167146310e553a90bd25d4a34d47cbb21b Mon Sep 17 00:00:00 2001 From: oleiade Date: Tue, 10 Sep 2024 12:22:25 +0200 Subject: [PATCH 9/9] Apply Pull Request suggestions --- cmd/config.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/config.go b/cmd/config.go index f11d239486e..42b3b75143b 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -30,7 +30,11 @@ func configFlagSet() *pflag.FlagSet { flags.SortFlags = false flags.StringArrayP("out", "o", []string{}, "`uri` for an external metrics database") flags.BoolP("linger", "l", false, "keep the API server alive past test end") - flags.Bool("no-usage-report", false, "don't send anonymous stats to the developers") + flags.Bool( + "no-usage-report", + false, + "don't send anonymous usage"+"stats (https://grafana.com/docs/k6/latest/set-up/usage-collection/)", + ) return flags }