Add podTrait to specify customPodTemplateSpec
close #1657
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 255feaf..1cd8fb8 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -746,7 +746,15 @@
// $ kamel run --trait <trait>.<property>=<value_1>,...,<trait>.<property>=<value_N>
traits[id][prop] = []string{v, value}
case nil:
- traits[id][prop] = value
+ //check if value is a path to the file
+ if _, err := os.Stat(value); err == nil {
+ rsc, errResolve := ResolveSources(context.TODO(), []string{value}, false)
+ if errResolve == nil && len(rsc) > 0 {
+ traits[id][prop] = rsc[0].Content
+ }
+ } else {
+ traits[id][prop] = value
+ }
}
}
diff --git a/pkg/trait/pod.go b/pkg/trait/pod.go
new file mode 100644
index 0000000..051f68d
--- /dev/null
+++ b/pkg/trait/pod.go
@@ -0,0 +1,111 @@
+/*
+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.
+*/
+
+package trait
+
+import (
+ "fmt"
+ v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+ "github.com/ghodss/yaml"
+ v12 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/util/json"
+ "k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch"
+
+ //"github.com/ghodss/yaml"
+ // cmd "github.com/apache/camel-k/pkg/cmd"
+
+ appsv1 "k8s.io/api/apps/v1"
+)
+
+// The Pod trait allows to modify the custom PodTemplateSpec resource for the Integration pods
+//
+// +camel-k:trait=pdb
+type podTrait struct {
+ BaseTrait `property:",squash"`
+ Template string `property:"template"`
+}
+
+func newPodTrait() Trait {
+ return &podTrait{
+ BaseTrait: NewBaseTrait("pod", 1800),
+ }
+}
+
+func (t *podTrait) Configure(e *Environment) (bool, error) {
+ if t.Enabled == nil || !*t.Enabled {
+ return false, nil
+ }
+
+ if t.Template == "" {
+ return false, fmt.Errorf("template must be specified")
+ }
+
+ if _, err := t.parseTemplate(); err != nil {
+ return false, err
+ }
+
+ return e.IntegrationInPhase(
+ v1.IntegrationPhaseDeploying,
+ v1.IntegrationPhaseRunning,
+ ), nil
+}
+
+func (t *podTrait) Apply(e *Environment) error {
+ var deployment *appsv1.Deployment
+
+ e.Resources.VisitDeployment(func(d *appsv1.Deployment) {
+ if d.Name == e.Integration.Name {
+ deployment = d
+ }
+ })
+
+ modifiedTemplate, err := t.mergeIntoTemplateSpec(deployment.Spec.Template, []byte(t.Template))
+ if err != nil {
+ return err
+ }
+ deployment.Spec.Template = *modifiedTemplate
+ return nil
+}
+
+func (t *podTrait) parseTemplate() (*v12.PodTemplateSpec, error) {
+ var template *v12.PodTemplateSpec
+
+ if err := yaml.Unmarshal([]byte(t.Template), &template); err != nil {
+ return nil, err
+ }
+ return template, nil
+}
+
+func (t *podTrait) mergeIntoTemplateSpec(template v12.PodTemplateSpec, changesBytes []byte) (mergedTemplate *v12.PodTemplateSpec, err error) {
+ patch, err := yaml.YAMLToJSON(changesBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ sourceJson, err := json.Marshal(template)
+ if err != nil {
+ return nil, err
+ }
+
+ patched, err := strategicpatch.StrategicMergePatch(sourceJson, patch, v12.PodTemplateSpec{})
+ if err != nil {
+ return nil, err
+ }
+
+ err = json.Unmarshal(patched, &mergedTemplate)
+ return
+}
diff --git a/pkg/trait/pod_test.go b/pkg/trait/pod_test.go
new file mode 100755
index 0000000..61cc77e
--- /dev/null
+++ b/pkg/trait/pod_test.go
@@ -0,0 +1,156 @@
+package trait
+
+import (
+ v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+ "github.com/apache/camel-k/pkg/util/kubernetes"
+
+ "github.com/ghodss/yaml"
+ "github.com/stretchr/testify/assert"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "testing"
+)
+
+func TestConfigurePodTraitDoesSucceed(t *testing.T) {
+ trait, environment, _ := createPodTest()
+ configured, err := trait.Configure(environment)
+
+ assert.False(t, configured)
+ assert.NotNil(t, err)
+
+ trait.Template = "{}"
+ configured, err = trait.Configure(environment)
+
+ assert.True(t, configured)
+ assert.Nil(t, err)
+}
+
+func TestSimpleChange(t *testing.T) {
+ templateString := "metadata:\n name: template-test\n\nspec:\n containers:\n - name: second-container"
+ template := testPodTemplateSpec(t, templateString)
+
+ assert.Equal(t, "template-test", template.Name)
+ assert.Equal(t, 3, len(template.Spec.Containers))
+}
+
+func TestMergeArrays(t *testing.T) {
+ templateString := "{metadata: {name: test-template}, " +
+ "spec: {containers: [{name: second-container, " +
+ "env: [{name: SOME_VARIABLE, value: SOME_VALUE}, {name: SOME_VARIABLE2, value: SOME_VALUE2}]}, " +
+ "{name: integration, env: [{name: TEST_ADDED_CUSTOM_VARIABLE, value: value}]}" +
+ "]" +
+ "}}"
+ templateSpec := testPodTemplateSpec(t, templateString)
+
+ assert.Equal(t, "test-template", templateSpec.Name)
+ assert.NotNil(t, getContainer(templateSpec.Spec.Containers, "second-container"))
+ assert.Equal(t, "SOME_VALUE", containsEnvVariables(templateSpec, "second-container", "SOME_VARIABLE"))
+ assert.Equal(t, "SOME_VALUE2", containsEnvVariables(templateSpec, "second-container", "SOME_VARIABLE2"))
+ assert.True(t, len(getContainer(templateSpec.Spec.Containers, "integration").Env) > 1)
+ assert.Equal(t, "value", containsEnvVariables(templateSpec, "integration", "TEST_ADDED_CUSTOM_VARIABLE"))
+}
+
+func TestChangeEnvVariables(t *testing.T) {
+ templateString := "{metadata: {name: test-template}, " +
+ "spec: {containers: [" +
+ "{name: second, env: [{name: TEST_VARIABLE, value: TEST_VALUE}]}, " +
+ "{name: integration, env: [{name: CAMEL_K_DIGEST, value: new_value}]}" +
+ "]}}"
+ templateSpec := testPodTemplateSpec(t, templateString)
+
+ //check if env var was added in second container
+ assert.Equal(t, containsEnvVariables(templateSpec, "second", "TEST_VARIABLE"), "TEST_VALUE")
+ assert.Equal(t, 3, len(getContainer(templateSpec.Spec.Containers, "second").Env))
+
+ //check if env var was changed
+ assert.Equal(t, containsEnvVariables(templateSpec, "integration", "CAMEL_K_DIGEST"), "new_value")
+}
+func TestRemoveArray(t *testing.T) {
+ templateString := "{metadata: " +
+ "{name: test-template}, " +
+ "spec:" +
+ " {containers: " +
+ "[{name: second-container, env: [{name: SOME_VARIABLE, value: SOME_VALUE}, {name: SOME_VARIABLE2, value: SOME_VALUE2}]}, " +
+ "{name: integration, env: null}" +
+ "]}}"
+
+ templateSpec := testPodTemplateSpec(t, templateString)
+ assert.True(t, len(getContainer(templateSpec.Spec.Containers, "integration").Env) == 0)
+}
+
+func createPodTest() (*podTrait, *Environment, *appsv1.Deployment) {
+ trait := newPodTrait().(*podTrait)
+ enabled := true
+ trait.Enabled = &enabled
+
+ specTemplateYamlString := "{metadata: {name: example-template, creationTimestamp: null, " +
+ "labels: {camel.apache.org/integration: test}}, " +
+ "spec: {volumes: [" +
+ "{name: i-source-000," + "configMap: {name: test-source-000, items: [{key: content, path: test.groovy}], defaultMode: 420}}, " +
+ "{name: application-properties, configMap: {name: test-application-properties, items: [{key: application.properties, path: application.properties}], defaultMode: 420}}], " +
+ "containers: [" +
+ "{name: second, env: [{name: SOME_VARIABLE, value: SOME_VALUE}, {name: SOME_VARIABLE2, value: SOME_VALUE2}]}," +
+ "{name: integration, command: [/bin/sh, '-c'], env: [{name: CAMEL_K_DIGEST, value: vO3wwJHC7-uGEiFFVac0jq6rZT5EZNw56Ae5gKKFZZsk}, {name: CAMEL_K_CONF, value: /etc/camel/conf/application.properties}, {name: CAMEL_K_CONF_D, value: /etc/camel/conf.d},{name: CAMEL_K_VERSION, value: 1.3.0-SNAPSHOT}, {name: CAMEL_K_INTEGRATION, value: test}, {name: CAMEL_K_RUNTIME_VERSION, value: 1.5.0}, {name: CAMEL_K_MOUNT_PATH_CONFIGMAPS, value: /etc/camel/conf.d/_configmaps}, {name: CAMEL_K_MOUNT_PATH_SECRETS, value: /etc/camel/conf.d/_secrets}, {name: NAMESPACE, valueFrom: {fieldRef: {apiVersion: v1, fieldPath: metadata.namespace}}}, {name: POD_NAME, valueFrom: {fieldRef: {apiVersion: v1, fieldPath: metadata.name}}}], imagePullPolicy: IfNotPresent, volumeMounts: [{name: i-source-000, mountPath: /etc/camel/sources/i-source-000}, {name: application-properties, mountPath: /etc/camel/conf}], terminationMessagePolicy: File, image: 'image-registry.openshift-image-registry.svc:5000/podtrait/camel-k-kit-bvd7utv170hult6ju26g@sha256:1c091437ef986f2852733da5f3fce7a5f48a5ea51e409f0bdcb0c13ff620e6b2', workingDir: /deployments, args: ['echo exec java -cp ./resources:/etc/camel/conf:/etc/camel/resources:/etc/camel/sources/i-source-000:dependencies/camel-k-integration-1.3.0-SNAPSHOT-runner.jar:dependencies/io.quarkus.arc.arc-1.8.0.Final.jar:dependencies/io.quarkus.quarkus-arc-1.8.0.Final.jar:dependencies/io.quarkus.quarkus-bootstrap-runner-1.8.0.Final.jar:dependencies/io.quarkus.quarkus-core-1.8.0.Final.jar:dependencies/io.quarkus.quarkus-development-mode-spi-1.8.0.Final.jar:dependencies/io.quarkus.quarkus-ide-launcher-1.8.0.Final.jar:dependencies/io.smallrye.common.smallrye-common-annotation-1.3.0.jar:dependencies/io.smallrye.common.smallrye-common-constraint-1.1.0.jar:dependencies/io.smallrye.common.smallrye-common-expression-1.1.0.jar:dependencies/io.smallrye.common.smallrye-common-function-1.1.0.jar:dependencies/io.smallrye.config.smallrye-config-1.8.6.jar:dependencies/io.smallrye.config.smallrye-config-common-1.8.6.jar:dependencies/jakarta.annotation.jakarta.annotation-api-1.3.5.jar:dependencies/jakarta.el.jakarta.el-api-3.0.3.jar:dependencies/jakarta.enterprise.jakarta.enterprise.cdi-api-2.0.2.jar:dependencies/jakarta.inject.jakarta.inject-api-1.0.jar:dependencies/jakarta.interceptor.jakarta.interceptor-api-1.2.5.jar:dependencies/jakarta.transaction.jakarta.transaction-api-1.3.3.jar:dependencies/org.apache.camel.camel-api-3.5.0.jar:dependencies/org.apache.camel.camel-base-3.5.0.jar:dependencies/org.apache.camel.camel-bean-3.5.0.jar:dependencies/org.apache.camel.camel-componentdsl-3.5.0.jar:dependencies/org.apache.camel.camel-core-catalog-3.5.0.jar:dependencies/org.apache.camel.camel-core-engine-3.5.0.jar:dependencies/org.apache.camel.camel-core-languages-3.5.0.jar:dependencies/org.apache.camel.camel-endpointdsl-3.5.0.jar:dependencies/org.apache.camel.camel-groovy-3.5.0.jar:dependencies/org.apache.camel.camel-log-3.5.0.jar:dependencies/org.apache.camel.camel-main-3.5.0.jar:dependencies/org.apache.camel.camel-management-api-3.5.0.jar:dependencies/org.apache.camel.camel-microprofile-config-3.5.0.jar:dependencies/org.apache.camel.camel-support-3.5.0.jar:dependencies/org.apache.camel.camel-timer-3.5.0.jar:dependencies/org.apache.camel.camel-tooling-model-3.5.0.jar:dependencies/org.apache.camel.camel-util-3.5.0.jar:dependencies/org.apache.camel.camel-util-json-3.5.0.jar:dependencies/org.apache.camel.k.camel-k-loader-groovy-1.5.0.jar:dependencies/org.apache.camel.k.camel-k-quarkus-core-1.5.0.jar:dependencies/org.apache.camel.k.camel-k-quarkus-loader-groovy-1.5.0.jar:dependencies/org.apache.camel.k.camel-k-runtime-core-1.5.0.jar:dependencies/org.apache.camel.k.camel-k-runtime-quarkus-1.5.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-bean-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-core-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-endpointdsl-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-log-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-main-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-support-common-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-timer-1.1.0.jar:dependencies/org.codehaus.groovy.groovy-3.0.5.jar:dependencies/org.eclipse.microprofile.config.microprofile-config-api-1.4.jar:dependencies/org.eclipse.microprofile.context-propagation.microprofile-context-propagation-api-1.0.1.jar:dependencies/org.graalvm.sdk.graal-sdk-20.2.0.jar:dependencies/org.jboss.logging.jboss-logging-3.3.2.Final.jar:dependencies/org.jboss.logging.jboss-logging-annotations-2.1.0.Final.jar:dependencies/org.jboss.logmanager.jboss-logmanager-embedded-1.0.4.jar:dependencies/org.jboss.slf4j.slf4j-jboss-logging-1.2.0.Final.jar:dependencies/org.jboss.threads.jboss-threads-3.1.1.Final.jar:dependencies/org.slf4j.slf4j-api-1.7.30.jar:dependencies/org.wildfly.common.wildfly-common-1.5.4.Final-format-001.jar io.quarkus.runner.GeneratedMain && exec java -cp ./resources:/etc/camel/conf:/etc/camel/resources:/etc/camel/sources/i-source-000:dependencies/camel-k-integration-1.3.0-SNAPSHOT-runner.jar:dependencies/io.quarkus.arc.arc-1.8.0.Final.jar:dependencies/io.quarkus.quarkus-arc-1.8.0.Final.jar:dependencies/io.quarkus.quarkus-bootstrap-runner-1.8.0.Final.jar:dependencies/io.quarkus.quarkus-core-1.8.0.Final.jar:dependencies/io.quarkus.quarkus-development-mode-spi-1.8.0.Final.jar:dependencies/io.quarkus.quarkus-ide-launcher-1.8.0.Final.jar:dependencies/io.smallrye.common.smallrye-common-annotation-1.3.0.jar:dependencies/io.smallrye.common.smallrye-common-constraint-1.1.0.jar:dependencies/io.smallrye.common.smallrye-common-expression-1.1.0.jar:dependencies/io.smallrye.common.smallrye-common-function-1.1.0.jar:dependencies/io.smallrye.config.smallrye-config-1.8.6.jar:dependencies/io.smallrye.config.smallrye-config-common-1.8.6.jar:dependencies/jakarta.annotation.jakarta.annotation-api-1.3.5.jar:dependencies/jakarta.el.jakarta.el-api-3.0.3.jar:dependencies/jakarta.enterprise.jakarta.enterprise.cdi-api-2.0.2.jar:dependencies/jakarta.inject.jakarta.inject-api-1.0.jar:dependencies/jakarta.interceptor.jakarta.interceptor-api-1.2.5.jar:dependencies/jakarta.transaction.jakarta.transaction-api-1.3.3.jar:dependencies/org.apache.camel.camel-api-3.5.0.jar:dependencies/org.apache.camel.camel-base-3.5.0.jar:dependencies/org.apache.camel.camel-bean-3.5.0.jar:dependencies/org.apache.camel.camel-componentdsl-3.5.0.jar:dependencies/org.apache.camel.camel-core-catalog-3.5.0.jar:dependencies/org.apache.camel.camel-core-engine-3.5.0.jar:dependencies/org.apache.camel.camel-core-languages-3.5.0.jar:dependencies/org.apache.camel.camel-endpointdsl-3.5.0.jar:dependencies/org.apache.camel.camel-groovy-3.5.0.jar:dependencies/org.apache.camel.camel-log-3.5.0.jar:dependencies/org.apache.camel.camel-main-3.5.0.jar:dependencies/org.apache.camel.camel-management-api-3.5.0.jar:dependencies/org.apache.camel.camel-microprofile-config-3.5.0.jar:dependencies/org.apache.camel.camel-support-3.5.0.jar:dependencies/org.apache.camel.camel-timer-3.5.0.jar:dependencies/org.apache.camel.camel-tooling-model-3.5.0.jar:dependencies/org.apache.camel.camel-util-3.5.0.jar:dependencies/org.apache.camel.camel-util-json-3.5.0.jar:dependencies/org.apache.camel.k.camel-k-loader-groovy-1.5.0.jar:dependencies/org.apache.camel.k.camel-k-quarkus-core-1.5.0.jar:dependencies/org.apache.camel.k.camel-k-quarkus-loader-groovy-1.5.0.jar:dependencies/org.apache.camel.k.camel-k-runtime-core-1.5.0.jar:dependencies/org.apache.camel.k.camel-k-runtime-quarkus-1.5.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-bean-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-core-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-endpointdsl-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-log-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-main-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-support-common-1.1.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-timer-1.1.0.jar:dependencies/org.codehaus.groovy.groovy-3.0.5.jar:dependencies/org.eclipse.microprofile.config.microprofile-config-api-1.4.jar:dependencies/org.eclipse.microprofile.context-propagation.microprofile-context-propagation-api-1.0.1.jar:dependencies/org.graalvm.sdk.graal-sdk-20.2.0.jar:dependencies/org.jboss.logging.jboss-logging-3.3.2.Final.jar:dependencies/org.jboss.logging.jboss-logging-annotations-2.1.0.Final.jar:dependencies/org.jboss.logmanager.jboss-logmanager-embedded-1.0.4.jar:dependencies/org.jboss.slf4j.slf4j-jboss-logging-1.2.0.Final.jar:dependencies/org.jboss.threads.jboss-threads-3.1.1.Final.jar:dependencies/org.slf4j.slf4j-api-1.7.30.jar:dependencies/org.wildfly.common.wildfly-common-1.5.4.Final-format-001.jar io.quarkus.runner.GeneratedMain']}], restartPolicy: Always, terminationGracePeriodSeconds: 90, dnsPolicy: ClusterFirst, securityContext: {}, schedulerName: default-scheduler}}"
+ var template corev1.PodTemplateSpec
+ _ = yaml.Unmarshal([]byte(specTemplateYamlString), &template)
+
+ deployment := &appsv1.Deployment{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pod-template-test-integration",
+ },
+ Spec: appsv1.DeploymentSpec{
+ Template: template,
+ },
+ }
+ environment := &Environment{
+ Integration: &v1.Integration{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pod-template-test-integration",
+ },
+ Status: v1.IntegrationStatus{
+ Phase: v1.IntegrationPhaseDeploying,
+ },
+ },
+ Resources: kubernetes.NewCollection(deployment),
+ }
+ return trait, environment, deployment
+}
+
+func containsEnvVariables(template corev1.PodTemplateSpec, containerName string, name string) string {
+ container := getContainer(template.Spec.Containers, containerName)
+
+ for i := range container.Env {
+ envv := container.Env[i]
+ if envv.Name == name {
+ return envv.Value
+ }
+ }
+ return "not found!"
+}
+
+func getContainer(containers []corev1.Container, name string) *corev1.Container {
+ for i := range containers {
+ if containers[i].Name == name {
+ return &containers[i]
+ }
+ }
+ return nil
+}
+
+func testPodTemplateSpec(t *testing.T, template string) corev1.PodTemplateSpec {
+ trait, environment, _ := createPodTest()
+ trait.Template = template
+
+ _, err := trait.Configure(environment)
+ assert.Nil(t, err)
+
+ err = trait.Apply(environment)
+ assert.Nil(t, err)
+
+ deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool {
+ return deployment.Name == "pod-template-test-integration"
+ })
+
+ return deployment.Spec.Template
+}
diff --git a/pkg/trait/trait_register.go b/pkg/trait/trait_register.go
index f8e0794..3f642d0 100644
--- a/pkg/trait/trait_register.go
+++ b/pkg/trait/trait_register.go
@@ -50,5 +50,6 @@
AddToTraits(newServiceBindingTrait)
AddToTraits(newOwnerTrait)
AddToTraits(newPdbTrait)
+ AddToTraits(newPodTrait)
AddToTraits(newLoggingTraitTrait)
}