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


The following commit(s) were added to refs/heads/main by this push:
     new 3ec5298ef feat(trait): mount volume from storage class
3ec5298ef is described below

commit 3ec5298efc547b03cf0c18a29553eed8f765472c
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Sat Oct 12 09:04:36 2024 +0200

    feat(trait): mount volume from storage class
    
    Closes #2994
---
 docs/modules/ROOT/partials/apis/camel-k-crds.adoc  |   4 +-
 docs/modules/traits/pages/mount.adoc               |  15 ++-
 helm/camel-k/crds/camel-k-crds.yaml                |  48 ++++++----
 pkg/apis/camel/v1/trait/mount.go                   |   4 +-
 .../camel.apache.org_integrationplatforms.yaml     |  12 ++-
 .../camel.apache.org_integrationprofiles.yaml      |  12 ++-
 .../crd/bases/camel.apache.org_integrations.yaml   |  12 ++-
 .../bases/camel.apache.org_kameletbindings.yaml    |   6 +-
 .../config/crd/bases/camel.apache.org_pipes.yaml   |   6 +-
 pkg/trait/mount.go                                 |  95 ++++++++++++++++++-
 pkg/trait/mount_test.go                            | 105 ++++++++++++++++++++-
 pkg/util/kubernetes/factory.go                     |   5 +-
 pkg/util/kubernetes/lookup.go                      |  26 ++++-
 13 files changed, 305 insertions(+), 45 deletions(-)

diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc 
b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
index 98771dac8..cae7017d4 100644
--- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
+++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
@@ -8008,7 +8008,9 @@ Syntax: [configmap{vbar}secret]:name[/key][@path], where 
name represents the res
 |
 
 
-A list of Persistent Volume Claims to be mounted. Syntax: 
[pvcname:/container/path]
+A list of Persistent Volume Claims to be mounted. Syntax: 
[pvcname:/container/path]. If the PVC is not found, the Integration fails.
+You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+or the default cluster Storage Class. However, if the PVC exists, the operator 
would mount it.
 
 |`emptyDirs` +
 []string
diff --git a/docs/modules/traits/pages/mount.adoc 
b/docs/modules/traits/pages/mount.adoc
index 64cbadf8e..0d5d6b476 100644
--- a/docs/modules/traits/pages/mount.adoc
+++ b/docs/modules/traits/pages/mount.adoc
@@ -45,7 +45,9 @@ Syntax: [configmap\|secret]:name[/key][@path], where name 
represents the resourc
 
 | mount.volumes
 | []string
-| A list of Persistent Volume Claims to be mounted. Syntax: 
[pvcname:/container/path]
+| A list of Persistent Volume Claims to be mounted. Syntax: 
[pvcname:/container/path]. If the PVC is not found, the Integration fails.
+You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+or the default cluster Storage Class. However, if the PVC exists, the operator 
would mount it.
 
 | mount.empty-dirs
 | []string
@@ -64,3 +66,14 @@ changes in metadata.
 |===
 
 // End of autogenerated code - DO NOT EDIT! (configuration)
+
+== Dynamic creation of PersistentVolumeClaims
+
+If your cluster has some StorageClass defined, you can configure 
`mount.volume` to create one PersistentVolume on your behalf. This is going to 
be mounted to the application Pod, according to the configuration given. See 
Kubernetes documentation about StorageClass to learn more.
+
+[source,console]
+$ kamel run test.yaml -t mount.volumes=my-pvc:/tmp/my-pvc:10Mi:ReadOnlyMany
+
+The above command would create a new volume on the fly if it does not exists 
using "default" StorageClass. You can specify any StorageClass as configuration.
+
+NOTE: if the volume exists, then it would reuse it.
diff --git a/helm/camel-k/crds/camel-k-crds.yaml 
b/helm/camel-k/crds/camel-k-crds.yaml
index 6c17ef703..12e238d8a 100644
--- a/helm/camel-k/crds/camel-k-crds.yaml
+++ b/helm/camel-k/crds/camel-k-crds.yaml
@@ -4831,8 +4831,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
@@ -6969,8 +6971,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
@@ -9010,8 +9014,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
@@ -11027,8 +11033,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
@@ -19085,8 +19093,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
@@ -21064,8 +21074,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
@@ -29197,8 +29209,10 @@ spec:
                               2.5.'
                             type: boolean
                           volumes:
-                            description: 'A list of Persistent Volume Claims 
to be
-                              mounted. Syntax: [pvcname:/container/path]'
+                            description: |-
+                              A list of Persistent Volume Claims to be 
mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the 
Integration fails.
+                              You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                              or the default cluster Storage Class. However, 
if the PVC exists, the operator would mount it.
                             items:
                               type: string
                             type: array
@@ -40121,8 +40135,10 @@ spec:
                               2.5.'
                             type: boolean
                           volumes:
-                            description: 'A list of Persistent Volume Claims 
to be
-                              mounted. Syntax: [pvcname:/container/path]'
+                            description: |-
+                              A list of Persistent Volume Claims to be 
mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the 
Integration fails.
+                              You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                              or the default cluster Storage Class. However, 
if the PVC exists, the operator would mount it.
                             items:
                               type: string
                             type: array
diff --git a/pkg/apis/camel/v1/trait/mount.go b/pkg/apis/camel/v1/trait/mount.go
index 048b95f30..6fa83b745 100644
--- a/pkg/apis/camel/v1/trait/mount.go
+++ b/pkg/apis/camel/v1/trait/mount.go
@@ -32,7 +32,9 @@ type MountTrait struct {
        // The destination path can be either a default location or any path 
specified by the user.
        // 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]
+       // A list of Persistent Volume Claims to be mounted. Syntax: 
[pvcname:/container/path]. If the PVC is not found, the Integration fails.
+       // You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+       // or the default cluster Storage Class. However, if the PVC exists, 
the operator would mount it.
        Volumes []string `property:"volumes" json:"volumes,omitempty"`
        // A list of EmptyDir volumes to be mounted. Syntax: 
[name:/container/path]
        EmptyDirs []string `property:"empty-dirs" json:"emptyDirs,omitempty"`
diff --git 
a/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml 
b/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml
index d4d043e9e..163232d42 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml
@@ -1673,8 +1673,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
@@ -3811,8 +3813,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
diff --git 
a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml 
b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml
index b4178e276..fd964cb8e 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml
@@ -1542,8 +1542,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
@@ -3559,8 +3561,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml 
b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
index 406bd06ee..520f496bc 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
@@ -7562,8 +7562,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
@@ -9541,8 +9543,10 @@ spec:
                           2.5.'
                         type: boolean
                       volumes:
-                        description: 'A list of Persistent Volume Claims to be 
mounted.
-                          Syntax: [pvcname:/container/path]'
+                        description: |-
+                          A list of Persistent Volume Claims to be mounted. 
Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration 
fails.
+                          You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                          or the default cluster Storage Class. However, if 
the PVC exists, the operator would mount it.
                         items:
                           type: string
                         type: array
diff --git 
a/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml 
b/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml
index c6db8e8c2..93ead8554 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml
@@ -7630,8 +7630,10 @@ spec:
                               2.5.'
                             type: boolean
                           volumes:
-                            description: 'A list of Persistent Volume Claims 
to be
-                              mounted. Syntax: [pvcname:/container/path]'
+                            description: |-
+                              A list of Persistent Volume Claims to be 
mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the 
Integration fails.
+                              You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                              or the default cluster Storage Class. However, 
if the PVC exists, the operator would mount it.
                             items:
                               type: string
                             type: array
diff --git a/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml 
b/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml
index 2600c4080..91af8ae44 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml
@@ -7628,8 +7628,10 @@ spec:
                               2.5.'
                             type: boolean
                           volumes:
-                            description: 'A list of Persistent Volume Claims 
to be
-                              mounted. Syntax: [pvcname:/container/path]'
+                            description: |-
+                              A list of Persistent Volume Claims to be 
mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the 
Integration fails.
+                              You can use the syntax 
[pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic 
PVC based on the Storage Class provided
+                              or the default cluster Storage Class. However, 
if the PVC exists, the operator would mount it.
                             items:
                               type: string
                             type: array
diff --git a/pkg/trait/mount.go b/pkg/trait/mount.go
index 5fe56da65..33a9b85fc 100644
--- a/pkg/trait/mount.go
+++ b/pkg/trait/mount.go
@@ -25,6 +25,8 @@ import (
        appsv1 "k8s.io/api/apps/v1"
        batchv1 "k8s.io/api/batch/v1"
        corev1 "k8s.io/api/core/v1"
+       storagev1 "k8s.io/api/storage/v1"
+       "k8s.io/apimachinery/pkg/api/resource"
 
        serving "knative.dev/serving/pkg/apis/serving/v1"
 
@@ -113,7 +115,7 @@ func (t *mountTrait) Apply(e *Environment) error {
                // 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)
+               err := t.configureVolumesAndMounts(e, volumes, 
&container.VolumeMounts)
                if err != nil {
                        return err
                }
@@ -122,7 +124,7 @@ func (t *mountTrait) Apply(e *Environment) error {
        return nil
 }
 
-func (t *mountTrait) configureVolumesAndMounts(vols *[]corev1.Volume, mnts 
*[]corev1.VolumeMount) error {
+func (t *mountTrait) configureVolumesAndMounts(e *Environment, 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)
@@ -138,11 +140,12 @@ func (t *mountTrait) configureVolumesAndMounts(vols 
*[]corev1.Volume, mnts *[]co
                }
        }
        for _, v := range t.Volumes {
-               if vol, parseErr := utilResource.ParseVolume(v); parseErr == 
nil {
-                       t.mountResource(vols, mnts, vol)
-               } else {
+               volume, volumeMount, parseErr := ParseAndCreateVolume(e, v)
+               if parseErr != nil {
                        return parseErr
                }
+               *vols = append(*vols, *volume)
+               *mnts = append(*mnts, *volumeMount)
        }
        for _, v := range t.EmptyDirs {
                if vol, parseErr := utilResource.ParseEmptyDirVolume(v); 
parseErr == nil {
@@ -186,3 +189,85 @@ func (t *mountTrait) addServiceBindingSecret(e 
*Environment) {
                }
        })
 }
+
+// ParseAndCreateVolume will parse a volume configuration. If the volume does 
not exist it tries to create one based on the storage
+// class configuration provided or default.
+// item is expected to be as: 
name:path/to/mount<:size:accessMode<:storageClassName>>.
+func ParseAndCreateVolume(e *Environment, item string) (*corev1.Volume, 
*corev1.VolumeMount, error) {
+       volumeParts := strings.Split(item, ":")
+       volumeName := volumeParts[0]
+       pvc, err := kubernetes.LookupPersistentVolumeClaim(e.Ctx, e.Client, 
e.Integration.Namespace, volumeName)
+       if err != nil {
+               return nil, nil, err
+       }
+       var volume *corev1.Volume
+       if pvc == nil {
+               if len(volumeParts) == 2 {
+                       return nil, nil, fmt.Errorf("volume %s does not exist. 
"+
+                               "Make sure to provide one or configure a 
dynamic PVC as trait volume configuration 
pvcName:path/to/mount:size:accessMode<:storageClassName>",
+                               volumeName,
+                       )
+               }
+               if err = createPVC(e, volumeParts); err != nil {
+                       return nil, nil, err
+               }
+       }
+
+       volume = &corev1.Volume{
+               Name: kubernetes.SanitizeLabel(volumeName),
+               VolumeSource: corev1.VolumeSource{
+                       PersistentVolumeClaim: 
&corev1.PersistentVolumeClaimVolumeSource{
+                               ClaimName: volumeName,
+                       },
+               },
+       }
+
+       volumeMount := getMount(volumeName, volumeParts[1], "", false)
+       return volume, volumeMount, nil
+}
+
+// createPVC is in charge to create a PersistentVolumeClaim based on the 
configuration provided. Or it fail within the intent.
+// volumeParts is expected to be as: name, path/to/mount, size, accessMode, 
<storageClassName>.
+func createPVC(e *Environment, volumeParts []string) error {
+       if len(volumeParts) < 4 || len(volumeParts) > 5 {
+               return fmt.Errorf(
+                       "volume mount syntax error, must be 
name:path/to/mount:size:accessMode<:storageClassName> was %s",
+                       strings.Join(volumeParts, ":"),
+               )
+       }
+       volumeName := volumeParts[0]
+       size := volumeParts[2]
+       accessMode := volumeParts[3]
+       sizeQty, err := resource.ParseQuantity(size)
+       if err != nil {
+               return fmt.Errorf("could not parse size %s, %s", size, 
err.Error())
+       }
+
+       var sc *storagev1.StorageClass
+       //nolint: nestif
+       if len(volumeParts) == 5 {
+               scName := volumeParts[4]
+               sc, err = kubernetes.LookupStorageClass(e.Ctx, e.Client, 
e.Integration.Namespace, scName)
+               if err != nil {
+                       return fmt.Errorf("error looking up for StorageClass 
%s, %w", scName, err)
+               }
+               if sc == nil {
+                       return fmt.Errorf("could not find any %s StorageClass", 
scName)
+               }
+       } else {
+               sc, err = kubernetes.LookupDefaultStorageClass(e.Ctx, e.Client)
+               if err != nil {
+                       return fmt.Errorf("error looking up for default 
StorageClass, %w", err)
+               }
+               if sc == nil {
+                       return fmt.Errorf("could not find any default 
StorageClass")
+               }
+       }
+
+       pvc := kubernetes.NewPersistentVolumeClaim(e.Integration.Namespace, 
volumeName, sc.Name, sizeQty, corev1.PersistentVolumeAccessMode(accessMode))
+       if err := e.Client.Create(e.Ctx, pvc); err != nil {
+               return err
+       }
+
+       return nil
+}
diff --git a/pkg/trait/mount_test.go b/pkg/trait/mount_test.go
index 0a480e314..df551dbf5 100644
--- a/pkg/trait/mount_test.go
+++ b/pkg/trait/mount_test.go
@@ -26,6 +26,7 @@ import (
 
        appsv1 "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
+       storagev1 "k8s.io/api/storage/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
        v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
@@ -63,7 +64,6 @@ func TestMountVolumesEmpty(t *testing.T) {
 
 func TestMountVolumesIntegrationPhaseDeploying(t *testing.T) {
        traitCatalog := NewCatalog(nil)
-
        environment := getNominalEnv(t, traitCatalog)
        environment.Platform.ResyncStatusFullConfig()
 
@@ -180,7 +180,17 @@ func TestMountVolumesIntegrationPhaseInitialization(t 
*testing.T) {
 
 func getNominalEnv(t *testing.T, traitCatalog *Catalog) *Environment {
        t.Helper()
-       fakeClient, _ := test.NewFakeClient()
+       pvc := corev1.PersistentVolumeClaim{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "PersistentVolumeClaim",
+                       APIVersion: corev1.SchemeGroupVersion.String(),
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Namespace: "default",
+                       Name:      "my-pvc",
+               },
+       }
+       fakeClient, _ := test.NewFakeClient(&pvc)
        catalog, _ := camel.DefaultCatalog()
        compressedRoute, _ := 
gzip.CompressBase64([]byte(`from("platform-http:test").log("hello")`))
 
@@ -240,3 +250,94 @@ func getNominalEnv(t *testing.T, traitCatalog *Catalog) 
*Environment {
                Resources:      kubernetes.NewCollection(),
        }
 }
+
+func TestMountVolumesExist(t *testing.T) {
+       traitCatalog := NewCatalog(nil)
+       e := getNominalEnv(t, traitCatalog)
+       vol, vm, err := ParseAndCreateVolume(e, "my-pvc:/tmp/my-pvc")
+       assert.NoError(t, err)
+       assert.Equal(t, "my-pvc", vol.PersistentVolumeClaim.ClaimName)
+       assert.Equal(t, "my-pvc", vm.Name)
+       assert.Equal(t, "/tmp/my-pvc", vm.MountPath)
+}
+
+func TestMountVolumesNotExistAndFail(t *testing.T) {
+       traitCatalog := NewCatalog(nil)
+       e := getNominalEnv(t, traitCatalog)
+       _, _, err := ParseAndCreateVolume(e, "my-pvc-2:/tmp/my-pvc")
+       assert.Error(t, err)
+       assert.Equal(t,
+               "volume my-pvc-2 does not exist. Make sure to provide one or 
configure a dynamic PVC as trait volume configuration "+
+                       
"pvcName:path/to/mount:size:accessMode<:storageClassName>", err.Error())
+       // Wrong configuration
+       _, _, err = ParseAndCreateVolume(e, "my-pvc-2:/tmp/my-pvc:fail")
+       assert.Error(t, err)
+       assert.Equal(t, "volume mount syntax error, must be 
name:path/to/mount:size:accessMode<:storageClassName> was 
my-pvc-2:/tmp/my-pvc:fail", err.Error())
+       // Wrong size parsing
+       _, _, err = ParseAndCreateVolume(e, 
"my-pvc-2:/tmp/my-pvc:10MM:ReadOnly")
+       assert.Error(t, err)
+       assert.Equal(t, "could not parse size 10MM, unable to parse quantity's 
suffix", err.Error())
+       // No default storage class
+       _, _, err = ParseAndCreateVolume(e, 
"my-pvc-2:/tmp/my-pvc:10Mi:ReadOnly")
+       assert.Error(t, err)
+       assert.Equal(t, "could not find any default StorageClass", err.Error())
+       // No given storage class
+       _, _, err = ParseAndCreateVolume(e, 
"my-pvc-2:/tmp/my-pvc:10Mi:ReadOnly:my-storage-class")
+       assert.Error(t, err)
+       assert.Equal(t, "could not find any my-storage-class StorageClass", 
err.Error())
+}
+
+func TestMountVolumesCreateDefaultStorageClass(t *testing.T) {
+       traitCatalog := NewCatalog(nil)
+       e := getNominalEnv(t, traitCatalog)
+       sc := storagev1.StorageClass{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "StorageClass",
+                       APIVersion: storagev1.SchemeGroupVersion.String(),
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Namespace: "default",
+                       Name:      "default-sc",
+                       Annotations: map[string]string{
+                               "storageclass.kubernetes.io/is-default-class": 
"true",
+                       },
+               },
+       }
+       fakeClient, _ := test.NewFakeClient(&sc)
+       e.Client = fakeClient
+       // Default storage class
+       vol, vm, err := ParseAndCreateVolume(e, 
"my-pvc:/tmp/my-pvc:10Mi:ReadOnly")
+       assert.NoError(t, err)
+       assert.Equal(t, "my-pvc", vol.PersistentVolumeClaim.ClaimName)
+       assert.Equal(t, "my-pvc", vm.Name)
+       assert.Equal(t, "/tmp/my-pvc", vm.MountPath)
+       pvc, err := kubernetes.LookupPersistentVolumeClaim(e.Ctx, e.Client, 
e.Integration.Namespace, "my-pvc")
+       assert.NoError(t, err)
+       assert.NotNil(t, pvc)
+}
+
+func TestMountVolumesCreateUserStorageClass(t *testing.T) {
+       traitCatalog := NewCatalog(nil)
+       e := getNominalEnv(t, traitCatalog)
+       sc := storagev1.StorageClass{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "StorageClass",
+                       APIVersion: storagev1.SchemeGroupVersion.String(),
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Namespace: "default",
+                       Name:      "my-sc",
+               },
+       }
+       fakeClient, _ := test.NewFakeClient(&sc)
+       e.Client = fakeClient
+       // Default storage class
+       vol, vm, err := ParseAndCreateVolume(e, 
"my-pvc:/tmp/my-pvc:10Mi:ReadOnly:my-sc")
+       assert.NoError(t, err)
+       assert.Equal(t, "my-pvc", vol.PersistentVolumeClaim.ClaimName)
+       assert.Equal(t, "my-pvc", vm.Name)
+       assert.Equal(t, "/tmp/my-pvc", vm.MountPath)
+       pvc, err := kubernetes.LookupPersistentVolumeClaim(e.Ctx, e.Client, 
e.Integration.Namespace, "my-pvc")
+       assert.NoError(t, err)
+       assert.NotNil(t, pvc)
+}
diff --git a/pkg/util/kubernetes/factory.go b/pkg/util/kubernetes/factory.go
index 595f75e97..9c0e50497 100644
--- a/pkg/util/kubernetes/factory.go
+++ b/pkg/util/kubernetes/factory.go
@@ -158,7 +158,8 @@ func NewConfigMap(namespace, cmName, originalFilename 
string, generatedKey strin
 }
 
 // NewPersistentVolumeClaim will create a NewPersistentVolumeClaim based on a 
StorageClass.
-func NewPersistentVolumeClaim(ns, name, storageClassName, capacityStorage 
string, accessMode corev1.PersistentVolumeAccessMode) 
*corev1.PersistentVolumeClaim {
+func NewPersistentVolumeClaim(
+       ns, name, storageClassName string, capacityStorage resource.Quantity, 
accessMode corev1.PersistentVolumeAccessMode) *corev1.PersistentVolumeClaim {
        pvc := corev1.PersistentVolumeClaim{
                TypeMeta: metav1.TypeMeta{
                        Kind:       "PersistentVolumeClaim",
@@ -175,7 +176,7 @@ func NewPersistentVolumeClaim(ns, name, storageClassName, 
capacityStorage string
                        },
                        Resources: corev1.VolumeResourceRequirements{
                                Requests: corev1.ResourceList{
-                                       "storage": 
resource.MustParse(capacityStorage),
+                                       "storage": capacityStorage,
                                },
                        },
                },
diff --git a/pkg/util/kubernetes/lookup.go b/pkg/util/kubernetes/lookup.go
index 7b69880da..2ce961d5b 100644
--- a/pkg/util/kubernetes/lookup.go
+++ b/pkg/util/kubernetes/lookup.go
@@ -109,7 +109,31 @@ func LookupPersistentVolumeClaim(ctx context.Context, c 
client.Client, ns string
        return &pvc, nil
 }
 
-// LookupDefaultStorageClass will look for the default k8s StorageClass in a 
given namespace.
+// LookupStorageClass will look for any k8s StorageClass with a given name in 
a given namespace.
+func LookupStorageClass(ctx context.Context, c client.Client, ns string, name 
string) (*storagev1.StorageClass, error) {
+       sc := storagev1.StorageClass{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "StorageClass",
+                       APIVersion: storagev1.SchemeGroupVersion.String(),
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Namespace: ns,
+                       Name:      name,
+               },
+       }
+       key := ctrl.ObjectKey{
+               Namespace: ns,
+               Name:      name,
+       }
+       if err := c.Get(ctx, key, &sc); err != nil && k8serrors.IsNotFound(err) 
{
+               return nil, nil
+       } else if err != nil {
+               return nil, err
+       }
+       return &sc, nil
+}
+
+// LookupDefaultStorageClass will look for the default k8s StorageClass in the 
cluster.
 func LookupDefaultStorageClass(ctx context.Context, c client.Client) 
(*storagev1.StorageClass, error) {
        storageClasses, err := c.StorageV1().StorageClasses().List(ctx, 
metav1.ListOptions{})
        if err != nil {

Reply via email to