From ae382e79a57dd99b282cdb2ce41d9b45f56f0d9b Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 10 Apr 2026 15:53:21 +0300 Subject: [PATCH 1/3] feat(plz): pass new config from GCk6 to PLZ test runs --- pkg/cloud/test_runs.go | 3 +- pkg/cloud/types.go | 163 ++++++++++++++++++++++++++---- pkg/cloud/types_test.go | 66 ++++++++++++ pkg/plz/worker.go | 33 +++++- pkg/plz/worker_test.go | 25 +++-- pkg/resources/jobs/initializer.go | 3 + pkg/resources/jobs/runner.go | 5 + pkg/resources/jobs/runner_test.go | 1 + 8 files changed, 265 insertions(+), 34 deletions(-) diff --git a/pkg/cloud/test_runs.go b/pkg/cloud/test_runs.go index 11e3c2b5..a7d8df0c 100644 --- a/pkg/cloud/test_runs.go +++ b/pkg/cloud/test_runs.go @@ -103,13 +103,12 @@ func getTestRun(client *cloudapi.Client, url string) (*TestRunData, error) { if err = client.Do(req, &trData); err != nil { return nil, err } - return &trData, nil } // called by PLZworker func GetTestRunData(client *cloudapi.Client, refID string) (*TestRunData, error) { - url := fmt.Sprintf("%s/loadtests/v4/test_runs(%s)?$select=id,run_status,k8s_load_zones_config,k6_runtime_config,test_run_token,secrets_config", strings.TrimSuffix(client.BaseURL(), "/v1"), refID) + url := fmt.Sprintf("%s/loadtests/v4/test_runs(%s)?$select=id,run_status,k8s_load_zones_config,k6_runtime_config,test_run_token,secrets_config,load_zone_distribution", strings.TrimSuffix(client.BaseURL(), "/v1"), refID) return getTestRun(client, url) } diff --git a/pkg/cloud/types.go b/pkg/cloud/types.go index a7f0b14d..c12729f6 100644 --- a/pkg/cloud/types.go +++ b/pkg/cloud/types.go @@ -2,7 +2,9 @@ package cloud import ( "fmt" + "maps" "sort" + "strings" "go.k6.io/k6/cloudapi" "go.k6.io/k6/lib/types" @@ -10,6 +12,26 @@ import ( corev1 "k8s.io/api/core/v1" ) +// GCk6 can set only a limited number of env vars to k6 process: +// these are known and whitelisted with this "const" map. +var reservedGCk6EnvVars = map[string]struct{}{} + +const ( + // Reserved vars set for PLZ tests, as described here: + // https://grafana.com/docs/grafana-cloud/testing/k6/author-run/cloud-scripting-extras/cloud-execution-context-variables/ + // These are not passed from GCk6, but set by k6-operator directly. + lzCloudExecVar = "K6_CLOUDRUN_LOAD_ZONE" + distrCloudExecVar = "K6_CLOUDRUN_DISTRIBUTION" + trIDCloudExecVar = "K6_CLOUDRUN_TEST_RUN_ID" + // IIDCloudExecVar is exported as it must be set in external package, as part of TestRun CRD flow + IIDCloudExecVar = "K6_CLOUDRUN_INSTANCE_ID" + + secretSourceEnvVar = "K6_SECRET_SOURCE" + secretSourceURLTemplate = "K6_SECRET_SOURCE_URL_URL_TEMPLATE" + secretSourceURLRespPath = "K6_SECRET_SOURCE_URL_RESPONSE_PATH" + secretSourceURLAuthKey = "K6_SECRET_SOURCE_URL_HEADER_AUTHORIZATION" +) + // InspectOutput is the parsed output from `k6 inspect --execution-requirements`. type InspectOutput struct { External struct { // legacy way of defining the options.cloud @@ -63,13 +85,6 @@ type SecretsConfig struct { ResponsePath string `json:"response_path"` } -const ( - secretSourceEnvVar = "K6_SECRET_SOURCE" - secretSourceURLTemplate = "K6_SECRET_SOURCE_URL_URL_TEMPLATE" - secretSourceURLRespPath = "K6_SECRET_SOURCE_URL_RESPONSE_PATH" - secretSourceURLAuthKey = "K6_SECRET_SOURCE_URL_HEADER_AUTHORIZATION" -) - // TestRunData holds the output from /loadtests/v4/test_runs(%s) type TestRunData struct { TestRunId int `json:"id"` @@ -78,41 +93,137 @@ type TestRunData struct { RunStatus cloudapi.RunStatus `json:"run_status"` RuntimeConfig cloudapi.Config `json:"k6_runtime_config"` // SecretsToken is a short-lived, test-run-scoped token for read-only access to secrets. - SecretsToken string `json:"test_run_token,omitempty"` - SecretsConfig *SecretsConfig `json:"secrets_config,omitempty"` + SecretsToken string `json:"test_run_token,omitempty"` + SecretsConfig *SecretsConfig `json:"secrets_config,omitempty"` + // LZDistribution holds label -> distribution mapping relevant + // for the given script and PLZ + LZDistribution `json:"load_zone_distribution,omitempty"` + + // Pre-processed k6 arguments, populated by Preprocess(). + TagArgs string `json:"-"` + EnvArgs string `json:"-"` + UserAgentArg string `json:"-"` +} + +func (trd *TestRunData) TestRunID() string { + return fmt.Sprintf("%d", trd.TestRunId) +} + +// Preprocess adds specific for GCk6 tags and env vars to data, +// and produces sorted CLI argument strings for tags and environment. +// Returns error if distribution is empty. +func (trd *TestRunData) Preprocess() error { + if len(trd.LZDistribution) != 1 { + return fmt.Errorf("only tests with one load zone are supported, provided: %+v", trd.LZDistribution) + } + + if len(trd.UserAgent) > 0 { + trd.UserAgentArg = fmt.Sprintf(`--user-agent="%s"`, trd.UserAgent) + } + + if trd.Tags == nil { + trd.Tags = make(map[string]string) + } + + if trd.Environment == nil { + trd.Environment = make(map[string]string) + } + + // The potential overwrite here is deliberate: these keys are reserved by + // GCk6, described in docs and considered higher priority in PLZ tests + // for the sake of consistency between public & private cloud tests. + + trd.Tags["load_zone"] = trd.LZLabel() + + trd.Environment[lzCloudExecVar] = trd.LZName() + trd.Environment[distrCloudExecVar] = trd.LZLabel() + trd.Environment[trIDCloudExecVar] = trd.TestRunID() + + trd.TagArgs = sortedArgs("--tag", trd.Tags) + trd.EnvArgs = sortedArgs("-e", trd.Environment) + + return nil +} + +// sortedArgs builds a CLI argument string from a map, sorted by key. +func sortedArgs(flag string, m map[string]string) string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + + parts := make([]string, 0, len(keys)) + for _, k := range keys { + parts = append(parts, flag+" "+k+"="+m[k]) + } + return strings.Join(parts, " ") } type LZConfig struct { - RunnerImage string `json:"load_runner_image,omitempty"` - InstanceCount int `json:"instance_count,omitempty"` - ArchiveURL string `json:"k6_archive_temp_public_url,omitempty"` - Environment map[string]string `json:"environment,omitempty"` + RunnerImage string `json:"load_runner_image,omitempty"` + InstanceCount int `json:"instance_count,omitempty"` + ArchiveURL string `json:"k6_archive_temp_public_url,omitempty"` + CLIArgs `json:"cli_flags,omitempty"` + // Environment holds values passed by user via: + // 1. `-e` CLI option of k6 + // 2. cloud environment variables of GCk6 -> Settings + // They are passed to k6 runners via `-e` + Environment map[string]string `json:"environment,omitempty"` + // GCk6EnvVars holds key-value pairs generated by GCk6 and + // meant to configure k6 process with reserved env vars. + GCk6EnvVars map[string]string `json:"gck6_env_vars,omitempty"` } -func (trd *TestRunData) TestRunID() string { - return fmt.Sprintf("%d", trd.TestRunId) +type CLIArgs struct { + BlacklistIPs []string `json:"blacklist_ips,omitempty"` + BlockedHostnames []string `json:"blocked_hostnames,omitempty"` + IncludeSystemEnvVars bool `json:"include_system_env_vars,omitempty"` + Tags map[string]string `json:"tags,omitempty"` + UserAgent string `json:"user_agent,omitempty"` } +type LZDistribution map[string]Distribution + +type Distribution struct { + LoadZone string `json:"loadZone"` + Percent int `json:"percent"` +} + +// EnvVars makes up the corev1 struct from Go map. func (lz *LZConfig) EnvVars() []corev1.EnvVar { - ev := make([]corev1.EnvVar, len(lz.Environment)) + whitelisted := maps.Collect( + func(yield func(_, _ string) bool) { + for k, v := range lz.GCk6EnvVars { + if _, ok := reservedGCk6EnvVars[k]; ok { + if !yield(k, v) { + return + } + } + } + }, + ) + + ev := make([]corev1.EnvVar, len(whitelisted)) i := 0 - for k, v := range lz.Environment { + for k, v := range whitelisted { ev[i] = corev1.EnvVar{ Name: k, Value: v, } i++ } + // to have deterministic order in the array sort.Slice(ev, func(i, j int) bool { return ev[i].Name < ev[j].Name }) - return ev } // SecretsEnvVars returns the env vars required by the k6 URL secret source. // Returns nil when no secrets configuration is present. +// TODO: make this private and move it to EnvVars() / Preprocess() func (trd *TestRunData) SecretsEnvVars() []corev1.EnvVar { if trd.SecretsConfig == nil { return nil @@ -131,6 +242,22 @@ func (trd *TestRunData) SecretsEnvVars() []corev1.EnvVar { return ev } +// LZLabel assumes there is only one LZ. +func (lzd *LZDistribution) LZLabel() string { + for k := range *lzd { + return k + } + return "unknown_lz_label" +} + +// LZName assumes there is only one LZ. +func (lzd *LZDistribution) LZName() string { + for _, v := range *lzd { + return v.LoadZone + } + return "unknown_lz_name" +} + type TestRunStatus cloudapi.RunStatus func (trs TestRunStatus) Aborted() bool { diff --git a/pkg/cloud/types_test.go b/pkg/cloud/types_test.go index e82bcff3..bacb6a57 100644 --- a/pkg/cloud/types_test.go +++ b/pkg/cloud/types_test.go @@ -111,6 +111,72 @@ func TestTestRunData_SecretsEnvVars(t *testing.T) { } } +func TestLZConfig_EnvVars(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + lz LZConfig + reserved map[string]struct{} + expected []corev1.EnvVar + }{ + { + name: "empty GCk6EnvVar", + lz: LZConfig{}, + expected: []corev1.EnvVar{}, + }, + { + name: "no keys match whitelist", + lz: LZConfig{ + GCk6EnvVars: map[string]string{"NOT_RESERVED": "val"}, + }, + reserved: map[string]struct{}{"RESERVED_A": {}}, + expected: []corev1.EnvVar{}, + }, + { + name: "all keys match whitelist", + lz: LZConfig{ + GCk6EnvVars: map[string]string{"VAR_B": "b", "VAR_A": "a"}, + }, + reserved: map[string]struct{}{"VAR_A": {}, "VAR_B": {}}, + expected: []corev1.EnvVar{ + {Name: "VAR_A", Value: "a"}, + {Name: "VAR_B", Value: "b"}, + }, + }, + { + name: "some keys match whitelist", + lz: LZConfig{ + GCk6EnvVars: map[string]string{"VAR_B": "b", "SKIP": "no", "VAR_A": "a"}, + }, + reserved: map[string]struct{}{"VAR_B": {}, "VAR_A": {}}, + expected: []corev1.EnvVar{ + {Name: "VAR_A", Value: "a"}, + {Name: "VAR_B", Value: "b"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Deliberately not parallel: subtests mutate package-level reservedGCk6EnvVars. + orig := reservedGCk6EnvVars + reservedGCk6EnvVars = tt.reserved + defer func() { reservedGCk6EnvVars = orig }() + + got := tt.lz.EnvVars() + if len(got) != len(tt.expected) { + t.Fatalf("len = %d, want %d", len(got), len(tt.expected)) + } + for i := range got { + if got[i] != tt.expected[i] { + t.Errorf("EnvVars()[%d] = %v, want %v", i, got[i], tt.expected[i]) + } + } + }) + } +} + func TestInspectOutput_SetTestName(t *testing.T) { t.Parallel() diff --git a/pkg/plz/worker.go b/pkg/plz/worker.go index 989e4b66..4ceaf052 100644 --- a/pkg/plz/worker.go +++ b/pkg/plz/worker.go @@ -3,6 +3,8 @@ package plz import ( "context" "fmt" + "slices" + "strings" "github.com/go-logr/logr" "github.com/grafana/k6-operator/api/v1alpha1" @@ -185,10 +187,30 @@ func (w *PLZWorker) complete(tr *v1alpha1.TestRun, trData *cloud.TestRunData) { } tr.Spec.Runner.Env = envVars tr.Spec.Parallelism = int32(trData.InstanceCount) - tr.Spec.Arguments = fmt.Sprintf(`--out cloud --no-thresholds --log-output=loki=https://cloudlogs.k6.io/api/v1/push,label.lz=%s,label.test_run_id=%s,header.Authorization="Token $(K6_CLOUD_TOKEN)"`, - w.plz.Name, - trData.TestRunID()) + tr.Spec.TestRunID = trData.TestRunID() + + // building argument list to k6 + bips := `--blacklist-ip="` + strings.Join(trData.BlacklistIPs, ",") + `"` + bhns := `--block-hostnames="` + strings.Join(trData.BlockedHostnames, ",") + `"` + + args := []string{ + "--out cloud", + bips, + bhns, + trData.TagArgs, + "--no-thresholds", + trData.UserAgentArg, + fmt.Sprintf(`--log-output=loki=https://cloudlogs.k6.io/api/v1/push,label.lz=%s,label.test_run_id=%s,header.Authorization="Token $(K6_CLOUD_TOKEN)"`, w.plz.Name, trData.TestRunID()), + trData.EnvArgs, + } + if trData.IncludeSystemEnvVars { + args = append(args, "--include-system-env-vars", "--verbose") + } + + args = slices.DeleteFunc(args, func(s string) bool { return s == "" }) + tr.Spec.Arguments = strings.Join(args, " ") + } // handle creates a new PLZ TestRun from the given test run id @@ -214,6 +236,11 @@ func (w *PLZWorker) handle(testRunId string) { w.logger.Error(err, fmt.Sprintf("Failed to retrieve test run data for `%s`", testRunId)) return } + if err = trData.Preprocess(); err != nil { + w.logger.Error(err, fmt.Sprintf("Failed to sort out test run data for `%s`", testRunId)) + return + } + w.complete(tr, trData) w.logger.Info(fmt.Sprintf("PLZ test run has been prepared with image `%s` and `%d` instances", diff --git a/pkg/plz/worker_test.go b/pkg/plz/worker_test.go index a09e4518..1ad8ebef 100644 --- a/pkg/plz/worker_test.go +++ b/pkg/plz/worker_test.go @@ -109,7 +109,7 @@ func Test_complete_correctDefinitionOfTestRun(t *testing.T) { }, Parallelism: int32(0), Separate: false, - Arguments: "--out cloud --no-thresholds --log-output=loki=https://cloudlogs.k6.io/api/v1/push,label.lz=,label.test_run_id=0,header.Authorization=\"Token $(K6_CLOUD_TOKEN)\"", + Arguments: `--out cloud --blacklist-ip="" --block-hostnames="" --no-thresholds --log-output=loki=https://cloudlogs.k6.io/api/v1/push,label.lz=,label.test_run_id=0,header.Authorization="Token $(K6_CLOUD_TOKEN)"`, Cleanup: v1alpha1.Cleanup("post"), TestRunID: "0", @@ -133,6 +133,9 @@ func Test_complete_correctDefinitionOfTestRun(t *testing.T) { "ENV": "VALUE", "foo": "bar", } + someLZDistribution = cloud.LZDistribution{ + "some-label": cloud.Distribution{LoadZone: "some-zone", Percent: 100}, + } // podTemplate test values someAllowPrivEscalation = false someRunAsUser int64 = 1000 @@ -199,16 +202,11 @@ func Test_complete_correctDefinitionOfTestRun(t *testing.T) { cloudFieldsTestRun.Spec.Parallelism = int32(someInstances) cloudEnvVarsTestRun = cloudFieldsTestRun // build up on top of cloud fields case - cloudEnvVarsTestRun.Spec.Runner.Env = append([]corev1.EnvVar{ - { - Name: "ENV", - Value: "VALUE", - }, - { - Name: "foo", - Value: "bar", - }, - }, defaultTestRun.Spec.Runner.Env...) + cloudEnvVarsTestRun.Spec.Arguments = strings.Replace(cloudEnvVarsTestRun.Spec.Arguments, + `--block-hostnames="" --no-thresholds`, + `--block-hostnames="" --tag load_zone=some-label --no-thresholds`, + 1) + cloudEnvVarsTestRun.Spec.Arguments += " -e ENV=VALUE -e K6_CLOUDRUN_DISTRIBUTION=some-label -e K6_CLOUDRUN_LOAD_ZONE=some-zone -e K6_CLOUDRUN_TEST_RUN_ID=6543 -e foo=bar" podTemplateTolerationsTestRun = requiredFieldsTestRun podTemplateTolerationsTestRun.Spec.Runner.Tolerations = someTolerations @@ -330,7 +328,11 @@ func Test_complete_correctDefinitionOfTestRun(t *testing.T) { InstanceCount: someInstances, ArchiveURL: someArchiveURL, Environment: someEnvVars, + CLIArgs: cloud.CLIArgs{ + Tags: map[string]string{}, + }, }, + LZDistribution: someLZDistribution, }, ingestUrl: mainIngest, expected: &cloudEnvVarsTestRun, @@ -457,6 +459,7 @@ func Test_complete_correctDefinitionOfTestRun(t *testing.T) { worker := NewPLZWorker(testCase.plz, "token", c, logr.Logger{}) tr := worker.template.Create() + _ = testCase.cloudData.Preprocess() worker.complete(tr, testCase.cloudData) if diff := deep.Equal(tr, testCase.expected); diff != nil { diff --git a/pkg/resources/jobs/initializer.go b/pkg/resources/jobs/initializer.go index b1e0d5da..a73a71c5 100644 --- a/pkg/resources/jobs/initializer.go +++ b/pkg/resources/jobs/initializer.go @@ -55,6 +55,9 @@ func NewInitializerJob(k6 *v1alpha1.TestRun, argLine string) (*batchv1.Job, erro } // NOTE: only .env are passed to k6 CLI, not .envFrom + // This is esp. relevant for the cloud output test where + // duration of the test may depend on env var values. IOW, + // these env vars must always be passed in cloud output mode. var envVarString string for _, ev := range k6.GetSpec().Initializer.Env { envVarString += fmt.Sprintf(` -e %s="%s"`, ev.Name, ev.Value) diff --git a/pkg/resources/jobs/runner.go b/pkg/resources/jobs/runner.go index e7d1c714..f5dde28d 100644 --- a/pkg/resources/jobs/runner.go +++ b/pkg/resources/jobs/runner.go @@ -77,6 +77,11 @@ func NewRunnerJob(k6 *v1alpha1.TestRun, index int, tokenInfo *cloud.TokenInfo) ( command = append(command, "--no-setup", "--no-teardown", "--linger") } + // For PLZ tests, we add a reserved env var containing instance ID. + if len(k6.TestRunID()) > 0 && v1alpha1.IsTrue(k6, v1alpha1.CloudPLZTestRun) { + command = append(command, "-e", fmt.Sprintf(`%s=%d`, cloud.IIDCloudExecVar, index)) + } + command = script.UpdateCommand(command) var ( diff --git a/pkg/resources/jobs/runner_test.go b/pkg/resources/jobs/runner_test.go index 79481da1..dea7842a 100644 --- a/pkg/resources/jobs/runner_test.go +++ b/pkg/resources/jobs/runner_test.go @@ -444,6 +444,7 @@ func Test_NewRunnerJob(t *testing.T) { "k6", "run", "--quiet", "/test/test.js", "--address=0.0.0.0:6565", "--paused", "--tag", "instance_id=1", "--tag", "job_name=test-1", "--no-setup", "--no-teardown", "--linger", + "-e", "K6_CLOUDRUN_INSTANCE_ID=1", } j.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{ {Name: "K6_CLOUD_PUSH_REF_ID", Value: "plz-run-123"}, From dd1929f45d307976de3e890cc17e2919ca5becb8 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 10 Apr 2026 15:53:59 +0300 Subject: [PATCH 2/3] fix: filter additional args from k6 CLI to initializer --- pkg/types/k6cli.go | 11 ++++++++- pkg/types/k6cli_test.go | 53 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/pkg/types/k6cli.go b/pkg/types/k6cli.go index cbf14b52..56df9607 100644 --- a/pkg/types/k6cli.go +++ b/pkg/types/k6cli.go @@ -48,7 +48,16 @@ func ParseCLI(arguments string) (*CLI, error) { // `k6 archive` ignores this argument but if it contains an env var // for token (cloud logs for PLZ test runs), it will break the shell; // so omit it. - break + i = end + continue + } + + // Unsupported by `k6 archive`. + if strings.HasPrefix(args[i], "--block-hostnames") || + strings.HasPrefix(args[i], "--blacklist-ip") || + strings.HasPrefix(args[i], "--user-agent") { + i = end + continue } switch args[i] { diff --git a/pkg/types/k6cli_test.go b/pkg/types/k6cli_test.go index 47193d32..9aa2b9f2 100644 --- a/pkg/types/k6cli_test.go +++ b/pkg/types/k6cli_test.go @@ -8,10 +8,10 @@ import ( func Test_ParseCLI(t *testing.T) { tests := []struct { - name string - argLine string - cli CLI - validArguments bool + name string + argLine string + cli CLI + invalidArguments bool }{ { "EmptyArgs", @@ -87,12 +87,54 @@ func Test_ParseCLI(t *testing.T) { }, false, }, + { + "OmitLogOutputInDiffOrder", + `--out cloud --log-output=loki=https://cloudlogs.k6.io/api/v1/push,label.lz=my-plz,label.test_run_id=1111,header.Authorization="Token $(K6_CLOUD_TOKEN)" --no-thresholds`, + CLI{ + ArchiveArgs: "--no-thresholds", + HasCloudOut: true, + }, + false, + }, { "InvalidArguments", `run this-argument-does-not-matter.js -o json`, CLI{}, true, }, + { + "SkipBlockHostnamesEquals", + `--vus 10 --block-hostnames="google.com" --duration 5s`, + CLI{ + ArchiveArgs: "--vus 10 --duration 5s", + }, + false, + }, + { + "SkipBlacklistIpEquals", + `--vus 10 --blacklist-ip="8.8.8.8/32" --duration 5s`, + CLI{ + ArchiveArgs: "--vus 10 --duration 5s", + }, + false, + }, + { + "SkipUserAgentEquals", + `--vus 10 --user-agent="foo" --duration 5s`, + CLI{ + ArchiveArgs: "--vus 10 --duration 5s", + }, + false, + }, + { + "IncludeSystemEnvVars", + `--out cloud --include-system-env-vars`, + CLI{ + ArchiveArgs: "--include-system-env-vars", + HasCloudOut: true, + }, + false, + }, } for _, test := range tests { @@ -100,8 +142,7 @@ func Test_ParseCLI(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() cli, err := ParseCLI(test.argLine) - - assert.Equal(t, test.validArguments, err != nil) + assert.Equal(t, test.invalidArguments, err != nil) assert.Equal(t, test.cli.ArchiveArgs, cli.ArchiveArgs) assert.Equal(t, test.cli.HasCloudOut, cli.HasCloudOut) }) From b1b0bf4e0287ef51349d9ab7ea8dc3b05dd511ab Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Sun, 19 Apr 2026 23:33:48 +0300 Subject: [PATCH 3/3] feat(plz): pass temporary K6_CLOUD_TOKEN from GCk6 when its availble --- pkg/cloud/types.go | 4 +++- pkg/resources/jobs/runner.go | 39 ++++++++++++++++++++----------- pkg/resources/jobs/runner_test.go | 10 ++++---- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/pkg/cloud/types.go b/pkg/cloud/types.go index c12729f6..65871063 100644 --- a/pkg/cloud/types.go +++ b/pkg/cloud/types.go @@ -14,7 +14,9 @@ import ( // GCk6 can set only a limited number of env vars to k6 process: // these are known and whitelisted with this "const" map. -var reservedGCk6EnvVars = map[string]struct{}{} +var reservedGCk6EnvVars = map[string]struct{}{ + "K6_CLOUD_TOKEN": struct{}{}, +} const ( // Reserved vars set for PLZ tests, as described here: diff --git a/pkg/resources/jobs/runner.go b/pkg/resources/jobs/runner.go index f5dde28d..ab07e4e6 100644 --- a/pkg/resources/jobs/runner.go +++ b/pkg/resources/jobs/runner.go @@ -124,26 +124,37 @@ func NewRunnerJob(k6 *v1alpha1.TestRun, index int, tokenInfo *cloud.TokenInfo) ( env := newIstioEnvVar(k6.GetSpec().Scuttle, istioEnabled) + // TODO: refactor this + // this is a cloud test run: either cloud output or PLZ if len(k6.TestRunID()) > 0 { - // cloud output case - tokenVar := corev1.EnvVar{ - Name: "K6_CLOUD_TOKEN", - Value: tokenInfo.Value(), - } - if v1alpha1.IsTrue(k6, v1alpha1.CloudPLZTestRun) { - tokenVar = corev1.EnvVar{ - Name: "K6_CLOUD_TOKEN", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{Name: tokenInfo.SecretName()}, - Key: "token", + // If K6_CLOUD_TOKEN is absent from Runner.Env (not provided by GCk6), + // fall back to loading it from the Kubernetes Secret. + hasCloudToken := false + for _, e := range k6.GetSpec().Runner.Env { + if e.Name == "K6_CLOUD_TOKEN" { + hasCloudToken = true + break + } + } + if !hasCloudToken { + env = append(env, corev1.EnvVar{ + Name: "K6_CLOUD_TOKEN", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: tokenInfo.SecretName()}, + Key: "token", + }, }, - }, + }) } } else { // cloud output case + env = append(env, corev1.EnvVar{ + Name: "K6_CLOUD_TOKEN", + Value: tokenInfo.Value(), + }) aggregationVars, err := cloud.DecodeAggregationConfig(k6.GetStatus().AggregationVars) if err != nil { return nil, err @@ -154,7 +165,7 @@ func NewRunnerJob(k6 *v1alpha1.TestRun, index int, tokenInfo *cloud.TokenInfo) ( env = append(env, corev1.EnvVar{ Name: "K6_CLOUD_PUSH_REF_ID", Value: k6.TestRunID(), - }, tokenVar) + }) } env = append(env, k6.GetSpec().Runner.Env...) diff --git a/pkg/resources/jobs/runner_test.go b/pkg/resources/jobs/runner_test.go index dea7842a..03829cb4 100644 --- a/pkg/resources/jobs/runner_test.go +++ b/pkg/resources/jobs/runner_test.go @@ -257,9 +257,11 @@ func Test_NewRunnerJob(t *testing.T) { "k6", "run", "--quiet", "--out", "cloud", "/test/test.js", "--address=0.0.0.0:6565", "--paused", "--tag", "instance_id=1", "--tag", "job_name=test-1", } - j.Spec.Template.Spec.Containers[0].Env = append(aggregationEnvVars, - corev1.EnvVar{Name: "K6_CLOUD_PUSH_REF_ID", Value: "testrunid"}, - corev1.EnvVar{Name: "K6_CLOUD_TOKEN", Value: "token"}, + j.Spec.Template.Spec.Containers[0].Env = append( + []corev1.EnvVar{{Name: "K6_CLOUD_TOKEN", Value: "token"}}, + append(aggregationEnvVars, + corev1.EnvVar{Name: "K6_CLOUD_PUSH_REF_ID", Value: "testrunid"}, + )..., ) }, }, @@ -447,7 +449,6 @@ func Test_NewRunnerJob(t *testing.T) { "-e", "K6_CLOUDRUN_INSTANCE_ID=1", } j.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{ - {Name: "K6_CLOUD_PUSH_REF_ID", Value: "plz-run-123"}, { Name: "K6_CLOUD_TOKEN", ValueFrom: &corev1.EnvVarSource{ @@ -457,6 +458,7 @@ func Test_NewRunnerJob(t *testing.T) { }, }, }, + {Name: "K6_CLOUD_PUSH_REF_ID", Value: "plz-run-123"}, } }, },