diff --git a/e2e/advanced/environment_test.go b/e2e/advanced/environment_test.go index eadde81f63..7038be0980 100644 --- a/e2e/advanced/environment_test.go +++ b/e2e/advanced/environment_test.go @@ -39,7 +39,56 @@ import ( "github.com/apache/camel-k/v2/pkg/util/defaults" ) -func TestEnvironmentTrait(t *testing.T) { +func TestEnvironmentTraitVars(t *testing.T) { + t.Parallel() + + WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) { + operatorID := "camel-k-trait-environment" + g.Expect(CopyCamelCatalog(t, ctx, ns, operatorID)).To(Succeed()) + g.Expect(CopyIntegrationKits(t, ctx, ns, operatorID)).To(Succeed()) + g.Expect(KamelInstallWithID(t, ctx, operatorID, ns)).To(Succeed()) + + g.Eventually(SelectedPlatformPhase(t, ctx, ns, operatorID), TestTimeoutMedium).Should(Equal(v1.IntegrationPlatformPhaseReady)) + + // Create configmap + var cmData = make(map[string]string) + cmData["my-cm-key"] = "hello configmap" + g.Expect(CreatePlainTextConfigmap(t, ctx, ns, "my-cm", cmData)).Should(Succeed()) + + // Create secret + var secData = make(map[string]string) + secData["my-sec-key"] = "very top secret" + g.Expect(CreatePlainTextSecret(t, ctx, ns, "my-sec", secData)).Should(Succeed()) + + t.Run("Run simple env-var", func(t *testing.T) { + name := RandomizedSuffixName("envvar") + g.Expect(KamelRunWithID(t, ctx, operatorID, ns, "--name", name, "-t", "environment.vars=MY_ENV_VAR='hello world'", "files/envvar.yaml").Execute()).To(Succeed()) + g.Eventually(IntegrationPodPhase(t, ctx, ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + g.Eventually(IntegrationConditionStatus(t, ctx, ns, name, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("hello world")) + }) + + t.Run("Run env-var from configmap", func(t *testing.T) { + name := RandomizedSuffixName("envvar-configmap") + g.Expect(KamelRunWithID(t, ctx, operatorID, ns, "--name", name, "-t", "environment.vars=MY_ENV_VAR=configmap:my-cm/my-cm-key", "files/envvar.yaml").Execute()).To(Succeed()) + g.Eventually(IntegrationPodPhase(t, ctx, ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + g.Eventually(IntegrationConditionStatus(t, ctx, ns, name, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("hello configmap")) + }) + + t.Run("Run env-var from secret", func(t *testing.T) { + name := RandomizedSuffixName("envvar-secret") + g.Expect(KamelRunWithID(t, ctx, operatorID, ns, "--name", name, "-t", "environment.vars=MY_ENV_VAR=secret:my-sec/my-sec-key", "files/envvar.yaml").Execute()).To(Succeed()) + g.Eventually(IntegrationPodPhase(t, ctx, ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + g.Eventually(IntegrationConditionStatus(t, ctx, ns, name, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("very top secret")) + }) + + g.Expect(Kamel(t, ctx, "delete", "--all", "-n", ns).Execute()).To(Succeed()) + }) +} + +func TestEnvironmentTraitHttpProxy(t *testing.T) { t.Parallel() WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) { @@ -67,7 +116,7 @@ func TestEnvironmentTrait(t *testing.T) { } // Install Camel K with the HTTP proxy environment variable - operatorID := "camel-k-trait-environment" + operatorID := "camel-k-trait-environment-http" g.Expect(CopyCamelCatalog(t, ctx, ns, operatorID)).To(Succeed()) g.Expect(CopyIntegrationKits(t, ctx, ns, operatorID)).To(Succeed()) g.Expect(KamelInstallWithID(t, ctx, operatorID, ns, "--operator-env-vars", fmt.Sprintf("HTTP_PROXY=%s", httpProxy), "--operator-env-vars", "NO_PROXY="+strings.Join(noProxy, ","))).To(Succeed()) diff --git a/e2e/advanced/files/envvar.yaml b/e2e/advanced/files/envvar.yaml new file mode 100644 index 0000000000..97141c2f65 --- /dev/null +++ b/e2e/advanced/files/envvar.yaml @@ -0,0 +1,25 @@ +# camel-k: language=yaml + +# --------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# --------------------------------------------------------------------------- + +- from: + uri: "timer:tick" + steps: + - setBody: + simple: "${env:MY_ENV_VAR}" + - to: "log:info" diff --git a/pkg/apis/camel/v1/trait/environment.go b/pkg/apis/camel/v1/trait/environment.go index da3c423e5e..47e11efab3 100644 --- a/pkg/apis/camel/v1/trait/environment.go +++ b/pkg/apis/camel/v1/trait/environment.go @@ -29,6 +29,7 @@ type EnvironmentTrait struct { HTTPProxy *bool `property:"http-proxy" json:"httpProxy,omitempty"` // A list of environment variables to be added to the integration container. // The syntax is KEY=VALUE, e.g., `MY_VAR="my value"`. + // The value may also be a reference to a configmap or secret, e.g. `MY_VAR=configmap:my-cm/my-cm-key` // These take precedence over the previously defined environment variables. Vars []string `property:"vars" json:"vars,omitempty"` } diff --git a/pkg/trait/environment.go b/pkg/trait/environment.go index 7dda54826c..5d7edfc281 100644 --- a/pkg/trait/environment.go +++ b/pkg/trait/environment.go @@ -19,7 +19,9 @@ package trait import ( "os" + "strings" + corev1 "k8s.io/api/core/v1" "k8s.io/utils/pointer" traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" @@ -46,7 +48,7 @@ const ( envVarCamelKRuntimeVersion = "CAMEL_K_RUNTIME_VERSION" envVarMountPathConfigMaps = "CAMEL_K_MOUNT_PATH_CONFIGMAPS" - // Disabling gosec linter as it may triggers: + // Disabling gosec linter as it may trigger: // // pkg/trait/environment.go:41: G101: Potential hardcoded credentials (gosec) // envVarMountPathSecrets = "CAMEL_K_MOUNT_PATH_SECRETS" @@ -99,12 +101,81 @@ func (t *environmentTrait) Apply(e *Environment) error { } } - if t.Vars != nil { - for _, env := range t.Vars { - k, v := property.SplitPropertyFileEntry(env) + for _, env := range t.Vars { + k, v := property.SplitPropertyFileEntry(env) + switch { + case strings.HasPrefix(v, "configmap:"): + path := strings.TrimPrefix(v, "configmap:") + setValFromConfigMapKeySelector(&e.EnvVars, k, path) + case strings.HasPrefix(v, "secret:"): + path := strings.TrimPrefix(v, "secret:") + setValFromSecretKeySelector(&e.EnvVars, k, path) + default: envvar.SetVal(&e.EnvVars, k, v) } } - return nil } + +func setValFromConfigMapKeySelector(vars *[]corev1.EnvVar, envName string, path string) { + refName, srcKey := splitValueToNameAndKey(path) + if envVar := envvar.Get(*vars, envName); envVar != nil { + envVar.Value = "" + envVar.ValueFrom = &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: refName, + }, + Key: srcKey, + }, + } + } else { + *vars = append(*vars, corev1.EnvVar{ + Name: envName, + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: refName, + }, + Key: srcKey, + }, + }, + }) + } +} + +func setValFromSecretKeySelector(vars *[]corev1.EnvVar, envName string, path string) { + refName, srcKey := splitValueToNameAndKey(path) + if envVar := envvar.Get(*vars, envName); envVar != nil { + envVar.Value = "" + envVar.ValueFrom = &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: refName, + }, + Key: srcKey, + }, + } + } else { + *vars = append(*vars, corev1.EnvVar{ + Name: envName, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: refName, + }, + Key: srcKey, + }, + }, + }) + } +} + +func splitValueToNameAndKey(path string) (string, string) { + toks := strings.SplitN(path, "/", 2) + name, key := toks[0], "" + if len(toks) > 1 { + key = toks[1] + } + return name, key +} diff --git a/pkg/trait/environment_test.go b/pkg/trait/environment_test.go index f83043702f..d9ae488028 100644 --- a/pkg/trait/environment_test.go +++ b/pkg/trait/environment_test.go @@ -192,6 +192,76 @@ func TestCustomEnvVars(t *testing.T) { assert.True(t, userK2) } +func TestCustomEnvVarsFromConfigMap(t *testing.T) { + c, err := camel.DefaultCatalog() + require.NoError(t, err) + + env := mockEnvironment(c) + env.Integration.Spec.Traits = v1.Traits{ + Environment: &traitv1.EnvironmentTrait{ + Vars: []string{"key1=configmap:my-cm/cmk1", "key2=configmap:my-cm/cmk2"}, + }, + } + env.Platform.ResyncStatusFullConfig() + + conditions, err := NewEnvironmentTestCatalog().apply(&env) + require.NoError(t, err) + assert.NotEmpty(t, conditions) + + var userK1, userK2 string + + env.Resources.VisitDeployment(func(deployment *appsv1.Deployment) { + for _, e := range deployment.Spec.Template.Spec.Containers[0].Env { + if e.Name == "key1" { + assert.Equal(t, "my-cm", e.ValueFrom.ConfigMapKeyRef.Name) + userK1 = e.ValueFrom.ConfigMapKeyRef.Key + } + if e.Name == "key2" { + assert.Equal(t, "my-cm", e.ValueFrom.ConfigMapKeyRef.Name) + userK2 = e.ValueFrom.ConfigMapKeyRef.Key + } + } + }) + + assert.Equal(t, "cmk1", userK1) + assert.Equal(t, "cmk2", userK2) +} + +func TestCustomEnvVarsFromSecret(t *testing.T) { + c, err := camel.DefaultCatalog() + require.NoError(t, err) + + env := mockEnvironment(c) + env.Integration.Spec.Traits = v1.Traits{ + Environment: &traitv1.EnvironmentTrait{ + Vars: []string{"key1=secret:my-sec/sec1", "key2=secret:my-sec/sec2"}, + }, + } + env.Platform.ResyncStatusFullConfig() + + conditions, err := NewEnvironmentTestCatalog().apply(&env) + require.NoError(t, err) + assert.NotEmpty(t, conditions) + + var userK1, userK2 string + + env.Resources.VisitDeployment(func(deployment *appsv1.Deployment) { + for _, e := range deployment.Spec.Template.Spec.Containers[0].Env { + if e.Name == "key1" { + assert.Equal(t, "my-sec", e.ValueFrom.SecretKeyRef.Name) + userK1 = e.ValueFrom.SecretKeyRef.Key + } + if e.Name == "key2" { + assert.Equal(t, "my-sec", e.ValueFrom.SecretKeyRef.Name) + userK2 = e.ValueFrom.SecretKeyRef.Key + } + } + }) + + assert.Equal(t, "sec1", userK1) + assert.Equal(t, "sec2", userK2) +} + func NewEnvironmentTestCatalog() *Catalog { return NewCatalog(nil) } diff --git a/script/Makefile b/script/Makefile index 6ba3626bcc..005a363dcc 100644 --- a/script/Makefile +++ b/script/Makefile @@ -397,9 +397,13 @@ endif ./script/bundle_kamelets.sh $(KAMELET_CATALOG_REPO) $(KAMELET_CATALOG_REPO_TAG) build-compile-integration-tests: +ifndef NOTEST @echo "####### Compiling integration tests..." export CAMEL_K_E2E_JUST_COMPILE="true"; \ go test -run nope -tags="integration" ./e2e/... +else + @echo "####### Skipping e2e test compile..." +endif clean: # disable gomodules when executing go clean: