Repository: apache/camel-k
Pull request #2323 (2021-05-24 22:43:42)

Fix Route Trait Crash
<!-- Description -->

Fixes https://github.com/apache/camel-k/issues/2313


<!--
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/.github/workflows/build.yml b/.github/workflows/build.yml
index dc0a8c426..9ff81c0f5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -40,7 +40,7 @@ on:
       - 'NOTICE'
 
 concurrency:
-  group: ${{ github.head_ref }}-${{ github.workflow }}
+  group: ${{ github.ref }}-${{ github.workflow }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml
index 6a3e92f20..db4dc2d84 100644
--- a/.github/workflows/builder.yml
+++ b/.github/workflows/builder.yml
@@ -40,7 +40,7 @@ on:
       - 'NOTICE'
 
 concurrency:
-  group: ${{ github.head_ref }}-${{ github.workflow }}
+  group: ${{ github.ref }}-${{ github.workflow }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/knative.yml b/.github/workflows/knative.yml
index 8d0c04b38..e3b5dfae3 100644
--- a/.github/workflows/knative.yml
+++ b/.github/workflows/knative.yml
@@ -40,7 +40,7 @@ on:
       - 'NOTICE'
 
 concurrency:
-  group: ${{ github.head_ref }}-${{ github.workflow }}
+  group: ${{ github.ref }}-${{ github.workflow }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/kubernetes.yml b/.github/workflows/kubernetes.yml
index e83767ac0..e074b617b 100644
--- a/.github/workflows/kubernetes.yml
+++ b/.github/workflows/kubernetes.yml
@@ -40,7 +40,7 @@ on:
       - 'NOTICE'
 
 concurrency:
-  group: ${{ github.head_ref }}-${{ github.workflow }}
+  group: ${{ github.ref }}-${{ github.workflow }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/local.yml b/.github/workflows/local.yml
index 75c360744..0103a3321 100644
--- a/.github/workflows/local.yml
+++ b/.github/workflows/local.yml
@@ -40,7 +40,7 @@ on:
       - 'NOTICE'
 
 concurrency:
-  group: ${{ github.head_ref }}-${{ github.workflow }}
+  group: ${{ github.ref }}-${{ github.workflow }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/openshift.yml b/.github/workflows/openshift.yml
index 259e21dae..e9acac3f4 100644
--- a/.github/workflows/openshift.yml
+++ b/.github/workflows/openshift.yml
@@ -40,7 +40,7 @@ on:
       - 'NOTICE'
 
 concurrency:
-  group: ${{ github.head_ref }}-${{ github.workflow }}
+  group: ${{ github.ref }}-${{ github.workflow }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/upgrade.yml b/.github/workflows/upgrade.yml
index 4b9f9465e..1c6b0fffc 100644
--- a/.github/workflows/upgrade.yml
+++ b/.github/workflows/upgrade.yml
@@ -40,7 +40,7 @@ on:
       - 'NOTICE'
 
 concurrency:
-  group: ${{ github.head_ref }}-${{ github.workflow }}
+  group: ${{ github.ref }}-${{ github.workflow }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 24a15f001..32ffc078d 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -28,7 +28,7 @@ on:
       - "release-*"
 
 concurrency:
-  group: ${{ github.head_ref }}-${{ github.workflow }}
+  group: ${{ github.ref }}-${{ github.workflow }}
   cancel-in-progress: true
 
 jobs:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 13d2b7a80..acb93c672 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
 
 **Closed issues:**
 
+- Timeout when debugging the operator locally [\#2315](https://github.com/apache/camel-k/issues/2315)
 - Structured logs: remaining bits [\#2286](https://github.com/apache/camel-k/issues/2286)
 - \[Docs\] Broken links on autogenerated API [\#2280](https://github.com/apache/camel-k/issues/2280)
 - Normalize Spectrum build logs [\#2276](https://github.com/apache/camel-k/issues/2276)
@@ -30,7 +31,13 @@
 
 **Merged pull requests:**
 
+- Added K3s to the registry section of navbar [\#2325](https://github.com/apache/camel-k/pull/2325) ([oscerd](https://github.com/oscerd))
+- Nav Bar: Titles fixed [\#2324](https://github.com/apache/camel-k/pull/2324) ([oscerd](https://github.com/oscerd))
+- Updated the nav bar with all the installations [\#2322](https://github.com/apache/camel-k/pull/2322) ([oscerd](https://github.com/oscerd))
+- \[TEST\] Increase timeouts for slow infra [\#2319](https://github.com/apache/camel-k/pull/2319) ([bouskaJ](https://github.com/bouskaJ))
+- Fix \#2315: Add leader-election flag to Operator command [\#2316](https://github.com/apache/camel-k/pull/2316) ([johnpoth](https://github.com/johnpoth))
 - Check if the environment can produce structured Maven logs before parsing them [\#2311](https://github.com/apache/camel-k/pull/2311) ([orpiske](https://github.com/orpiske))
+- chore\(ci\): Use `github.ref` instead of `github.head_ref` as concurrency group key [\#2310](https://github.com/apache/camel-k/pull/2310) ([astefanutti](https://github.com/astefanutti))
 - chore\(ci\): Cancel running workflows when PRs are updated [\#2304](https://github.com/apache/camel-k/pull/2304) ([astefanutti](https://github.com/astefanutti))
 - chore: Make phase structured logging key a string [\#2303](https://github.com/apache/camel-k/pull/2303) ([astefanutti](https://github.com/astefanutti))
 - chore\(cli\): Update CRDs when force installing cluster resources [\#2301](https://github.com/apache/camel-k/pull/2301) ([astefanutti](https://github.com/astefanutti))
diff --git a/deploy/traits.yaml b/deploy/traits.yaml
index a62ef6699..aa4636e31 100755
--- a/deploy/traits.yaml
+++ b/deploy/traits.yaml
@@ -53,6 +53,9 @@ traits:
     type: bool
     description: Enable verbose logging on build components that support it (e.g.
       Kaniko build pod).
+  - name: build-time-properties
+    type: '[]string'
+    description: A list of properties to be provided to the build task
 - name: camel
   platform: true
   profiles:
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index af750449e..75027a45a 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -1,12 +1,12 @@
 * xref:installation/installation.adoc[Installation]
 ** xref:installation/minikube.adoc[Minikube]
 ** xref:installation/minishift.adoc[Minishift]
-** xref:installation/crc.adoc[Minishift]
+** xref:installation/crc.adoc[Red Hat CodeReady Containers (CRC)]
 ** xref:installation/digitalocean.adoc[DigitalOcean Kubernetes Cluster]
 ** xref:installation/gke.adoc[Google Kubernetes Engine (GKE)]
 ** xref:installation/openshift.adoc[OpenShift]
 ** xref:installation/iks.adoc[IBM Kubernetes Service (IKS)]
-** xref:installation/k3s.adoc[Kind]
+** xref:installation/k3s.adoc[K3s]
 ** xref:installation/kind.adoc[Kind]
 ** xref:installation/registry/registry.adoc[Configuring Registry]
 *** xref:installation/registry/digitalocean.adoc[DigitalOcean]
@@ -14,6 +14,7 @@
 *** xref:installation/registry/github.adoc[Github Packages]
 *** xref:installation/registry/gcr.adoc[Gcr.io]
 *** xref:installation/registry/icr.adoc[IBM Container Registry]
+*** xref:installation/registry/k3s.adoc[K3s]
 ** xref:installation/scheduling.adoc[Pod scheduling]
 * xref:running/running.adoc[Running]
 ** xref:running/dev-mode.adoc[Dev Mode]
diff --git a/docs/modules/traits/pages/builder.adoc b/docs/modules/traits/pages/builder.adoc
index 26e5ab3d3..c3f42877f 100755
--- a/docs/modules/traits/pages/builder.adoc
+++ b/docs/modules/traits/pages/builder.adoc
@@ -32,6 +32,10 @@ The following configuration options are available:
 | bool
 | Enable verbose logging on build components that support it (e.g. Kaniko build pod).
 
+| builder.build-time-properties
+| []string
+| A list of properties to be provided to the build task
+
 |===
 
 // End of autogenerated code - DO NOT EDIT! (configuration)
diff --git a/examples/user-config/build-property-route.groovy b/examples/user-config/build-property-route.groovy
new file mode 100644
index 000000000..c32e5e654
--- /dev/null
+++ b/examples/user-config/build-property-route.groovy
@@ -0,0 +1,29 @@
+// camel-k: language=groovy
+/*
+ * 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.
+ */
+
+//
+// Provide a build time configuration property (the application name)
+//
+// To run this integrations use:
+//
+// kamel run --build-property=quarkus.application.name=my-super-application build-property-route.groovy --dev
+//
+
+from('timer:build-property')
+    .routeId('build-property')
+    .log('The application name: {{quarkus.application.name}}')
diff --git a/pkg/cmd/operator.go b/pkg/cmd/operator.go
index f3d758e06..a6af5f4e1 100644
--- a/pkg/cmd/operator.go
+++ b/pkg/cmd/operator.go
@@ -39,6 +39,7 @@ func newCmdOperator() (*cobra.Command, *operatorCmdOptions) {
 
 	cmd.Flags().Int32("health-port", 8081, "The port of the health endpoint")
 	cmd.Flags().Int32("monitoring-port", 8080, "The port of the metrics endpoint")
+	cmd.Flags().Bool("leader-election", true, "Use leader election")
 
 	return &cmd, &options
 }
@@ -46,8 +47,9 @@ func newCmdOperator() (*cobra.Command, *operatorCmdOptions) {
 type operatorCmdOptions struct {
 	HealthPort     int32 `mapstructure:"health-port"`
 	MonitoringPort int32 `mapstructure:"monitoring-port"`
+	LeaderElection bool  `mapstructure:"leader-election"`
 }
 
 func (o *operatorCmdOptions) run(_ *cobra.Command, _ []string) {
-	operator.Run(o.HealthPort, o.MonitoringPort)
+	operator.Run(o.HealthPort, o.MonitoringPort, o.LeaderElection)
 }
diff --git a/pkg/cmd/operator/operator.go b/pkg/cmd/operator/operator.go
index c64f50352..d10bd66ac 100644
--- a/pkg/cmd/operator/operator.go
+++ b/pkg/cmd/operator/operator.go
@@ -65,7 +65,7 @@ func printVersion() {
 }
 
 // Run starts the Camel K operator
-func Run(healthPort, monitoringPort int32) {
+func Run(healthPort, monitoringPort int32, leaderElection bool) {
 	rand.Seed(time.Now().UTC().UnixNano())
 
 	flag.Parse()
@@ -102,8 +102,6 @@ func Run(healthPort, monitoringPort int32) {
 		log.Info("Event broadcasting is disabled because of missing permissions to create Events")
 	}
 
-	leaderElection := true
-
 	operatorNamespace := platform.GetOperatorNamespace()
 	if operatorNamespace == "" {
 		// Fallback to using the watch namespace when the operator is not in-cluster.
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 36418a808..1c341d9d6 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -79,6 +79,7 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *runCmdOptions)
 	cmd.Flags().BoolP("wait", "w", false, "Wait for the integration to be running")
 	cmd.Flags().StringP("kit", "k", "", "The kit used to run the integration")
 	cmd.Flags().StringArrayP("property", "p", nil, "Add a camel property")
+	cmd.Flags().StringArray("build-property", nil, "Add a build time property")
 	cmd.Flags().StringArray("configmap", nil, "Add a ConfigMap")
 	cmd.Flags().StringArray("secret", nil, "Add a Secret")
 	cmd.Flags().StringArray("maven-repository", nil, "Add a maven repository")
@@ -127,6 +128,7 @@ type runCmdOptions struct {
 	OpenAPIs        []string `mapstructure:"open-apis" yaml:",omitempty"`
 	Dependencies    []string `mapstructure:"dependencies" yaml:",omitempty"`
 	Properties      []string `mapstructure:"properties" yaml:",omitempty"`
+	BuildProperties []string `mapstructure:"build-properties" yaml:",omitempty"`
 	ConfigMaps      []string `mapstructure:"configmaps" yaml:",omitempty"`
 	Secrets         []string `mapstructure:"secrets" yaml:",omitempty"`
 	Repositories    []string `mapstructure:"maven-repositories" yaml:",omitempty"`
@@ -505,7 +507,7 @@ func (o *runCmdOptions) updateIntegrationCode(c client.Client, sources []string,
 		}
 	}
 
-	err = resolvePodTemplate(context.Background(),o.PodTemplate, &integration.Spec)
+	err = resolvePodTemplate(context.Background(), o.PodTemplate, &integration.Spec)
 	if err != nil {
 		return nil, err
 	}
@@ -550,6 +552,10 @@ func (o *runCmdOptions) updateIntegrationCode(c client.Client, sources []string,
 	for _, item := range o.Properties {
 		integration.Spec.AddConfiguration("property", item)
 	}
+	// convert each build configuration to a builder trait property
+	for _, item := range o.BuildProperties {
+		o.Traits = append(o.Traits, fmt.Sprintf("builder.properties=%s", item))
+	}
 	for _, item := range o.LoggingLevels {
 		integration.Spec.AddConfiguration("property", "logging.level."+item)
 	}
@@ -754,14 +760,14 @@ func resolvePodTemplate(ctx context.Context, templateSrc string, spec *v1.Integr
 	//template is inline
 	templateBytes := []byte(templateSrc)
 
-	jsonTemplate,err:= yaml.ToJSON(templateBytes)
-	if err!= nil {
+	jsonTemplate, err := yaml.ToJSON(templateBytes)
+	if err != nil {
 		jsonTemplate = templateBytes
 	}
 	err = json.Unmarshal(jsonTemplate, &template)
 
 	if err == nil {
-		spec.PodTemplate = &v1.PodSpecTemplate {
+		spec.PodTemplate = &v1.PodSpecTemplate{
 			Spec: template,
 		}
 	}
diff --git a/pkg/controller/integration/util.go b/pkg/controller/integration/util.go
index 986b499e2..2087bd6be 100644
--- a/pkg/controller/integration/util.go
+++ b/pkg/controller/integration/util.go
@@ -122,6 +122,10 @@ func LookupKitForIntegration(ctx context.Context, c k8sclient.Reader, integratio
 
 // HasMatchingTraits compare traits defined on kit against those defined on integration.
 func HasMatchingTraits(kit *v1.IntegrationKit, integration *v1.Integration) (bool, error) {
+	// The kit has no trait, but the integration need some
+	if len(kit.Spec.Traits) == 0 && len(integration.Spec.Traits) > 0 {
+		return false, nil
+	}
 	for name, kitTrait := range kit.Spec.Traits {
 		intTrait, ok := integration.Spec.Traits[name]
 		if !ok {
@@ -153,7 +157,7 @@ func HasMatchingTraits(kit *v1.IntegrationKit, integration *v1.Integration) (boo
 				// in integration trait
 				return false, nil
 			}
-			if iv != cv {
+			if !equal(iv, cv) {
 				// skip it because trait configured on kit has a value that differs from
 				// the one configured on integration
 				return false, nil
@@ -163,3 +167,34 @@ func HasMatchingTraits(kit *v1.IntegrationKit, integration *v1.Integration) (boo
 
 	return true, nil
 }
+
+// We need to try to perform a slice equality in order to prevent a runtime panic
+func equal(a, b interface{}) bool {
+	aSlice, aOk := a.([]interface{})
+	bSlice, bOk := b.([]interface{})
+
+	if aOk && bOk {
+		// Both are slices
+		return sliceEqual(aSlice, bSlice)
+	}
+
+	if aOk || bOk {
+		// One of the 2 is a slice
+		return false
+	}
+
+	// None is a slice
+	return a == b
+}
+
+func sliceEqual(a, b []interface{}) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i, v := range a {
+		if v != b[i] {
+			return false
+		}
+	}
+	return true
+}
diff --git a/pkg/trait/builder.go b/pkg/trait/builder.go
index 07d740103..f2d0775d7 100644
--- a/pkg/trait/builder.go
+++ b/pkg/trait/builder.go
@@ -18,10 +18,14 @@ limitations under the License.
 package trait
 
 import (
+	"fmt"
 	"sort"
+	"strings"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/builder"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 // The builder trait is internally used to determine the best strategy to
@@ -32,6 +36,8 @@ type builderTrait struct {
 	BaseTrait `property:",squash"`
 	// Enable verbose logging on build components that support it (e.g. Kaniko build pod).
 	Verbose *bool `property:"verbose" json:"verbose,omitempty"`
+	// A list of properties to be provided to the build task
+	Properties []string `property:"properties" json:"properties,omitempty"`
 }
 
 func newBuilderTrait() Trait {
@@ -59,7 +65,16 @@ func (t *builderTrait) Configure(e *Environment) (bool, error) {
 }
 
 func (t *builderTrait) Apply(e *Environment) error {
-	builderTask := t.builderTask(e)
+	builderTask, err := t.builderTask(e)
+	if err != nil {
+		e.IntegrationKit.Status.Phase = v1.IntegrationKitPhaseError
+		e.IntegrationKit.Status.SetCondition("IntegrationKitPropertiesFormatValid", corev1.ConditionFalse,
+			"IntegrationKitPropertiesFormatValid", fmt.Sprintf("One or more properties where not formatted as expected: %s", err.Error()))
+		if _, err := e.Client.CamelV1().IntegrationKits(e.IntegrationKit.Namespace).UpdateStatus(e.C, e.IntegrationKit, metav1.UpdateOptions{}); err != nil {
+			return err
+		}
+		return nil
+	}
 
 	e.BuildTasks = append(e.BuildTasks, v1.Task{Builder: builderTask})
 
@@ -118,7 +133,7 @@ func (t *builderTrait) Apply(e *Environment) error {
 	return nil
 }
 
-func (t *builderTrait) builderTask(e *Environment) *v1.BuilderTask {
+func (t *builderTrait) builderTask(e *Environment) (*v1.BuilderTask, error) {
 	task := &v1.BuilderTask{
 		BaseTask: v1.BaseTask{
 			Name: "builder",
@@ -130,6 +145,22 @@ func (t *builderTrait) builderTask(e *Environment) *v1.BuilderTask {
 		Maven:        e.Platform.Status.Build.Maven,
 	}
 
+	// initialize properties if nil
+	if task.Properties == nil {
+		task.Properties = make(map[string]string)
+	}
+	// User provided build time configuration properties
+	if t.Properties != nil {
+		for _, v := range t.Properties {
+			split := strings.Split(v, "=")
+			if len(split) != 2 {
+				return nil, fmt.Errorf("Build time configuration property must have key=value format, it was %v", v)
+			}
+
+			task.Properties[split[0]] = split[1]
+		}
+	}
+
 	steps := make([]builder.Step, 0)
 	steps = append(steps, builder.DefaultSteps...)
 
@@ -143,7 +174,7 @@ func (t *builderTrait) builderTask(e *Environment) *v1.BuilderTask {
 
 	task.Steps = builder.StepIDsFor(steps...)
 
-	return task
+	return task, nil
 }
 
 func getImageName(e *Environment) string {
diff --git a/pkg/trait/route.go b/pkg/trait/route.go
index 4cc05f621..c664ff517 100644
--- a/pkg/trait/route.go
+++ b/pkg/trait/route.go
@@ -82,12 +82,14 @@ func (t *routeTrait) IsAllowedInProfile(profile v1.TraitProfile) bool {
 
 func (t *routeTrait) Configure(e *Environment) (bool, error) {
 	if t.Enabled != nil && !*t.Enabled {
-		e.Integration.Status.SetCondition(
-			v1.IntegrationConditionExposureAvailable,
-			corev1.ConditionFalse,
-			v1.IntegrationConditionRouteNotAvailableReason,
-			"explicitly disabled",
-		)
+		if e.Integration != nil {
+			e.Integration.Status.SetCondition(
+				v1.IntegrationConditionExposureAvailable,
+				corev1.ConditionFalse,
+				v1.IntegrationConditionRouteNotAvailableReason,
+				"explicitly disabled",
+			)
+		}
 
 		return false, nil
 	}
@@ -98,12 +100,14 @@ func (t *routeTrait) Configure(e *Environment) (bool, error) {
 
 	t.service = e.Resources.GetUserServiceForIntegration(e.Integration)
 	if t.service == nil {
-		e.Integration.Status.SetCondition(
-			v1.IntegrationConditionExposureAvailable,
-			corev1.ConditionFalse,
-			v1.IntegrationConditionRouteNotAvailableReason,
-			"no target service found",
-		)
+		if e.Integration != nil {
+			e.Integration.Status.SetCondition(
+				v1.IntegrationConditionExposureAvailable,
+				corev1.ConditionFalse,
+				v1.IntegrationConditionRouteNotAvailableReason,
+				"no target service found",
+			)
+		}
 
 		return false, nil
 	}


Test patch:
diff --git a/e2e/common/config/config_test.go b/e2e/common/config/config_test.go
index 03a0add95..ece48fa79 100644
--- a/e2e/common/config/config_test.go
+++ b/e2e/common/config/config_test.go
@@ -119,5 +119,25 @@ func TestRunConfigExamples(t *testing.T) {
 			Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
 		})
 
+		// Build-Properties
+		t.Run("Build time property", func(t *testing.T) {
+			Expect(Kamel("run", "-n", ns, "./files/build-property-route.groovy", "--build-property", "quarkus.application.name=my-super-application").Execute()).To(Succeed())
+			Eventually(IntegrationPodPhase(ns, "build-property-route"), TestTimeoutMedium).Should(Equal(v1.PodRunning))
+			Eventually(IntegrationCondition(ns, "build-property-route", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue))
+			Eventually(IntegrationLogs(ns, "build-property-route"), TestTimeoutShort).Should(ContainSubstring("my-super-application"))
+			// Don't delete - we need it for next test execution
+		})
+
+		// We need to check also that the property (which is available in the IntegrationKit) is correctly replaced and we don't reuse the same kit
+		t.Run("Build time property updated", func(t *testing.T) {
+			Expect(Kamel("run", "-n", ns, "./files/build-property-route.groovy", "--name", "build-property-route-updated",
+				"--build-property", "quarkus.application.name=my-super-application-updated").Execute()).To(Succeed())
+			Eventually(IntegrationPodPhase(ns, "build-property-route-updated"), TestTimeoutMedium).Should(Equal(v1.PodRunning))
+			Eventually(IntegrationCondition(ns, "build-property-route-updated", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue))
+			Eventually(IntegrationLogs(ns, "build-property-route-updated"), TestTimeoutShort).Should(ContainSubstring("my-super-application-updated"))
+			// Verify the integration kits are different
+			Expect(IntegrationKit(ns, "build-property-route")).ShouldNot(Equal(IntegrationKit(ns, "build-property-route-updated")))
+			Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+		})
 	})
 }
diff --git a/e2e/common/config/files/build-property-route.groovy b/e2e/common/config/files/build-property-route.groovy
new file mode 100644
index 000000000..c32e5e654
--- /dev/null
+++ b/e2e/common/config/files/build-property-route.groovy
@@ -0,0 +1,29 @@
+// camel-k: language=groovy
+/*
+ * 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.
+ */
+
+//
+// Provide a build time configuration property (the application name)
+//
+// To run this integrations use:
+//
+// kamel run --build-property=quarkus.application.name=my-super-application build-property-route.groovy --dev
+//
+
+from('timer:build-property')
+    .routeId('build-property')
+    .log('The application name: {{quarkus.application.name}}')
diff --git a/e2e/yaks/common/kamelet-binding-broker/yaks-config.yaml b/e2e/yaks/common/kamelet-binding-broker/yaks-config.yaml
index e8414f85c..fbaf5889e 100644
--- a/e2e/yaks/common/kamelet-binding-broker/yaks-config.yaml
+++ b/e2e/yaks/common/kamelet-binding-broker/yaks-config.yaml
@@ -32,5 +32,5 @@ pre:
 
     kubectl apply -f timer-source-binding.yaml -n $YAKS_NAMESPACE
     kubectl apply -f logger-sink-binding.yaml -n $YAKS_NAMESPACE
-    kubectl wait kameletbinding timer-source-binding --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
-    kubectl wait kameletbinding logger-sink-binding --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
+    kubectl wait kameletbinding timer-source-binding --for=condition=Ready --timeout=15m -n $YAKS_NAMESPACE
+    kubectl wait kameletbinding logger-sink-binding --for=condition=Ready --timeout=15m -n $YAKS_NAMESPACE
diff --git a/e2e/yaks/common/kamelet-binding-http/yaks-config.yaml b/e2e/yaks/common/kamelet-binding-http/yaks-config.yaml
index de3709b89..847d512d3 100644
--- a/e2e/yaks/common/kamelet-binding-http/yaks-config.yaml
+++ b/e2e/yaks/common/kamelet-binding-http/yaks-config.yaml
@@ -27,4 +27,4 @@ pre:
 
     kamel run display.groovy -w -n $YAKS_NAMESPACE
     cat timer-source-binding-display.yaml | sed  's/{namespace}/'"${YAKS_NAMESPACE}"'/' | kubectl apply -n $YAKS_NAMESPACE -f -
-    kubectl wait kameletbinding timer-source-binding-display --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
+    kubectl wait kameletbinding timer-source-binding-display --for=condition=Ready --timeout=15m -n $YAKS_NAMESPACE
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index 711bba11e..f11e15781 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -409,6 +409,18 @@ func TestRunVolumeFlagWrongPVCFormat(t *testing.T) {
 	assert.NotNil(t, err)
 }
 
+func TestRunBuildPropertyFlag(t *testing.T) {
+	runCmdOptions, rootCmd, _ := initializeRunCmdOptions(t)
+	_, err := test.ExecuteCommand(rootCmd, cmdRun,
+		"--build-property", "build-prop1=val1",
+		"--build-property", "build-prop2=val2",
+		integrationSource)
+	assert.Nil(t, err)
+	assert.Len(t, runCmdOptions.BuildProperties, 2)
+	assert.Equal(t, "build-prop1=val1", runCmdOptions.BuildProperties[0])
+	assert.Equal(t, "build-prop2=val2", runCmdOptions.BuildProperties[1])
+}
+
 func TestRunValidateArgs(t *testing.T) {
 	runCmdOptions, rootCmd, _ := initializeRunCmdOptions(t)
 	args := []string{}
diff --git a/pkg/controller/integration/util_test.go b/pkg/controller/integration/util_test.go
index d6b813b47..0ec08462e 100644
--- a/pkg/controller/integration/util_test.go
+++ b/pkg/controller/integration/util_test.go
@@ -271,3 +271,89 @@ func TestLookupKitForIntegration_DiscardKitsWithIncompatibleTraits(t *testing.T)
 	assert.NotNil(t, i)
 	assert.Equal(t, "my-kit-4", i.Name)
 }
+
+func TestHasMatchingTraits_KitNoTraitShouldNotBePicked(t *testing.T) {
+	integration := &v1.Integration{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: v1.SchemeGroupVersion.String(),
+			Kind:       v1.IntegrationKind,
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "ns",
+			Name:      "my-integration",
+		},
+		Spec: v1.IntegrationSpec{
+			Traits: map[string]v1.TraitSpec{
+				"builder": test.TraitSpecFromMap(t, map[string]interface{}{
+					"enabled": "true",
+				}),
+			},
+		},
+	}
+
+	integrationKitSpec := &v1.IntegrationKit{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: v1.SchemeGroupVersion.String(),
+			Kind:       v1.IntegrationKitKind,
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "ns",
+			Name:      "my-kit",
+		},
+		Spec: v1.IntegrationKitSpec{
+			Traits: map[string]v1.TraitSpec{},
+		},
+	}
+
+	ok, err := HasMatchingTraits(integrationKitSpec, integration)
+	assert.Nil(t, err)
+	assert.False(t, ok)
+}
+
+func TestHasMatchingTraits_KitSameTraitShouldBePicked(t *testing.T) {
+	integration := &v1.Integration{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: v1.SchemeGroupVersion.String(),
+			Kind:       v1.IntegrationKind,
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "ns",
+			Name:      "my-integration",
+		},
+		Spec: v1.IntegrationSpec{
+			Traits: map[string]v1.TraitSpec{
+				"builder": test.TraitSpecFromMap(t, map[string]interface{}{
+					"enabled": "true",
+					"buildTimeProperties": []string{
+						"build-key1=build-value1",
+					},
+				}),
+			},
+		},
+	}
+
+	integrationKitSpec := &v1.IntegrationKit{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: v1.SchemeGroupVersion.String(),
+			Kind:       v1.IntegrationKitKind,
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "ns",
+			Name:      "my-kit",
+		},
+		Spec: v1.IntegrationKitSpec{
+			Traits: map[string]v1.TraitSpec{
+				"builder": test.TraitSpecFromMap(t, map[string]interface{}{
+					"enabled": "true",
+					"buildTimeProperties": []string{
+						"build-key1=build-value1",
+					},
+				}),
+			},
+		},
+	}
+
+	ok, err := HasMatchingTraits(integrationKitSpec, integration)
+	assert.Nil(t, err)
+	assert.True(t, ok)
+}
diff --git a/pkg/trait/builder_test.go b/pkg/trait/builder_test.go
index 2bce1b57d..31fea5211 100644
--- a/pkg/trait/builder_test.go
+++ b/pkg/trait/builder_test.go
@@ -27,6 +27,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/util"
 	"github.com/apache/camel-k/pkg/util/camel"
 	"github.com/apache/camel-k/pkg/util/defaults"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
@@ -150,3 +151,21 @@ func createBuilderTestEnv(cluster v1.IntegrationPlatformCluster, strategy v1.Int
 func NewBuilderTestCatalog() *Catalog {
 	return NewCatalog(context.TODO(), nil)
 }
+
+func TestBuildtimeConfigurationBuilderTrait(t *testing.T) {
+	env := createBuilderTestEnv(v1.IntegrationPlatformClusterKubernetes, v1.IntegrationPlatformBuildPublishStrategyKaniko)
+	builderTrait := createNominalBuilderTraitTest()
+	builderTrait.Properties = append(builderTrait.Properties, "build-time-prop1=build-time-value1")
+
+	err := builderTrait.Apply(env)
+
+	assert.Nil(t, err)
+	assert.Equal(t, "build-time-value1", env.BuildTasks[0].Builder.Properties["build-time-prop1"])
+}
+
+func createNominalBuilderTraitTest() *builderTrait {
+	builderTrait := newBuilderTrait().(*builderTrait)
+	builderTrait.Enabled = util.BoolP(true)
+
+	return builderTrait
+}
diff --git a/pkg/trait/route_test.go b/pkg/trait/route_test.go
index 11b7eea04..764fedc4e 100644
--- a/pkg/trait/route_test.go
+++ b/pkg/trait/route_test.go
@@ -140,6 +140,20 @@ func TestRoute_Disabled(t *testing.T) {
 	assert.Nil(t, route)
 }
 
+func TestRoute_Configure_IntegrationKitOnly(t *testing.T) {
+	name := xid.New().String()
+	environment := createTestRouteEnvironment(t, name)
+	environment.Integration = nil
+
+	routeTrait := newRouteTrait().(*routeTrait)
+	enabled := false
+	routeTrait.Enabled = &enabled
+
+	result, err := routeTrait.Configure(environment)
+	assert.False(t, result)
+	assert.Nil(t, err)
+}
+
 func TestRoute_TLS(t *testing.T) {
 	name := xid.New().String()
 	environment := createTestRouteEnvironment(t, name)