diff --git a/go.mod b/go.mod index a1723a92..f75e9522 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/grafana/k6-operator go 1.23.0 require ( + github.com/buildkite/interpolate v0.1.5 github.com/go-logr/logr v1.4.2 github.com/go-test/deep v1.0.7 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index bf0e0585..ea2f9534 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/buildkite/interpolate v0.1.5 h1:v2Ji3voik69UZlbfoqzx+qfcsOKLA61nHdU79VV+tPU= +github.com/buildkite/interpolate v0.1.5/go.mod h1:dHnrwHew5O8VNOAgMDpwRlFnhL5VSN6M1bHVmRZ9Ccc= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= diff --git a/pkg/resources/jobs/helpers.go b/pkg/resources/jobs/helpers.go index 0433402f..dfb72dd9 100644 --- a/pkg/resources/jobs/helpers.go +++ b/pkg/resources/jobs/helpers.go @@ -147,3 +147,13 @@ func getInitContainers(pod *v1alpha1.Pod, script *types.Script) []corev1.Contain return initContainers } + +func convertEnvVarsToStringSlice(envVars []corev1.EnvVar) []string { + var envSlice []string + for _, envVar := range envVars { + if envVar.Value != "" { + envSlice = append(envSlice, envVar.Name+"="+envVar.Value) + } + } + return envSlice +} diff --git a/pkg/resources/jobs/helpers_test.go b/pkg/resources/jobs/helpers_test.go index eee2eb27..43e2eeb1 100644 --- a/pkg/resources/jobs/helpers_test.go +++ b/pkg/resources/jobs/helpers_test.go @@ -153,3 +153,69 @@ func TestNewIstioEnvVarFalseValues(t *testing.T) { t.Errorf("new envVars were incorrect, got: %v, want: %v.", envVars, expectedOutcome) } } +func TestConvertEnvVars(t *testing.T) { + testCases := []struct { + name string + input []corev1.EnvVar + expected []string + }{ + { + name: "empty slice", + input: []corev1.EnvVar{}, + expected: []string{}, + }, + { + name: "single env var", + input: []corev1.EnvVar{ + {Name: "TEST_VAR", Value: "test_value"}, + }, + expected: []string{"TEST_VAR=test_value"}, + }, + { + name: "multiple env vars", + input: []corev1.EnvVar{ + {Name: "VAR1", Value: "value1"}, + {Name: "VAR2", Value: "value2"}, + {Name: "my-env", Value: "myValue"}, + }, + expected: []string{"VAR1=value1", "VAR2=value2", "my-env=myValue"}, + }, + { + name: "empty value should be skipped", + input: []corev1.EnvVar{ + {Name: "EMPTY_VAR", Value: ""}, + {Name: "VALID_VAR", Value: "valid_value"}, + }, + expected: []string{"VALID_VAR=valid_value"}, + }, + { + name: "special characters in value", + input: []corev1.EnvVar{ + {Name: "SPECIAL", Value: "value with spaces!@#"}, + {Name: "URL", Value: "https://example.com?param=value"}, + }, + expected: []string{"SPECIAL=value with spaces!@#", "URL=https://example.com?param=value"}, + }, + { + name: "nil slice", + input: nil, + expected: []string{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := convertEnvVarsToStringSlice(tc.input) + + // Check if both are empty (handles nil vs empty slice issue) + if len(result) == 0 && len(tc.expected) == 0 { + return // Both empty, test passes + } + + // For non-empty cases, use reflect.DeepEqual + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("%s failed. Got %v, expected %v", tc.name, result, tc.expected) + } + }) + } +} diff --git a/pkg/resources/jobs/runner.go b/pkg/resources/jobs/runner.go index 2ff9296a..8dac8532 100644 --- a/pkg/resources/jobs/runner.go +++ b/pkg/resources/jobs/runner.go @@ -2,6 +2,7 @@ package jobs import ( "fmt" + "github.com/buildkite/interpolate" "strconv" "strings" @@ -48,8 +49,50 @@ func NewRunnerJob(k6 *v1alpha1.TestRun, index int, tokenInfo *cloud.TokenInfo) ( return nil, err } + // Conslidate envs + env := newIstioEnvVar(k6.GetSpec().Scuttle, istioEnabled) + + // this is a cloud test run + if len(k6.TestRunID()) > 0 { + // cloud output case + tokenVar := corev1.EnvVar{ + Name: "K6_CLOUD_TOKEN", + Value: tokenInfo.Value(), + } + + if v1alpha1.IsTrue(k6, v1alpha1.CloudPLZTestRun) { + // temporary hack + k6.GetStatus().AggregationVars = "2|5s|3s|10s|10" + tokenVar = corev1.EnvVar{ + Name: "K6_CLOUD_TOKEN", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: tokenInfo.SecretName()}, + Key: "token", + }, + }, + } + } + + aggregationVars, err := cloud.DecodeAggregationConfig(k6.GetStatus().AggregationVars) + if err != nil { + return nil, err + } + env = append(env, aggregationVars...) + env = append(env, corev1.EnvVar{ + Name: "K6_CLOUD_PUSH_REF_ID", + Value: k6.GetStatus().TestRunID, + }, tokenVar) + } + + env = append(env, k6.GetSpec().Runner.Env...) + if k6.GetSpec().Arguments != "" { - args := strings.Split(k6.GetSpec().Arguments, " ") + // Expand on environment variables using envmap from `env` + envStringSlice := convertEnvVarsToStringSlice(env) + envMap := interpolate.NewSliceEnv(envStringSlice) + expandedArgs, _ := interpolate.Interpolate(envMap, k6.GetSpec().Arguments) + args := strings.Split(expandedArgs, " ") command = append(command, args...) } @@ -117,43 +160,6 @@ func NewRunnerJob(k6 *v1alpha1.TestRun, index int, tokenInfo *cloud.TokenInfo) ( ports := []corev1.ContainerPort{{ContainerPort: 6565}} ports = append(ports, k6.GetSpec().Ports...) - env := newIstioEnvVar(k6.GetSpec().Scuttle, istioEnabled) - - // this is a cloud test run - if len(k6.TestRunID()) > 0 { - // cloud output case - tokenVar := corev1.EnvVar{ - Name: "K6_CLOUD_TOKEN", - Value: tokenInfo.Value(), - } - - if v1alpha1.IsTrue(k6, v1alpha1.CloudPLZTestRun) { - // temporary hack - k6.GetStatus().AggregationVars = "2|5s|3s|10s|10" - tokenVar = corev1.EnvVar{ - Name: "K6_CLOUD_TOKEN", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{Name: tokenInfo.SecretName()}, - Key: "token", - }, - }, - } - } - - aggregationVars, err := cloud.DecodeAggregationConfig(k6.GetStatus().AggregationVars) - if err != nil { - return nil, err - } - env = append(env, aggregationVars...) - env = append(env, corev1.EnvVar{ - Name: "K6_CLOUD_PUSH_REF_ID", - Value: k6.GetStatus().TestRunID, - }, tokenVar) - } - - env = append(env, k6.GetSpec().Runner.Env...) - volumes := script.Volume() volumes = append(volumes, k6.GetSpec().Runner.Volumes...)