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:09:59 UTC
[camel-k] 04/21: kamelets: initial scaffolding of 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 9f0c5ec4370ad61751b18a2d05886f617601f2cf
Author: Nicola Ferraro <ni...@gmail.com>
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