From 2993aef576ba12647e72d052eeffc34290c9864d Mon Sep 17 00:00:00 2001 From: Pasquale Congiusti Date: Tue, 10 Oct 2023 10:54:20 +0200 Subject: [PATCH] feat(trait): mount kamelets for runtime * Add a configmap as a bundle for all kamelets used by the Integration * Mount the bundle in /etc/camel/kamelets * Let the runtime use the Kamelets instead of trasforming into RouteTemplates * Let the operator use the Kamelet template for capability parsing Closes #4618 --- config/crd/bases/camel.apache.org_builds.yaml | 8 ++ .../camel.apache.org_integrationkits.yaml | 3 + ...camel.apache.org_integrationplatforms.yaml | 8 ++ .../bases/camel.apache.org_integrations.yaml | 10 ++ .../camel.apache.org_kameletbindings.yaml | 7 ++ .../crd/bases/camel.apache.org_kamelets.yaml | 6 + config/crd/bases/camel.apache.org_pipes.yaml | 7 ++ .../ROOT/partials/apis/camel-k-crds.adoc | 14 +++ docs/modules/traits/pages/kamelets.adoc | 4 + helm/camel-k/crds/crd-build.yaml | 8 ++ helm/camel-k/crds/crd-integration-kit.yaml | 3 + .../crds/crd-integration-platform.yaml | 8 ++ helm/camel-k/crds/crd-integration.yaml | 10 ++ helm/camel-k/crds/crd-kamelet-binding.yaml | 7 ++ helm/camel-k/crds/crd-kamelet.yaml | 6 + helm/camel-k/crds/crd-pipe.yaml | 7 ++ pkg/apis/camel/v1/common_types.go | 2 + pkg/apis/camel/v1/common_types_support.go | 5 + pkg/apis/camel/v1/trait/kamelets.go | 2 + .../applyconfiguration/camel/v1/sourcespec.go | 9 ++ pkg/trait/camel.go | 1 + pkg/trait/kamelets.go | 117 ++++++++++-------- pkg/trait/kamelets_test.go | 6 +- pkg/trait/trait_types.go | 74 ++++++----- pkg/util/kubernetes/factory.go | 3 + resources/traits.yaml | 4 + 26 files changed, 251 insertions(+), 88 deletions(-) diff --git a/config/crd/bases/camel.apache.org_builds.yaml b/config/crd/bases/camel.apache.org_builds.yaml index 08a6cc2133..caacc731d8 100644 --- a/config/crd/bases/camel.apache.org_builds.yaml +++ b/config/crd/bases/camel.apache.org_builds.yaml @@ -686,6 +686,10 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a + Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post @@ -1424,6 +1428,10 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a + Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post diff --git a/config/crd/bases/camel.apache.org_integrationkits.yaml b/config/crd/bases/camel.apache.org_integrationkits.yaml index fade604f6a..8853d50858 100644 --- a/config/crd/bases/camel.apache.org_integrationkits.yaml +++ b/config/crd/bases/camel.apache.org_integrationkits.yaml @@ -143,6 +143,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources diff --git a/config/crd/bases/camel.apache.org_integrationplatforms.yaml b/config/crd/bases/camel.apache.org_integrationplatforms.yaml index 2ba99f660f..51a3a1bb36 100644 --- a/config/crd/bases/camel.apache.org_integrationplatforms.yaml +++ b/config/crd/bases/camel.apache.org_integrationplatforms.yaml @@ -1198,6 +1198,10 @@ spec: description: Comma separated list of Kamelet names to load into the current integration type: string + mountPoint: + description: The directory where the application mounts and + reads Kamelet spec (default `/etc/camel/kamelets`) + type: string type: object keda: description: 'Deprecated: for backward compatibility.' @@ -3024,6 +3028,10 @@ spec: description: Comma separated list of Kamelet names to load into the current integration type: string + mountPoint: + description: The directory where the application mounts and + reads Kamelet spec (default `/etc/camel/kamelets`) + type: string type: object keda: description: 'Deprecated: for backward compatibility.' diff --git a/config/crd/bases/camel.apache.org_integrations.yaml b/config/crd/bases/camel.apache.org_integrations.yaml index fd022c6438..af2a6706e0 100644 --- a/config/crd/bases/camel.apache.org_integrations.yaml +++ b/config/crd/bases/camel.apache.org_integrations.yaml @@ -189,6 +189,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources @@ -7115,6 +7118,10 @@ spec: description: Comma separated list of Kamelet names to load into the current integration type: string + mountPoint: + description: The directory where the application mounts and + reads Kamelet spec (default `/etc/camel/kamelets`) + type: string type: object keda: description: 'Deprecated: for backward compatibility.' @@ -7928,6 +7935,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources diff --git a/config/crd/bases/camel.apache.org_kameletbindings.yaml b/config/crd/bases/camel.apache.org_kameletbindings.yaml index 8ff36efee1..68c7fc88a3 100644 --- a/config/crd/bases/camel.apache.org_kameletbindings.yaml +++ b/config/crd/bases/camel.apache.org_kameletbindings.yaml @@ -189,6 +189,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources @@ -7401,6 +7404,10 @@ spec: description: Comma separated list of Kamelet names to load into the current integration type: string + mountPoint: + description: The directory where the application mounts + and reads Kamelet spec (default `/etc/camel/kamelets`) + type: string type: object keda: description: 'Deprecated: for backward compatibility.' diff --git a/config/crd/bases/camel.apache.org_kamelets.yaml b/config/crd/bases/camel.apache.org_kamelets.yaml index 3131ac0e24..aeb9739e22 100644 --- a/config/crd/bases/camel.apache.org_kamelets.yaml +++ b/config/crd/bases/camel.apache.org_kamelets.yaml @@ -466,6 +466,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources @@ -1163,6 +1166,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources diff --git a/config/crd/bases/camel.apache.org_pipes.yaml b/config/crd/bases/camel.apache.org_pipes.yaml index 8980595486..ff384991a4 100644 --- a/config/crd/bases/camel.apache.org_pipes.yaml +++ b/config/crd/bases/camel.apache.org_pipes.yaml @@ -187,6 +187,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources @@ -7399,6 +7402,10 @@ spec: description: Comma separated list of Kamelet names to load into the current integration type: string + mountPoint: + description: The directory where the application mounts + and reads Kamelet spec (default `/etc/camel/kamelets`) + type: string type: object keda: description: 'Deprecated: for backward compatibility.' diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc index b2aaca8138..9fad0b20b3 100644 --- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc +++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc @@ -5064,6 +5064,13 @@ Type defines the kind of source described by this object List of property names defined in the source (e.g. if type is "template") +|`from-kamelet` + +bool +| + + +True if the spec is generated from a Kamelet + |=== @@ -6930,6 +6937,13 @@ string Comma separated list of Kamelet names to load into the current integration +|`mountPoint` + +string +| + + +The directory where the application mounts and reads Kamelet spec (default `/etc/camel/kamelets`) + |=== diff --git a/docs/modules/traits/pages/kamelets.adoc b/docs/modules/traits/pages/kamelets.adoc index e4fa394ff9..295343f067 100755 --- a/docs/modules/traits/pages/kamelets.adoc +++ b/docs/modules/traits/pages/kamelets.adoc @@ -35,6 +35,10 @@ The following configuration options are available: | string | Comma separated list of Kamelet names to load into the current integration +| kamelets.mount-point +| string +| The directory where the application mounts and reads Kamelet spec (default `/etc/camel/kamelets`) + |=== // End of autogenerated code - DO NOT EDIT! (configuration) diff --git a/helm/camel-k/crds/crd-build.yaml b/helm/camel-k/crds/crd-build.yaml index 08a6cc2133..caacc731d8 100644 --- a/helm/camel-k/crds/crd-build.yaml +++ b/helm/camel-k/crds/crd-build.yaml @@ -686,6 +686,10 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a + Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post @@ -1424,6 +1428,10 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a + Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post diff --git a/helm/camel-k/crds/crd-integration-kit.yaml b/helm/camel-k/crds/crd-integration-kit.yaml index fade604f6a..8853d50858 100644 --- a/helm/camel-k/crds/crd-integration-kit.yaml +++ b/helm/camel-k/crds/crd-integration-kit.yaml @@ -143,6 +143,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources diff --git a/helm/camel-k/crds/crd-integration-platform.yaml b/helm/camel-k/crds/crd-integration-platform.yaml index 2ba99f660f..51a3a1bb36 100644 --- a/helm/camel-k/crds/crd-integration-platform.yaml +++ b/helm/camel-k/crds/crd-integration-platform.yaml @@ -1198,6 +1198,10 @@ spec: description: Comma separated list of Kamelet names to load into the current integration type: string + mountPoint: + description: The directory where the application mounts and + reads Kamelet spec (default `/etc/camel/kamelets`) + type: string type: object keda: description: 'Deprecated: for backward compatibility.' @@ -3024,6 +3028,10 @@ spec: description: Comma separated list of Kamelet names to load into the current integration type: string + mountPoint: + description: The directory where the application mounts and + reads Kamelet spec (default `/etc/camel/kamelets`) + type: string type: object keda: description: 'Deprecated: for backward compatibility.' diff --git a/helm/camel-k/crds/crd-integration.yaml b/helm/camel-k/crds/crd-integration.yaml index fd022c6438..af2a6706e0 100644 --- a/helm/camel-k/crds/crd-integration.yaml +++ b/helm/camel-k/crds/crd-integration.yaml @@ -189,6 +189,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources @@ -7115,6 +7118,10 @@ spec: description: Comma separated list of Kamelet names to load into the current integration type: string + mountPoint: + description: The directory where the application mounts and + reads Kamelet spec (default `/etc/camel/kamelets`) + type: string type: object keda: description: 'Deprecated: for backward compatibility.' @@ -7928,6 +7935,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources diff --git a/helm/camel-k/crds/crd-kamelet-binding.yaml b/helm/camel-k/crds/crd-kamelet-binding.yaml index 8ff36efee1..68c7fc88a3 100644 --- a/helm/camel-k/crds/crd-kamelet-binding.yaml +++ b/helm/camel-k/crds/crd-kamelet-binding.yaml @@ -189,6 +189,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources @@ -7401,6 +7404,10 @@ spec: description: Comma separated list of Kamelet names to load into the current integration type: string + mountPoint: + description: The directory where the application mounts + and reads Kamelet spec (default `/etc/camel/kamelets`) + type: string type: object keda: description: 'Deprecated: for backward compatibility.' diff --git a/helm/camel-k/crds/crd-kamelet.yaml b/helm/camel-k/crds/crd-kamelet.yaml index 3131ac0e24..aeb9739e22 100644 --- a/helm/camel-k/crds/crd-kamelet.yaml +++ b/helm/camel-k/crds/crd-kamelet.yaml @@ -466,6 +466,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources @@ -1163,6 +1166,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources diff --git a/helm/camel-k/crds/crd-pipe.yaml b/helm/camel-k/crds/crd-pipe.yaml index 8980595486..ff384991a4 100644 --- a/helm/camel-k/crds/crd-pipe.yaml +++ b/helm/camel-k/crds/crd-pipe.yaml @@ -187,6 +187,9 @@ spec: contentType: description: the content type (tipically text or binary) type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean interceptors: description: Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader uses to pre/post process sources @@ -7399,6 +7402,10 @@ spec: description: Comma separated list of Kamelet names to load into the current integration type: string + mountPoint: + description: The directory where the application mounts + and reads Kamelet spec (default `/etc/camel/kamelets`) + type: string type: object keda: description: 'Deprecated: for backward compatibility.' diff --git a/pkg/apis/camel/v1/common_types.go b/pkg/apis/camel/v1/common_types.go index f63f4db351..21c202f1a4 100644 --- a/pkg/apis/camel/v1/common_types.go +++ b/pkg/apis/camel/v1/common_types.go @@ -414,6 +414,8 @@ type SourceSpec struct { Type SourceType `json:"type,omitempty"` // List of property names defined in the source (e.g. if type is "template") PropertyNames []string `json:"property-names,omitempty"` + // True if the spec is generated from a Kamelet + FromKamelet bool `json:"from-kamelet,omitempty"` } // SourceType represents an available source type. diff --git a/pkg/apis/camel/v1/common_types_support.go b/pkg/apis/camel/v1/common_types_support.go index a7a956f98c..c8adc78093 100644 --- a/pkg/apis/camel/v1/common_types_support.go +++ b/pkg/apis/camel/v1/common_types_support.go @@ -211,3 +211,8 @@ func DecodeValueSource(input string, defaultKey string, errorMessage string) (Va return ValueSource{}, fmt.Errorf(errorMessage) } + +// IsGeneratedFromKamelet determines is a source spec is derived from a Kamelet +func (s *SourceSpec) IsGeneratedFromKamelet() bool { + return s.FromKamelet +} diff --git a/pkg/apis/camel/v1/trait/kamelets.go b/pkg/apis/camel/v1/trait/kamelets.go index 9467b48839..8b9cfd6b03 100644 --- a/pkg/apis/camel/v1/trait/kamelets.go +++ b/pkg/apis/camel/v1/trait/kamelets.go @@ -26,4 +26,6 @@ type KameletsTrait struct { Auto *bool `property:"auto" json:"auto,omitempty"` // Comma separated list of Kamelet names to load into the current integration List string `property:"list" json:"list,omitempty"` + // The directory where the application mounts and reads Kamelet spec (default `/etc/camel/kamelets`) + MountPoint string `property:"mount-point" json:"mountPoint,omitempty"` } diff --git a/pkg/client/camel/applyconfiguration/camel/v1/sourcespec.go b/pkg/client/camel/applyconfiguration/camel/v1/sourcespec.go index 8c844b1891..0cf5fdac3c 100644 --- a/pkg/client/camel/applyconfiguration/camel/v1/sourcespec.go +++ b/pkg/client/camel/applyconfiguration/camel/v1/sourcespec.go @@ -32,6 +32,7 @@ type SourceSpecApplyConfiguration struct { Interceptors []string `json:"interceptors,omitempty"` Type *camelv1.SourceType `json:"type,omitempty"` PropertyNames []string `json:"property-names,omitempty"` + FromKamelet *bool `json:"from-kamelet,omitempty"` } // SourceSpecApplyConfiguration constructs an declarative configuration of the SourceSpec type for use with @@ -149,3 +150,11 @@ func (b *SourceSpecApplyConfiguration) WithPropertyNames(values ...string) *Sour } return b } + +// WithFromKamelet sets the FromKamelet field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FromKamelet field is set to the value of the last call. +func (b *SourceSpecApplyConfiguration) WithFromKamelet(value bool) *SourceSpecApplyConfiguration { + b.FromKamelet = &value + return b +} diff --git a/pkg/trait/camel.go b/pkg/trait/camel.go index 149d6006b7..1709d0de9c 100644 --- a/pkg/trait/camel.go +++ b/pkg/trait/camel.go @@ -229,6 +229,7 @@ func (t *camelTrait) computeConfigMaps(e *Environment) []ctrl.Object { Labels: map[string]string{ v1.IntegrationLabel: e.Integration.Name, "camel.apache.org/properties.type": "user", + kubernetes.ConfigMapTypeLabel: "camel-properties", }, }, Data: map[string]string{ diff --git a/pkg/trait/kamelets.go b/pkg/trait/kamelets.go index 9d1b1c308f..aed2c8ff31 100644 --- a/pkg/trait/kamelets.go +++ b/pkg/trait/kamelets.go @@ -20,6 +20,7 @@ package trait import ( "errors" "fmt" + "path/filepath" "sort" "strconv" "strings" @@ -35,9 +36,11 @@ import ( "github.com/apache/camel-k/v2/pkg/kamelet/repository" "github.com/apache/camel-k/v2/pkg/platform" "github.com/apache/camel-k/v2/pkg/util" + "github.com/apache/camel-k/v2/pkg/util/camel" "github.com/apache/camel-k/v2/pkg/util/digest" "github.com/apache/camel-k/v2/pkg/util/dsl" "github.com/apache/camel-k/v2/pkg/util/kamelets" + "github.com/apache/camel-k/v2/pkg/util/kubernetes" ) type kameletsTrait struct { @@ -58,8 +61,8 @@ func newConfigurationKey(kamelet, configurationID string) configurationKey { } const ( - contentKey = "content" - + contentKey = "content" + KameletLocationProperty = "camel.component.kamelet.location" kameletLabel = "camel.apache.org/kamelet" kameletConfigurationLabel = "camel.apache.org/kamelet.configuration" ) @@ -94,6 +97,10 @@ func (t *kameletsTrait) Configure(e *Environment) (bool, error) { sort.Strings(kamelets) t.List = strings.Join(kamelets, ",") } + + if t.MountPoint == "" { + t.MountPoint = filepath.Join(camel.BasePath, "kamelets") + } } return len(t.getKameletKeys()) > 0, nil @@ -107,8 +114,6 @@ func (t *kameletsTrait) Apply(e *Environment) error { } if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) { return t.addConfigurationSecrets(e) - } else if e.IntegrationInRunningPhases() { - return t.configureApplicationProperties(e) } return nil @@ -134,7 +139,6 @@ func (t *kameletsTrait) collectKamelets(e *Environment) (map[string]*v1.Kamelet, missingKamelets = append(missingKamelets, key) } else { availableKamelets = append(availableKamelets, key) - // Initialize remote kamelets kamelets[key], err = kameletutils.Initialize(kamelet) if err != nil { @@ -147,7 +151,7 @@ func (t *kameletsTrait) collectKamelets(e *Environment) (map[string]*v1.Kamelet, sort.Strings(missingKamelets) if len(missingKamelets) > 0 { - message := fmt.Sprintf("kamelets [%s] found, [%s] not found in repositories: %s", + message := fmt.Sprintf("kamelets [%s] found, kamelets [%s] not found in %s repositories", strings.Join(availableKamelets, ","), strings.Join(missingKamelets, ","), repo.String()) @@ -166,7 +170,7 @@ func (t *kameletsTrait) collectKamelets(e *Environment) (map[string]*v1.Kamelet, v1.IntegrationConditionKameletsAvailable, corev1.ConditionTrue, v1.IntegrationConditionKameletsAvailableReason, - fmt.Sprintf("kamelets [%s] found in repositories: %s", strings.Join(availableKamelets, ","), repo.String()), + fmt.Sprintf("kamelets [%s] found in %s repositories", strings.Join(availableKamelets, ","), repo.String()), ) return kamelets, nil @@ -178,59 +182,61 @@ func (t *kameletsTrait) addKamelets(e *Environment) error { if err != nil { return err } - + kameletsBundleConfigmap := initializeConfigmapBundle(e.Integration.Name, e.Integration.Namespace) for _, key := range t.getKameletKeys() { kamelet := kamelets[key] - if kamelet.Status.Phase != v1.KameletPhaseReady { return fmt.Errorf("kamelet %q is not %s: %s", key, v1.KameletPhaseReady, kamelet.Status.Phase) } - + // Add source for parsing capabilities if err := t.addKameletAsSource(e, kamelet); err != nil { return err } - - // Adding dependencies from Kamelets + // Adding explicit dependencies from Kamelets util.StringSliceUniqueConcat(&e.Integration.Status.Dependencies, kamelet.Spec.Dependencies) + // Add to Kamelet bundle configmap + serialized, err := kubernetes.ToYAMLNoManagedFields(kamelet) + if err != nil { + return err + } + kameletsBundleConfigmap.Data[fmt.Sprintf("%s.kamelet.yaml", kamelet.Name)] = string(serialized) + } + // set kamelets runtime location + if e.ApplicationProperties == nil { + e.ApplicationProperties = map[string]string{} } + e.ApplicationProperties[KameletLocationProperty] = fmt.Sprintf("file:%s", t.MountPoint) + e.Resources.Add(&kameletsBundleConfigmap) // resort dependencies sort.Strings(e.Integration.Status.Dependencies) } return nil } -func (t *kameletsTrait) configureApplicationProperties(e *Environment) error { - if len(t.getKameletKeys()) > 0 { - kamelets, err := t.collectKamelets(e) - if err != nil { - return err - } - - for _, key := range t.getKameletKeys() { - kamelet := kamelets[key] - // Configuring defaults from Kamelet - for _, prop := range kamelet.Status.Properties { - if prop.Default != "" { - // Check whether user specified a value - userDefined := false - propName := fmt.Sprintf("camel.kamelet.%s.%s", kamelet.Name, prop.Name) - propPrefix := propName + "=" - for _, userProp := range e.Integration.Spec.Configuration { - if strings.HasPrefix(userProp.Value, propPrefix) { - userDefined = true - break - } - } - if !userDefined { - e.ApplicationProperties[propName] = prop.Default - } - } - } - } +func initializeConfigmapBundle(name, namespace string) corev1.ConfigMap { + return corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("kamelets-bundle-%s", name), + Namespace: namespace, + Labels: map[string]string{ + v1.IntegrationLabel: name, + kubernetes.ConfigMapTypeLabel: "kamelets-bundle", + }, + Annotations: map[string]string{ + kubernetes.ConfigMapAutogenLabel: "true", + }, + }, + Data: map[string]string{}, } - return nil } +// This func will add a Kamelet as a generated Integration source. The source included here is going to be used in order to parse the Kamelet +// for any component or capability (ie, rest) which is included in the Kamelet spec itself. However, the generated source is marked as coming `FromKamelet`. +// When mounting the sources, these generated sources won't be mounted as sources but as Kamelet instead. func (t *kameletsTrait) addKameletAsSource(e *Environment, kamelet *v1.Kamelet) error { sources := make([]v1.SourceSpec, 0) @@ -246,7 +252,8 @@ func (t *kameletsTrait) addKameletAsSource(e *Environment, kamelet *v1.Kamelet) Name: fmt.Sprintf("%s.yaml", kamelet.Name), Content: string(flowData), }, - Language: v1.LanguageYaml, + Language: v1.LanguageYaml, + FromKamelet: true, } flowSource, err = integrationSourceFromKameletSource(e, kamelet, flowSource, fmt.Sprintf("%s-kamelet-%s-template", e.Integration.Name, kamelet.Name)) if err != nil { @@ -368,18 +375,28 @@ func integrationSourceFromKameletSource(e *Environment, kamelet *v1.Kamelet, sou if err != nil { return v1.SourceSpec{}, err } + cm := initializeConfigmapKameletSource(source, hash, name, e.Integration.Namespace, e.Integration.Name, kamelet.Name) + e.Resources.Add(&cm) - cm := corev1.ConfigMap{ + target := source.DeepCopy() + target.Content = "" + target.ContentRef = name + target.ContentKey = contentKey + return *target, nil +} + +func initializeConfigmapKameletSource(source v1.SourceSpec, hash, name, namespace, itName, kamName string) corev1.ConfigMap { + return corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, - Namespace: e.Integration.Namespace, + Namespace: namespace, Labels: map[string]string{ - "camel.apache.org/integration": e.Integration.Name, - "camel.apache.org/kamelet": kamelet.Name, + "camel.apache.org/integration": itName, + "camel.apache.org/kamelet": kamName, }, Annotations: map[string]string{ "camel.apache.org/source.language": string(source.Language), @@ -394,12 +411,4 @@ func integrationSourceFromKameletSource(e *Environment, kamelet *v1.Kamelet, sou contentKey: source.Content, }, } - - e.Resources.Add(&cm) - - target := source.DeepCopy() - target.Content = "" - target.ContentRef = name - target.ContentKey = contentKey - return *target, nil } diff --git a/pkg/trait/kamelets_test.go b/pkg/trait/kamelets_test.go index a91bc9c71c..96a80b5a74 100644 --- a/pkg/trait/kamelets_test.go +++ b/pkg/trait/kamelets_test.go @@ -109,7 +109,7 @@ func TestKameletLookup(t *testing.T) { require.NoError(t, err) cm := environment.Resources.GetConfigMap(func(_ *corev1.ConfigMap) bool { return true }) assert.NotNil(t, cm) - assert.Equal(t, "it-kamelet-timer-template", cm.Name) + assert.Equal(t, "kamelets-bundle-it", cm.Name) assert.Equal(t, "test", cm.Namespace) assert.Len(t, environment.Integration.Status.GeneratedSources, 1) @@ -209,7 +209,7 @@ func TestNonYAMLKameletLookup(t *testing.T) { require.NoError(t, err) cm := environment.Resources.GetConfigMap(func(_ *corev1.ConfigMap) bool { return true }) assert.NotNil(t, cm) - assert.Equal(t, "it-kamelet-timer-000", cm.Name) + assert.Equal(t, "kamelets-bundle-it", cm.Name) assert.Equal(t, "test", cm.Namespace) assert.Len(t, environment.Integration.Status.GeneratedSources, 1) @@ -480,7 +480,7 @@ func TestKameletConditionFalse(t *testing.T) { assert.Equal(t, corev1.ConditionFalse, cond.Status) assert.Equal(t, v1.IntegrationConditionKameletsAvailableReason, cond.Reason) assert.Contains(t, cond.Message, "[timer] found") - assert.Contains(t, cond.Message, "[none] not found") + assert.Contains(t, cond.Message, "kamelets [none] not found") } func TestKameletConditionTrue(t *testing.T) { diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go index 7114f56670..8543e138eb 100644 --- a/pkg/trait/trait_types.go +++ b/pkg/trait/trait_types.go @@ -399,6 +399,7 @@ func (e *Environment) computeApplicationProperties() (*corev1.ConfigMap, error) Labels: map[string]string{ v1.IntegrationLabel: e.Integration.Name, "camel.apache.org/properties.type": "application", + kubernetes.ConfigMapTypeLabel: "camel-properties", }, }, Data: map[string]string{ @@ -416,7 +417,8 @@ func (e *Environment) addSourcesProperties() { } idx := 0 for _, s := range e.Integration.Sources() { - if e.isEmbedded(s) { + // We don't process routes embedded (native) or Kamelets + if e.isEmbedded(s) || s.IsGeneratedFromKamelet() { continue } srcName := strings.TrimPrefix(filepath.ToSlash(s.Name), "/") @@ -461,14 +463,14 @@ func (e *Environment) addSourcesProperties() { } func (e *Environment) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]corev1.VolumeMount) { - // - // Volumes :: Sources - // + // Sources idx := 0 for _, s := range e.Integration.Sources() { - if e.isEmbedded(s) { + // We don't process routes embedded (native) or Kamelets + if e.isEmbedded(s) || s.IsGeneratedFromKamelet() { continue } + // Routes are copied under /etc/camel/sources and discovered by the runtime accordingly cmName := fmt.Sprintf("%s-source-%03d", e.Integration.Name, idx) if s.ContentRef != "" { cmName = s.ContentRef @@ -487,24 +489,38 @@ func (e *Environment) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]c *mnts = append(*mnts, *mnt) idx++ } - + // Resources (likely application properties or kamelets) if e.Resources != nil { e.Resources.VisitConfigMap(func(configMap *corev1.ConfigMap) { - propertiesType := configMap.Labels["camel.apache.org/properties.type"] - resName := propertiesType + ".properties" - - var mountPath string - switch propertiesType { - case "application": - mountPath = filepath.Join(camel.BasePath, resName) - case "user": - mountPath = filepath.Join(camel.ConfDPath, resName) - } - - if propertiesType != "" { - refName := propertiesType + "-properties" - vol := getVolume(refName, "configmap", configMap.Name, "application.properties", resName) - mnt := getMount(refName, mountPath, resName, true) + // Camel properties + if configMap.Labels[kubernetes.ConfigMapTypeLabel] == "camel-properties" { + propertiesType := configMap.Labels["camel.apache.org/properties.type"] + resName := propertiesType + ".properties" + + var mountPath string + switch propertiesType { + case "application": + mountPath = filepath.Join(camel.BasePath, resName) + case "user": + mountPath = filepath.Join(camel.ConfDPath, resName) + } + + if propertiesType != "" { + refName := propertiesType + "-properties" + vol := getVolume(refName, "configmap", configMap.Name, "application.properties", resName) + mnt := getMount(refName, mountPath, resName, true) + + *vols = append(*vols, *vol) + *mnts = append(*mnts, *mnt) + } else { + log.WithValues("Function", "trait.configureVolumesAndMounts").Infof("Warning: could not determine camel properties type %s", propertiesType) + } + } else if configMap.Labels[kubernetes.ConfigMapTypeLabel] == "kamelets-bundle" { + // Kamelets bundle configmap + kameletMountPoint := strings.ReplaceAll(e.ApplicationProperties[KameletLocationProperty], "file:", "") + refName := "kamelets-bundle" + vol := getVolume(refName, "configmap", configMap.Name, "", "") + mnt := getMount(refName, kameletMountPoint, "", true) *vols = append(*vols, *vol) *mnts = append(*mnts, *mnt) @@ -512,9 +528,8 @@ func (e *Environment) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]c }) } - // - // Volumes :: Additional ConfigMaps - // + // Deprecated - should use mount trait + // User provided configmaps for _, configmaps := range e.collectConfigurations("configmap") { refName := kubernetes.SanitizeLabel(configmaps["value"]) mountPath := getMountPoint(configmaps["value"], configmaps["resourceMountPoint"], "configmap", configmaps["resourceType"]) @@ -525,9 +540,8 @@ func (e *Environment) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]c *mnts = append(*mnts, *mnt) } - // - // Volumes :: Additional Secrets - // + // Deprecated - should use mount trait + // User provided secrets for _, secret := range e.collectConfigurations("secret") { refName := kubernetes.SanitizeLabel(secret["value"]) mountPath := getMountPoint(secret["value"], secret["resourceMountPoint"], "secret", secret["resourceType"]) @@ -548,10 +562,8 @@ func (e *Environment) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]c *vols = append(*vols, *vol) *mnts = append(*mnts, *mnt) } - - // - // Volumes :: Additional user provided volumes - // + // Deprecated - should use mount trait + // User provided volumes for _, volumeConfig := range e.collectConfigurationValues("volume") { configParts := strings.Split(volumeConfig, ":") diff --git a/pkg/util/kubernetes/factory.go b/pkg/util/kubernetes/factory.go index 1915c76d94..5bf26ce129 100644 --- a/pkg/util/kubernetes/factory.go +++ b/pkg/util/kubernetes/factory.go @@ -39,6 +39,9 @@ const ConfigMapAutogenLabel = "camel.apache.org/generated" // ConfigMapOriginalFileNameLabel -- . const ConfigMapOriginalFileNameLabel = "camel.apache.org/filename" +// ConfigMapTypeLabel -- . +const ConfigMapTypeLabel = "camel.apache.org/config.type" + // NewTolerations build an array of Tolerations from an array of string. func NewTolerations(taints []string) ([]corev1.Toleration, error) { tolerations := make([]corev1.Toleration, 0) diff --git a/resources/traits.yaml b/resources/traits.yaml index ca60f9824c..7df1c036e1 100755 --- a/resources/traits.yaml +++ b/resources/traits.yaml @@ -916,6 +916,10 @@ traits: - name: list type: string description: Comma separated list of Kamelet names to load into the current integration + - name: mount-point + type: string + description: The directory where the application mounts and reads Kamelet spec + (default `/etc/camel/kamelets`) - name: keda platform: false profiles: