Repository: apache/camel-k
Pull request #2897 (2022-01-20 17:56:02)

feat(trait): environment variables (1.6.x backport)
Backport #2650 to 1.6.x branch as it's required by #2885.

**Release Note**
```release-note
feat(trait): environment variables
```



Patch:
diff --git a/deploy/traits.yaml b/deploy/traits.yaml
index 92500da59..7888a1943 100755
--- a/deploy/traits.yaml
+++ b/deploy/traits.yaml
@@ -287,6 +287,10 @@ traits:
     type: bool
     description: Enables injection of `NAMESPACE` and `POD_NAME` environment variables
       (default `true`)
+  - name: vars
+    type: '[]string'
+    description: A list of variables to be created on the Pod. Must have KEY=VALUE
+      syntax (ie, MY_VAR="my value").
 - name: error-handler
   platform: true
   profiles:
diff --git a/docs/modules/traits/pages/environment.adoc b/docs/modules/traits/pages/environment.adoc
index 896d3690f..f3d9c0eb8 100755
--- a/docs/modules/traits/pages/environment.adoc
+++ b/docs/modules/traits/pages/environment.adoc
@@ -32,6 +32,10 @@ The following configuration options are available:
 | bool
 | Enables injection of `NAMESPACE` and `POD_NAME` environment variables (default `true`)
 
+| environment.vars
+| []string
+| A list of variables to be created on the Pod. Must have KEY=VALUE syntax (ie, MY_VAR="my value").
+
 |===
 
 // End of autogenerated code - DO NOT EDIT! (configuration)
diff --git a/examples/README.md b/examples/README.md
index 03b605f30..04b29b539 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -9,6 +9,7 @@ In this section you will find the most basic examples. Useful to start learning
 | Type  |  Description | Link  |
 |---|---|---|
 | Languages | Simple integrations developed in various supported languages | [see examples](./languages/)|
+| Basic | Simple integrations with basic configuration | [see examples](./basic/)|
 | Cron | How to create a `cront` integration | [see examples](./cron/)|
 | User Config | Explore how to include a `property`, `secret`, `configmap` or file `resource` in your integration | [see examples](./user-config/)|
 | Processor | Show how to include `Processor`s logic | [see examples](./processor/)|
@@ -20,7 +21,6 @@ In this section you will find the most basic examples. Useful to start learning
 
 In this section you can find a few examples of certain [`Camel` components](https://camel.apache.org/components/latest/index.html). This is a limited number of the wide variety of components supported by Apache Camel. You can also find useful examples [in this repository](https://github.com/apache/camel-k-examples).
 
-
 | Type  |  Description | Link  |
 |---|---|---|
 | AMQP | Component usage | [see examples](./amqp/)|
@@ -50,4 +50,5 @@ Traits configuration will be very helpful to fine tune your `Integration`. Here
 
 | Type  |  Description | Link  |
 |---|---|---|
-| JVM | How to use `JVM` trait| [see examples](./traits/jvm/)|
\ No newline at end of file
+| Container | How to customize with `container` trait| [see examples](./traits/container/)|
+| JVM | How to use `jvm` trait| [see examples](./traits/jvm/)|
\ No newline at end of file
diff --git a/examples/basic/Env.java b/examples/basic/Env.java
new file mode 100644
index 000000000..988da150d
--- /dev/null
+++ b/examples/basic/Env.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+// To run this integrations use:
+//
+// kamel run --env MY_ENV_VAR="hello world" Env.java --dev
+//
+
+import org.apache.camel.builder.RouteBuilder;
+
+public class Env extends RouteBuilder {
+  @Override
+  public void configure() throws Exception {
+	  from("timer:tick")
+        .log("${env:MY_ENV_VAR}");
+  }
+}
\ No newline at end of file
diff --git a/examples/basic/README.md b/examples/basic/README.md
new file mode 100644
index 000000000..9bdef5cd6
--- /dev/null
+++ b/examples/basic/README.md
@@ -0,0 +1,3 @@
+# Camel K basic examples
+
+Find useful examples about how to run an integration in Camel K.
\ No newline at end of file
diff --git a/examples/kamelets/kameletbindings/env-var-writer.kamelet.yaml b/examples/kamelets/kameletbindings/env-var-writer.kamelet.yaml
new file mode 100644
index 000000000..840e9feba
--- /dev/null
+++ b/examples/kamelets/kameletbindings/env-var-writer.kamelet.yaml
@@ -0,0 +1,31 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+apiVersion: camel.apache.org/v1alpha1
+kind: Kamelet
+metadata:
+  name: env-var-writer
+spec:
+  definition:
+    title: "Log some env vars"   
+  flow:
+    from:
+      uri: kamelet:source
+      steps:
+      - set-body:
+          simple: "${env:MY_ENV_VAR1} / ${env:MY_ENV_VAR2}"
+      - to: "log:bar"  
diff --git a/examples/kamelets/kameletbindings/kb-env-vars.yaml b/examples/kamelets/kameletbindings/kb-env-vars.yaml
index 7e42760a6..4ce9e5ad4 100644
--- a/examples/kamelets/kameletbindings/kb-env-vars.yaml
+++ b/examples/kamelets/kameletbindings/kb-env-vars.yaml
@@ -15,17 +15,23 @@
 # limitations under the License.
 # ---------------------------------------------------------------------------
 
+# Apply kamelet used in this binding
+# kubectl apply -f env-var-writer.kamelet.yaml
+#
+# Apply kamelet binding
+# kubect apply -f kb-env-vars.yaml
+# 
 apiVersion: camel.apache.org/v1alpha1
 kind: KameletBinding
 metadata:
   name: timer-to-log
-  namespace: default
+  annotations: 
+    trait.camel.apache.org/environment.vars: "[\"MY_ENV_VAR1 = value1\", \"MY_ENV_VAR2 = value2\"]"
 spec:
-  integration:
-    configuration:
-    - type: env
-      value: X=hello world!
-  sink:
-    uri: log:bar
   source:
-    uri: timer:foo
\ No newline at end of file
+    uri: timer:foo
+  sink:
+    ref:
+      kind: Kamelet
+      apiVersion: camel.apache.org/v1alpha1
+      name: env-var-writer
diff --git a/examples/kamelets/kameletbindings/kb-trait-annotation.yaml b/examples/kamelets/kameletbindings/kb-log-trait-annotation.yaml
similarity index 100%
rename from examples/kamelets/kameletbindings/kb-trait-annotation.yaml
rename to examples/kamelets/kameletbindings/kb-log-trait-annotation.yaml
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 8cb1ebf82..2b8faac61 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -621,7 +621,7 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd *cobra.Command, c client.C
 		integration.Spec.AddConfiguration("volume", item)
 	}
 	for _, item := range o.EnvVars {
-		integration.Spec.AddConfiguration("env", item)
+		o.Traits = append(o.Traits, fmt.Sprintf("environment.vars=%s", item))
 	}
 
 	if err := o.configureTraits(integration, o.Traits, catalog); err != nil {
diff --git a/pkg/trait/environment.go b/pkg/trait/environment.go
index ed5ee41e8..b4ebfa8de 100644
--- a/pkg/trait/environment.go
+++ b/pkg/trait/environment.go
@@ -20,6 +20,7 @@ package trait
 import (
 	"github.com/apache/camel-k/pkg/util/defaults"
 	"github.com/apache/camel-k/pkg/util/envvar"
+	"github.com/apache/camel-k/pkg/util/property"
 )
 
 // The environment trait is used internally to inject standard environment variables in the integration container,
@@ -30,6 +31,8 @@ type environmentTrait struct {
 	BaseTrait `property:",squash"`
 	// Enables injection of `NAMESPACE` and `POD_NAME` environment variables (default `true`)
 	ContainerMeta *bool `property:"container-meta" json:"containerMeta,omitempty"`
+	// A list of variables to be created on the Pod. Must have KEY=VALUE syntax (ie, MY_VAR="my value").
+	Vars []string `property:"vars" json:"vars,omitempty"`
 }
 
 const (
@@ -78,6 +81,13 @@ func (t *environmentTrait) Apply(e *Environment) error {
 		envvar.SetValFrom(&e.EnvVars, envVarPodName, "metadata.name")
 	}
 
+	if t.Vars != nil {
+		for _, env := range t.Vars {
+			k, v := property.SplitPropertyFileEntry(env)
+			envvar.SetVal(&e.EnvVars, k, v)
+		}
+	}
+
 	return nil
 }
 


Test patch:
diff --git a/pkg/trait/environment_test.go b/pkg/trait/environment_test.go
index ad0d3be01..13cb07015 100644
--- a/pkg/trait/environment_test.go
+++ b/pkg/trait/environment_test.go
@@ -37,34 +37,7 @@ func TestDefaultEnvironment(t *testing.T) {
 	catalog, err := camel.DefaultCatalog()
 	assert.Nil(t, err)
 
-	env := Environment{
-		CamelCatalog: catalog,
-		Catalog:      NewCatalog(context.TODO(), nil),
-		Integration: &v1.Integration{
-			Status: v1.IntegrationStatus{
-				Phase: v1.IntegrationPhaseDeploying,
-			},
-			Spec: v1.IntegrationSpec{
-				Profile: v1.TraitProfileOpenShift,
-			},
-		},
-		IntegrationKit: &v1.IntegrationKit{
-			Status: v1.IntegrationKitStatus{
-				Phase: v1.IntegrationKitPhaseReady,
-			},
-		},
-		Platform: &v1.IntegrationPlatform{
-			ObjectMeta: metav1.ObjectMeta{
-				Namespace: "ns",
-			},
-			Spec: v1.IntegrationPlatformSpec{
-				Cluster: v1.IntegrationPlatformClusterOpenShift,
-			},
-		},
-		EnvVars:        make([]corev1.EnvVar, 0),
-		ExecutedTraits: make([]Trait, 0),
-		Resources:      kubernetes.NewCollection(),
-	}
+	env := mockEnvironment(catalog)
 	env.Platform.ResyncStatusFullConfig()
 
 	err = NewEnvironmentTestCatalog().apply(&env)
@@ -108,38 +81,11 @@ func TestEnabledContainerMetaDataEnvVars(t *testing.T) {
 	c, err := camel.DefaultCatalog()
 	assert.Nil(t, err)
 
-	env := Environment{
-		CamelCatalog: c,
-		Catalog:      NewCatalog(context.TODO(), nil),
-		Integration: &v1.Integration{
-			Status: v1.IntegrationStatus{
-				Phase: v1.IntegrationPhaseDeploying,
-			},
-			Spec: v1.IntegrationSpec{
-				Profile: v1.TraitProfileOpenShift,
-				Traits: map[string]v1.TraitSpec{
-					"environment": test.TraitSpecFromMap(t, map[string]interface{}{
-						"containerMeta": true,
-					}),
-				},
-			},
-		},
-		IntegrationKit: &v1.IntegrationKit{
-			Status: v1.IntegrationKitStatus{
-				Phase: v1.IntegrationKitPhaseReady,
-			},
-		},
-		Platform: &v1.IntegrationPlatform{
-			ObjectMeta: metav1.ObjectMeta{
-				Namespace: "ns",
-			},
-			Spec: v1.IntegrationPlatformSpec{
-				Cluster: v1.IntegrationPlatformClusterOpenShift,
-			},
-		},
-		EnvVars:        make([]corev1.EnvVar, 0),
-		ExecutedTraits: make([]Trait, 0),
-		Resources:      kubernetes.NewCollection(),
+	env := mockEnvironment(c)
+	env.Integration.Spec.Traits = map[string]v1.TraitSpec{
+		"environment": test.TraitSpecFromMap(t, map[string]interface{}{
+			"containerMeta": true,
+		}),
 	}
 	env.Platform.ResyncStatusFullConfig()
 
@@ -174,39 +120,13 @@ func TestDisabledContainerMetaDataEnvVars(t *testing.T) {
 	c, err := camel.DefaultCatalog()
 	assert.Nil(t, err)
 
-	env := Environment{
-		CamelCatalog: c,
-		Catalog:      NewCatalog(context.TODO(), nil),
-		Integration: &v1.Integration{
-			Status: v1.IntegrationStatus{
-				Phase: v1.IntegrationPhaseDeploying,
-			},
-			Spec: v1.IntegrationSpec{
-				Profile: v1.TraitProfileOpenShift,
-				Traits: map[string]v1.TraitSpec{
-					"environment": test.TraitSpecFromMap(t, map[string]interface{}{
-						"containerMeta": false,
-					}),
-				},
-			},
-		},
-		IntegrationKit: &v1.IntegrationKit{
-			Status: v1.IntegrationKitStatus{
-				Phase: v1.IntegrationKitPhaseReady,
-			},
-		},
-		Platform: &v1.IntegrationPlatform{
-			ObjectMeta: metav1.ObjectMeta{
-				Namespace: "ns",
-			},
-			Spec: v1.IntegrationPlatformSpec{
-				Cluster: v1.IntegrationPlatformClusterOpenShift,
-			},
-		},
-		EnvVars:        make([]corev1.EnvVar, 0),
-		ExecutedTraits: make([]Trait, 0),
-		Resources:      kubernetes.NewCollection(),
+	env := mockEnvironment(c)
+	env.Integration.Spec.Traits = map[string]v1.TraitSpec{
+		"environment": test.TraitSpecFromMap(t, map[string]interface{}{
+			"containerMeta": false,
+		}),
 	}
+
 	env.Platform.ResyncStatusFullConfig()
 
 	err = NewEnvironmentTestCatalog().apply(&env)
@@ -236,6 +156,71 @@ func TestDisabledContainerMetaDataEnvVars(t *testing.T) {
 	assert.True(t, ck)
 }
 
+func TestCustomEnvVars(t *testing.T) {
+	c, err := camel.DefaultCatalog()
+	assert.Nil(t, err)
+
+	env := mockEnvironment(c)
+	env.Integration.Spec.Traits = map[string]v1.TraitSpec{
+		"environment": test.TraitSpecFromMap(t, map[string]interface{}{
+			"vars": []string{"key1=val1", "key2 = val2"},
+		}),
+	}
+	env.Platform.ResyncStatusFullConfig()
+
+	err = NewEnvironmentTestCatalog().apply(&env)
+
+	assert.Nil(t, err)
+
+	userK1 := false
+	userK2 := false
+
+	env.Resources.VisitDeployment(func(deployment *appsv1.Deployment) {
+		for _, e := range deployment.Spec.Template.Spec.Containers[0].Env {
+			if e.Name == "key1" {
+				userK1 = e.Value == "val1"
+			}
+			if e.Name == "key2" {
+				userK2 = e.Value == "val2"
+			}
+		}
+	})
+
+	assert.True(t, userK1)
+	assert.True(t, userK2)
+}
+
 func NewEnvironmentTestCatalog() *Catalog {
 	return NewCatalog(context.TODO(), nil)
 }
+
+func mockEnvironment(catalog *camel.RuntimeCatalog) Environment {
+	return Environment{
+		CamelCatalog: catalog,
+		Catalog:      NewCatalog(context.TODO(), nil),
+		Integration: &v1.Integration{
+			Status: v1.IntegrationStatus{
+				Phase: v1.IntegrationPhaseDeploying,
+			},
+			Spec: v1.IntegrationSpec{
+				Profile: v1.TraitProfileOpenShift,
+			},
+		},
+		IntegrationKit: &v1.IntegrationKit{
+			Status: v1.IntegrationKitStatus{
+				Phase: v1.IntegrationKitPhaseReady,
+			},
+		},
+		Platform: &v1.IntegrationPlatform{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns",
+			},
+			Spec: v1.IntegrationPlatformSpec{
+				Cluster: v1.IntegrationPlatformClusterOpenShift,
+			},
+		},
+		EnvVars:        make([]corev1.EnvVar, 0),
+		ExecutedTraits: make([]Trait, 0),
+		Resources:      kubernetes.NewCollection(),
+	}
+}