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 <[email protected]> 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{
