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:25 UTC

[camel-k] 19/24: feat(trait/mount): moving volumes logic

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 2568fad4c43d047e15229106ea4934986be326ec
Author: Pasquale Congiusti <pa...@gmail.com>
AuthorDate: Tue Dec 28 15:12:00 2021 +0100

    feat(trait/mount): moving volumes logic
---
 docs/modules/ROOT/nav.adoc               |   1 +
 docs/modules/traits/pages/container.adoc |  12 ---
 docs/modules/traits/pages/mount.adoc     |  45 ++++++++
 pkg/cmd/run.go                           |   4 +-
 pkg/trait/container.go                   |  77 -------------
 pkg/trait/mount.go                       | 177 ++++++++++++++++++++++++++++++
 pkg/trait/mount_test.go                  | 179 +++++++++++++++++++++++++++++++
 pkg/trait/trait_register.go              |   3 +-
 pkg/trait/trait_test.go                  |   2 +-
 resources/traits.yaml                    |  39 ++++---
 10 files changed, 433 insertions(+), 106 deletions(-)

diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 5bc1a65..593ab84 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -67,6 +67,7 @@
 ** xref:traits:knative.adoc[Knative]
 ** xref:traits:logging.adoc[Logging]
 ** xref:traits:master.adoc[Master]
+** xref:traits:mount.adoc[Mount]
 ** xref:traits:openapi.adoc[Openapi]
 ** xref:traits:owner.adoc[Owner]
 ** xref:traits:pdb.adoc[Pdb]
diff --git a/docs/modules/traits/pages/container.adoc b/docs/modules/traits/pages/container.adoc
index 0d608e4..f5be96f 100755
--- a/docs/modules/traits/pages/container.adoc
+++ b/docs/modules/traits/pages/container.adoc
@@ -81,18 +81,6 @@ The following configuration options are available:
 | PullPolicy
 | The pull policy: Always\|Never\|IfNotPresent
 
-| container.configs
-| []string
-| A list of configuration pointing to configmap/secret. Syntax: [configmap\|secret]:name[key], where name represents the resource name and key optionally represents the resource key to be filtered
-
-| container.resources
-| []string
-| A list of resources pointing to configmap/secret. Syntax: [configmap\|secret]:name[/key][@path], where name represents the resource name, key optionally represents the resource key to be filtered and path represents the destination path
-
-| container.volumes
-| []string
-| A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]
-
 | container.probes-enabled
 | bool
 | DeprecatedProbesEnabled enable/disable probes on the container (default `false`)
diff --git a/docs/modules/traits/pages/mount.adoc b/docs/modules/traits/pages/mount.adoc
new file mode 100644
index 0000000..d5e2c19
--- /dev/null
+++ b/docs/modules/traits/pages/mount.adoc
@@ -0,0 +1,45 @@
+= Mount Trait
+
+// Start of autogenerated code - DO NOT EDIT! (description)
+The Mount trait can be used to configure volumes mounted on the Integration Pod.
+
+nolint: tagliatelle
+
+This trait is available in the following profiles: **Kubernetes, Knative, OpenShift**.
+
+WARNING: The mount trait is a *platform trait*: disabling it may compromise the platform functionality.
+
+// End of autogenerated code - DO NOT EDIT! (description)
+// Start of autogenerated code - DO NOT EDIT! (configuration)
+== Configuration
+
+Trait properties can be specified when running any integration with the CLI:
+[source,console]
+----
+$ kamel run --trait mount.[key]=[value] --trait mount.[key2]=[value2] integration.groovy
+----
+The following configuration options are available:
+
+[cols="2m,1m,5a"]
+|===
+|Property | Type | Description
+
+| mount.enabled
+| bool
+| Can be used to enable or disable a trait. All traits share this common property.
+
+| mount.configs
+| []string
+| A list of configuration pointing to configmap/secret. Syntax: [configmap\|secret]:name[key], where name represents the resource name and key optionally represents the resource key to be filtered
+
+| mount.resources
+| []string
+| A list of resources pointing to configmap/secret. Syntax: [configmap\|secret]:name[/key][@path], where name represents the resource name, key optionally represents the resource key to be filtered and path represents the destination path
+
+| mount.volumes
+| []string
+| A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]
+
+|===
+
+// End of autogenerated code - DO NOT EDIT! (configuration)
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 1c1b183..35f2c0a 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -576,12 +576,12 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd *cobra.Command, c client.C
 	}
 
 	generatedConfigmaps := make([]*corev1.ConfigMap, 0)
-	resCms, err := o.parseAndConvertToTrait(c, integration, o.Resources, resource.ParseResource, func(c *resource.Config) string { return c.String() }, "container.resources")
+	resCms, err := o.parseAndConvertToTrait(c, integration, o.Resources, resource.ParseResource, func(c *resource.Config) string { return c.String() }, "mount.resources")
 	if err != nil {
 		return nil, err
 	}
 	generatedConfigmaps = append(generatedConfigmaps, resCms...)
-	confCms, err := o.parseAndConvertToTrait(c, integration, o.Configs, resource.ParseConfig, func(c *resource.Config) string { return c.String() }, "container.configs")
+	confCms, err := o.parseAndConvertToTrait(c, integration, o.Configs, resource.ParseConfig, func(c *resource.Config) string { return c.String() }, "mount.configs")
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/trait/container.go b/pkg/trait/container.go
index 0abf914..5fc7da8 100644
--- a/pkg/trait/container.go
+++ b/pkg/trait/container.go
@@ -20,7 +20,6 @@ package trait
 import (
 	"fmt"
 	"path"
-	"strings"
 
 	appsv1 "k8s.io/api/apps/v1"
 	"k8s.io/api/batch/v1beta1"
@@ -36,7 +35,6 @@ import (
 	"github.com/apache/camel-k/pkg/util/defaults"
 	"github.com/apache/camel-k/pkg/util/envvar"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
-	utilResource "github.com/apache/camel-k/pkg/util/resource"
 )
 
 const (
@@ -83,12 +81,6 @@ type containerTrait struct {
 	Image string `property:"image" json:"image,omitempty"`
 	// The pull policy: Always|Never|IfNotPresent
 	ImagePullPolicy corev1.PullPolicy `property:"image-pull-policy" json:"imagePullPolicy,omitempty"`
-	// A list of configuration pointing to configmap/secret. Syntax: [configmap|secret]:name[key], where name represents the resource name and key optionally represents the resource key to be filtered
-	Configs []string `property:"configs" json:"configs,omitempty"`
-	// A list of resources pointing to configmap/secret. Syntax: [configmap|secret]:name[/key][@path], where name represents the resource name, key optionally represents the resource key to be filtered and path represents the destination path
-	Resources []string `property:"resources" json:"resources,omitempty"`
-	// A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]
-	Volumes []string `property:"volumes" json:"volumes,omitempty"`
 
 	// DeprecatedProbesEnabled enable/disable probes on the container (default `false`)
 	// Deprecated: replaced by the health trait.
@@ -168,23 +160,6 @@ func (t *containerTrait) Configure(e *Environment) (bool, error) {
 		return false, fmt.Errorf("unsupported pull policy %s", t.ImagePullPolicy)
 	}
 
-	// Validate resources and pvcs
-	for _, c := range t.Configs {
-		if !strings.HasPrefix(c, "configmap:") && !strings.HasPrefix(c, "secret:") {
-			return false, fmt.Errorf("unsupported config %s, must be a configmap or secret resource", c)
-		}
-	}
-	for _, r := range t.Resources {
-		if !strings.HasPrefix(r, "configmap:") && !strings.HasPrefix(r, "secret:") {
-			return false, fmt.Errorf("unsupported resource %s, must be a configmap or secret resource", r)
-		}
-	}
-	for _, r := range t.Volumes {
-		if !strings.HasPrefix(r, "pvc:") {
-			return false, fmt.Errorf("unsupported volume %s, must be a pvc", r)
-		}
-	}
-
 	return true, nil
 }
 
@@ -287,7 +262,6 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 	}
 	t.configureCapabilities(e)
 
-	var volumes *[]corev1.Volume
 	var containers *[]corev1.Container
 	visited := false
 
@@ -297,7 +271,6 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 			envvar.SetVar(&container.Env, envVar)
 		}
 
-		volumes = &deployment.Spec.Template.Spec.Volumes
 		containers = &deployment.Spec.Template.Spec.Containers
 		visited = true
 		return nil
@@ -322,7 +295,6 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 			}
 		}
 
-		volumes = &service.Spec.ConfigurationSpec.Template.Spec.Volumes
 		containers = &service.Spec.ConfigurationSpec.Template.Spec.Containers
 		visited = true
 		return nil
@@ -336,7 +308,6 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 			envvar.SetVar(&container.Env, envVar)
 		}
 
-		volumes = &cron.Spec.JobTemplate.Spec.Template.Spec.Volumes
 		containers = &cron.Spec.JobTemplate.Spec.Template.Spec.Containers
 		visited = true
 		return nil
@@ -345,60 +316,12 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 	}
 
 	if visited {
-		// Volumes declared in the Integration resources
-		e.configureVolumesAndMounts(volumes, &container.VolumeMounts)
-		// Volumes declared in the trait config/resource options
-		err := t.configureVolumesAndMounts(volumes, &container.VolumeMounts)
-		if err != nil {
-			return err
-		}
 		*containers = append(*containers, container)
 	}
 
 	return nil
 }
 
-func (t *containerTrait) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]corev1.VolumeMount) error {
-	for _, c := range t.Configs {
-		if conf, parseErr := utilResource.ParseConfig(c); parseErr == nil {
-			t.mountResource(vols, mnts, conf)
-		} else {
-			return parseErr
-		}
-	}
-	for _, r := range t.Resources {
-		if res, parseErr := utilResource.ParseResource(r); parseErr == nil {
-			t.mountResource(vols, mnts, res)
-		} else {
-			return parseErr
-		}
-	}
-	for _, v := range t.Volumes {
-		if vol, parseErr := utilResource.ParseVolume(v); parseErr == nil {
-			t.mountResource(vols, mnts, vol)
-		} else {
-			return parseErr
-		}
-	}
-
-	return nil
-}
-
-func (t *containerTrait) mountResource(vols *[]corev1.Volume, mnts *[]corev1.VolumeMount, conf *utilResource.Config) {
-	refName := kubernetes.SanitizeLabel(conf.Name())
-	vol := getVolume(refName, string(conf.StorageType()), conf.Name(), conf.Key(), conf.Key())
-	mntPath := getMountPoint(conf.Name(), conf.DestinationPath(), string(conf.StorageType()), string(conf.ContentType()))
-	readOnly := true
-	if conf.StorageType() == utilResource.StorageTypePVC {
-		readOnly = false
-	}
-	// No need to specify a subpath, as we mount the entire configmap/secret
-	mnt := getMount(refName, mntPath, "", readOnly)
-
-	*vols = append(*vols, *vol)
-	*mnts = append(*mnts, *mnt)
-}
-
 func (t *containerTrait) configureService(e *Environment, container *corev1.Container) {
 	service := e.Resources.GetServiceForIntegration(e.Integration)
 	if service == nil {
diff --git a/pkg/trait/mount.go b/pkg/trait/mount.go
new file mode 100644
index 0000000..18114bb
--- /dev/null
+++ b/pkg/trait/mount.go
@@ -0,0 +1,177 @@
+/*
+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"
+	"strings"
+
+	appsv1 "k8s.io/api/apps/v1"
+	"k8s.io/api/batch/v1beta1"
+	corev1 "k8s.io/api/core/v1"
+
+	serving "knative.dev/serving/pkg/apis/serving/v1"
+
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+	utilResource "github.com/apache/camel-k/pkg/util/resource"
+)
+
+// The Mount trait can be used to configure volumes mounted on the Integration Pod.
+//
+// +camel-k:trait=mount
+// nolint: tagliatelle
+type mountTrait struct {
+	BaseTrait `property:",squash"`
+	// A list of configuration pointing to configmap/secret. Syntax: [configmap|secret]:name[key], where name represents the resource name and key optionally represents the resource key to be filtered
+	Configs []string `property:"configs" json:"configs,omitempty"`
+	// A list of resources pointing to configmap/secret. Syntax: [configmap|secret]:name[/key][@path], where name represents the resource name, key optionally represents the resource key to be filtered and path represents the destination path
+	Resources []string `property:"resources" json:"resources,omitempty"`
+	// A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]
+	Volumes []string `property:"volumes" json:"volumes,omitempty"`
+}
+
+func newMountTrait() Trait {
+	return &mountTrait{
+		// Must follow immediately the container trait
+		BaseTrait: NewBaseTrait("mount", 1610),
+	}
+}
+
+func (t *mountTrait) Configure(e *Environment) (bool, error) {
+	if IsFalse(t.Enabled) {
+		return false, nil
+	}
+
+	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization) && !e.IntegrationInRunningPhases() {
+		return false, nil
+	}
+
+	// Validate resources and pvcs
+	for _, c := range t.Configs {
+		if !strings.HasPrefix(c, "configmap:") && !strings.HasPrefix(c, "secret:") {
+			return false, fmt.Errorf("unsupported config %s, must be a configmap or secret resource", c)
+		}
+	}
+	for _, r := range t.Resources {
+		if !strings.HasPrefix(r, "configmap:") && !strings.HasPrefix(r, "secret:") {
+			return false, fmt.Errorf("unsupported resource %s, must be a configmap or secret resource", r)
+		}
+	}
+
+	return true, nil
+}
+
+func (t *mountTrait) Apply(e *Environment) error {
+	if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) {
+		return nil
+	}
+
+	container := e.GetIntegrationContainer()
+	if container == nil {
+		return fmt.Errorf("unable to find integration container: %s", e.Integration.Name)
+	}
+
+	var volumes *[]corev1.Volume
+	visited := false
+
+	// Deployment
+	if err := e.Resources.VisitDeploymentE(func(deployment *appsv1.Deployment) error {
+		volumes = &deployment.Spec.Template.Spec.Volumes
+		visited = true
+		return nil
+	}); err != nil {
+		return err
+	}
+
+	// Knative Service
+	if err := e.Resources.VisitKnativeServiceE(func(service *serving.Service) error {
+		volumes = &service.Spec.ConfigurationSpec.Template.Spec.Volumes
+		visited = true
+		return nil
+	}); err != nil {
+		return err
+	}
+
+	// CronJob
+	if err := e.Resources.VisitCronJobE(func(cron *v1beta1.CronJob) error {
+		volumes = &cron.Spec.JobTemplate.Spec.Template.Spec.Volumes
+		visited = true
+		return nil
+	}); err != nil {
+		return err
+	}
+
+	if visited {
+		// Volumes declared in the Integration resources
+		e.configureVolumesAndMounts(volumes, &container.VolumeMounts)
+		// Volumes declared in the trait config/resource options
+		err := t.configureVolumesAndMounts(volumes, &container.VolumeMounts)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (t *mountTrait) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]corev1.VolumeMount) error {
+	for _, c := range t.Configs {
+		if conf, parseErr := utilResource.ParseConfig(c); parseErr == nil {
+			t.mountResource(vols, mnts, conf)
+		} else {
+			return parseErr
+		}
+	}
+	for _, r := range t.Resources {
+		if res, parseErr := utilResource.ParseResource(r); parseErr == nil {
+			t.mountResource(vols, mnts, res)
+		} else {
+			return parseErr
+		}
+	}
+	for _, v := range t.Volumes {
+		if vol, parseErr := utilResource.ParseVolume(v); parseErr == nil {
+			t.mountResource(vols, mnts, vol)
+		} else {
+			return parseErr
+		}
+	}
+
+	return nil
+}
+
+func (t *mountTrait) mountResource(vols *[]corev1.Volume, mnts *[]corev1.VolumeMount, conf *utilResource.Config) {
+	refName := kubernetes.SanitizeLabel(conf.Name())
+	vol := getVolume(refName, string(conf.StorageType()), conf.Name(), conf.Key(), conf.Key())
+	mntPath := getMountPoint(conf.Name(), conf.DestinationPath(), string(conf.StorageType()), string(conf.ContentType()))
+	readOnly := true
+	if conf.StorageType() == utilResource.StorageTypePVC {
+		readOnly = false
+	}
+	// No need to specify a subpath, as we mount the entire configmap/secret
+	mnt := getMount(refName, mntPath, "", readOnly)
+
+	*vols = append(*vols, *vol)
+	*mnts = append(*mnts, *mnt)
+}
+
+// IsPlatformTrait overrides base class method.
+func (t *mountTrait) IsPlatformTrait() bool {
+	return true
+}
diff --git a/pkg/trait/mount_test.go b/pkg/trait/mount_test.go
new file mode 100644
index 0000000..eee4bc0
--- /dev/null
+++ b/pkg/trait/mount_test.go
@@ -0,0 +1,179 @@
+/*
+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 (
+	"testing"
+
+	"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"
+
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/util/camel"
+	"github.com/apache/camel-k/pkg/util/gzip"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/test"
+)
+
+func TestMountVolumesEmpty(t *testing.T) {
+	traitCatalog := NewCatalog(nil)
+
+	environment := getNominalEnv(t, traitCatalog)
+	environment.Integration.Spec.Traits = map[string]v1.TraitSpec{}
+	environment.Platform.ResyncStatusFullConfig()
+
+	err := traitCatalog.apply(environment)
+
+	assert.Nil(t, err)
+	assert.NotEmpty(t, environment.ExecutedTraits)
+	assert.NotNil(t, environment.GetTrait("mount"))
+
+	s := environment.Resources.GetDeployment(func(service *appsv1.Deployment) bool {
+		return service.Name == "hello"
+	})
+	assert.NotNil(t, s)
+	spec := s.Spec.Template.Spec
+
+	assert.Len(t, spec.Containers[0].VolumeMounts, 2)
+	assert.Len(t, spec.Volumes, 2)
+}
+
+func TestMountVolumesIntegrationPhaseDeploying(t *testing.T) {
+	traitCatalog := NewCatalog(nil)
+
+	environment := getNominalEnv(t, traitCatalog)
+	environment.Platform.ResyncStatusFullConfig()
+
+	err := traitCatalog.apply(environment)
+
+	assert.Nil(t, err)
+	assert.NotEmpty(t, environment.ExecutedTraits)
+	assert.NotNil(t, environment.GetTrait("mount"))
+
+	s := environment.Resources.GetDeployment(func(service *appsv1.Deployment) bool {
+		return service.Name == "hello"
+	})
+	assert.NotNil(t, s)
+	spec := s.Spec.Template.Spec
+
+	assert.Len(t, spec.Containers[0].VolumeMounts, 5)
+	assert.Len(t, spec.Volumes, 5)
+
+	assert.Condition(t, func() bool {
+		for _, v := range spec.Containers[0].VolumeMounts {
+			if v.Name == "my-pvc" {
+				return true
+			}
+		}
+		return false
+	})
+	assert.Condition(t, func() bool {
+		for _, v := range spec.Containers[0].VolumeMounts {
+			if v.Name == "my-cm" {
+				return true
+			}
+		}
+		return false
+	})
+	assert.Condition(t, func() bool {
+		for _, v := range spec.Volumes {
+			if v.Name == "my-secret" {
+				return true
+			}
+		}
+		return false
+	})
+}
+
+func TestMountVolumesIntegrationPhaseInitialization(t *testing.T) {
+	traitCatalog := NewCatalog(nil)
+
+	environment := getNominalEnv(t, traitCatalog)
+	environment.Integration.Status.Phase = v1.IntegrationPhaseInitialization
+	environment.Platform.ResyncStatusFullConfig()
+
+	err := traitCatalog.apply(environment)
+
+	assert.Nil(t, err)
+	assert.NotEmpty(t, environment.ExecutedTraits)
+	assert.NotNil(t, environment.GetTrait("mount"))
+
+	s := environment.Resources.GetDeployment(func(service *appsv1.Deployment) bool {
+		return service.Name == "hello"
+	})
+	assert.Nil(t, s)
+}
+
+func getNominalEnv(t *testing.T, traitCatalog *Catalog) *Environment {
+	t.Helper()
+	catalog, _ := camel.DefaultCatalog()
+	compressedRoute, _ := gzip.CompressBase64([]byte(`from("undertow:test").log("hello")`))
+
+	return &Environment{
+		CamelCatalog: catalog,
+		Catalog:      traitCatalog,
+		Integration: &v1.Integration{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "hello",
+				Namespace: "default",
+			},
+			Status: v1.IntegrationStatus{
+				Phase: v1.IntegrationPhaseDeploying,
+			},
+			Spec: v1.IntegrationSpec{
+				Sources: []v1.SourceSpec{
+					{
+						DataSpec: v1.DataSpec{
+							Name:        "routes.js",
+							Content:     string(compressedRoute),
+							Compression: true,
+						},
+						Language: v1.LanguageJavaScript,
+					},
+				},
+				Traits: map[string]v1.TraitSpec{
+					"mount": test.TraitSpecFromMap(t, map[string]interface{}{
+						"configs":   []string{"configmap:my-cm"},
+						"resources": []string{"secret:my-secret"},
+						"volumes":   []string{"my-pvc:/over/the/rainbow"},
+					}),
+				},
+			},
+		},
+		IntegrationKit: &v1.IntegrationKit{
+			Status: v1.IntegrationKitStatus{
+				Phase: v1.IntegrationKitPhaseReady,
+			},
+		},
+		Platform: &v1.IntegrationPlatform{
+			Spec: v1.IntegrationPlatformSpec{
+				Cluster: v1.IntegrationPlatformClusterOpenShift,
+				Build: v1.IntegrationPlatformBuildSpec{
+					PublishStrategy: v1.IntegrationPlatformBuildPublishStrategyS2I,
+					Registry:        v1.RegistrySpec{Address: "registry"},
+				},
+			},
+		},
+		EnvVars:        make([]corev1.EnvVar, 0),
+		ExecutedTraits: make([]Trait, 0),
+		Resources:      kubernetes.NewCollection(),
+	}
+}
diff --git a/pkg/trait/trait_register.go b/pkg/trait/trait_register.go
index 68d2997..4f2a6d1 100644
--- a/pkg/trait/trait_register.go
+++ b/pkg/trait/trait_register.go
@@ -32,6 +32,7 @@ func init() {
 	AddToTraits(newErrorHandlerTrait)
 	AddToTraits(newGarbageCollectorTrait)
 	AddToTraits(newHealthTrait)
+	AddToTraits(newInitTrait)
 	AddToTraits(newIngressTrait)
 	AddToTraits(newIstioTrait)
 	AddToTraits(newJolokiaTrait)
@@ -40,7 +41,7 @@ func init() {
 	AddToTraits(newKnativeTrait)
 	AddToTraits(newKnativeServiceTrait)
 	AddToTraits(newLoggingTraitTrait)
-	AddToTraits(newInitTrait)
+	AddToTraits(newMountTrait)
 	AddToTraits(newOpenAPITrait)
 	AddToTraits(newOwnerTrait)
 	AddToTraits(newPdbTrait)
diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go
index bef6932..0bc1f41 100644
--- a/pkg/trait/trait_test.go
+++ b/pkg/trait/trait_test.go
@@ -495,7 +495,7 @@ func TestOnlySomeTraitsInfluenceBuild(t *testing.T) {
 func TestOnlySomeTraitsArePlatform(t *testing.T) {
 	c := NewTraitTestCatalog()
 	platformTraits := []string{
-		"builder", "camel", "jvm", "runtime", "container", "dependencies", "deployer",
+		"builder", "camel", "jvm", "runtime", "container", "mount", "dependencies", "deployer",
 		"deployment", "environment", "error-handler", "kamelets", "openapi", "owner", "platform", "quarkus",
 	}
 
diff --git a/resources/traits.yaml b/resources/traits.yaml
index d3ec499..2b66ec4 100755
--- a/resources/traits.yaml
+++ b/resources/traits.yaml
@@ -132,19 +132,6 @@ traits:
   - name: image-pull-policy
     type: PullPolicy
     description: 'The pull policy: Always|Never|IfNotPresent'
-  - name: configs
-    type: '[]string'
-    description: 'A list of configuration pointing to configmap/secret. Syntax: [configmap|secret]:name[key],
-      where name represents the resource name and key optionally represents the resource
-      key to be filtered'
-  - name: resources
-    type: '[]string'
-    description: 'A list of resources pointing to configmap/secret. Syntax: [configmap|secret]:name[/key][@path],
-      where name represents the resource name, key optionally represents the resource
-      key to be filtered and path represents the destination path'
-  - name: volumes
-    type: '[]string'
-    description: 'A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]'
   - name: probes-enabled
     type: bool
     description: 'DeprecatedProbesEnabled enable/disable probes on the container (default
@@ -765,6 +752,32 @@ traits:
     type: string
     description: Label value that will be used to identify all pods contending the
       lock. Defaults to the integration name.
+- name: mount
+  platform: true
+  profiles:
+  - Kubernetes
+  - Knative
+  - OpenShift
+  description: 'The Mount trait can be used to configure volumes mounted on the Integration
+    Pod. nolint: tagliatelle'
+  properties:
+  - name: enabled
+    type: bool
+    description: Can be used to enable or disable a trait. All traits share this common
+      property.
+  - name: configs
+    type: '[]string'
+    description: 'A list of configuration pointing to configmap/secret. Syntax: [configmap|secret]:name[key],
+      where name represents the resource name and key optionally represents the resource
+      key to be filtered'
+  - name: resources
+    type: '[]string'
+    description: 'A list of resources pointing to configmap/secret. Syntax: [configmap|secret]:name[/key][@path],
+      where name represents the resource name, key optionally represents the resource
+      key to be filtered and path represents the destination path'
+  - name: volumes
+    type: '[]string'
+    description: 'A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]'
 - name: openapi
   platform: true
   profiles: