You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by lb...@apache.org on 2022/08/05 13:21:12 UTC

[camel-k] branch main updated (a6f223656 -> e7bc5ecca)

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

lburgazzoli pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git


    from a6f223656 Fix grammar in integration-kit.adoc
     new b1b70e0fb fix: camel-k ignores changes to traits configured using annotations #3479
     new beff4641a fix: resolve pr review findings #3479
     new e7bc5ecca fix: KameletBinding vs Integration traits comparison shouyld be based on the traits configured on the KameletBinding only #3479

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../common/kamelet_binding_with_image_test.go      | 116 +++++++++++
 e2e/support/test_support.go                        |  44 ++++
 .../integration/integration_controller.go          | 203 ++++++++++--------
 pkg/controller/integration/kits.go                 |  69 ++++---
 pkg/controller/integration/kits_test.go            |  10 +-
 .../kameletbinding/kamelet_binding_controller.go   |  15 ++
 pkg/controller/kameletbinding/monitor.go           |  16 +-
 pkg/trait/util.go                                  | 228 ++++++++++++++++++++-
 pkg/trait/util_test.go                             | 177 +++++++++++++++-
 pkg/util/digest/digest.go                          |   4 +
 pkg/util/kubernetes/util.go                        |   3 +-
 11 files changed, 757 insertions(+), 128 deletions(-)
 create mode 100644 e2e/global/common/kamelet_binding_with_image_test.go


[camel-k] 02/03: fix: resolve pr review findings #3479

Posted by lb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit beff4641a7d7f5a99209207886f9b03784ac95bd
Author: Luca Burgazzoli <lb...@gmail.com>
AuthorDate: Wed Aug 3 13:47:26 2022 +0200

    fix: resolve pr review findings #3479
---
 .../common/kamelet_binding_with_image_test.go      | 16 ++++++----------
 e2e/support/test_support.go                        |  6 +++++-
 .../integration/integration_controller.go          |  4 ++--
 pkg/controller/integration/kits.go                 |  2 +-
 pkg/controller/kameletbinding/monitor.go           |  4 ++--
 pkg/trait/util.go                                  | 22 +++++++++++-----------
 pkg/trait/util_test.go                             |  2 +-
 7 files changed, 28 insertions(+), 28 deletions(-)

diff --git a/e2e/global/common/kamelet_binding_with_image_test.go b/e2e/global/common/kamelet_binding_with_image_test.go
index 129089e51..e28132c59 100644
--- a/e2e/global/common/kamelet_binding_with_image_test.go
+++ b/e2e/global/common/kamelet_binding_with_image_test.go
@@ -79,13 +79,11 @@ func TestBindingWithImage(t *testing.T) {
 				To(Succeed())
 			Eventually(IntegrationGeneration(ns, bindingID)).
 				Should(gstruct.PointTo(BeNumerically("==", 1)))
-			Eventually(IntegrationAnnotations(ns, bindingID)).
-				Should(HaveKeyWithValue("test", "1"))
-			Eventually(IntegrationAnnotations(ns, bindingID)).
-				Should(HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage))
+			Eventually(Integration(ns, bindingID)).Should(WithTransform(Annotations, And(
+				HaveKeyWithValue("test", "1"))),
+				HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage))
 			Eventually(IntegrationStatusImage(ns, bindingID)).
 				Should(Equal(expectedImage))
-
 			Eventually(IntegrationPodPhase(ns, bindingID), TestTimeoutLong).
 				Should(Equal(corev1.PodRunning))
 			Eventually(IntegrationPodImage(ns, bindingID)).
@@ -101,13 +99,11 @@ func TestBindingWithImage(t *testing.T) {
 				To(Succeed())
 			Eventually(IntegrationGeneration(ns, bindingID)).
 				Should(gstruct.PointTo(BeNumerically("==", 1)))
-			Eventually(IntegrationAnnotations(ns, bindingID)).
-				Should(HaveKeyWithValue("test", "2"))
-			Eventually(IntegrationAnnotations(ns, bindingID)).
-				Should(HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage))
+			Eventually(Integration(ns, bindingID)).Should(WithTransform(Annotations, And(
+				HaveKeyWithValue("test", "2"))),
+				HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage))
 			Eventually(IntegrationStatusImage(ns, bindingID)).
 				Should(Equal(expectedImage))
-
 			Eventually(IntegrationPodPhase(ns, bindingID), TestTimeoutLong).
 				Should(Equal(corev1.PodRunning))
 			Eventually(IntegrationPodImage(ns, bindingID)).
diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go
index 8cfa8d1fa..509c3b3ff 100644
--- a/e2e/support/test_support.go
+++ b/e2e/support/test_support.go
@@ -555,7 +555,7 @@ func IntegrationGeneration(ns string, name string) func() *int64 {
 	}
 }
 
-func IntegrationStatusObserverGeneration(ns string, name string) func() *int64 {
+func IntegrationObservedGeneration(ns string, name string) func() *int64 {
 	return func() *int64 {
 		it := Integration(ns, name)()
 		if it == nil {
@@ -643,6 +643,10 @@ func AssignIntegrationToOperator(ns, name, operator string) error {
 	return TestClient().Update(TestContext, it)
 }
 
+func Annotations(object metav1.Object) map[string]string {
+	return object.GetAnnotations()
+}
+
 func Lease(ns string, name string) func() *coordination.Lease {
 	return func() *coordination.Lease {
 		lease := coordination.Lease{}
diff --git a/pkg/controller/integration/integration_controller.go b/pkg/controller/integration/integration_controller.go
index 0d0f61d20..bd9a19937 100644
--- a/pkg/controller/integration/integration_controller.go
+++ b/pkg/controller/integration/integration_controller.go
@@ -127,11 +127,11 @@ func integrationKitEnqueueRequestsFromMapFunc(c client.Client, kit *v1.Integrati
 
 	for i := range list.Items {
 		integration := &list.Items[i]
-		log.Debug("Integration Controller: Assessing integration", "integration", integration.Name, "namespace", integration.Namespace)
+		Log.Debug("Integration Controller: Assessing integration", "integration", integration.Name, "namespace", integration.Namespace)
 
 		match, err := sameOrMatch(kit, integration)
 		if err != nil {
-			log.Errorf(err, "Error matching integration %q with kit %q", integration.Name, kit.Name)
+			Log.ForIntegration(integration).Errorf(err, "Error matching integration %q with kit %q", integration.Name, kit.Name)
 			continue
 		}
 		if !match {
diff --git a/pkg/controller/integration/kits.go b/pkg/controller/integration/kits.go
index 72b504e86..1e4bd9b84 100644
--- a/pkg/controller/integration/kits.go
+++ b/pkg/controller/integration/kits.go
@@ -189,7 +189,7 @@ func kitMatches(kit1 *v1.IntegrationKit, kit2 *v1.IntegrationKit) (bool, error)
 	return true, nil
 }
 
-func hasMatchingTraits(traitMap trait.Unstructured, kitTraitMap trait.Unstructured) (bool, error) {
+func hasMatchingTraits(traitMap trait.Options, kitTraitMap trait.Options) (bool, error) {
 	catalog := trait.NewCatalog(nil)
 
 	for _, t := range catalog.AllTraits() {
diff --git a/pkg/controller/kameletbinding/monitor.go b/pkg/controller/kameletbinding/monitor.go
index dc62ea1a9..04a4e546c 100644
--- a/pkg/controller/kameletbinding/monitor.go
+++ b/pkg/controller/kameletbinding/monitor.go
@@ -92,10 +92,10 @@ func (action *monitorAction) Handle(ctx context.Context, kameletbinding *v1alpha
 
 	if !semanticEquality || operatorIDChanged || !sameTraits {
 		action.L.Info(
-			"Monitor: KameletBinding needs a rebuild",
+			"KameletBinding needs a rebuild",
 			"semantic-equality", !semanticEquality,
 			"operatorid-changed", operatorIDChanged,
-			"traites-changed", !sameTraits)
+			"traits-changed", !sameTraits)
 
 		// KameletBinding has changed and needs rebuild
 		target := kameletbinding.DeepCopy()
diff --git a/pkg/trait/util.go b/pkg/trait/util.go
index 2b71cd960..737b54883 100644
--- a/pkg/trait/util.go
+++ b/pkg/trait/util.go
@@ -41,9 +41,9 @@ import (
 	"github.com/apache/camel-k/pkg/util/property"
 )
 
-type Unstructured map[string]map[string]interface{}
+type Options map[string]map[string]interface{}
 
-func (u Unstructured) Get(id string) (map[string]interface{}, bool) {
+func (u Options) Get(id string) (map[string]interface{}, bool) {
 	if t, ok := u[id]; ok {
 		return t, true
 	}
@@ -256,7 +256,7 @@ func AssertTraitsType(traits interface{}) error {
 }
 
 // ToTraitMap accepts either v1.Traits or v1.IntegrationKitTraits and converts it to a map of traits.
-func ToTraitMap(traits interface{}) (Unstructured, error) {
+func ToTraitMap(traits interface{}) (Options, error) {
 	if err := AssertTraitsType(traits); err != nil {
 		return nil, err
 	}
@@ -265,7 +265,7 @@ func ToTraitMap(traits interface{}) (Unstructured, error) {
 	if err != nil {
 		return nil, err
 	}
-	traitMap := make(Unstructured)
+	traitMap := make(Options)
 	if err = json.Unmarshal(data, &traitMap); err != nil {
 		return nil, err
 	}
@@ -341,7 +341,7 @@ func getBuilderTask(tasks []v1.Task) *v1.BuilderTask {
 }
 
 // Equals return if traits are the same.
-func Equals(i1 Unstructured, i2 Unstructured) bool {
+func Equals(i1 Options, i2 Options) bool {
 	return reflect.DeepEqual(i1, i2)
 }
 
@@ -415,7 +415,7 @@ func IntegrationAndKitHaveSameTraits(i1 *v1.Integration, i2 *v1.IntegrationKit)
 	return Equals(c1, c2), nil
 }
 
-func NewUnstructuredTraitsForIntegration(i *v1.Integration) (Unstructured, error) {
+func NewUnstructuredTraitsForIntegration(i *v1.Integration) (Options, error) {
 	m1, err := ToTraitMap(i.Spec.Traits)
 	if err != nil {
 		return nil, err
@@ -433,7 +433,7 @@ func NewUnstructuredTraitsForIntegration(i *v1.Integration) (Unstructured, error
 	return m1, nil
 }
 
-func NewUnstructuredTraitsForIntegrationKit(i *v1.IntegrationKit) (Unstructured, error) {
+func NewUnstructuredTraitsForIntegrationKit(i *v1.IntegrationKit) (Options, error) {
 	m1, err := ToTraitMap(i.Spec.Traits)
 	if err != nil {
 		return nil, err
@@ -451,7 +451,7 @@ func NewUnstructuredTraitsForIntegrationKit(i *v1.IntegrationKit) (Unstructured,
 	return m1, nil
 }
 
-func NewUnstructuredTraitsForIntegrationPlatform(i *v1.IntegrationPlatform) (Unstructured, error) {
+func NewUnstructuredTraitsForIntegrationPlatform(i *v1.IntegrationPlatform) (Options, error) {
 	m1, err := ToTraitMap(i.Spec.Traits)
 	if err != nil {
 		return nil, err
@@ -469,7 +469,7 @@ func NewUnstructuredTraitsForIntegrationPlatform(i *v1.IntegrationPlatform) (Uns
 	return m1, nil
 }
 
-func NewUnstructuredTraitsForKameletBinding(i *v1alpha1.KameletBinding) (Unstructured, error) {
+func NewUnstructuredTraitsForKameletBinding(i *v1alpha1.KameletBinding) (Options, error) {
 	if i.Spec.Integration != nil {
 		m1, err := ToTraitMap(i.Spec.Integration.Traits)
 		if err != nil {
@@ -496,8 +496,8 @@ func NewUnstructuredTraitsForKameletBinding(i *v1alpha1.KameletBinding) (Unstruc
 	return m1, nil
 }
 
-func FromAnnotations(meta *metav1.ObjectMeta) (Unstructured, error) {
-	options := make(Unstructured)
+func FromAnnotations(meta *metav1.ObjectMeta) (Options, error) {
+	options := make(Options)
 
 	for k, v := range meta.Annotations {
 		if strings.HasPrefix(k, v1.TraitAnnotationPrefix) {
diff --git a/pkg/trait/util_test.go b/pkg/trait/util_test.go
index 192943147..7c19d9332 100644
--- a/pkg/trait/util_test.go
+++ b/pkg/trait/util_test.go
@@ -58,7 +58,7 @@ func TestToTraitMap(t *testing.T) {
 			}),
 		},
 	}
-	expected := Unstructured{
+	expected := Options{
 		"container": {
 			"enabled":         true,
 			"auto":            false,


[camel-k] 01/03: fix: camel-k ignores changes to traits configured using annotations #3479

Posted by lb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit b1b70e0fbb04b4c445a0e87b82f2c87c64cf3f6e
Author: Luca Burgazzoli <lb...@gmail.com>
AuthorDate: Sun Jul 24 15:04:37 2022 +0200

    fix: camel-k ignores changes to traits configured using annotations #3479
---
 .../common/kamelet_binding_with_image_test.go      | 120 +++++++++++
 e2e/support/test_support.go                        |  40 ++++
 .../integration/integration_controller.go          | 203 +++++++++++--------
 pkg/controller/integration/kits.go                 |  69 ++++---
 pkg/controller/integration/kits_test.go            |  10 +-
 .../kameletbinding/kamelet_binding_controller.go   |  15 ++
 pkg/controller/kameletbinding/monitor.go           |  16 +-
 pkg/trait/util.go                                  | 219 ++++++++++++++++++++-
 pkg/trait/util_test.go                             | 177 ++++++++++++++++-
 pkg/util/digest/digest.go                          |   4 +
 pkg/util/kubernetes/util.go                        |   3 +-
 11 files changed, 748 insertions(+), 128 deletions(-)

diff --git a/e2e/global/common/kamelet_binding_with_image_test.go b/e2e/global/common/kamelet_binding_with_image_test.go
new file mode 100644
index 000000000..129089e51
--- /dev/null
+++ b/e2e/global/common/kamelet_binding_with_image_test.go
@@ -0,0 +1,120 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+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 common
+
+import (
+	"github.com/onsi/gomega/gstruct"
+	"testing"
+
+	. "github.com/apache/camel-k/e2e/support"
+	. "github.com/onsi/gomega"
+
+	corev1 "k8s.io/api/core/v1"
+
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+)
+
+func TestBindingWithImage(t *testing.T) {
+	WithNewTestNamespace(t, func(ns string) {
+		operatorID := "camel-k-binding-image"
+		bindingID := "with-image-binding"
+
+		Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed())
+
+		from := corev1.ObjectReference{
+			Kind:       "Kamelet",
+			Name:       "my-own-timer-source",
+			APIVersion: v1alpha1.SchemeGroupVersion.String(),
+		}
+
+		to := corev1.ObjectReference{
+			Kind:       "Kamelet",
+			Name:       "my-own-log-sink",
+			APIVersion: v1alpha1.SchemeGroupVersion.String(),
+		}
+
+		emptyMap := map[string]string{}
+
+		annotations1 := map[string]string{
+			"trait.camel.apache.org/container.image":      "docker.io/jmalloc/echo-server:0.3.2",
+			"trait.camel.apache.org/jvm.enabled":          "false",
+			"trait.camel.apache.org/kamelets.enabled":     "false",
+			"trait.camel.apache.org/dependencies.enabled": "false",
+			"test": "1",
+		}
+		annotations2 := map[string]string{
+			"trait.camel.apache.org/container.image":      "docker.io/jmalloc/echo-server:0.3.3",
+			"trait.camel.apache.org/jvm.enabled":          "false",
+			"trait.camel.apache.org/kamelets.enabled":     "false",
+			"trait.camel.apache.org/dependencies.enabled": "false",
+			"test": "2",
+		}
+
+		t.Run("run with initial image", func(t *testing.T) {
+			expectedImage := annotations1["trait.camel.apache.org/container.image"]
+
+			RegisterTestingT(t)
+
+			Expect(BindKameletTo(ns, bindingID, annotations1, from, to, emptyMap, emptyMap)()).
+				To(Succeed())
+			Eventually(IntegrationGeneration(ns, bindingID)).
+				Should(gstruct.PointTo(BeNumerically("==", 1)))
+			Eventually(IntegrationAnnotations(ns, bindingID)).
+				Should(HaveKeyWithValue("test", "1"))
+			Eventually(IntegrationAnnotations(ns, bindingID)).
+				Should(HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage))
+			Eventually(IntegrationStatusImage(ns, bindingID)).
+				Should(Equal(expectedImage))
+
+			Eventually(IntegrationPodPhase(ns, bindingID), TestTimeoutLong).
+				Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationPodImage(ns, bindingID)).
+				Should(Equal(expectedImage))
+		})
+
+		t.Run("run with new image", func(t *testing.T) {
+			expectedImage := annotations2["trait.camel.apache.org/container.image"]
+
+			RegisterTestingT(t)
+
+			Expect(BindKameletTo(ns, bindingID, annotations2, from, to, emptyMap, emptyMap)()).
+				To(Succeed())
+			Eventually(IntegrationGeneration(ns, bindingID)).
+				Should(gstruct.PointTo(BeNumerically("==", 1)))
+			Eventually(IntegrationAnnotations(ns, bindingID)).
+				Should(HaveKeyWithValue("test", "2"))
+			Eventually(IntegrationAnnotations(ns, bindingID)).
+				Should(HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage))
+			Eventually(IntegrationStatusImage(ns, bindingID)).
+				Should(Equal(expectedImage))
+
+			Eventually(IntegrationPodPhase(ns, bindingID), TestTimeoutLong).
+				Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationPodImage(ns, bindingID)).
+				Should(Equal(expectedImage))
+		})
+
+		// Cleanup
+		Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil())
+	})
+}
diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go
index 1d690cfb1..8cfa8d1fa 100644
--- a/e2e/support/test_support.go
+++ b/e2e/support/test_support.go
@@ -545,6 +545,26 @@ func IntegrationSpecReplicas(ns string, name string) func() *int32 {
 	}
 }
 
+func IntegrationGeneration(ns string, name string) func() *int64 {
+	return func() *int64 {
+		it := Integration(ns, name)()
+		if it == nil {
+			return nil
+		}
+		return &it.Generation
+	}
+}
+
+func IntegrationStatusObserverGeneration(ns string, name string) func() *int64 {
+	return func() *int64 {
+		it := Integration(ns, name)()
+		if it == nil {
+			return nil
+		}
+		return &it.Status.ObservedGeneration
+	}
+}
+
 func IntegrationStatusReplicas(ns string, name string) func() *int32 {
 	return func() *int32 {
 		it := Integration(ns, name)()
@@ -555,6 +575,26 @@ func IntegrationStatusReplicas(ns string, name string) func() *int32 {
 	}
 }
 
+func IntegrationStatusImage(ns string, name string) func() string {
+	return func() string {
+		it := Integration(ns, name)()
+		if it == nil {
+			return ""
+		}
+		return it.Status.Image
+	}
+}
+
+func IntegrationAnnotations(ns string, name string) func() map[string]string {
+	return func() map[string]string {
+		it := Integration(ns, name)()
+		if it == nil {
+			return map[string]string{}
+		}
+		return it.Annotations
+	}
+}
+
 func IntegrationCondition(ns string, name string, conditionType v1.IntegrationConditionType) func() *v1.IntegrationCondition {
 	return func() *v1.IntegrationCondition {
 		it := Integration(ns, name)()
diff --git a/pkg/controller/integration/integration_controller.go b/pkg/controller/integration/integration_controller.go
index c17a1babf..0d0f61d20 100644
--- a/pkg/controller/integration/integration_controller.go
+++ b/pkg/controller/integration/integration_controller.go
@@ -23,6 +23,8 @@ import (
 	"reflect"
 	"time"
 
+	"github.com/apache/camel-k/pkg/trait"
+
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
@@ -75,6 +77,115 @@ func newReconciler(mgr manager.Manager, c client.Client) reconcile.Reconciler {
 	)
 }
 
+func integrationUpdateFunc(old *v1.Integration, it *v1.Integration) bool {
+	// Observe the time to first readiness metric
+	previous := old.Status.GetCondition(v1.IntegrationConditionReady)
+	if next := it.Status.GetCondition(v1.IntegrationConditionReady); (previous == nil || previous.Status != corev1.ConditionTrue && (previous.FirstTruthyTime == nil || previous.FirstTruthyTime.IsZero())) &&
+		next != nil && next.Status == corev1.ConditionTrue && next.FirstTruthyTime != nil && !next.FirstTruthyTime.IsZero() &&
+		it.Status.InitializationTimestamp != nil {
+		duration := next.FirstTruthyTime.Time.Sub(it.Status.InitializationTimestamp.Time)
+		Log.WithValues("request-namespace", it.Namespace, "request-name", it.Name, "ready-after", duration.Seconds()).
+			ForIntegration(it).Infof("First readiness after %s", duration)
+		timeToFirstReadiness.Observe(duration.Seconds())
+	}
+
+	// If traits have changed, the reconciliation loop must kick in as
+	// traits may have impact
+	sameTraits, err := trait.IntegrationsHaveSameTraits(old, it)
+	if err != nil {
+		Log.ForIntegration(it).Error(
+			err,
+			"unable to determine if old and new resource have the same traits")
+	}
+	if !sameTraits {
+		return true
+	}
+
+	// Ignore updates to the integration status in which case metadata.Generation does not change,
+	// or except when the integration phase changes as it's used to transition from one phase
+	// to another.
+	return old.Generation != it.Generation ||
+		old.Status.Phase != it.Status.Phase
+}
+
+func integrationKitEnqueueRequestsFromMapFunc(c client.Client, kit *v1.IntegrationKit) []reconcile.Request {
+	var requests []reconcile.Request
+	if kit.Status.Phase != v1.IntegrationKitPhaseReady && kit.Status.Phase != v1.IntegrationKitPhaseError {
+		return requests
+	}
+
+	list := &v1.IntegrationList{}
+	// Do global search in case of global operator (it may be using a global platform)
+	var opts []ctrl.ListOption
+	if !platform.IsCurrentOperatorGlobal() {
+		opts = append(opts, ctrl.InNamespace(kit.Namespace))
+	}
+	if err := c.List(context.Background(), list, opts...); err != nil {
+		log.Error(err, "Failed to retrieve integration list")
+		return requests
+	}
+
+	for i := range list.Items {
+		integration := &list.Items[i]
+		log.Debug("Integration Controller: Assessing integration", "integration", integration.Name, "namespace", integration.Namespace)
+
+		match, err := sameOrMatch(kit, integration)
+		if err != nil {
+			log.Errorf(err, "Error matching integration %q with kit %q", integration.Name, kit.Name)
+			continue
+		}
+		if !match {
+			continue
+		}
+
+		if integration.Status.Phase == v1.IntegrationPhaseBuildingKit ||
+			integration.Status.Phase == v1.IntegrationPhaseRunning {
+			log.Infof("Kit %s ready, notify integration: %s", kit.Name, integration.Name)
+			requests = append(requests, reconcile.Request{
+				NamespacedName: types.NamespacedName{
+					Namespace: integration.Namespace,
+					Name:      integration.Name,
+				},
+			})
+		}
+	}
+
+	return requests
+}
+
+func integrationPlatformEnqueueRequestsFromMapFunc(c client.Client, p *v1.IntegrationPlatform) []reconcile.Request {
+	var requests []reconcile.Request
+
+	if p.Status.Phase == v1.IntegrationPlatformPhaseReady {
+		list := &v1.IntegrationList{}
+
+		// Do global search in case of global operator (it may be using a global platform)
+		var opts []ctrl.ListOption
+		if !platform.IsCurrentOperatorGlobal() {
+			opts = append(opts, ctrl.InNamespace(p.Namespace))
+		}
+
+		if err := c.List(context.Background(), list, opts...); err != nil {
+			log.Error(err, "Failed to list integrations")
+			return requests
+		}
+
+		for _, integration := range list.Items {
+			if integration.Status.Phase == v1.IntegrationPhaseWaitingForPlatform {
+				log.Infof("Platform %s ready, wake-up integration: %s", p.Name, integration.Name)
+				requests = append(requests, reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: integration.Namespace,
+						Name:      integration.Name,
+					},
+				})
+			}
+		}
+	}
+
+	return requests
+}
+
 func add(mgr manager.Manager, c client.Client, r reconcile.Reconciler) error {
 	b := builder.ControllerManagedBy(mgr).
 		Named("integration-controller").
@@ -90,21 +201,8 @@ func add(mgr manager.Manager, c client.Client, r reconcile.Reconciler) error {
 					if !ok {
 						return false
 					}
-					// Observe the time to first readiness metric
-					previous := old.Status.GetCondition(v1.IntegrationConditionReady)
-					if next := it.Status.GetCondition(v1.IntegrationConditionReady); (previous == nil || previous.Status != corev1.ConditionTrue && (previous.FirstTruthyTime == nil || previous.FirstTruthyTime.IsZero())) &&
-						next != nil && next.Status == corev1.ConditionTrue && next.FirstTruthyTime != nil && !next.FirstTruthyTime.IsZero() &&
-						it.Status.InitializationTimestamp != nil {
-						duration := next.FirstTruthyTime.Time.Sub(it.Status.InitializationTimestamp.Time)
-						Log.WithValues("request-namespace", it.Namespace, "request-name", it.Name, "ready-after", duration.Seconds()).
-							ForIntegration(it).Infof("First readiness after %s", duration)
-						timeToFirstReadiness.Observe(duration.Seconds())
-					}
-					// Ignore updates to the integration status in which case metadata.Generation does not change,
-					// or except when the integration phase changes as it's used to transition from one phase
-					// to another.
-					return old.Generation != it.Generation ||
-						old.Status.Phase != it.Status.Phase
+
+					return integrationUpdateFunc(old, it)
 				},
 				DeleteFunc: func(e event.DeleteEvent) bool {
 					// Evaluates to false if the object has been confirmed deleted
@@ -116,92 +214,25 @@ func add(mgr manager.Manager, c client.Client, r reconcile.Reconciler) error {
 		// or running phase.
 		Watches(&source.Kind{Type: &v1.IntegrationKit{}},
 			handler.EnqueueRequestsFromMapFunc(func(a ctrl.Object) []reconcile.Request {
-				var requests []reconcile.Request
 				kit, ok := a.(*v1.IntegrationKit)
 				if !ok {
 					log.Error(fmt.Errorf("type assertion failed: %v", a), "Failed to retrieve integration list")
-					return requests
-				}
-
-				if kit.Status.Phase != v1.IntegrationKitPhaseReady && kit.Status.Phase != v1.IntegrationKitPhaseError {
-					return requests
-				}
-
-				list := &v1.IntegrationList{}
-				// Do global search in case of global operator (it may be using a global platform)
-				var opts []ctrl.ListOption
-				if !platform.IsCurrentOperatorGlobal() {
-					opts = append(opts, ctrl.InNamespace(kit.Namespace))
-				}
-				if err := c.List(context.Background(), list, opts...); err != nil {
-					log.Error(err, "Failed to retrieve integration list")
-					return requests
-				}
-
-				for i := range list.Items {
-					integration := &list.Items[i]
-					log.Debug("Integration Controller: Assessing integration", "integration", integration.Name, "namespace", integration.Namespace)
-
-					if match, err := integrationMatches(integration, kit); err != nil {
-						log.Errorf(err, "Error matching integration %q with kit %q", integration.Name, kit.Name)
-
-						continue
-					} else if !match {
-						continue
-					}
-					if integration.Status.Phase == v1.IntegrationPhaseBuildingKit ||
-						integration.Status.Phase == v1.IntegrationPhaseRunning {
-						log.Infof("Kit %s ready, notify integration: %s", kit.Name, integration.Name)
-						requests = append(requests, reconcile.Request{
-							NamespacedName: types.NamespacedName{
-								Namespace: integration.Namespace,
-								Name:      integration.Name,
-							},
-						})
-					}
+					return []reconcile.Request{}
 				}
 
-				return requests
+				return integrationKitEnqueueRequestsFromMapFunc(c, kit)
 			})).
 		// Watch for IntegrationPlatform phase transitioning to ready and enqueue
 		// requests for any integrations that are in phase waiting for platform
 		Watches(&source.Kind{Type: &v1.IntegrationPlatform{}},
 			handler.EnqueueRequestsFromMapFunc(func(a ctrl.Object) []reconcile.Request {
-				var requests []reconcile.Request
 				p, ok := a.(*v1.IntegrationPlatform)
 				if !ok {
 					log.Error(fmt.Errorf("type assertion failed: %v", a), "Failed to list integrations")
-					return requests
-				}
-
-				if p.Status.Phase == v1.IntegrationPlatformPhaseReady {
-					list := &v1.IntegrationList{}
-
-					// Do global search in case of global operator (it may be using a global platform)
-					var opts []ctrl.ListOption
-					if !platform.IsCurrentOperatorGlobal() {
-						opts = append(opts, ctrl.InNamespace(p.Namespace))
-					}
-
-					if err := c.List(context.Background(), list, opts...); err != nil {
-						log.Error(err, "Failed to list integrations")
-						return requests
-					}
-
-					for _, integration := range list.Items {
-						if integration.Status.Phase == v1.IntegrationPhaseWaitingForPlatform {
-							log.Infof("Platform %s ready, wake-up integration: %s", p.Name, integration.Name)
-							requests = append(requests, reconcile.Request{
-								NamespacedName: types.NamespacedName{
-									Namespace: integration.Namespace,
-									Name:      integration.Name,
-								},
-							})
-						}
-					}
+					return []reconcile.Request{}
 				}
 
-				return requests
+				return integrationPlatformEnqueueRequestsFromMapFunc(c, p)
 			})).
 		// Watch for the owned Deployments
 		Owns(&appsv1.Deployment{}, builder.WithPredicates(StatusChangedPredicate{})).
diff --git a/pkg/controller/integration/kits.go b/pkg/controller/integration/kits.go
index 4861d8972..72b504e86 100644
--- a/pkg/controller/integration/kits.go
+++ b/pkg/controller/integration/kits.go
@@ -26,9 +26,9 @@ import (
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/selection"
 
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/platform"
 	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util"
@@ -82,6 +82,18 @@ func lookupKitsForIntegration(ctx context.Context, c ctrl.Reader, integration *v
 	return kits, nil
 }
 
+// sameOrMatch returns whether the v1.IntegrationKit is the one used by the v1.Integration or if it meets the
+// requirements of the v1.Integration.
+func sameOrMatch(kit *v1.IntegrationKit, integration *v1.Integration) (bool, error) {
+	if integration.Status.IntegrationKit != nil {
+		if integration.Status.IntegrationKit.Namespace == kit.Namespace && integration.Status.IntegrationKit.Name == kit.Name {
+			return true, nil
+		}
+	}
+
+	return integrationMatches(integration, kit)
+}
+
 // integrationMatches returns whether the v1.IntegrationKit meets the requirements of the v1.Integration.
 func integrationMatches(integration *v1.Integration, kit *v1.IntegrationKit) (bool, error) {
 	ilog := log.ForIntegration(integration)
@@ -101,7 +113,17 @@ func integrationMatches(integration *v1.Integration, kit *v1.IntegrationKit) (bo
 	//
 	// A kit can be used only if it contains a subset of the traits and related configurations
 	// declared on integration.
-	if match, err := hasMatchingTraits(integration.Spec.Traits, kit.Spec.Traits); !match || err != nil {
+
+	itc, err := trait.NewUnstructuredTraitsForIntegration(integration)
+	if err != nil {
+		return false, err
+	}
+	ikc, err := trait.NewUnstructuredTraitsForIntegrationKit(kit)
+	if err != nil {
+		return false, err
+	}
+
+	if match, err := hasMatchingTraits(itc, ikc); !match || err != nil {
 		ilog.Debug("Integration and integration-kit traits do not match", "integration", integration.Name, "integration-kit", kit.Name, "namespace", integration.Namespace)
 		return false, err
 	}
@@ -147,7 +169,17 @@ func kitMatches(kit1 *v1.IntegrationKit, kit2 *v1.IntegrationKit) (bool, error)
 	if len(kit1.Spec.Dependencies) != len(kit2.Spec.Dependencies) {
 		return false, nil
 	}
-	if match, err := hasMatchingTraits(kit1.Spec.Traits, kit2.Spec.Traits); !match || err != nil {
+
+	c1, err := trait.NewUnstructuredTraitsForIntegrationKit(kit1)
+	if err != nil {
+		return false, err
+	}
+	c2, err := trait.NewUnstructuredTraitsForIntegrationKit(kit2)
+	if err != nil {
+		return false, err
+	}
+
+	if match, err := hasMatchingTraits(c1, c2); !match || err != nil {
 		return false, err
 	}
 	if !util.StringSliceContains(kit1.Spec.Dependencies, kit2.Spec.Dependencies) {
@@ -157,15 +189,7 @@ func kitMatches(kit1 *v1.IntegrationKit, kit2 *v1.IntegrationKit) (bool, error)
 	return true, nil
 }
 
-func hasMatchingTraits(traits interface{}, kitTraits interface{}) (bool, error) {
-	traitMap, err := trait.ToTraitMap(traits)
-	if err != nil {
-		return false, err
-	}
-	kitTraitMap, err := trait.ToTraitMap(kitTraits)
-	if err != nil {
-		return false, err
-	}
+func hasMatchingTraits(traitMap trait.Unstructured, kitTraitMap trait.Unstructured) (bool, error) {
 	catalog := trait.NewCatalog(nil)
 
 	for _, t := range catalog.AllTraits() {
@@ -173,9 +197,10 @@ func hasMatchingTraits(traits interface{}, kitTraits interface{}) (bool, error)
 			// We don't store the trait configuration if the trait cannot influence the kit behavior
 			continue
 		}
+
 		id := string(t.ID())
-		it, ok1 := findTrait(traitMap, id)
-		kt, ok2 := findTrait(kitTraitMap, id)
+		it, ok1 := traitMap.Get(id)
+		kt, ok2 := kitTraitMap.Get(id)
 
 		if !ok1 && !ok2 {
 			continue
@@ -198,22 +223,6 @@ func hasMatchingTraits(traits interface{}, kitTraits interface{}) (bool, error)
 	return true, nil
 }
 
-func findTrait(traitsMap map[string]map[string]interface{}, id string) (map[string]interface{}, bool) {
-	if trait, ok := traitsMap[id]; ok {
-		return trait, true
-	}
-
-	if addons, ok := traitsMap["addons"]; ok {
-		if addon, ok := addons[id]; ok {
-			if trait, ok := addon.(map[string]interface{}); ok {
-				return trait, true
-			}
-		}
-	}
-
-	return nil, false
-}
-
 func matchesComparableTrait(ct trait.ComparableTrait, it map[string]interface{}, kt map[string]interface{}) (bool, error) {
 	t1 := reflect.New(reflect.TypeOf(ct).Elem()).Interface()
 	if err := trait.ToTrait(it, &t1); err != nil {
diff --git a/pkg/controller/integration/kits_test.go b/pkg/controller/integration/kits_test.go
index 506d26f96..694be1521 100644
--- a/pkg/controller/integration/kits_test.go
+++ b/pkg/controller/integration/kits_test.go
@@ -21,15 +21,17 @@ import (
 	"context"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
-
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/pointer"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	traitv1 "github.com/apache/camel-k/pkg/apis/camel/v1/trait"
+
+	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util/log"
 	"github.com/apache/camel-k/pkg/util/test"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestLookupKitForIntegration_DiscardKitsInError(t *testing.T) {
@@ -277,7 +279,7 @@ func TestHasMatchingTraits_KitNoTraitShouldNotBePicked(t *testing.T) {
 	a := buildKitAction{}
 	a.InjectLogger(log.Log)
 
-	ok, err := hasMatchingTraits(integration.Spec.Traits, kit.Spec.Traits)
+	ok, err := trait.IntegrationAndKitHaveSameTraits(integration, kit)
 	assert.Nil(t, err)
 	assert.False(t, ok)
 }
@@ -332,7 +334,7 @@ func TestHasMatchingTraits_KitSameTraitShouldBePicked(t *testing.T) {
 	a := buildKitAction{}
 	a.InjectLogger(log.Log)
 
-	ok, err := hasMatchingTraits(integration.Spec.Traits, kit.Spec.Traits)
+	ok, err := trait.IntegrationAndKitHaveSameTraits(integration, kit)
 	assert.Nil(t, err)
 	assert.True(t, ok)
 }
diff --git a/pkg/controller/kameletbinding/kamelet_binding_controller.go b/pkg/controller/kameletbinding/kamelet_binding_controller.go
index 8cd8bebb1..77278f3c1 100644
--- a/pkg/controller/kameletbinding/kamelet_binding_controller.go
+++ b/pkg/controller/kameletbinding/kamelet_binding_controller.go
@@ -37,6 +37,8 @@ import (
 	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/client"
+	"github.com/apache/camel-k/pkg/trait"
+
 	camelevent "github.com/apache/camel-k/pkg/event"
 	"github.com/apache/camel-k/pkg/platform"
 	"github.com/apache/camel-k/pkg/util/monitoring"
@@ -87,6 +89,19 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 				if !ok {
 					return false
 				}
+
+				// If traits have changed, the reconciliation loop must kick in as
+				// traits may have impact
+				sameTraits, err := trait.KameletBindingsHaveSameTraits(oldKameletBinding, newKameletBinding)
+				if err != nil {
+					Log.ForKameletBinding(newKameletBinding).Error(
+						err,
+						"unable to determine if old and new resource have the same traits")
+				}
+				if !sameTraits {
+					return true
+				}
+
 				// Ignore updates to the kameletBinding status in which case metadata.Generation
 				// does not change, or except when the kameletBinding phase changes as it's used
 				// to transition from one phase to another
diff --git a/pkg/controller/kameletbinding/monitor.go b/pkg/controller/kameletbinding/monitor.go
index 841a313fc..dc62ea1a9 100644
--- a/pkg/controller/kameletbinding/monitor.go
+++ b/pkg/controller/kameletbinding/monitor.go
@@ -31,6 +31,7 @@ import (
 
 	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/trait"
 )
 
 // NewMonitorAction returns an action that monitors the KameletBinding after it's fully initialized.
@@ -76,14 +77,25 @@ func (action *monitorAction) Handle(ctx context.Context, kameletbinding *v1alpha
 	operatorIDChanged := v1.GetOperatorIDAnnotation(kameletbinding) != "" &&
 		(v1.GetOperatorIDAnnotation(kameletbinding) != v1.GetOperatorIDAnnotation(&it))
 
+	sameTraits, err := trait.IntegrationAndBindingSameTraits(&it, kameletbinding)
+	if err != nil {
+		return nil, err
+	}
+
 	// Check if the integration needs to be changed
 	expected, err := CreateIntegrationFor(ctx, action.client, kameletbinding)
 	if err != nil {
 		return nil, err
 	}
 
-	if !equality.Semantic.DeepDerivative(expected.Spec, it.Spec) || operatorIDChanged {
-		action.L.Info("Monitor: KameletBinding needs a rebuild")
+	semanticEquality := equality.Semantic.DeepDerivative(expected.Spec, it.Spec)
+
+	if !semanticEquality || operatorIDChanged || !sameTraits {
+		action.L.Info(
+			"Monitor: KameletBinding needs a rebuild",
+			"semantic-equality", !semanticEquality,
+			"operatorid-changed", operatorIDChanged,
+			"traites-changed", !sameTraits)
 
 		// KameletBinding has changed and needs rebuild
 		target := kameletbinding.DeepCopy()
diff --git a/pkg/trait/util.go b/pkg/trait/util.go
index c1310ec6e..2b71cd960 100644
--- a/pkg/trait/util.go
+++ b/pkg/trait/util.go
@@ -29,10 +29,11 @@ import (
 	user "github.com/mitchellh/go-homedir"
 	"github.com/pkg/errors"
 	"github.com/scylladb/go-set/strset"
-
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
 	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/client"
 	"github.com/apache/camel-k/pkg/metadata"
 	"github.com/apache/camel-k/pkg/util"
@@ -40,6 +41,24 @@ import (
 	"github.com/apache/camel-k/pkg/util/property"
 )
 
+type Unstructured map[string]map[string]interface{}
+
+func (u Unstructured) Get(id string) (map[string]interface{}, bool) {
+	if t, ok := u[id]; ok {
+		return t, true
+	}
+
+	if addons, ok := u["addons"]; ok {
+		if addon, ok := addons[id]; ok {
+			if t, ok := addon.(map[string]interface{}); ok {
+				return t, true
+			}
+		}
+	}
+
+	return nil, false
+}
+
 var exactVersionRegexp = regexp.MustCompile(`^(\d+)\.(\d+)\.([\w-.]+)$`)
 
 // getIntegrationKit retrieves the kit set on the integration.
@@ -237,7 +256,7 @@ func AssertTraitsType(traits interface{}) error {
 }
 
 // ToTraitMap accepts either v1.Traits or v1.IntegrationKitTraits and converts it to a map of traits.
-func ToTraitMap(traits interface{}) (map[string]map[string]interface{}, error) {
+func ToTraitMap(traits interface{}) (Unstructured, error) {
 	if err := AssertTraitsType(traits); err != nil {
 		return nil, err
 	}
@@ -246,7 +265,7 @@ func ToTraitMap(traits interface{}) (map[string]map[string]interface{}, error) {
 	if err != nil {
 		return nil, err
 	}
-	traitMap := make(map[string]map[string]interface{})
+	traitMap := make(Unstructured)
 	if err = json.Unmarshal(data, &traitMap); err != nil {
 		return nil, err
 	}
@@ -320,3 +339,197 @@ func getBuilderTask(tasks []v1.Task) *v1.BuilderTask {
 	}
 	return nil
 }
+
+// Equals return if traits are the same.
+func Equals(i1 Unstructured, i2 Unstructured) bool {
+	return reflect.DeepEqual(i1, i2)
+}
+
+// IntegrationsHaveSameTraits return if traits are the same.
+func IntegrationsHaveSameTraits(i1 *v1.Integration, i2 *v1.Integration) (bool, error) {
+	c1, err := NewUnstructuredTraitsForIntegration(i1)
+	if err != nil {
+		return false, err
+	}
+	c2, err := NewUnstructuredTraitsForIntegration(i2)
+	if err != nil {
+		return false, err
+	}
+
+	return Equals(c1, c2), nil
+}
+
+// IntegrationKitsHaveSameTraits return if traits are the same.
+func IntegrationKitsHaveSameTraits(i1 *v1.IntegrationKit, i2 *v1.IntegrationKit) (bool, error) {
+	c1, err := NewUnstructuredTraitsForIntegrationKit(i1)
+	if err != nil {
+		return false, err
+	}
+	c2, err := NewUnstructuredTraitsForIntegrationKit(i2)
+	if err != nil {
+		return false, err
+	}
+
+	return Equals(c1, c2), nil
+}
+
+// KameletBindingsHaveSameTraits return if traits are the same.
+func KameletBindingsHaveSameTraits(i1 *v1alpha1.KameletBinding, i2 *v1alpha1.KameletBinding) (bool, error) {
+	c1, err := NewUnstructuredTraitsForKameletBinding(i1)
+	if err != nil {
+		return false, err
+	}
+	c2, err := NewUnstructuredTraitsForKameletBinding(i2)
+	if err != nil {
+		return false, err
+	}
+
+	return Equals(c1, c2), nil
+}
+
+// IntegrationAndBindingSameTraits return if traits are the same.
+func IntegrationAndBindingSameTraits(i1 *v1.Integration, i2 *v1alpha1.KameletBinding) (bool, error) {
+	c1, err := NewUnstructuredTraitsForIntegration(i1)
+	if err != nil {
+		return false, err
+	}
+	c2, err := NewUnstructuredTraitsForKameletBinding(i2)
+	if err != nil {
+		return false, err
+	}
+
+	return Equals(c1, c2), nil
+}
+
+// IntegrationAndKitHaveSameTraits return if traits are the same.
+func IntegrationAndKitHaveSameTraits(i1 *v1.Integration, i2 *v1.IntegrationKit) (bool, error) {
+	c1, err := NewUnstructuredTraitsForIntegration(i1)
+	if err != nil {
+		return false, err
+	}
+	c2, err := NewUnstructuredTraitsForIntegrationKit(i2)
+	if err != nil {
+		return false, err
+	}
+
+	return Equals(c1, c2), nil
+}
+
+func NewUnstructuredTraitsForIntegration(i *v1.Integration) (Unstructured, error) {
+	m1, err := ToTraitMap(i.Spec.Traits)
+	if err != nil {
+		return nil, err
+	}
+
+	m2, err := FromAnnotations(&i.ObjectMeta)
+	if err != nil {
+		return nil, err
+	}
+
+	for k, v := range m2 {
+		m1[k] = v
+	}
+
+	return m1, nil
+}
+
+func NewUnstructuredTraitsForIntegrationKit(i *v1.IntegrationKit) (Unstructured, error) {
+	m1, err := ToTraitMap(i.Spec.Traits)
+	if err != nil {
+		return nil, err
+	}
+
+	m2, err := FromAnnotations(&i.ObjectMeta)
+	if err != nil {
+		return nil, err
+	}
+
+	for k, v := range m2 {
+		m1[k] = v
+	}
+
+	return m1, nil
+}
+
+func NewUnstructuredTraitsForIntegrationPlatform(i *v1.IntegrationPlatform) (Unstructured, error) {
+	m1, err := ToTraitMap(i.Spec.Traits)
+	if err != nil {
+		return nil, err
+	}
+
+	m2, err := FromAnnotations(&i.ObjectMeta)
+	if err != nil {
+		return nil, err
+	}
+
+	for k, v := range m2 {
+		m1[k] = v
+	}
+
+	return m1, nil
+}
+
+func NewUnstructuredTraitsForKameletBinding(i *v1alpha1.KameletBinding) (Unstructured, error) {
+	if i.Spec.Integration != nil {
+		m1, err := ToTraitMap(i.Spec.Integration.Traits)
+		if err != nil {
+			return nil, err
+		}
+
+		m2, err := FromAnnotations(&i.ObjectMeta)
+		if err != nil {
+			return nil, err
+		}
+
+		for k, v := range m2 {
+			m1[k] = v
+		}
+
+		return m1, nil
+	}
+
+	m1, err := FromAnnotations(&i.ObjectMeta)
+	if err != nil {
+		return nil, err
+	}
+
+	return m1, nil
+}
+
+func FromAnnotations(meta *metav1.ObjectMeta) (Unstructured, error) {
+	options := make(Unstructured)
+
+	for k, v := range meta.Annotations {
+		if strings.HasPrefix(k, v1.TraitAnnotationPrefix) {
+			configKey := strings.TrimPrefix(k, v1.TraitAnnotationPrefix)
+			if strings.Contains(configKey, ".") {
+				parts := strings.SplitN(configKey, ".", 2)
+				id := parts[0]
+				prop := parts[1]
+				if _, ok := options[id]; !ok {
+					options[id] = make(map[string]interface{})
+				}
+
+				propParts := util.ConfigTreePropertySplit(prop)
+				var current = options[id]
+				if len(propParts) > 1 {
+					c, err := util.NavigateConfigTree(current, propParts[0:len(propParts)-1])
+					if err != nil {
+						return options, err
+					}
+					if cc, ok := c.(map[string]interface{}); ok {
+						current = cc
+					} else {
+						return options, errors.New(`invalid array specification: to set an array value use the ["v1", "v2"] format`)
+					}
+				}
+				current[prop] = v
+
+			} else {
+				return options, fmt.Errorf("wrong format for trait annotation %q: missing trait ID", k)
+			}
+		}
+	}
+
+	return options, nil
+}
diff --git a/pkg/trait/util_test.go b/pkg/trait/util_test.go
index c44a3e26b..192943147 100644
--- a/pkg/trait/util_test.go
+++ b/pkg/trait/util_test.go
@@ -20,10 +20,12 @@ package trait
 import (
 	"testing"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/pointer"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	traitv1 "github.com/apache/camel-k/pkg/apis/camel/v1/trait"
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -56,7 +58,7 @@ func TestToTraitMap(t *testing.T) {
 			}),
 		},
 	}
-	expected := map[string]map[string]interface{}{
+	expected := Unstructured{
 		"container": {
 			"enabled":         true,
 			"auto":            false,
@@ -201,3 +203,176 @@ func TestToTrait(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, expected, trait)
 }
+
+func TestSameTraits(t *testing.T) {
+	t.Run("empty traits", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{},
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{},
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.True(t, ok)
+	})
+
+	t.Run("same traits", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Container: &traitv1.ContainerTrait{
+							Image: "foo/bar:1",
+						},
+					},
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Container: &traitv1.ContainerTrait{
+							Image: "foo/bar:1",
+						},
+					},
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.True(t, ok)
+	})
+
+	t.Run("not same traits", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Container: &traitv1.ContainerTrait{
+							Image: "foo/bar:1",
+						},
+					},
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Owner: &traitv1.OwnerTrait{
+							TargetAnnotations: []string{"foo"},
+						},
+					},
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.False(t, ok)
+	})
+
+	t.Run("same traits with annotations", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Container: &traitv1.ContainerTrait{
+							Image: "foo/bar:1",
+						},
+					},
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:1",
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.True(t, ok)
+	})
+
+	t.Run("same traits with annotations only", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:1",
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:1",
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.True(t, ok)
+	})
+
+	t.Run("not same traits with annotations", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Container: &traitv1.ContainerTrait{
+							Image: "foo/bar:1",
+						},
+					},
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:2",
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.False(t, ok)
+	})
+
+	t.Run("not same traits with annotations only", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:1",
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:2",
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.False(t, ok)
+	})
+}
diff --git a/pkg/util/digest/digest.go b/pkg/util/digest/digest.go
index 02a060411..2c1ea63b0 100644
--- a/pkg/util/digest/digest.go
+++ b/pkg/util/digest/digest.go
@@ -202,6 +202,10 @@ func ComputeForIntegrationKit(kit *v1.IntegrationKit) (string, error) {
 		return "", err
 	}
 
+	if _, err := hash.Write([]byte(kit.Spec.Image)); err != nil {
+		return "", err
+	}
+
 	for _, item := range kit.Spec.Dependencies {
 		if _, err := hash.Write([]byte(item)); err != nil {
 			return "", err
diff --git a/pkg/util/kubernetes/util.go b/pkg/util/kubernetes/util.go
index 3d029c202..2816ea15d 100644
--- a/pkg/util/kubernetes/util.go
+++ b/pkg/util/kubernetes/util.go
@@ -18,10 +18,9 @@ limitations under the License.
 package kubernetes
 
 import (
+	"github.com/apache/camel-k/pkg/util"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/json"
-
-	"github.com/apache/camel-k/pkg/util"
 )
 
 // ToJSON marshal to json format.


[camel-k] 03/03: fix: KameletBinding vs Integration traits comparison shouyld be based on the traits configured on the KameletBinding only #3479

Posted by lb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit e7bc5eccadf3c4cd89e998726c01a96e189fc6b7
Author: Luca Burgazzoli <lb...@gmail.com>
AuthorDate: Fri Aug 5 11:50:54 2022 +0200

    fix: KameletBinding vs Integration traits comparison shouyld be based on the traits configured on the KameletBinding only #3479
---
 pkg/controller/integration/kits.go |  8 ++++----
 pkg/trait/util.go                  | 41 +++++++++++++++++++++++---------------
 2 files changed, 29 insertions(+), 20 deletions(-)

diff --git a/pkg/controller/integration/kits.go b/pkg/controller/integration/kits.go
index 1e4bd9b84..433d96180 100644
--- a/pkg/controller/integration/kits.go
+++ b/pkg/controller/integration/kits.go
@@ -114,11 +114,11 @@ func integrationMatches(integration *v1.Integration, kit *v1.IntegrationKit) (bo
 	// A kit can be used only if it contains a subset of the traits and related configurations
 	// declared on integration.
 
-	itc, err := trait.NewUnstructuredTraitsForIntegration(integration)
+	itc, err := trait.NewTraitsOptionsForIntegration(integration)
 	if err != nil {
 		return false, err
 	}
-	ikc, err := trait.NewUnstructuredTraitsForIntegrationKit(kit)
+	ikc, err := trait.NewTraitsOptionsForIntegrationKit(kit)
 	if err != nil {
 		return false, err
 	}
@@ -170,11 +170,11 @@ func kitMatches(kit1 *v1.IntegrationKit, kit2 *v1.IntegrationKit) (bool, error)
 		return false, nil
 	}
 
-	c1, err := trait.NewUnstructuredTraitsForIntegrationKit(kit1)
+	c1, err := trait.NewTraitsOptionsForIntegrationKit(kit1)
 	if err != nil {
 		return false, err
 	}
-	c2, err := trait.NewUnstructuredTraitsForIntegrationKit(kit2)
+	c2, err := trait.NewTraitsOptionsForIntegrationKit(kit2)
 	if err != nil {
 		return false, err
 	}
diff --git a/pkg/trait/util.go b/pkg/trait/util.go
index 737b54883..880098c71 100644
--- a/pkg/trait/util.go
+++ b/pkg/trait/util.go
@@ -347,11 +347,11 @@ func Equals(i1 Options, i2 Options) bool {
 
 // IntegrationsHaveSameTraits return if traits are the same.
 func IntegrationsHaveSameTraits(i1 *v1.Integration, i2 *v1.Integration) (bool, error) {
-	c1, err := NewUnstructuredTraitsForIntegration(i1)
+	c1, err := NewTraitsOptionsForIntegration(i1)
 	if err != nil {
 		return false, err
 	}
-	c2, err := NewUnstructuredTraitsForIntegration(i2)
+	c2, err := NewTraitsOptionsForIntegration(i2)
 	if err != nil {
 		return false, err
 	}
@@ -361,11 +361,11 @@ func IntegrationsHaveSameTraits(i1 *v1.Integration, i2 *v1.Integration) (bool, e
 
 // IntegrationKitsHaveSameTraits return if traits are the same.
 func IntegrationKitsHaveSameTraits(i1 *v1.IntegrationKit, i2 *v1.IntegrationKit) (bool, error) {
-	c1, err := NewUnstructuredTraitsForIntegrationKit(i1)
+	c1, err := NewTraitsOptionsForIntegrationKit(i1)
 	if err != nil {
 		return false, err
 	}
-	c2, err := NewUnstructuredTraitsForIntegrationKit(i2)
+	c2, err := NewTraitsOptionsForIntegrationKit(i2)
 	if err != nil {
 		return false, err
 	}
@@ -375,11 +375,11 @@ func IntegrationKitsHaveSameTraits(i1 *v1.IntegrationKit, i2 *v1.IntegrationKit)
 
 // KameletBindingsHaveSameTraits return if traits are the same.
 func KameletBindingsHaveSameTraits(i1 *v1alpha1.KameletBinding, i2 *v1alpha1.KameletBinding) (bool, error) {
-	c1, err := NewUnstructuredTraitsForKameletBinding(i1)
+	c1, err := NewTraitsOptionsForKameletBinding(i1)
 	if err != nil {
 		return false, err
 	}
-	c2, err := NewUnstructuredTraitsForKameletBinding(i2)
+	c2, err := NewTraitsOptionsForKameletBinding(i2)
 	if err != nil {
 		return false, err
 	}
@@ -388,34 +388,43 @@ func KameletBindingsHaveSameTraits(i1 *v1alpha1.KameletBinding, i2 *v1alpha1.Kam
 }
 
 // IntegrationAndBindingSameTraits return if traits are the same.
+// The comparison is done for the subset of traits defines on the binding as during the trait processing,
+// some traits may be added to the Integration i.e. knative configuration in case of sink binding.
 func IntegrationAndBindingSameTraits(i1 *v1.Integration, i2 *v1alpha1.KameletBinding) (bool, error) {
-	c1, err := NewUnstructuredTraitsForIntegration(i1)
+	itOpts, err := NewTraitsOptionsForIntegration(i1)
 	if err != nil {
 		return false, err
 	}
-	c2, err := NewUnstructuredTraitsForKameletBinding(i2)
+	klbOpts, err := NewTraitsOptionsForKameletBinding(i2)
 	if err != nil {
 		return false, err
 	}
 
-	return Equals(c1, c2), nil
+	toCompare := make(Options)
+	for k := range klbOpts {
+		if v, ok := itOpts[k]; ok {
+			toCompare[k] = v
+		}
+	}
+
+	return Equals(klbOpts, toCompare), nil
 }
 
 // IntegrationAndKitHaveSameTraits return if traits are the same.
 func IntegrationAndKitHaveSameTraits(i1 *v1.Integration, i2 *v1.IntegrationKit) (bool, error) {
-	c1, err := NewUnstructuredTraitsForIntegration(i1)
+	itOpts, err := NewTraitsOptionsForIntegration(i1)
 	if err != nil {
 		return false, err
 	}
-	c2, err := NewUnstructuredTraitsForIntegrationKit(i2)
+	ikOpts, err := NewTraitsOptionsForIntegrationKit(i2)
 	if err != nil {
 		return false, err
 	}
 
-	return Equals(c1, c2), nil
+	return Equals(ikOpts, itOpts), nil
 }
 
-func NewUnstructuredTraitsForIntegration(i *v1.Integration) (Options, error) {
+func NewTraitsOptionsForIntegration(i *v1.Integration) (Options, error) {
 	m1, err := ToTraitMap(i.Spec.Traits)
 	if err != nil {
 		return nil, err
@@ -433,7 +442,7 @@ func NewUnstructuredTraitsForIntegration(i *v1.Integration) (Options, error) {
 	return m1, nil
 }
 
-func NewUnstructuredTraitsForIntegrationKit(i *v1.IntegrationKit) (Options, error) {
+func NewTraitsOptionsForIntegrationKit(i *v1.IntegrationKit) (Options, error) {
 	m1, err := ToTraitMap(i.Spec.Traits)
 	if err != nil {
 		return nil, err
@@ -451,7 +460,7 @@ func NewUnstructuredTraitsForIntegrationKit(i *v1.IntegrationKit) (Options, erro
 	return m1, nil
 }
 
-func NewUnstructuredTraitsForIntegrationPlatform(i *v1.IntegrationPlatform) (Options, error) {
+func NewTraitsOptionsForIntegrationPlatform(i *v1.IntegrationPlatform) (Options, error) {
 	m1, err := ToTraitMap(i.Spec.Traits)
 	if err != nil {
 		return nil, err
@@ -469,7 +478,7 @@ func NewUnstructuredTraitsForIntegrationPlatform(i *v1.IntegrationPlatform) (Opt
 	return m1, nil
 }
 
-func NewUnstructuredTraitsForKameletBinding(i *v1alpha1.KameletBinding) (Options, error) {
+func NewTraitsOptionsForKameletBinding(i *v1alpha1.KameletBinding) (Options, error) {
 	if i.Spec.Integration != nil {
 		m1, err := ToTraitMap(i.Spec.Integration.Traits)
 		if err != nil {