You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pc...@apache.org on 2022/01/07 13:48:07 UTC

[camel-k] 01/24: feat(cmd/run): autogenerated configmap for resource

This is an automated email from the ASF dual-hosted git repository.

pcongiusti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 8ebad22fcb5ed0e7e72258807728d5c1c4d4343c
Author: Pasquale Congiusti <pa...@gmail.com>
AuthorDate: Tue Nov 23 12:26:45 2021 +0100

    feat(cmd/run): autogenerated configmap for resource
    
    Ref #2320
---
 .../resource-file-base64-encoded-route.groovy      |   2 +-
 .../user-config/resource-file-binary-route.groovy  |   2 +-
 pkg/cmd/run.go                                     |  38 ++++---
 pkg/cmd/run_help.go                                | 124 ++++++++++++++++-----
 pkg/trait/container.go                             |   5 +-
 pkg/trait/environment.go                           |   5 +-
 pkg/trait/jvm.go                                   |   5 +-
 pkg/trait/jvm_test.go                              |  10 +-
 pkg/trait/service_binding.go                       |   3 +-
 pkg/trait/trait_test.go                            |   4 +-
 pkg/trait/trait_types.go                           |  35 ++----
 pkg/util/camel/camel_util.go                       |  12 ++
 pkg/util/kubernetes/factory.go                     |  32 ++++++
 13 files changed, 198 insertions(+), 79 deletions(-)

diff --git a/examples/user-config/resource-file-base64-encoded-route.groovy b/examples/user-config/resource-file-base64-encoded-route.groovy
index 281d6ad..b88244d 100644
--- a/examples/user-config/resource-file-base64-encoded-route.groovy
+++ b/examples/user-config/resource-file-base64-encoded-route.groovy
@@ -18,7 +18,7 @@
 
 //
 // To run this integrations use:
-// kamel run --resource resources-data.txt --compression=true resource-file-base64-encoded-route.groovy --dev
+// kamel run --resource file:resources-data.txt --compression=true resource-file-base64-encoded-route.groovy --dev
 //
 
 from('timer:resources-bas64')
diff --git a/examples/user-config/resource-file-binary-route.groovy b/examples/user-config/resource-file-binary-route.groovy
index eef04d1..7f88b9a 100644
--- a/examples/user-config/resource-file-binary-route.groovy
+++ b/examples/user-config/resource-file-binary-route.groovy
@@ -18,7 +18,7 @@
 
 //
 // To run this integrations use:
-// kamel run --resource resources-data.zip resource-file-binary-route.groovy -d camel-zipfile --dev
+// kamel run --resource file:resources-data.zip resource-file-binary-route.groovy -d camel-zipfile --dev
 //
 
 from('file:/etc/camel/resources/?fileName=resources-data.zip&noop=true&idempotent=false')
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 153c13c..767dada 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -568,10 +568,26 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd *cobra.Command, c client.C
 		return nil, err
 	}
 
+	generatedConfigmaps := make([]*corev1.ConfigMap, 0)
+
 	for _, resource := range o.Resources {
 		if config, parseErr := ParseResourceOption(resource); parseErr == nil {
-			if applyResourceOptionErr := ApplyResourceOption(o.Context, config, &integration.Spec, c, namespace, o.Compression); applyResourceOptionErr != nil {
+			if genCm, applyResourceOptionErr := ApplyResourceOption(o.Context, config, integration, c, namespace, o.Compression); applyResourceOptionErr != nil {
 				return nil, applyResourceOptionErr
+			} else if genCm != nil {
+				generatedConfigmaps = append(generatedConfigmaps, genCm)
+			}
+		} else {
+			return nil, parseErr
+		}
+	}
+
+	for _, item := range o.Configs {
+		if config, parseErr := ParseConfigOption(item); parseErr == nil {
+			if genCm, applyConfigOptionErr := ApplyConfigOption(o.Context, config, integration, c, namespace, o.Compression); applyConfigOptionErr != nil {
+				return nil, applyConfigOptionErr
+			} else if genCm != nil {
+				generatedConfigmaps = append(generatedConfigmaps, genCm)
 			}
 		} else {
 			return nil, parseErr
@@ -619,15 +635,6 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd *cobra.Command, c client.C
 		o.Traits = append(o.Traits, buildPropsTraits...)
 	}
 
-	for _, item := range o.Configs {
-		if config, parseErr := ParseConfigOption(item); parseErr == nil {
-			if applyConfigOptionErr := ApplyConfigOption(o.Context, config, &integration.Spec, c, namespace, o.Compression); applyConfigOptionErr != nil {
-				return nil, applyConfigOptionErr
-			}
-		} else {
-			return nil, parseErr
-		}
-	}
 	for _, item := range o.ConfigMaps {
 		integration.Spec.AddConfiguration("configmap", item)
 	}
@@ -670,18 +677,21 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd *cobra.Command, c client.C
 
 	if existing == nil {
 		err = c.Create(o.Context, integration)
+		fmt.Printf("Integration \"%s\" created\n", name)
 	} else {
 		err = c.Patch(o.Context, integration, ctrl.MergeFromWithOptions(existing, ctrl.MergeFromWithOptimisticLock{}))
+		fmt.Printf("Integration \"%s\" updated\n", name)
 	}
 
 	if err != nil {
 		return nil, err
 	}
 
-	if existing == nil {
-		fmt.Printf("Integration \"%s\" created\n", name)
-	} else {
-		fmt.Printf("Integration \"%s\" updated\n", name)
+	if generatedConfigmaps != nil {
+		err = bindGeneratedConfigmapsToIntegration(o.Context, c, integration, generatedConfigmaps)
+		if err != nil {
+			return integration, err
+		}
 	}
 	return integration, nil
 }
diff --git a/pkg/cmd/run_help.go b/pkg/cmd/run_help.go
index 5104ac9..5ccbe14 100644
--- a/pkg/cmd/run_help.go
+++ b/pkg/cmd/run_help.go
@@ -19,15 +19,21 @@ package cmd
 
 import (
 	"context"
+	"crypto/sha1"
 	"fmt"
 	"path"
+	"path/filepath"
 	"regexp"
 	"strings"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/client"
+	"github.com/apache/camel-k/pkg/util/camel"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/magiconair/properties"
+	corev1 "k8s.io/api/core/v1"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 var invalidPaths = []string{"/etc/camel", "/deployments/dependencies"}
@@ -181,8 +187,9 @@ func parseOption(item string) (*RunConfigOption, error) {
 	return configurationOption, nil
 }
 
-func applyOption(ctx context.Context, config *RunConfigOption, integrationSpec *v1.IntegrationSpec,
-	c client.Client, namespace string, enableCompression bool, resourceType v1.ResourceType) error {
+func applyOption(ctx context.Context, config *RunConfigOption, integration *v1.Integration,
+	c client.Client, namespace string, enableCompression bool, resourceType v1.ResourceType) (*corev1.ConfigMap, error) {
+	var maybeGenCm *corev1.ConfigMap
 	switch config.configType {
 	case ConfigOptionTypeConfigmap:
 		cm := kubernetes.LookupConfigmap(ctx, c, namespace, config.Name())
@@ -190,58 +197,97 @@ func applyOption(ctx context.Context, config *RunConfigOption, integrationSpec *
 			fmt.Printf("Warn: %s Configmap not found in %s namespace, make sure to provide it before the Integration can run\n",
 				config.Name(), namespace)
 		} else if resourceType != v1.ResourceTypeData && cm.BinaryData != nil {
-			return fmt.Errorf("you cannot provide a binary config, use a text file instead")
+			return maybeGenCm, fmt.Errorf("you cannot provide a binary config, use a text file instead")
 		}
-		integrationSpec.AddConfigurationAsResource(config.Type(), config.Name(), string(resourceType), config.DestinationPath(), config.Key())
 	case ConfigOptionTypeSecret:
 		secret := kubernetes.LookupSecret(ctx, c, namespace, config.Name())
 		if secret == nil {
 			fmt.Printf("Warn: %s Secret not found in %s namespace, make sure to provide it before the Integration can run\n",
 				config.Name(), namespace)
 		}
-		integrationSpec.AddConfigurationAsResource(string(config.configType), config.Name(), string(resourceType), config.DestinationPath(), config.Key())
 	case ConfigOptionTypeFile:
-		// Don't allow a file size longer than 1 MiB
-		fileSize, err := fileSize(config.Name())
-		printSize := fmt.Sprintf("%.2f", float64(fileSize)/Megabyte)
-		if err != nil {
-			return err
-		} else if fileSize > Megabyte {
-			return fmt.Errorf("you cannot provide a file larger than 1 MB (it was %s MB), check configmap option or --volume instead", printSize)
-		}
 		// Don't allow a binary non compressed resource
 		rawData, contentType, err := loadRawContent(ctx, config.Name())
 		if err != nil {
-			return err
+			return maybeGenCm, err
 		}
 		if resourceType != v1.ResourceTypeData && !enableCompression && isBinary(contentType) {
-			return fmt.Errorf("you cannot provide a binary config, use a text file or check --resource flag instead")
+			return maybeGenCm, fmt.Errorf("you cannot provide a binary config, use a text file or check --resource flag instead")
 		}
 		resourceSpec, err := binaryOrTextResource(path.Base(config.Name()), rawData, contentType, enableCompression, resourceType, config.DestinationPath())
 		if err != nil {
-			return err
+			return maybeGenCm, err
+		}
+		maybeGenCm, err = convertFileToConfigmap(ctx, c, resourceSpec, config, integration.Namespace, resourceType)
+		if err != nil {
+			return maybeGenCm, err
 		}
-		integrationSpec.AddResources(resourceSpec)
 	default:
 		// Should never reach this
-		return fmt.Errorf("invalid option type %s", config.configType)
+		return maybeGenCm, fmt.Errorf("invalid option type %s", config.configType)
 	}
 
-	return nil
+	integration.Spec.AddConfigurationAsResource(config.Type(), config.Name(), string(resourceType), config.DestinationPath(), config.Key())
+
+	return maybeGenCm, nil
 }
 
-// ApplyConfigOption will set the proper --config option behavior to the IntegrationSpec.
-func ApplyConfigOption(ctx context.Context, config *RunConfigOption, integrationSpec *v1.IntegrationSpec, c client.Client, namespace string, enableCompression bool) error {
+func convertFileToConfigmap(ctx context.Context, c client.Client, resourceSpec v1.ResourceSpec, config *RunConfigOption,
+	namespace string, resourceType v1.ResourceType) (*corev1.ConfigMap, error) {
+	if config.DestinationPath() == "" {
+		config.resourceKey = filepath.Base(config.Name())
+		// As we are changing the resource to a configmap type
+		// we need to declare the mount path not to use the
+		// default behavior of a configmap (which include a subdirectory with the configmap name)
+		if resourceType == v1.ResourceTypeData {
+			config.destinationPath = camel.ResourcesDefaultMountPath
+		} else {
+			config.destinationPath = camel.ConfigResourcesMountPath
+		}
+	} else {
+		config.resourceKey = filepath.Base(config.DestinationPath())
+		config.destinationPath = filepath.Dir(config.DestinationPath())
+	}
+	genCmName := fmt.Sprintf("cm-%s", hashFrom([]byte(resourceSpec.Content), resourceSpec.RawContent))
+	cm := kubernetes.NewConfigmap(namespace, genCmName, config.Name(), config.Key(), resourceSpec.Content, resourceSpec.RawContent)
+	err := c.Create(ctx, cm)
+	if err != nil {
+		if k8serrors.IsAlreadyExists(err) {
+			// We'll reuse it, as is
+		} else {
+			return cm, err
+		}
+	}
+	config.configType = ConfigOptionTypeConfigmap
+	config.resourceName = cm.Name
+
+	return cm, nil
+}
+
+func hashFrom(contents ...[]byte) string {
+	// SHA1 because we need to limit the lenght to less than 64 chars
+	hash := sha1.New()
+	for _, c := range contents {
+		hash.Write(c)
+	}
+
+	return fmt.Sprintf("%x", hash.Sum(nil))
+}
+
+// ApplyConfigOption will set the proper --config option behavior to the IntegrationSpec
+func ApplyConfigOption(ctx context.Context, config *RunConfigOption, integration *v1.Integration, c client.Client,
+	namespace string, enableCompression bool) (*corev1.ConfigMap, error) {
 	// A config option cannot specify destination path
 	if config.DestinationPath() != "" {
-		return fmt.Errorf("cannot specify a destination path for this option type")
+		return nil, fmt.Errorf("cannot specify a destination path for this option type")
 	}
-	return applyOption(ctx, config, integrationSpec, c, namespace, enableCompression, v1.ResourceTypeConfig)
+	return applyOption(ctx, config, integration, c, namespace, enableCompression, v1.ResourceTypeConfig)
 }
 
-// ApplyResourceOption will set the proper --resource option behavior to the IntegrationSpec.
-func ApplyResourceOption(ctx context.Context, config *RunConfigOption, integrationSpec *v1.IntegrationSpec, c client.Client, namespace string, enableCompression bool) error {
-	return applyOption(ctx, config, integrationSpec, c, namespace, enableCompression, v1.ResourceTypeData)
+// ApplyResourceOption will set the proper --resource option behavior to the IntegrationSpec
+func ApplyResourceOption(ctx context.Context, config *RunConfigOption, integration *v1.Integration, c client.Client,
+	namespace string, enableCompression bool) (*corev1.ConfigMap, error) {
+	return applyOption(ctx, config, integration, c, namespace, enableCompression, v1.ResourceTypeData)
 }
 
 func binaryOrTextResource(fileName string, data []byte, contentType string, base64Compression bool, resourceType v1.ResourceType, destinationPath string) (v1.ResourceSpec, error) {
@@ -319,3 +365,29 @@ func extractProperties(value string) (*properties.Properties, error) {
 func keyValueProps(value string) (*properties.Properties, error) {
 	return properties.Load([]byte(value), properties.UTF8)
 }
+
+func bindGeneratedConfigmapsToIntegration(ctx context.Context, c client.Client, i *v1.Integration, configmaps []*corev1.ConfigMap) error {
+	controller := true
+	blockOwnerDeletion := true
+	for _, cm := range configmaps {
+		cm.ObjectMeta.Labels[v1.IntegrationLabel] = i.Name
+		cm.ObjectMeta.Labels["camel.apache.org/autogenerated"] = "true"
+		// set owner references
+		cm.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
+			{
+				Kind:               v1.IntegrationKind,
+				APIVersion:         v1.SchemeGroupVersion.String(),
+				Name:               i.Name,
+				UID:                i.UID,
+				Controller:         &controller,
+				BlockOwnerDeletion: &blockOwnerDeletion,
+			},
+		}
+		err := c.Update(ctx, cm)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/pkg/trait/container.go b/pkg/trait/container.go
index d9eae9e..7e67ae7 100644
--- a/pkg/trait/container.go
+++ b/pkg/trait/container.go
@@ -31,6 +31,7 @@ import (
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/camel"
 	"github.com/apache/camel-k/pkg/util/defaults"
 	"github.com/apache/camel-k/pkg/util/envvar"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
@@ -245,8 +246,8 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 	}
 
 	envvar.SetVal(&container.Env, "CAMEL_K_DIGEST", e.Integration.Status.Digest)
-	envvar.SetVal(&container.Env, "CAMEL_K_CONF", path.Join(basePath, "application.properties"))
-	envvar.SetVal(&container.Env, "CAMEL_K_CONF_D", confDPath)
+	envvar.SetVal(&container.Env, "CAMEL_K_CONF", path.Join(camel.BasePath, "application.properties"))
+	envvar.SetVal(&container.Env, "CAMEL_K_CONF_D", camel.ConfDPath)
 
 	e.addSourcesProperties()
 
diff --git a/pkg/trait/environment.go b/pkg/trait/environment.go
index 3f96d24..2a21580 100644
--- a/pkg/trait/environment.go
+++ b/pkg/trait/environment.go
@@ -18,6 +18,7 @@ limitations under the License.
 package trait
 
 import (
+	"github.com/apache/camel-k/pkg/util/camel"
 	"github.com/apache/camel-k/pkg/util/defaults"
 	"github.com/apache/camel-k/pkg/util/envvar"
 	"github.com/apache/camel-k/pkg/util/property"
@@ -73,8 +74,8 @@ func (t *environmentTrait) Apply(e *Environment) error {
 		envvar.SetVal(&e.EnvVars, envVarCamelKIntegration, e.Integration.Name)
 	}
 	envvar.SetVal(&e.EnvVars, envVarCamelKRuntimeVersion, e.RuntimeVersion)
-	envvar.SetVal(&e.EnvVars, envVarMountPathConfigMaps, configConfigmapsMountPath)
-	envvar.SetVal(&e.EnvVars, envVarMountPathSecrets, configSecretsMountPath)
+	envvar.SetVal(&e.EnvVars, envVarMountPathConfigMaps, camel.ConfigConfigmapsMountPath)
+	envvar.SetVal(&e.EnvVars, envVarMountPathSecrets, camel.ConfigSecretsMountPath)
 
 	if IsNilOrTrue(t.ContainerMeta) {
 		envvar.SetValFrom(&e.EnvVars, envVarNamespace, "metadata.namespace")
diff --git a/pkg/trait/jvm.go b/pkg/trait/jvm.go
index 4787ca3..994a532 100644
--- a/pkg/trait/jvm.go
+++ b/pkg/trait/jvm.go
@@ -37,6 +37,7 @@ import (
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/builder"
 	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/camel"
 )
 
 // The JVM trait is used to configure the JVM that runs the integration.
@@ -108,8 +109,8 @@ func (t *jvmTrait) Apply(e *Environment) error {
 	classpath := strset.New()
 
 	classpath.Add("./resources")
-	classpath.Add(configResourcesMountPath)
-	classpath.Add(resourcesDefaultMountPath)
+	classpath.Add(camel.ConfigResourcesMountPath)
+	classpath.Add(camel.ResourcesDefaultMountPath)
 	if t.Classpath != "" {
 		classpath.Add(strings.Split(t.Classpath, ":")...)
 	}
diff --git a/pkg/trait/jvm_test.go b/pkg/trait/jvm_test.go
index 4360692..760dadf 100644
--- a/pkg/trait/jvm_test.go
+++ b/pkg/trait/jvm_test.go
@@ -102,12 +102,12 @@ func TestApplyJvmTraitWithDeploymentResource(t *testing.T) {
 
 	assert.Nil(t, err)
 
-	cp := strset.New("./resources", configResourcesMountPath, resourcesDefaultMountPath, "/mount/path").List()
+	cp := strset.New("./resources", camel.ConfigResourcesMountPath, camel.ResourcesDefaultMountPath, "/mount/path").List()
 	sort.Strings(cp)
 
 	assert.Equal(t, []string{
 		"-cp",
-		fmt.Sprintf("./resources:%s:%s:/mount/path", configResourcesMountPath, resourcesDefaultMountPath),
+		fmt.Sprintf("./resources:%s:%s:/mount/path", camel.ConfigResourcesMountPath, camel.ResourcesDefaultMountPath),
 		"io.quarkus.bootstrap.runner.QuarkusEntryPoint",
 	}, d.Spec.Template.Spec.Containers[0].Args)
 }
@@ -134,12 +134,12 @@ func TestApplyJvmTraitWithKNativeResource(t *testing.T) {
 
 	assert.Nil(t, err)
 
-	cp := strset.New("./resources", configResourcesMountPath, resourcesDefaultMountPath, "/mount/path").List()
+	cp := strset.New("./resources", camel.ConfigResourcesMountPath, camel.ResourcesDefaultMountPath, "/mount/path").List()
 	sort.Strings(cp)
 
 	assert.Equal(t, []string{
 		"-cp",
-		fmt.Sprintf("./resources:%s:%s:/mount/path", configResourcesMountPath, resourcesDefaultMountPath),
+		fmt.Sprintf("./resources:%s:%s:/mount/path", camel.ConfigResourcesMountPath, camel.ResourcesDefaultMountPath),
 		"io.quarkus.bootstrap.runner.QuarkusEntryPoint",
 	}, s.Spec.Template.Spec.Containers[0].Args)
 }
@@ -243,7 +243,7 @@ func TestApplyJvmTraitWithClasspath(t *testing.T) {
 	assert.Nil(t, err)
 	assert.Equal(t, []string{
 		"-cp",
-		fmt.Sprintf("./resources:%s:%s:/mount/path:%s:%s", configResourcesMountPath, resourcesDefaultMountPath,
+		fmt.Sprintf("./resources:%s:%s:/mount/path:%s:%s", camel.ConfigResourcesMountPath, camel.ResourcesDefaultMountPath,
 			"/path/to/another/dep.jar", "/path/to/my-dep.jar"),
 		"io.quarkus.bootstrap.runner.QuarkusEntryPoint",
 	}, d.Spec.Template.Spec.Containers[0].Args)
diff --git a/pkg/trait/service_binding.go b/pkg/trait/service_binding.go
index 1b574f7..ab7a78a 100644
--- a/pkg/trait/service_binding.go
+++ b/pkg/trait/service_binding.go
@@ -18,6 +18,7 @@ limitations under the License.
 package trait
 
 import (
+	"github.com/apache/camel-k/pkg/util/camel"
 	"github.com/apache/camel-k/pkg/util/reference"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -77,7 +78,7 @@ func (t *serviceBindingTrait) Apply(e *Environment) error {
 	if secret != nil {
 		e.Resources.Add(secret)
 		e.ApplicationProperties["quarkus.kubernetes-service-binding.enabled"] = "true"
-		e.ApplicationProperties["SERVICE_BINDING_ROOT"] = serviceBindingsMountPath
+		e.ApplicationProperties["SERVICE_BINDING_ROOT"] = camel.ServiceBindingsMountPath
 		e.ServiceBindingSecret = secret.GetName()
 	}
 	return nil
diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go
index 0f1f5d8..bef6932 100644
--- a/pkg/trait/trait_test.go
+++ b/pkg/trait/trait_test.go
@@ -334,7 +334,7 @@ func TestConfigureVolumesAndMountsTextResourcesAndProperties(t *testing.T) {
 
 	m = findVVolumeMount(mnts, func(m corev1.VolumeMount) bool { return m.Name == "test-configmap" })
 	assert.NotNil(t, m)
-	assert.Equal(t, path.Join(configConfigmapsMountPath, "test-configmap"), m.MountPath)
+	assert.Equal(t, path.Join(camel.ConfigConfigmapsMountPath, "test-configmap"), m.MountPath)
 
 	v = findVolume(vols, func(v corev1.Volume) bool { return v.Name == "test-secret" })
 	assert.NotNil(t, v)
@@ -343,7 +343,7 @@ func TestConfigureVolumesAndMountsTextResourcesAndProperties(t *testing.T) {
 
 	m = findVVolumeMount(mnts, func(m corev1.VolumeMount) bool { return m.Name == "test-secret" })
 	assert.NotNil(t, m)
-	assert.Equal(t, path.Join(configSecretsMountPath, "test-secret"), m.MountPath)
+	assert.Equal(t, path.Join(camel.ConfigSecretsMountPath, "test-secret"), m.MountPath)
 
 	v = findVolume(vols, func(v corev1.Volume) bool { return v.Name == "testvolume-data" })
 	assert.NotNil(t, v)
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index 1757614..bb00d49 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -48,18 +48,7 @@ const (
 	False = "false"
 )
 
-var (
-	basePath                  = "/etc/camel"
-	confDPath                 = path.Join(basePath, "conf.d")
-	sourcesMountPath          = path.Join(basePath, "sources")
-	resourcesDefaultMountPath = path.Join(basePath, "resources")
-	configResourcesMountPath  = path.Join(confDPath, "_resources")
-	configConfigmapsMountPath = path.Join(confDPath, "_configmaps")
-	configSecretsMountPath    = path.Join(confDPath, "_secrets")
-	serviceBindingsMountPath  = path.Join(confDPath, "_servicebindings")
-)
-
-// Identifiable represent an identifiable type.
+// Identifiable represent an identifiable type
 type Identifiable interface {
 	ID() ID
 }
@@ -403,7 +392,7 @@ func (e *Environment) addSourcesProperties() {
 	}
 	for i, s := range e.Integration.Sources() {
 		srcName := strings.TrimPrefix(s.Name, "/")
-		src := "file:" + path.Join(sourcesMountPath, srcName)
+		src := "file:" + path.Join(camel.SourcesMountPath, srcName)
 		e.ApplicationProperties[fmt.Sprintf("camel.k.sources[%d].location", i)] = src
 
 		simpleName := srcName
@@ -457,7 +446,7 @@ func (e *Environment) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]c
 		}
 		resName := strings.TrimPrefix(s.Name, "/")
 		refName := fmt.Sprintf("i-source-%03d", i)
-		resPath := path.Join(sourcesMountPath, resName)
+		resPath := path.Join(camel.SourcesMountPath, resName)
 
 		*vols = append(*vols, corev1.Volume{
 			Name: refName,
@@ -538,9 +527,9 @@ func (e *Environment) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]c
 			var mountPath string
 			switch propertiesType {
 			case "application":
-				mountPath = path.Join(basePath, resName)
+				mountPath = path.Join(camel.BasePath, resName)
 			case "user":
-				mountPath = path.Join(confDPath, resName)
+				mountPath = path.Join(camel.ConfDPath, resName)
 			}
 
 			if propertiesType != "" {
@@ -626,7 +615,7 @@ func (e *Environment) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]c
 
 		*mnts = append(*mnts, corev1.VolumeMount{
 			Name:      refName,
-			MountPath: path.Join(serviceBindingsMountPath, strings.ToLower(secret)),
+			MountPath: path.Join(camel.ServiceBindingsMountPath, strings.ToLower(secret)),
 		})
 	}
 
@@ -698,11 +687,11 @@ func getResourcePath(resourceName string, maybePath string, resourceType v1.Reso
 	}
 	// otherwise return a default path, according to the resource type
 	if resourceType == v1.ResourceTypeData {
-		return path.Join(resourcesDefaultMountPath, resourceName)
+		return path.Join(camel.ResourcesDefaultMountPath, resourceName)
 	}
 
 	// Default, config type
-	return path.Join(configResourcesMountPath, resourceName)
+	return path.Join(camel.ConfigResourcesMountPath, resourceName)
 }
 
 func getConfigmapMountPoint(resourceName string, maybeMountPoint string, resourceType string) string {
@@ -711,11 +700,11 @@ func getConfigmapMountPoint(resourceName string, maybeMountPoint string, resourc
 		return maybeMountPoint
 	}
 	if resourceType == "data" {
-		return path.Join(resourcesDefaultMountPath, resourceName)
+		return path.Join(camel.ResourcesDefaultMountPath, resourceName)
 	}
 
 	// Default, config type
-	return path.Join(configConfigmapsMountPath, resourceName)
+	return path.Join(camel.ConfigConfigmapsMountPath, resourceName)
 }
 
 func getSecretMountPoint(resourceName string, maybeMountPoint string, resourceType string) string {
@@ -724,11 +713,11 @@ func getSecretMountPoint(resourceName string, maybeMountPoint string, resourceTy
 		return maybeMountPoint
 	}
 	if resourceType == "data" {
-		return path.Join(resourcesDefaultMountPath, resourceName)
+		return path.Join(camel.ResourcesDefaultMountPath, resourceName)
 	}
 
 	// Default, config type
-	return path.Join(configSecretsMountPath, resourceName)
+	return path.Join(camel.ConfigSecretsMountPath, resourceName)
 }
 
 func (e *Environment) collectConfigurationValues(configurationType string) []string {
diff --git a/pkg/util/camel/camel_util.go b/pkg/util/camel/camel_util.go
index 2aee18b..f41dad2 100644
--- a/pkg/util/camel/camel_util.go
+++ b/pkg/util/camel/camel_util.go
@@ -18,6 +18,7 @@ limitations under the License.
 package camel
 
 import (
+	"path"
 	"sort"
 	"strings"
 
@@ -27,6 +28,17 @@ import (
 	"github.com/apache/camel-k/pkg/util/log"
 )
 
+var (
+	BasePath                  = "/etc/camel"
+	ConfDPath                 = path.Join(BasePath, "conf.d")
+	SourcesMountPath          = path.Join(BasePath, "sources")
+	ResourcesDefaultMountPath = path.Join(BasePath, "resources")
+	ConfigResourcesMountPath  = path.Join(ConfDPath, "_resources")
+	ConfigConfigmapsMountPath = path.Join(ConfDPath, "_configmaps")
+	ConfigSecretsMountPath    = path.Join(ConfDPath, "_secrets")
+	ServiceBindingsMountPath  = path.Join(ConfDPath, "_servicebindings")
+)
+
 func findBestMatch(catalogs []v1.CamelCatalog, runtime v1.RuntimeSpec) (*RuntimeCatalog, error) {
 	for _, catalog := range catalogs {
 		if catalog.Spec.Runtime.Version == runtime.Version && catalog.Spec.Runtime.Provider == runtime.Provider {
diff --git a/pkg/util/kubernetes/factory.go b/pkg/util/kubernetes/factory.go
index c1e7aa5..6fa42ec 100644
--- a/pkg/util/kubernetes/factory.go
+++ b/pkg/util/kubernetes/factory.go
@@ -24,6 +24,7 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 var (
@@ -114,3 +115,34 @@ func NewResourceRequirements(reqs []string) (corev1.ResourceRequirements, error)
 
 	return resReq, nil
 }
+
+// NewConfigmap will create a Configmap
+func NewConfigmap(namespace, cmName, originalFilename string, generatedKey string,
+	textData string, binaryData []byte) *corev1.ConfigMap {
+	immutable := true
+	cm := corev1.ConfigMap{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "ConfigMap",
+			APIVersion: "v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      cmName,
+			Namespace: namespace,
+			Labels: map[string]string{
+				"camel.apache.org/original-path": originalFilename,
+			},
+		},
+		Immutable: &immutable,
+	}
+	if textData != "" {
+		cm.Data = map[string]string{
+			generatedKey: textData,
+		}
+	}
+	if binaryData != nil {
+		cm.BinaryData = map[string][]byte{
+			generatedKey: binaryData,
+		}
+	}
+	return &cm
+}