From f4743d6e786cd11e4e4c7e16432662818f8f6e2d Mon Sep 17 00:00:00 2001 From: Pasquale Congiusti Date: Mon, 20 Sep 2021 14:33:22 +0200 Subject: [PATCH] feat(trait): environment variables Moved the logic to process environment variable to trait. It will simplify setting via annotations Closes #2512 --- docs/modules/traits/pages/environment.adoc | 4 + examples/README.md | 5 +- examples/basic/Env.java | 31 ++++ examples/basic/README.md | 3 + .../env-var-writer.kamelet.yaml | 31 ++++ .../kamelets/kameletbindings/kb-env-vars.yaml | 22 ++- ...tion.yaml => kb-log-trait-annotation.yaml} | 0 pkg/cmd/run.go | 2 +- pkg/trait/environment.go | 10 ++ pkg/trait/environment_test.go | 169 ++++++++---------- resources/traits.yaml | 4 + 11 files changed, 178 insertions(+), 103 deletions(-) create mode 100644 examples/basic/Env.java create mode 100644 examples/basic/README.md create mode 100644 examples/kamelets/kameletbindings/env-var-writer.kamelet.yaml rename examples/kamelets/kameletbindings/{kb-trait-annotation.yaml => kb-log-trait-annotation.yaml} (100%) diff --git a/docs/modules/traits/pages/environment.adoc b/docs/modules/traits/pages/environment.adoc index 896d3690ff..f3d9c0eb82 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 03b605f300..04b29b5398 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 0000000000..988da150d0 --- /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 0000000000..9bdef5cd69 --- /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 0000000000..840e9feba1 --- /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 7e42760a6d..4ce9e5ad4a 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 239259bf94..95fcefc522 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 ed5ee41e86..b4ebfa8de6 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 } diff --git a/pkg/trait/environment_test.go b/pkg/trait/environment_test.go index 61426f1678..df74cbae1c 100644 --- a/pkg/trait/environment_test.go +++ b/pkg/trait/environment_test.go @@ -36,34 +36,7 @@ func TestDefaultEnvironment(t *testing.T) { catalog, err := camel.DefaultCatalog() assert.Nil(t, err) - env := Environment{ - CamelCatalog: catalog, - Catalog: NewCatalog(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) @@ -107,38 +80,11 @@ func TestEnabledContainerMetaDataEnvVars(t *testing.T) { c, err := camel.DefaultCatalog() assert.Nil(t, err) - env := Environment{ - CamelCatalog: c, - Catalog: NewCatalog(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() @@ -173,39 +119,13 @@ func TestDisabledContainerMetaDataEnvVars(t *testing.T) { c, err := camel.DefaultCatalog() assert.Nil(t, err) - env := Environment{ - CamelCatalog: c, - Catalog: NewCatalog(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) @@ -235,6 +155,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(nil) } + +func mockEnvironment(catalog *camel.RuntimeCatalog) Environment { + return Environment{ + CamelCatalog: catalog, + Catalog: NewCatalog(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(), + } +} diff --git a/resources/traits.yaml b/resources/traits.yaml index 18cea7e512..cc0d88b871 100755 --- a/resources/traits.yaml +++ b/resources/traits.yaml @@ -288,6 +288,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: