You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2020/09/17 09:10:00 UTC

[camel-k] 05/21: kamelets: adding mount paths and implement kamelets trait

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

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

commit 69c2ecc6ad3100514b5c1eef258aaa596d460f89
Author: Nicola Ferraro <ni...@gmail.com>
AuthorDate: Wed Jun 24 12:54:55 2020 +0200

    kamelets: adding mount paths and implement kamelets trait
---
 pkg/apis/camel/v1alpha1/kamelet_types.go         |   2 +-
 pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go |   5 +-
 pkg/trait/kamelets.go                            | 112 +++++++++----
 pkg/trait/kamelets_test.go                       | 197 ++++++++++++++++++++++-
 pkg/trait/trait_test.go                          |   2 +-
 pkg/trait/trait_types.go                         |  17 +-
 6 files changed, 291 insertions(+), 44 deletions(-)

diff --git a/pkg/apis/camel/v1alpha1/kamelet_types.go b/pkg/apis/camel/v1alpha1/kamelet_types.go
index fa53584..a8e67fa 100644
--- a/pkg/apis/camel/v1alpha1/kamelet_types.go
+++ b/pkg/apis/camel/v1alpha1/kamelet_types.go
@@ -31,7 +31,7 @@ const (
 type KameletSpec struct {
 	Definition    JSONSchemaProps             `json:"definition,omitempty"`
 	Sources       []camelv1.SourceSpec        `json:"sources,omitempty"`
-	Flow          camelv1.Flow                `json:"flow,omitempty"`
+	Flow          *camelv1.Flow               `json:"flow,omitempty"`
 	Authorization AuthorizationSpec           `json:"authorization,omitempty"`
 	Types         map[EventSlot]EventTypeSpec `json:"types,omitempty"`
 	Dependencies  []string                    `json:"dependencies,omitempty"`
diff --git a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
index 2f7512c..61e8aa0 100644
--- a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
@@ -474,7 +474,10 @@ func (in *KameletSpec) DeepCopyInto(out *KameletSpec) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	in.Flow.DeepCopyInto(&out.Flow)
+	if in.Flow != nil {
+		in, out := &in.Flow, &out.Flow
+		*out = (*in).DeepCopy()
+	}
 	out.Authorization = in.Authorization
 	if in.Types != nil {
 		in, out := &in.Types, &out.Types
diff --git a/pkg/trait/kamelets.go b/pkg/trait/kamelets.go
index 4ba9d67..180985a 100644
--- a/pkg/trait/kamelets.go
+++ b/pkg/trait/kamelets.go
@@ -28,6 +28,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"regexp"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sort"
 	"strconv"
 	"strings"
@@ -54,6 +55,11 @@ func newKameletsTrait() Trait {
 	}
 }
 
+// IsPlatformTrait overrides base class method
+func (t *kameletsTrait) IsPlatformTrait() bool {
+	return true
+}
+
 func (t *kameletsTrait) Configure(e *Environment) (bool, error) {
 	if t.Enabled != nil && !*t.Enabled {
 		return false, nil
@@ -73,47 +79,85 @@ func (t *kameletsTrait) Configure(e *Environment) (bool, error) {
 
 	}
 
-	return t.List != "", nil
+	return len(t.getKamelets()) > 0, nil
 }
 
 func (t *kameletsTrait) Apply(e *Environment) error {
-
+	if err := t.addKamelets(e); err != nil {
+		return err
+	}
 	return nil
 }
 
-// IsPlatformTrait overrides base class method
-func (t *kameletsTrait) IsPlatformTrait() bool {
-	return true
+func (t *kameletsTrait) addKamelets(e *Environment) error {
+	for _, k := range t.getKamelets() {
+		var kamelet v1alpha1.Kamelet
+		key := client.ObjectKey{
+			Namespace: e.Integration.Namespace,
+			Name:      k,
+		}
+		if err := t.Client.Get(t.Ctx, key, &kamelet); err != nil {
+			return err
+		}
+		if err := t.addKameletAsSource(e, kamelet); err != nil {
+			return err
+		}
+	}
+	return nil
 }
 
-func (t *kameletsTrait) addKameletAsSource(e *Environment, kamelet *v1alpha1.Kamelet) error {
+func (t *kameletsTrait) addKameletAsSource(e *Environment, kamelet v1alpha1.Kamelet) error {
 	var sources []v1.SourceSpec
 
-	flowData, err := flows.Marshal([]v1.Flow{kamelet.Spec.Flow})
-	if err != nil {
-		return err
-	}
-	flowSource := v1.SourceSpec{
-		DataSpec: v1.DataSpec{
-			Name:    "flow.yaml",
-			Content: string(flowData),
-		},
-		Language: v1.LanguageYaml,
-		Type:     v1.SourceTypeKamelet,
-	}
-	flowSource, err = integrationSourceFromKameletSource(e, kamelet, flowSource, fmt.Sprintf("%s-kamelet-%s-flow", e.Integration.Name, kamelet.Name))
-	if err != nil {
-		return err
+	if kamelet.Spec.Flow != nil {
+		flowData, err := flows.Marshal([]v1.Flow{*kamelet.Spec.Flow})
+		if err != nil {
+			return err
+		}
+		flowSource := v1.SourceSpec{
+			DataSpec: v1.DataSpec{
+				Name:    fmt.Sprintf("%s.yaml", kamelet.Name),
+				Content: string(flowData),
+			},
+			Language: v1.LanguageYaml,
+			Type:     v1.SourceTypeKamelet,
+		}
+		flowSource, err = integrationSourceFromKameletSource(e, kamelet, flowSource, fmt.Sprintf("%s-kamelet-%s-flow", e.Integration.Name, kamelet.Name))
+		if err != nil {
+			return err
+		}
+		sources = append(sources, flowSource)
 	}
-	sources = append(sources, flowSource)
 
 	for idx, s := range kamelet.Spec.Sources {
-		intSource, err := integrationSourceFromKameletSource(e, kamelet, s, fmt.Sprintf("%s-kamelet-%s-source-%03d", e.Integration.Name, kamelet.Name, idx))
+		intSource, err := integrationSourceFromKameletSource(e, kamelet, s, fmt.Sprintf("%s-kamelet-%s-%03d", e.Integration.Name, kamelet.Name, idx))
 		if err != nil {
 			return err
 		}
 		sources = append(sources, intSource)
 	}
+
+	kameletCounter := 0
+	for _, source := range sources {
+		if source.Type == v1.SourceTypeKamelet {
+			kameletCounter++
+		}
+		replaced := false
+		for idx, existing := range e.Integration.Status.GeneratedSources {
+			if existing.Name == source.Name {
+				replaced = true
+				e.Integration.Status.GeneratedSources[idx] = source
+			}
+		}
+		if !replaced {
+			e.Integration.Status.GeneratedSources = append(e.Integration.Status.GeneratedSources, source)
+		}
+	}
+
+	if kameletCounter > 1 {
+		return fmt.Errorf(`kamelet %s contains %d sources of type "kamelet": at most one is allowed`, kamelet.Name, kameletCounter)
+	}
+
 	return nil
 }
 
@@ -128,9 +172,15 @@ func (t *kameletsTrait) getKamelets() []string {
 	return answer
 }
 
-func integrationSourceFromKameletSource(e *Environment, kamelet *v1alpha1.Kamelet, source v1.SourceSpec, name string) (v1.SourceSpec, error) {
+func integrationSourceFromKameletSource(e *Environment, kamelet v1alpha1.Kamelet, source v1.SourceSpec, name string) (v1.SourceSpec, error) {
+	if source.Type == v1.SourceTypeKamelet {
+		// Kamelets must be named "<kamelet-name>.extension"
+		language := source.InferLanguage()
+		source.Name = fmt.Sprintf("%s.%s", kamelet.Name, string(language))
+	}
+
 	if source.DataSpec.ContentRef != "" {
-		return renameSource(kamelet, source), nil
+		return source, nil
 	}
 
 	// Create configmaps to avoid storing kamelet definitions in the integration CR
@@ -169,19 +219,11 @@ func integrationSourceFromKameletSource(e *Environment, kamelet *v1alpha1.Kamele
 
 	e.Resources.Add(&cm)
 
-	target := renameSource(kamelet, source)
+	target := source.DeepCopy()
 	target.Content = ""
 	target.ContentRef = name
 	target.ContentKey = "content"
-	return target, nil
-}
-
-func renameSource(kamelet *v1alpha1.Kamelet, source v1.SourceSpec) v1.SourceSpec {
-	target := source.DeepCopy()
-	if !strings.HasPrefix(target.Name, fmt.Sprintf("kamelet-%s-", kamelet.Name)) {
-		target.Name = fmt.Sprintf("kamelet-%s-%s", kamelet.Name, target.Name)
-	}
-	return *target
+	return *target, nil
 }
 
 func extractKamelets(uris []string) (kamelets []string) {
diff --git a/pkg/trait/kamelets_test.go b/pkg/trait/kamelets_test.go
index b7d4fe7..30585c3 100644
--- a/pkg/trait/kamelets_test.go
+++ b/pkg/trait/kamelets_test.go
@@ -19,6 +19,10 @@ package trait
 
 import (
 	"context"
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"testing"
 
 	"github.com/apache/camel-k/pkg/util/camel"
@@ -30,7 +34,20 @@ import (
 	"github.com/apache/camel-k/pkg/util/test"
 )
 
-func TestKameletsFinding(t *testing.T) {
+func TestConfigurationNoKameletsUsed(t *testing.T) {
+	trait, environment := createKameletsTestEnvironment(`
+- from:
+    uri: timer:tick
+    steps:
+    - to: log:info
+`)
+	enabled, err := trait.Configure(environment)
+	assert.NoError(t, err)
+	assert.False(t, enabled)
+	assert.Equal(t, "", trait.List)
+}
+
+func TestConfigurationWithKamelets(t *testing.T) {
 	trait, environment := createKameletsTestEnvironment(`
 - from:
     uri: kamelet:c1
@@ -50,10 +67,180 @@ func TestKameletsFinding(t *testing.T) {
 	assert.Equal(t, []string{"c0", "c1", "c2", "complex-.-.-1a", "complex-.-.-1b", "complex-.-.-1c"}, trait.getKamelets())
 }
 
-func createKameletsTestEnvironment(flow string) (*kameletsTrait, *Environment) {
+func TestKameletLookup(t *testing.T) {
+	trait, environment := createKameletsTestEnvironment(`
+- from:
+    uri: kamelet:timer
+    steps:
+    - to: log:info
+`, &v1alpha1.Kamelet{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "timer",
+		},
+		Spec: v1alpha1.KameletSpec{
+			Flow: &v1.Flow{
+				"from": map[string]interface{}{
+					"uri": "timer:tick",
+				},
+			},
+		},
+	})
+	enabled, err := trait.Configure(environment)
+	assert.NoError(t, err)
+	assert.True(t, enabled)
+	assert.Equal(t, []string{"timer"}, trait.getKamelets())
+
+	err = trait.Apply(environment)
+	assert.NoError(t, err)
+	cm := environment.Resources.GetConfigMap(func(_ *corev1.ConfigMap) bool { return true })
+	assert.NotNil(t, cm)
+	assert.Equal(t, "it-kamelet-timer-flow", cm.Name)
+	assert.Equal(t, "test", cm.Namespace)
+
+	assert.Len(t, environment.Integration.Status.GeneratedSources, 1)
+	source := environment.Integration.Status.GeneratedSources[0]
+	assert.Equal(t, "timer.yaml", source.Name)
+	assert.Equal(t, "kamelet", string(source.Type))
+}
+
+func TestKameletSecondarySourcesLookup(t *testing.T) {
+	trait, environment := createKameletsTestEnvironment(`
+- from:
+    uri: kamelet:timer
+    steps:
+    - to: log:info
+`, &v1alpha1.Kamelet{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "timer",
+		},
+		Spec: v1alpha1.KameletSpec{
+			Flow: &v1.Flow{
+				"from": map[string]interface{}{
+					"uri": "timer:tick",
+				},
+			},
+			Sources: []v1.SourceSpec{
+				{
+					DataSpec: v1.DataSpec{
+						Name:    "support.groovy",
+						Content: "from('xxx:xxx').('to:log:info')",
+					},
+					Language: v1.LanguageGroovy,
+				},
+			},
+		},
+	})
+	enabled, err := trait.Configure(environment)
+	assert.NoError(t, err)
+	assert.True(t, enabled)
+	assert.Equal(t, []string{"timer"}, trait.getKamelets())
+
+	err = trait.Apply(environment)
+	assert.NoError(t, err)
+	cmFlow := environment.Resources.GetConfigMap(func(c *corev1.ConfigMap) bool { return c.Name == "it-kamelet-timer-flow" })
+	assert.NotNil(t, cmFlow)
+	cmRes := environment.Resources.GetConfigMap(func(c *corev1.ConfigMap) bool { return c.Name == "it-kamelet-timer-000" })
+	assert.NotNil(t, cmRes)
+
+	assert.Len(t, environment.Integration.Status.GeneratedSources, 2)
+
+	flowSource := environment.Integration.Status.GeneratedSources[0]
+	assert.Equal(t, "timer.yaml", flowSource.Name)
+	assert.Equal(t, "kamelet", string(flowSource.Type))
+	assert.Equal(t, "it-kamelet-timer-flow", flowSource.ContentRef)
+	assert.Equal(t, "content", flowSource.ContentKey)
+
+	supportSource := environment.Integration.Status.GeneratedSources[1]
+	assert.Equal(t, "support.groovy", supportSource.Name)
+	assert.Equal(t, "", string(supportSource.Type))
+	assert.Equal(t, "it-kamelet-timer-000", supportSource.ContentRef)
+	assert.Equal(t, "content", supportSource.ContentKey)
+}
+
+func TestNonYAMLKameletLookup(t *testing.T) {
+	trait, environment := createKameletsTestEnvironment(`
+- from:
+    uri: kamelet:timer
+    steps:
+    - to: log:info
+`, &v1alpha1.Kamelet{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "timer",
+		},
+		Spec: v1alpha1.KameletSpec{
+			Sources: []v1.SourceSpec{
+				{
+					DataSpec: v1.DataSpec{
+						Name:    "mykamelet.groovy",
+						Content: `from("timer").to("log:info")`,
+					},
+					Type: v1.SourceTypeKamelet,
+				},
+			},
+		},
+	})
+	enabled, err := trait.Configure(environment)
+	assert.NoError(t, err)
+	assert.True(t, enabled)
+	assert.Equal(t, []string{"timer"}, trait.getKamelets())
+
+	err = trait.Apply(environment)
+	assert.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, "test", cm.Namespace)
+
+	assert.Len(t, environment.Integration.Status.GeneratedSources, 1)
+	source := environment.Integration.Status.GeneratedSources[0]
+	assert.Equal(t, "timer.groovy", source.Name)
+	assert.Equal(t, "kamelet", string(source.Type))
+}
+
+func TestErrorMultipleKameletSources(t *testing.T) {
+	trait, environment := createKameletsTestEnvironment(`
+- from:
+    uri: kamelet:timer
+    steps:
+    - to: log:info
+`, &v1alpha1.Kamelet{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "timer",
+		},
+		Spec: v1alpha1.KameletSpec{
+			Sources: []v1.SourceSpec{
+				{
+					DataSpec: v1.DataSpec{
+						Name:    "mykamelet.groovy",
+						Content: `from("timer").to("log:info")`,
+					},
+					Type: v1.SourceTypeKamelet,
+				},
+			},
+			Flow: &v1.Flow{
+				"from": map[string]interface{}{
+					"uri": "timer:tick",
+				},
+			},
+		},
+	})
+	enabled, err := trait.Configure(environment)
+	assert.NoError(t, err)
+	assert.True(t, enabled)
+	assert.Equal(t, []string{"timer"}, trait.getKamelets())
+
+	err = trait.Apply(environment)
+	assert.Error(t, err)
+}
+
+func createKameletsTestEnvironment(flow string, objects ...runtime.Object) (*kameletsTrait, *Environment) {
 	catalog, _ := camel.DefaultCatalog()
 
-	client, _ := test.NewFakeClient()
+	client, _ := test.NewFakeClient(objects...)
 	trait := newKameletsTrait().(*kameletsTrait)
 	trait.Ctx = context.TODO()
 	trait.Client = client
@@ -62,6 +249,10 @@ func createKameletsTestEnvironment(flow string) (*kameletsTrait, *Environment) {
 		Catalog:      NewCatalog(context.TODO(), nil),
 		CamelCatalog: catalog,
 		Integration: &v1.Integration{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "test",
+				Name:      "it",
+			},
 			Spec: v1.IntegrationSpec{
 				Sources: []v1.SourceSpec{
 					{
diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go
index d50bd91..bcf3519 100644
--- a/pkg/trait/trait_test.go
+++ b/pkg/trait/trait_test.go
@@ -382,7 +382,7 @@ func TestOnlySomeTraitsInfluenceBuild(t *testing.T) {
 
 func TestOnlySomeTraitsArePlatform(t *testing.T) {
 	c := NewTraitTestCatalog()
-	platformTraits := []string{"builder", "camel", "jvm", "container", "dependencies", "deployer", "deployment", "environment", "openapi", "owner", "platform"}
+	platformTraits := []string{"builder", "camel", "jvm", "container", "dependencies", "deployer", "deployment", "environment", "kamelets", "openapi", "owner", "platform"}
 
 	for _, trait := range c.allTraits() {
 		if trait.IsPlatformTrait() {
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index babe121..e815469 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -54,6 +54,9 @@ var (
 	// SourcesMountPath --
 	SourcesMountPath = path.Join(BasePath, "sources")
 
+	// KameletsMountPath --
+	KameletsMountPath = path.Join(BasePath, "kamelets.d")
+
 	// ResourcesMountPath --
 	ResourcesMountPath = path.Join(BasePath, "resources")
 
@@ -527,12 +530,20 @@ func (e *Environment) ConfigureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]c
 
 	for i, s := range e.Integration.Sources() {
 		cmName := fmt.Sprintf("%s-source-%03d", e.Integration.Name, i)
+		if s.ContentRef != "" {
+			cmName = s.ContentRef
+		}
+
 		refName := fmt.Sprintf("i-source-%03d", i)
+		if s.Type == v1.SourceTypeKamelet {
+			refName = fmt.Sprintf("i-kamelet-source-%03d", i)
+		}
+
 		resName := strings.TrimPrefix(s.Name, "/")
-		resPath := path.Join(SourcesMountPath, refName)
 
-		if s.ContentRef != "" {
-			cmName = s.ContentRef
+		resPath := path.Join(SourcesMountPath, refName)
+		if s.Type == v1.SourceTypeKamelet {
+			resPath = KameletsMountPath
 		}
 
 		*vols = append(*vols, corev1.Volume{