Repository: apache/camel-k
Pull request #1988 (2021-02-02 13:59:17)

Backport fixes for #1928, #1939 and #1986 in 1.3.x
<!-- Description -->




<!--
Enter your extended release note in the below block. If the PR requires
additional action from users switching to the new release, include the string
"action required". If no release note is required, write "NONE". 

You can (optionally) mark this PR with labels "kind/bug" or "kind/feature" to make sure
the text is added to the right section of the release notes. 
-->

**Release Note**
```release-note
NONE
```



Patch:
diff --git a/README.adoc b/README.adoc
index 5b570e366..a15f4cbe9 100644
--- a/README.adoc
+++ b/README.adoc
@@ -101,7 +101,7 @@ camel-k-groovy-builder   0/1   Completed   0     75s
 ...
 ```
 
-After a couple of minutes you can see your integration runnning. To see the output log of your integration container get the pod id:
+After a couple of minutes you can see your integration running. To see the output log of your integration container get the pod id:
 
 ```
 kubectl get pods | grep sample
diff --git a/pkg/apis/camel/v1alpha1/kamelet_types.go b/pkg/apis/camel/v1alpha1/kamelet_types.go
index 7a1cef779..987593d2d 100644
--- a/pkg/apis/camel/v1alpha1/kamelet_types.go
+++ b/pkg/apis/camel/v1alpha1/kamelet_types.go
@@ -29,6 +29,7 @@ const (
 
 var (
 	reservedKameletNames = map[string]bool{"source": true, "sink": true}
+	KameletIDProperty    = "id"
 )
 
 // KameletSpec defines the desired state of Kamelet
@@ -94,8 +95,10 @@ const (
 )
 
 const (
-	// KameletConditionIllegalName --
-	KameletConditionIllegalName string = "IllegalName"
+	// KameletConditionReasonInvalidName --
+	KameletConditionReasonInvalidName string = "InvalidName"
+	// KameletConditionReasonInvalidProperty --
+	KameletConditionReasonInvalidProperty string = "InvalidProperty"
 )
 
 type KameletPhase string
diff --git a/pkg/apis/camel/v1alpha1/kamelet_types_support.go b/pkg/apis/camel/v1alpha1/kamelet_types_support.go
index 2aa2c857d..9e6f54026 100644
--- a/pkg/apis/camel/v1alpha1/kamelet_types_support.go
+++ b/pkg/apis/camel/v1alpha1/kamelet_types_support.go
@@ -140,3 +140,13 @@ func (in *KameletStatus) RemoveCondition(condType KameletConditionType) {
 func ValidKameletName(name string) bool {
 	return !reservedKameletNames[name]
 }
+
+func ValidKameletProperties(kamelet *Kamelet) bool {
+	if kamelet == nil || kamelet.Spec.Definition == nil || kamelet.Spec.Definition.Properties == nil {
+		return true
+	}
+	if _, idPresent := kamelet.Spec.Definition.Properties[KameletIDProperty]; idPresent {
+		return false
+	}
+	return true
+}
diff --git a/pkg/controller/kameletbinding/common.go b/pkg/controller/kameletbinding/common.go
index a50b2023f..1fc8dc67b 100644
--- a/pkg/controller/kameletbinding/common.go
+++ b/pkg/controller/kameletbinding/common.go
@@ -20,6 +20,8 @@ package kameletbinding
 import (
 	"context"
 	"encoding/json"
+	"fmt"
+	"sort"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
@@ -90,6 +92,23 @@ func createIntegrationFor(ctx context.Context, c client.Client, kameletbinding *
 		}
 	}
 
+	if len(from.ApplicationProperties) > 0 || len(to.ApplicationProperties) > 0 {
+		propList := make([]string, 0, len(from.ApplicationProperties)+len(to.ApplicationProperties))
+		for k, v := range from.ApplicationProperties {
+			propList = append(propList, fmt.Sprintf("%s=%s", k, v))
+		}
+		for k, v := range to.ApplicationProperties {
+			propList = append(propList, fmt.Sprintf("%s=%s", k, v))
+		}
+		sort.Strings(propList)
+		for _, p := range propList {
+			it.Spec.Configuration = append(it.Spec.Configuration, v1.ConfigurationSpec{
+				Type:  "property",
+				Value: p,
+			})
+		}
+	}
+
 	flow := map[string]interface{}{
 		"from": map[string]interface{}{
 			"uri": from.URI,
diff --git a/pkg/kamelet/initialize.go b/pkg/kamelet/initialize.go
index 755c9fc8e..7fd437378 100644
--- a/pkg/kamelet/initialize.go
+++ b/pkg/kamelet/initialize.go
@@ -30,14 +30,28 @@ import (
 func Initialize(kamelet *v1alpha1.Kamelet) (*v1alpha1.Kamelet, error) {
 	target := kamelet.DeepCopy()
 
+	ok := true
 	if !v1alpha1.ValidKameletName(kamelet.Name) {
-		target.Status.Phase = v1alpha1.KameletPhaseError
+		ok = false
 		target.Status.SetCondition(
 			v1alpha1.KameletConditionReady,
 			corev1.ConditionFalse,
-			v1alpha1.KameletConditionIllegalName,
+			v1alpha1.KameletConditionReasonInvalidName,
 			fmt.Sprintf("Kamelet name %q is reserved", kamelet.Name),
 		)
+	}
+	if !v1alpha1.ValidKameletProperties(kamelet) {
+		ok = false
+		target.Status.SetCondition(
+			v1alpha1.KameletConditionReady,
+			corev1.ConditionFalse,
+			v1alpha1.KameletConditionReasonInvalidProperty,
+			fmt.Sprintf("Kamelet property %q is reserved and cannot be part of the schema", v1alpha1.KameletIDProperty),
+		)
+	}
+
+	if !ok {
+		target.Status.Phase = v1alpha1.KameletPhaseError
 	} else {
 		target.Status.Phase = v1alpha1.KameletPhaseReady
 		target.Status.SetCondition(
diff --git a/pkg/util/bindings/api.go b/pkg/util/bindings/api.go
index 05ea12328..f5debd3ae 100644
--- a/pkg/util/bindings/api.go
+++ b/pkg/util/bindings/api.go
@@ -38,6 +38,8 @@ type Binding struct {
 	URI string
 	// Traits is a partial trait specification that should be merged into the integration
 	Traits map[string]v1.TraitSpec
+	// ApplicationProperties contain properties that should be set on the integration for the binding to work
+	ApplicationProperties map[string]string
 }
 
 // BindingProvider maps a KameletBinding endpoint into Camel K resources
diff --git a/pkg/util/bindings/kamelet.go b/pkg/util/bindings/kamelet.go
index 48fc456ab..ca832520a 100644
--- a/pkg/util/bindings/kamelet.go
+++ b/pkg/util/bindings/kamelet.go
@@ -22,7 +22,6 @@ import (
 	"net/url"
 
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-	"github.com/apache/camel-k/pkg/util/uri"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 )
 
@@ -44,16 +43,35 @@ func (k KameletBindingProvider) Translate(ctx BindingContext, endpointType v1alp
 	}
 	// it translates only Kamelet refs
 	if e.Ref.Kind == v1alpha1.KameletKind && gv.Group == v1alpha1.SchemeGroupVersion.Group {
-		kameletURI := fmt.Sprintf("kamelet:%s", url.PathEscape(e.Ref.Name))
+		kameletName := url.PathEscape(e.Ref.Name)
+		kameletURI := fmt.Sprintf("kamelet:%s", kameletName)
 
 		props, err := e.Properties.GetPropertyMap()
 		if err != nil {
 			return nil, err
 		}
-		kameletURI = uri.AppendParameters(kameletURI, props)
+
+		id, idPresent := props[v1alpha1.KameletIDProperty]
+		if idPresent {
+			delete(props, v1alpha1.KameletIDProperty)
+		} else {
+			// Let's use literal "source" or "sink" as ID for the Kamelet
+			id = string(endpointType)
+		}
+		kameletURI = fmt.Sprintf("%s/%s", kameletURI, url.PathEscape(id))
+
+		var applicationProperties map[string]string
+		if len(props) > 0 {
+			applicationProperties = make(map[string]string, len(props))
+			for k, v := range props {
+				propKey := fmt.Sprintf("camel.kamelet.%s.%s.%s", kameletName, id, k)
+				applicationProperties[propKey] = v
+			}
+		}
 
 		return &Binding{
-			URI: kameletURI,
+			URI:                   kameletURI,
+			ApplicationProperties: applicationProperties,
 		}, nil
 	}
 	return nil, nil
diff --git a/pkg/util/camel/camel_runtime_catalog.go b/pkg/util/camel/camel_runtime_catalog.go
index 2482effe3..dc71a4094 100644
--- a/pkg/util/camel/camel_runtime_catalog.go
+++ b/pkg/util/camel/camel_runtime_catalog.go
@@ -36,6 +36,15 @@ func NewRuntimeCatalog(spec v1.CamelCatalogSpec) *RuntimeCatalog {
 	for id, artifact := range catalog.Artifacts {
 		for _, scheme := range artifact.Schemes {
 			scheme := scheme
+
+			// In case of duplicate only, choose the "org.apache.camel.quarkus" artifact (if present).
+			// Workaround for https://github.com/apache/camel-k-runtime/issues/592
+			if _, duplicate := catalog.artifactByScheme[scheme.ID]; duplicate {
+				if artifact.GroupID != "org.apache.camel.quarkus" {
+					continue
+				}
+			}
+
 			catalog.artifactByScheme[scheme.ID] = id
 			catalog.schemesByID[scheme.ID] = scheme
 		}
diff --git a/script/gen_release_notes.sh b/script/gen_release_notes.sh
index 4caea9fed..070e08d5b 100755
--- a/script/gen_release_notes.sh
+++ b/script/gen_release_notes.sh
@@ -23,8 +23,8 @@ if [ "$#" -ne 2 ]; then
 fi
 
 location=$(dirname $0)
-last_tag=$1
-new_tag=$2
+last_tag=v$1
+new_tag=v$2
 
 echo "Generating release notes for version $new_tag starting from tag $last_tag"
 


Test patch:
diff --git a/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature
new file mode 100644
index 000000000..ddba9f0af
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature
@@ -0,0 +1,23 @@
+Feature: Camel K can load specific secrets for Kamelets
+
+  Background:
+    Given Disable auto removal of Kamelet resources
+    Given Disable auto removal of Kubernetes resources
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 20   |
+      | delayBetweenAttempts | 1000 |
+
+  Scenario: KameletBinding can load specific settings for Kamelet
+    Given create Kubernetes service stub-service-2 with target port 8081
+    And bind Kamelet timer-source to uri http://stub-service-2.${YAKS_NAMESPACE}.svc.cluster.local/specific
+    And KameletBinding source properties
+      | id  | specific |
+    When create KameletBinding binding-specific
+    Then KameletBinding binding-specific should be available
+
+ Scenario: Verify specific binding
+    Given HTTP server "stub-service-2"
+    And HTTP server timeout is 60000 ms
+    Then expect HTTP request body: specific
+    And receive POST /specific
+    And delete KameletBinding binding-specific
diff --git a/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature
new file mode 100644
index 000000000..c92a12684
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature
@@ -0,0 +1,21 @@
+Feature: Camel K can load default secrets for Kamelets
+
+  Background:
+    Given Disable auto removal of Kamelet resources
+    Given Disable auto removal of Kubernetes resources
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 20   |
+      | delayBetweenAttempts | 1000 |
+
+  Scenario: KameletBinding can load default settings for Kamelet
+    Given create Kubernetes service stub-service with target port 8080
+    And bind Kamelet timer-source to uri http://stub-service.${YAKS_NAMESPACE}.svc.cluster.local/default
+    When create KameletBinding binding
+    Then KameletBinding binding should be available
+
+ Scenario: Verify default binding
+    Given HTTP server "stub-service"
+    And HTTP server timeout is 60000 ms
+    Then expect HTTP request body: default
+    And receive POST /default
+    And delete KameletBinding binding
diff --git a/e2e/yaks/common/kamelet-binding-autoload/secret-default.yaml b/e2e/yaks/common/kamelet-binding-autoload/secret-default.yaml
new file mode 100644
index 000000000..edb12cee1
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-autoload/secret-default.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: secret-default
+  labels:
+    camel.apache.org/kamelet: timer-source
+type: Opaque
+data:
+  # camel.kamelet.timer-source.message=default
+  application.properties: Y2FtZWwua2FtZWxldC50aW1lci1zb3VyY2UubWVzc2FnZT1kZWZhdWx0Cg==
diff --git a/e2e/yaks/common/kamelet-binding-autoload/secret-specific.yaml b/e2e/yaks/common/kamelet-binding-autoload/secret-specific.yaml
new file mode 100644
index 000000000..b5cbfcba3
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-autoload/secret-specific.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: secret-specific
+  labels:
+    camel.apache.org/kamelet: timer-source
+    camel.apache.org/kamelet.configuration: specific
+type: Opaque
+data:
+  # camel.kamelet.timer-source.specific.message=specific
+  application.properties: Y2FtZWwua2FtZWxldC50aW1lci1zb3VyY2Uuc3BlY2lmaWMubWVzc2FnZT1zcGVjaWZpYwo=
diff --git a/e2e/yaks/common/kamelet-binding-autoload/timer-source.kamelet.yaml b/e2e/yaks/common/kamelet-binding-autoload/timer-source.kamelet.yaml
new file mode 100644
index 000000000..eb7433433
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-autoload/timer-source.kamelet.yaml
@@ -0,0 +1,48 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+apiVersion: camel.apache.org/v1alpha1
+kind: Kamelet
+metadata:
+  name: timer-source
+  labels:
+    camel.apache.org/kamelet.type: "source"
+spec:
+  definition:
+    title: "Timer"
+    description: "Produces periodic events with a custom payload"
+    required:
+      - message
+    properties:
+      period:
+        title: Period
+        description: The time interval between two events
+        type: integer
+        default: 1000
+      message:
+        title: Message
+        description: The message to generate
+        type: string
+  flow:
+    from:
+      uri: timer:tick
+      parameters:
+        period: "{{period}}"
+      steps:
+        - set-body:
+            constant: "{{message}}"
+        - to: "kamelet:sink"
diff --git a/e2e/yaks/common/kamelet-binding-autoload/yaks-config.yaml b/e2e/yaks/common/kamelet-binding-autoload/yaks-config.yaml
new file mode 100644
index 000000000..a71f271a9
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-autoload/yaks-config.yaml
@@ -0,0 +1,29 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+config:
+  namespace:
+    temporary: true
+pre:
+- name: installation
+  run: |
+    kamel install -n $YAKS_NAMESPACE
+
+    kubectl apply -f secret-default.yaml -n $YAKS_NAMESPACE
+    kubectl apply -f secret-specific.yaml -n $YAKS_NAMESPACE
+
+    kubectl apply -f timer-source.kamelet.yaml -n $YAKS_NAMESPACE
diff --git a/pkg/util/bindings/bindings_test.go b/pkg/util/bindings/bindings_test.go
index 72e8ce5bb..084309aa6 100644
--- a/pkg/util/bindings/bindings_test.go
+++ b/pkg/util/bindings/bindings_test.go
@@ -39,6 +39,7 @@ func TestBindings(t *testing.T) {
 		profile      camelv1.TraitProfile
 		uri          string
 		traits       map[string]camelv1.TraitSpec
+		props        map[string]string
 	}{
 		{
 			endpointType: v1alpha1.EndpointTypeSink,
@@ -113,6 +114,7 @@ func TestBindings(t *testing.T) {
 			uri: "knative:event/myeventtype?apiVersion=eventing.knative.dev%2Fv1beta1&kind=Broker",
 		},
 		{
+			endpointType: v1alpha1.EndpointTypeSource,
 			endpoint: v1alpha1.Endpoint{
 				Ref: &corev1.ObjectReference{
 					Kind:       "Kamelet",
@@ -120,9 +122,10 @@ func TestBindings(t *testing.T) {
 					Name:       "mykamelet",
 				},
 			},
-			uri: "kamelet:mykamelet",
+			uri: "kamelet:mykamelet/source",
 		},
 		{
+			endpointType: v1alpha1.EndpointTypeSink,
 			endpoint: v1alpha1.Endpoint{
 				Ref: &corev1.ObjectReference{
 					Kind:       "Kamelet",
@@ -134,7 +137,28 @@ func TestBindings(t *testing.T) {
 					"encodedkey?": "encoded=val",
 				}),
 			},
-			uri: "kamelet:mykamelet?encodedkey%3F=encoded%3Dval&mymessage=myval",
+			uri: "kamelet:mykamelet/sink",
+			props: map[string]string{
+				"camel.kamelet.mykamelet.sink.encodedkey?": "encoded=val",
+				"camel.kamelet.mykamelet.sink.mymessage":   "myval",
+			},
+		},
+		{
+			endpoint: v1alpha1.Endpoint{
+				Ref: &corev1.ObjectReference{
+					Kind:       "Kamelet",
+					APIVersion: "camel.apache.org/v1any1",
+					Name:       "mykamelet",
+				},
+				Properties: asEndpointProperties(map[string]string{
+					"id":        "myid?",
+					"mymessage": "myval",
+				}),
+			},
+			uri: "kamelet:mykamelet/myid%3F",
+			props: map[string]string{
+				"camel.kamelet.mykamelet.myid?.mymessage": "myval",
+			},
 		},
 		{
 			endpointType: v1alpha1.EndpointTypeSink,
@@ -192,6 +216,7 @@ func TestBindings(t *testing.T) {
 			assert.NotNil(t, binding)
 			assert.Equal(t, tc.uri, binding.URI)
 			assert.Equal(t, tc.traits, binding.Traits)
+			assert.Equal(t, tc.props, binding.ApplicationProperties)
 		})
 	}
 }