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 9f0c5ec4370ad61751b18a2d05886f617601f2cf
Author: Nicola Ferraro <[email protected]>
AuthorDate: Wed Jun 24 10:37:39 2020 +0200

    kamelets: initial scaffolding of kamelets trait
---
 pkg/apis/camel/v1/integration_types.go           |   9 ++
 pkg/apis/camel/v1alpha1/kamelet_types.go         |   4 +-
 pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go |  34 +++-
 pkg/trait/kamelets.go                            | 195 +++++++++++++++++++++++
 pkg/trait/kamelets_test.go                       |  84 ++++++++++
 pkg/trait/trait_register.go                      |   1 +
 pkg/util/digest/digest.go                        |  44 +++++
 7 files changed, 362 insertions(+), 9 deletions(-)

diff --git a/pkg/apis/camel/v1/integration_types.go 
b/pkg/apis/camel/v1/integration_types.go
index 1f473d5..7e985a2 100644
--- a/pkg/apis/camel/v1/integration_types.go
+++ b/pkg/apis/camel/v1/integration_types.go
@@ -125,8 +125,17 @@ type SourceSpec struct {
        // Interceptors are optional identifiers the 
org.apache.camel.k.RoutesLoader
        // uses to pre/post process sources
        Interceptors []string `json:"interceptors,omitempty"`
+       // Type defines the kind of source described by this object
+       Type SourceType `json:"type,omitempty"`
 }
 
+type SourceType string
+
+const (
+       SourceTypeDefault SourceType = ""
+       SourceTypeKamelet SourceType = "kamelet"
+)
+
 // Language --
 type Language string
 
diff --git a/pkg/apis/camel/v1alpha1/kamelet_types.go 
b/pkg/apis/camel/v1alpha1/kamelet_types.go
index 0855489..fa53584 100644
--- a/pkg/apis/camel/v1alpha1/kamelet_types.go
+++ b/pkg/apis/camel/v1alpha1/kamelet_types.go
@@ -30,8 +30,8 @@ const (
 // KameletSpec defines the desired state of Kamelet
 type KameletSpec struct {
        Definition    JSONSchemaProps             `json:"definition,omitempty"`
-       Sources       *camelv1.SourceSpec         `json:"sources,omitempty"`
-       Flow          *camelv1.Flow               `json:"flow,omitempty"`
+       Sources       []camelv1.SourceSpec        `json:"sources,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 b81d3ca..2f7512c 100644
--- a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
@@ -26,6 +26,27 @@ func (in *AuthorizationSpec) DeepCopy() *AuthorizationSpec {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *EventTypeSpec) DeepCopyInto(out *EventTypeSpec) {
+       *out = *in
+       if in.Schema != nil {
+               in, out := &in.Schema, &out.Schema
+               *out = new(JSONSchemaProps)
+               (*in).DeepCopyInto(*out)
+       }
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new EventTypeSpec.
+func (in *EventTypeSpec) DeepCopy() *EventTypeSpec {
+       if in == nil {
+               return nil
+       }
+       out := new(EventTypeSpec)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *ExternalDocumentation) DeepCopyInto(out *ExternalDocumentation) {
        *out = *in
        return
@@ -448,17 +469,16 @@ func (in *KameletSpec) DeepCopyInto(out *KameletSpec) {
        in.Definition.DeepCopyInto(&out.Definition)
        if in.Sources != nil {
                in, out := &in.Sources, &out.Sources
-               *out = new(v1.SourceSpec)
-               (*in).DeepCopyInto(*out)
-       }
-       if in.Flow != nil {
-               in, out := &in.Flow, &out.Flow
-               *out = (*in).DeepCopy()
+               *out = make([]v1.SourceSpec, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
        }
+       in.Flow.DeepCopyInto(&out.Flow)
        out.Authorization = in.Authorization
        if in.Types != nil {
                in, out := &in.Types, &out.Types
-               *out = make(map[string]JSONSchemaProps, len(*in))
+               *out = make(map[EventSlot]EventTypeSpec, len(*in))
                for key, val := range *in {
                        (*out)[key] = *val.DeepCopy()
                }
diff --git a/pkg/trait/kamelets.go b/pkg/trait/kamelets.go
new file mode 100644
index 0000000..4ba9d67
--- /dev/null
+++ b/pkg/trait/kamelets.go
@@ -0,0 +1,195 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package trait
+
+import (
+       "fmt"
+       v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+       "github.com/apache/camel-k/pkg/metadata"
+       "github.com/apache/camel-k/pkg/util"
+       "github.com/apache/camel-k/pkg/util/digest"
+       "github.com/apache/camel-k/pkg/util/flows"
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "regexp"
+       "sort"
+       "strconv"
+       "strings"
+)
+
+// The kamelets trait is a platform trait used to inject Kamelets into the 
integration runtime.
+//
+// +camel-k:trait=kamelets
+type kameletsTrait struct {
+       BaseTrait `property:",squash"`
+       // Automatically inject all referenced Kamelets and their default 
configuration (enabled by default)
+       Auto *bool `property:"auto"`
+       // Comma separated list of Kamelet names to load into the current 
integration
+       List string `property:"list"`
+}
+
+var (
+       kameletNameRegexp = 
regexp.MustCompile("kamelet:(?://)?([a-z0-9-.]+)(?:$|[^a-z0-9-.].*)")
+)
+
+func newKameletsTrait() Trait {
+       return &kameletsTrait{
+               BaseTrait: NewBaseTrait("kamelets", 450),
+       }
+}
+
+func (t *kameletsTrait) Configure(e *Environment) (bool, error) {
+       if t.Enabled != nil && !*t.Enabled {
+               return false, nil
+       }
+
+       if t.Auto == nil || *t.Auto {
+               if t.List == "" {
+                       var kamelets []string
+                       metadata.Each(e.CamelCatalog, e.Integration.Sources(), 
func(_ int, meta metadata.IntegrationMetadata) bool {
+                               util.StringSliceUniqueConcat(&kamelets, 
extractKamelets(meta.FromURIs))
+                               util.StringSliceUniqueConcat(&kamelets, 
extractKamelets(meta.ToURIs))
+                               return true
+                       })
+                       sort.Strings(kamelets)
+                       t.List = strings.Join(kamelets, ",")
+               }
+
+       }
+
+       return t.List != "", nil
+}
+
+func (t *kameletsTrait) Apply(e *Environment) error {
+
+       return nil
+}
+
+// IsPlatformTrait overrides base class method
+func (t *kameletsTrait) IsPlatformTrait() bool {
+       return true
+}
+
+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
+       }
+       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))
+               if err != nil {
+                       return err
+               }
+               sources = append(sources, intSource)
+       }
+       return nil
+}
+
+func (t *kameletsTrait) getKamelets() []string {
+       answer := make([]string, 0)
+       for _, item := range strings.Split(t.List, ",") {
+               i := strings.Trim(item, " \t\"")
+               if i != "" {
+                       answer = append(answer, i)
+               }
+       }
+       return answer
+}
+
+func integrationSourceFromKameletSource(e *Environment, kamelet 
*v1alpha1.Kamelet, source v1.SourceSpec, name string) (v1.SourceSpec, error) {
+       if source.DataSpec.ContentRef != "" {
+               return renameSource(kamelet, source), nil
+       }
+
+       // Create configmaps to avoid storing kamelet definitions in the 
integration CR
+
+       // Compute the input digest and store it along with the configmap
+       hash, err := digest.ComputeForSource(source)
+       if err != nil {
+               return v1.SourceSpec{}, err
+       }
+
+       cm := corev1.ConfigMap{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "ConfigMap",
+                       APIVersion: "v1",
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      name,
+                       Namespace: e.Integration.Namespace,
+                       Labels: map[string]string{
+                               "camel.apache.org/integration": 
e.Integration.Name,
+                               "camel.apache.org/kamelet":     kamelet.Name,
+                       },
+                       Annotations: map[string]string{
+                               "camel.apache.org/source.language":    
string(source.Language),
+                               "camel.apache.org/source.name":        name,
+                               "camel.apache.org/source.compression": 
strconv.FormatBool(source.Compression),
+                               "camel.apache.org/source.generated":   "true",
+                               "camel.apache.org/source.type":        
string(source.Type),
+                               "camel.apache.org/source.digest":      hash,
+                       },
+               },
+               Data: map[string]string{
+                       "content": source.Content,
+               },
+       }
+
+       e.Resources.Add(&cm)
+
+       target := renameSource(kamelet, source)
+       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
+}
+
+func extractKamelets(uris []string) (kamelets []string) {
+       for _, uri := range uris {
+               matches := kameletNameRegexp.FindStringSubmatch(uri)
+               if len(matches) == 2 {
+                       kamelets = append(kamelets, matches[1])
+               }
+       }
+       return
+}
diff --git a/pkg/trait/kamelets_test.go b/pkg/trait/kamelets_test.go
new file mode 100644
index 0000000..b7d4fe7
--- /dev/null
+++ b/pkg/trait/kamelets_test.go
@@ -0,0 +1,84 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package trait
+
+import (
+       "context"
+       "testing"
+
+       "github.com/apache/camel-k/pkg/util/camel"
+
+       "github.com/stretchr/testify/assert"
+
+       v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/pkg/util/kubernetes"
+       "github.com/apache/camel-k/pkg/util/test"
+)
+
+func TestKameletsFinding(t *testing.T) {
+       trait, environment := createKameletsTestEnvironment(`
+- from:
+    uri: kamelet:c1
+    steps:
+    - to: kamelet:c2
+    - to: telegram:bots
+    - to: kamelet://c0?prop=x
+    - to: kamelet://complex-.-.-1a?prop=x&prop2
+    - to: kamelet://complex-.-.-1b
+    - to: kamelet:complex-.-.-1b
+    - to: kamelet://complex-.-.-1b/a
+    - to: kamelet://complex-.-.-1c/b
+`)
+       enabled, err := trait.Configure(environment)
+       assert.NoError(t, err)
+       assert.True(t, enabled)
+       assert.Equal(t, []string{"c0", "c1", "c2", "complex-.-.-1a", 
"complex-.-.-1b", "complex-.-.-1c"}, trait.getKamelets())
+}
+
+func createKameletsTestEnvironment(flow string) (*kameletsTrait, *Environment) 
{
+       catalog, _ := camel.DefaultCatalog()
+
+       client, _ := test.NewFakeClient()
+       trait := newKameletsTrait().(*kameletsTrait)
+       trait.Ctx = context.TODO()
+       trait.Client = client
+
+       environment := &Environment{
+               Catalog:      NewCatalog(context.TODO(), nil),
+               CamelCatalog: catalog,
+               Integration: &v1.Integration{
+                       Spec: v1.IntegrationSpec{
+                               Sources: []v1.SourceSpec{
+                                       {
+                                               DataSpec: v1.DataSpec{
+                                                       Name:    "flow.yaml",
+                                                       Content: flow,
+                                               },
+                                               Language: v1.LanguageYaml,
+                                       },
+                               },
+                       },
+                       Status: v1.IntegrationStatus{
+                               Phase: v1.IntegrationPhaseInitialization,
+                       },
+               },
+               Resources: kubernetes.NewCollection(),
+       }
+
+       return trait, environment
+}
diff --git a/pkg/trait/trait_register.go b/pkg/trait/trait_register.go
index 993efdf..30c4356 100644
--- a/pkg/trait/trait_register.go
+++ b/pkg/trait/trait_register.go
@@ -25,6 +25,7 @@ func init() {
        AddToTraits(newCamelTrait)
        AddToTraits(newOpenAPITrait)
        AddToTraits(newKnativeTrait)
+       AddToTraits(newKameletsTrait)
        AddToTraits(newDependenciesTrait)
        AddToTraits(newBuilderTrait)
        AddToTraits(newQuarkusTrait)
diff --git a/pkg/util/digest/digest.go b/pkg/util/digest/digest.go
index 097d08a..6ccd3ed 100644
--- a/pkg/util/digest/digest.go
+++ b/pkg/util/digest/digest.go
@@ -184,6 +184,50 @@ func ComputeForResource(res v1.ResourceSpec) (string, 
error) {
        return digest, nil
 }
 
+// ComputeForSource returns a digest for the specific source
+func ComputeForSource(s v1.SourceSpec) (string, error) {
+       hash := sha256.New()
+       // Operator version is relevant
+       if _, err := hash.Write([]byte(defaults.Version)); err != nil {
+               return "", err
+       }
+
+       if _, err := hash.Write([]byte(s.Content)); err != nil {
+               return "", err
+       }
+       if _, err := hash.Write([]byte(s.Name)); err != nil {
+               return "", err
+       }
+       if _, err := hash.Write([]byte(s.Type)); err != nil {
+               return "", err
+       }
+       if _, err := hash.Write([]byte(s.Language)); err != nil {
+               return "", err
+       }
+       if _, err := hash.Write([]byte(s.ContentKey)); err != nil {
+               return "", err
+       }
+       if _, err := hash.Write([]byte(s.ContentRef)); err != nil {
+               return "", err
+       }
+       if _, err := hash.Write([]byte(s.Loader)); err != nil {
+               return "", err
+       }
+       for _, i := range s.Interceptors {
+               if _, err := hash.Write([]byte(i)); err != nil {
+                       return "", err
+               }
+       }
+
+       if _, err := hash.Write([]byte(strconv.FormatBool(s.Compression))); err 
!= nil {
+               return "", err
+       }
+
+       // Add a letter at the beginning and use URL safe encoding
+       digest := "v" + base64.RawURLEncoding.EncodeToString(hash.Sum(nil))
+       return digest, nil
+}
+
 func sortedTraitSpecMapKeys(m map[string]v1.TraitSpec) []string {
        res := make([]string, len(m))
        i := 0

Reply via email to