Repository: apache/camel-k
Pull request #2377 (2021-06-08 13:10:10)

Removed the deprecated --logging-level option (issue #2000)
<!-- 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
```

orpiske: > We'll need to rebase camel-main on main at some point.

+1. I think we need to do it this week to not get too far behind on camel-main
oscerd: > > We'll need to rebase camel-main on main at some point.
> 
> +1. I think we need to do it this week to not get too far behind on camel-main

Yeah, here and on camel-k-runtime
orpiske: > Nice cleaning work. I think we should include some documentation though in order to let the people know how to use/edit the logging trait. If we have it missing at all, we should take the opportunity and add a little entry (maybe under running/log menu section) explaining how to do that.

Thanks. We actually have the `logging.trait` documented within the traits: https://camel.apache.org/camel-k/latest/traits/logging.html. 

Maybe we should make it more easily accessible in the home page?


astefanutti: > > Nice cleaning work. I think we should include some documentation though in order to let the people know how to use/edit the logging trait. If we have it missing at all, we should take the opportunity and add a little entry (maybe under running/log menu section) explaining how to do that.
> 
> Thanks. We actually have the `logging.trait` documented within the traits: https://camel.apache.org/camel-k/latest/traits/logging.html.
> 
> Maybe we should make it more easily accessible in the home page?

I'd suggest to create a "Logging" page, under the "Observability" one, along with the existing monitoring documentation: https://camel.apache.org/camel-k/latest/observability/monitoring.html. At least that was the original intent of the "Observability" section, grouping Logging, Monitoring, and Tracing.
squakez: Yeah, I think we should have a dedicated section that will link to the log trait page as well. I'd say a short entry under /running/log would be enough for now.
orpiske: @astefanutti @squakez Thanks. Let me add the documentation as part of this PR before we merge this. 

I'm also wondering if we should include some simpler/smaller version of this as well: https://camel.apache.org/blog/2021/05/new-camel-k-logging-features/. 

If you have any suggestions, please let me know :) 


squakez: > @astefanutti @squakez Thanks. Let me add the documentation as part of this PR before we merge this.
> 
> I'm also wondering if we should include some simpler/smaller version of this as well: https://camel.apache.org/blog/2021/05/new-camel-k-logging-features/.
> 
> If you have any suggestions, please let me know :)

Thanks @orpiske, I think a short version with basic info should be okey. From there you can add a link to the trait page where we explain more in details all those aspects.
astefanutti: @orpiske the content from https://camel.apache.org/blog/2021/05/new-camel-k-logging-features/ is would be exactly what's needed. I'd suggest to mimic the existing organisation for monitoring documentation, that is two pages, Operator and Integration, within Observability > Logging. Also a link from the `Running` page to the Integration logging could be good as suggested by @squakez.
orpiske: Added a bit of documentation. 
astefanutti: Awesome, thanks @orpiske!

Patch:
diff --git a/docs/modules/ROOT/pages/observability/integration-logging.adoc b/docs/modules/ROOT/pages/observability/integration-logging.adoc
new file mode 100644
index 000000000..6c6f87723
--- /dev/null
+++ b/docs/modules/ROOT/pages/observability/integration-logging.adoc
@@ -0,0 +1,36 @@
+[[integration-logging]]
+= Camel K Integration Logging
+
+Starting with https://camel.apache.org/blog/2021/05/new-camel-k-logging-features/[Camel K 1.5], we introduced a new
+xref:traits:logging.adoc[logging trait] that simplifies access to the logging configuration available on the runtime.
+
+
+[[integration-logging-level]]
+== Logging Level
+
+To configure the log level, pass one of the supported log levels to the trait. For example:
+
+```
+--trait logging.level=DEBUG
+```
+
+
+[[integration-logging-format]]
+== Logging Format
+
+The format of the logging message can also be configured using one of the supported log format options from
+https://quarkus.io/guides/logging[Quarkus] (which provides the logging framework for Camel Quarkus which is leveraged by
+Camel K).
+
+A sample custom configuration would look like this:
+
+```
+--trait logging.format='%d{HH:mm:ss} %-5p (%t) %s%e%n'.
+```
+
+[[integration-logging-structured]]
+== Structured Logs in Json Format
+
+Like the operator, the integrations will use structured logs to facilitate collection and parsing. This can be turned on
+or off using the `--trait logging.json` trait. Subsequently, it can also be customized to output pretty json logs by
+setting the `logging.json.pretty-print` trait.
\ No newline at end of file
diff --git a/docs/modules/ROOT/pages/observability/logging.adoc b/docs/modules/ROOT/pages/observability/logging.adoc
new file mode 100644
index 000000000..ee2cd22d9
--- /dev/null
+++ b/docs/modules/ROOT/pages/observability/logging.adoc
@@ -0,0 +1,6 @@
+[[logging]]
+= Camel K Logging
+
+The Camel K logging has two major areas. The first one is related to the logging provided by the
+xref:observability/operator-logging.adoc[operator itself], whereas the second is related to the
+xref:observability/integration-logging.adoc[integrations] ran by Camel K.
\ No newline at end of file
diff --git a/docs/modules/ROOT/pages/observability/operator-logging.adoc b/docs/modules/ROOT/pages/observability/operator-logging.adoc
new file mode 100644
index 000000000..172f87faa
--- /dev/null
+++ b/docs/modules/ROOT/pages/observability/operator-logging.adoc
@@ -0,0 +1,16 @@
+[[logging]]
+= Camel K Operator Logging
+
+The Camel K Operator logging is mostly managed by the Kubernetes infrastructure, with some specific aspects managed by
+Camel K itself. The operator uses https://kubernetes.io/blog/2020/09/04/kubernetes-1-19-introducing-structured-logs/[structured logs]
+so that the logs are more easily parseable.
+
+This includes the output of components managed by the operator, such as the maven build, and the integration container build.
+For example, the maven build logs should be displayed like this:
+
+
+```
+{"level":"info","ts":1620393185.321101,"logger":"camel-k.maven.build","msg":"Downloading from repository-000: http://my.repository.com:8081/artifactory/fuse-brno/org/jboss/shrinkwrap/resolver/shrinkwrap-resolver-bom/2.2.4/shrinkwrap-resolver-bom-2.2.4.pom"}
+```
+
+There are some exceptions to this, though, and they are mostly related to running the operator for development purposes.
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 9312782c2..8c2e45f36 100644
--- a/go.mod
+++ b/go.mod
@@ -18,7 +18,7 @@ require (
 	github.com/google/go-github/v32 v32.1.0
 	github.com/google/uuid v1.2.0
 	github.com/jpillora/backoff v1.0.0
-	github.com/magiconair/properties v1.8.1
+	github.com/magiconair/properties v1.8.5
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/mitchellh/mapstructure v1.1.2
 	github.com/onsi/gomega v1.10.3
diff --git a/go.sum b/go.sum
index b633fdfc6..5185b9489 100644
--- a/go.sum
+++ b/go.sum
@@ -660,6 +660,8 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
+github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index b097cf188..403a7996a 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -30,6 +30,7 @@ import (
 	"strings"
 	"syscall"
 
+	"github.com/apache/camel-k/pkg/util/property"
 	"github.com/magiconair/properties"
 	"github.com/mitchellh/mapstructure"
 	"github.com/pkg/errors"
@@ -90,7 +91,6 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *runCmdOptions)
 	cmd.Flags().Bool("use-flows", true, "Write yaml sources as Flow objects in the integration custom resource")
 	cmd.Flags().String("profile", "", "Trait profile used for deployment")
 	cmd.Flags().StringArrayP("trait", "t", nil, "Configure a trait. E.g. \"-t service.enabled=false\"")
-	cmd.Flags().StringArray("logging-level", nil, "Configure the logging level. e.g. \"--logging-level org.apache.camel=DEBUG\"")
 	cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml")
 	cmd.Flags().Bool("compression", false, "Enable storage of sources and resources as a compressed binary blobs")
 	cmd.Flags().StringArray("resource", nil, "Add a resource")
@@ -135,7 +135,6 @@ type runCmdOptions struct {
 	Secrets         []string `mapstructure:"secrets" yaml:",omitempty"`
 	Repositories    []string `mapstructure:"maven-repositories" yaml:",omitempty"`
 	Traits          []string `mapstructure:"traits" yaml:",omitempty"`
-	LoggingLevels   []string `mapstructure:"logging-levels" yaml:",omitempty"`
 	Volumes         []string `mapstructure:"volumes" yaml:",omitempty"`
 	EnvVars         []string `mapstructure:"envs" yaml:",omitempty"`
 	// Deprecated: PropertyFiles has been deprecated in 1.5
@@ -586,15 +585,16 @@ func (o *runCmdOptions) updateIntegrationCode(c client.Client, sources []string,
 		for _, k := range props.Keys() {
 			v, ok := props.Get(k)
 			if ok {
-				o.Traits = append(o.Traits, fmt.Sprintf("builder.properties=%s", escapePropertyFileItem(k)+"="+escapePropertyFileItem(v)))
+				entry, err := property.EncodePropertyFileEntry(k, v)
+				if err != nil {
+					return nil, err
+				}
+				o.Traits = append(o.Traits, fmt.Sprintf("builder.properties=%s", entry))
 			} else {
 				return nil, err
 			}
 		}
 	}
-	for _, item := range o.LoggingLevels {
-		integration.Spec.AddConfiguration("property", "logging.level."+item)
-	}
 	for _, item := range o.Configs {
 		if config, parseErr := ParseConfigOption(item); parseErr == nil {
 			if applyConfigOptionErr := ApplyConfigOption(config, &integration.Spec, c, namespace, o.Compression); applyConfigOptionErr != nil {
@@ -789,9 +789,13 @@ func isLocalAndFileExists(fileName string) (bool, error) {
 func addIntegrationProperties(props *properties.Properties, spec *v1.IntegrationSpec) error {
 	for _, k := range props.Keys() {
 		v, _ := props.Get(k)
+		entry, err := property.EncodePropertyFileEntry(k, v)
+		if err != nil {
+			return err
+		}
 		spec.AddConfiguration(
 			"property",
-			escapePropertyFileItem(k)+"="+escapePropertyFileItem(v),
+			entry,
 		)
 	}
 	return nil
@@ -809,12 +813,6 @@ func loadPropertyFile(fileName string) (*properties.Properties, error) {
 	return p, nil
 }
 
-func escapePropertyFileItem(item string) string {
-	item = strings.ReplaceAll(item, `=`, `\=`)
-	item = strings.ReplaceAll(item, `:`, `\:`)
-	return item
-}
-
 func resolvePodTemplate(ctx context.Context, templateSrc string, spec *v1.IntegrationSpec) (err error) {
 	// check if template is set
 	if templateSrc == "" {
diff --git a/pkg/controller/kameletbinding/common.go b/pkg/controller/kameletbinding/common.go
index 4ad94ba60..7591f8063 100644
--- a/pkg/controller/kameletbinding/common.go
+++ b/pkg/controller/kameletbinding/common.go
@@ -20,7 +20,6 @@ package kameletbinding
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"sort"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
@@ -31,6 +30,7 @@ import (
 	"github.com/apache/camel-k/pkg/util/bindings"
 	"github.com/apache/camel-k/pkg/util/knative"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/property"
 	"github.com/pkg/errors"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -127,7 +127,11 @@ func createIntegrationFor(ctx context.Context, c client.Client, kameletbinding *
 			it.Spec.Traits[k] = v
 		}
 		for k, v := range b.ApplicationProperties {
-			propList = append(propList, fmt.Sprintf("%s=%s", k, v))
+			entry, err := property.EncodePropertyFileEntry(k, v)
+			if err != nil {
+				return nil, err
+			}
+			propList = append(propList, entry)
 		}
 	}
 
diff --git a/pkg/trait/builder.go b/pkg/trait/builder.go
index a7e354a0d..ec8280703 100644
--- a/pkg/trait/builder.go
+++ b/pkg/trait/builder.go
@@ -20,8 +20,8 @@ package trait
 import (
 	"fmt"
 	"sort"
-	"strings"
 
+	"github.com/apache/camel-k/pkg/util/property"
 	corev1 "k8s.io/api/core/v1"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
@@ -156,12 +156,12 @@ func (t *builderTrait) builderTask(e *Environment) (*v1.BuilderTask, error) {
 	// User provided Maven properties
 	if t.Properties != nil {
 		for _, v := range t.Properties {
-			split := strings.Split(v, "=")
-			if len(split) != 2 {
+			key, value := property.SplitPropertyFileEntry(v)
+			if len(key) == 0 || len(value) == 0 {
 				return nil, fmt.Errorf("maven property must have key=value format, it was %v", v)
 			}
 
-			task.Maven.Properties[split[0]] = split[1]
+			task.Maven.Properties[key] = value
 		}
 	}
 
diff --git a/pkg/trait/container.go b/pkg/trait/container.go
index 65781bb08..60bac5a10 100644
--- a/pkg/trait/container.go
+++ b/pkg/trait/container.go
@@ -252,7 +252,9 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 		for _, envVar := range e.EnvVars {
 			envvar.SetVar(&container.Env, envVar)
 		}
-		if props := e.computeApplicationProperties(); props != nil {
+		if props, err := e.computeApplicationProperties(); err != nil {
+			return err
+		} else if props != nil {
 			e.Resources.Add(props)
 		}
 
@@ -291,7 +293,9 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 				envvar.SetVar(&container.Env, env)
 			}
 		}
-		if props := e.computeApplicationProperties(); props != nil {
+		if props, err := e.computeApplicationProperties(); err != nil {
+			return err
+		} else if props != nil {
 			e.Resources.Add(props)
 		}
 
@@ -318,7 +322,9 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 		for _, envVar := range e.EnvVars {
 			envvar.SetVar(&container.Env, envVar)
 		}
-		if props := e.computeApplicationProperties(); props != nil {
+		if props, err := e.computeApplicationProperties(); err != nil {
+			return err
+		} else if props != nil {
 			e.Resources.Add(props)
 		}
 
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index 2ca1095ab..6e7426ce8 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -25,6 +25,8 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/apache/camel-k/pkg/util/property"
+	"github.com/pkg/errors"
 	appsv1 "k8s.io/api/apps/v1"
 	"k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
@@ -363,12 +365,11 @@ func (e *Environment) DetermineCatalogNamespace() string {
 	return ""
 }
 
-func (e *Environment) computeApplicationProperties() *corev1.ConfigMap {
+func (e *Environment) computeApplicationProperties() (*corev1.ConfigMap, error) {
 	// application properties
-	applicationProperties := ""
-
-	for key, val := range e.ApplicationProperties {
-		applicationProperties += fmt.Sprintf("%s=%s\n", key, val)
+	applicationProperties, err := property.EncodePropertyFile(e.ApplicationProperties)
+	if err != nil {
+		return nil, errors.Wrapf(err, "could not compute application properties")
 	}
 
 	if applicationProperties != "" {
@@ -388,10 +389,10 @@ func (e *Environment) computeApplicationProperties() *corev1.ConfigMap {
 			Data: map[string]string{
 				"application.properties": applicationProperties,
 			},
-		}
+		}, nil
 	}
 
-	return nil
+	return nil, nil
 }
 
 func (e *Environment) computeConfigMaps() []ctrl.Object {
@@ -403,6 +404,7 @@ func (e *Environment) computeConfigMaps() []ctrl.Object {
 	userProperties := ""
 
 	for _, prop := range e.collectConfigurationPairs("property") {
+		// properties in resource configuration are expected to be pre-encoded using properties format
 		userProperties += fmt.Sprintf("%s=%s\n", prop.Name, prop.Value)
 	}
 
diff --git a/pkg/trait/util.go b/pkg/trait/util.go
index 695b7ee41..366d59498 100644
--- a/pkg/trait/util.go
+++ b/pkg/trait/util.go
@@ -25,6 +25,7 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/apache/camel-k/pkg/util/property"
 	user "github.com/mitchellh/go-homedir"
 	"github.com/scylladb/go-set/strset"
 
@@ -93,14 +94,7 @@ func collectConfigurationPairs(configurationType string, configurable ...v1.Conf
 
 		for _, entry := range entries {
 			if entry.Type == configurationType {
-				pair := strings.SplitN(entry.Value, "=", 2)
-				var k, v string
-				if len(pair) >= 1 {
-					k = strings.TrimSpace(pair[0])
-				}
-				if len(pair) == 2 {
-					v = strings.TrimSpace(pair[1])
-				}
+				k, v := property.SplitPropertyFileEntry(entry.Value)
 				if k == "" {
 					continue
 				}
diff --git a/pkg/util/property/property.go b/pkg/util/property/property.go
new file mode 100644
index 000000000..e321d03a4
--- /dev/null
+++ b/pkg/util/property/property.go
@@ -0,0 +1,67 @@
+/*
+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 property
+
+import (
+	"bytes"
+	"strings"
+
+	"github.com/magiconair/properties"
+	"github.com/pkg/errors"
+)
+
+// EncodePropertyFileEntry converts the given key/value pair into a .properties file entry
+func EncodePropertyFileEntry(key, value string) (string, error) {
+	p := properties.NewProperties()
+	p.DisableExpansion = true
+	if _, _, err := p.Set(key, value); err != nil {
+		return "", err
+	}
+	buf := new(bytes.Buffer)
+	if _, err := p.Write(buf, properties.UTF8); err != nil {
+		return "", err
+	}
+	pair := strings.TrimSuffix(buf.String(), "\n")
+	return pair, nil
+}
+
+// EncodePropertyFile encodes a property map into a .properties file
+func EncodePropertyFile(sourceProperties map[string]string) (string, error) {
+	props := properties.LoadMap(sourceProperties)
+	props.DisableExpansion = true
+	props.Sort()
+	buf := new(bytes.Buffer)
+	_, err := props.Write(buf, properties.UTF8)
+	if err != nil {
+		return "", errors.Wrapf(err, "could not compute application properties")
+	}
+	return buf.String(), nil
+}
+
+// SplitPropertyFileEntry splits an encoded property into key/value pair, without decoding the content
+func SplitPropertyFileEntry(entry string) (string, string) {
+	pair := strings.SplitN(entry, "=", 2)
+	var k, v string
+	if len(pair) >= 1 {
+		k = strings.TrimSpace(pair[0])
+	}
+	if len(pair) == 2 {
+		v = strings.TrimSpace(pair[1])
+	}
+	return k, v
+}


Test patch:
diff --git a/e2e/yaks/common/kamelet-binding-property-encoding/properties-binding.yaml b/e2e/yaks/common/kamelet-binding-property-encoding/properties-binding.yaml
new file mode 100644
index 000000000..f482ecee3
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-property-encoding/properties-binding.yaml
@@ -0,0 +1,19 @@
+kind: KameletBinding
+apiVersion: camel.apache.org/v1alpha1
+metadata:
+  name: properties-binding
+spec:
+  source:
+    ref:
+      apiVersion: camel.apache.org/v1alpha1
+      kind: Kamelet
+      name: timer-source
+    properties:
+      message: |
+        {
+          "content": "thecontent",
+          "key2": "val2"
+        }
+      contentType: "application/json"
+  sink:
+    uri: http://probe-service/events
diff --git a/e2e/yaks/common/kamelet-binding-property-encoding/properties.feature b/e2e/yaks/common/kamelet-binding-property-encoding/properties.feature
new file mode 100644
index 000000000..1d7f7958d
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-property-encoding/properties.feature
@@ -0,0 +1,26 @@
+Feature: Ensure that Kamelets support multiline configuration
+
+  Background:
+    Given Disable auto removal of Kamelet resources
+    Given Disable auto removal of Kubernetes resources
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 60   |
+      | delayBetweenAttempts | 3000 |
+
+  Scenario: Wait for binding to start
+    Given create Kubernetes service probe-service with target port 8080
+    Then Camel-K integration properties-binding should be running
+
+  Scenario: Verify binding
+    Given HTTP server "probe-service"
+    And HTTP server timeout is 300000 ms
+    Then expect HTTP request body
+    """
+    {
+      "content": "thecontent",
+      "key2": "val2"
+    }
+    """
+    And expect HTTP request header: Content-Type="application/json;charset=UTF-8"
+    And receive POST /events
+    And delete KameletBinding properties-binding
diff --git a/e2e/yaks/common/kamelet-binding-property-encoding/timer-source.kamelet.yaml b/e2e/yaks/common/kamelet-binding-property-encoding/timer-source.kamelet.yaml
new file mode 100644
index 000000000..a9342e27c
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-property-encoding/timer-source.kamelet.yaml
@@ -0,0 +1,69 @@
+# ---------------------------------------------------------------------------
+# 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
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "main-SNAPSHOT"
+    camel.apache.org/kamelet.icon: data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gU3ZnIFZlY3RvciBJY29ucyA6IGh0dHA6Ly93d3cub25saW5ld2ViZm9udHMuY29tL2ljb24gLS0+DQo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwMCAxMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxMDAwIDEwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPG1ldGFkYXRhPiBTdmcgVmVjdG9yIEljb25zIDogaHR0cDovL3d3dy5vbmxpbmV3ZWJmb250cy5jb20vaWNvbiA8L21ldGFkYXRhPg0KPGc+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsNTExLjAwMDAwMCkgc2NhbGUoMC4xMDAwMDAsLTAuMTAwMDAwKSI+PHBhdGggZD0iTTM4ODguMSw0Nzc0Ljl2LTIzNS4xaDQxMS40aDQxNC4zbC04LjgtMzI5LjFsLTguOC0zMzJsLTExNy41LTguOGMtMjI5LjItMTQuNy02MjAtOTkuOS05MjUuNi0xOTYuOUMyMjU3LjQsMzIyMC42LDExNjcuMiwyMDY1LjgsODAyLjksNjQ5LjZjLTUxMS4zLTE5ODYuMywzODQuOS00MDAyLDIyMDYuNy00OTY1LjhjMzAyLjYtMTYxLjYsNzU4LjEtMzIwLjIsMTE1NC44LTQwNS41YzQyNi4xLTkxLjEsMTI1MS43LTkxLjEsMTY4MC43LDBjMTc2OC45LDM4MiwzMDQ0LjEsMTY1Ny4yLDM0MjYuMSwzNDI2LjFjOTEuMSw0MjYuMSw5MS4xLDEyNTQuNiwwLDE2NzcuOGMtNDIwLjIsMTk0Mi4yLTE5MzYuNCwzMzAyLjYtMzg5MC4zLDM0OTYuNmwtMTk5LjgsMjAuNnYzMjAuM3YzMjAuM2g0MTEuNGg0MTEuNHYyMzUuMVY1MDEwSDQ5NDUuOUgzODg4LjFWNDc3NC45eiBNNTc1My45LDMzNDkuOWM3NzguNy0xNjEuNiwxNDE5LjItNTA4LjMsMTk4My40LTEwNzIuNWM1NjQuMi01NjEuMiw4ODcuNC0xMTU3LjcsMTA2MC43LTE5NDIuMmM5OS45LTQzNy44LDk5LjktMTE0MywzLTE1ODAuOEM4NTYzLTIzMDYuNCw3OTY2LjUtMzE1OC41LDcwNDMuOS0zNzUyYy0zMzUtMjE0LjUtNzg3LjUtMzk2LjctMTI0OC44LTQ5OS41Yy00MzcuOC05Ny0xMTQzLTk3LTE1ODAuOCwyLjljLTc4NC41LDE3My4zLTEzODEsNDk2LjYtMTk0Mi4yLDEwNjAuN2MtNTcwLDU2Ny4xLTkwNy45LDExOTguOC0xMDc4LjQsMTk5OC4xYy03My41LDM0Ni43LTczLjUsMTEyMi40LDAsMTQ2OS4yYzE3MC40LDc5OS4yLDUwOC4zLDE0MzEsMTA3OC40LDE5OThDMjg5NSwyOTAwLjMsMzYzOC40LDMyNzMuNSw0NDkzLjQsMzM5MUM0Nzc4LjQsMzQzMi4xLDU0NzQuOCwzNDA4LjYsNTc1My45LDMzNDkuOXoiLz48cGF0aCBkPSJNNDcxMC44LDEzNzUuM1YyMDUuOUw0NTUyLjIsNjcuOGMtMzE3LjMtMjc5LjEtMzQwLjgtNjc4LjctNTUuOC05OTMuMWMyODcuOS0zMjAuMyw2OTMuNC0zMTcuMywxMDEzLjcsNS45bDE3MC40LDE3MC40aDEwNDMuMWgxMDQzLjFWLTUxNFYtMjc5SDY3MjkuNUg1NjkyLjJsLTQ5LjksMTE0LjZjLTU4LjgsMTMyLjItMjUyLjcsMzE3LjMtMzc2LjEsMzYxLjRsLTg1LjIsMjkuNHYxMTU3Ljd2MTE1Ny43aC0yMzUuMWgtMjM1LjFWMTM3NS4zeiBNNTE2Ni4zLTI5My42YzE0Ni45LTE0NCw0NC4xLTM5Ni43LTE2MS42LTM5Ni43Yy01NS44LDAtMTE3LjUsMjYuNC0xNjEuNiw3My40Yy00Nyw0NC4xLTczLjUsMTA1LjgtNzMuNSwxNjEuNnMyNi40LDExNy41LDczLjUsMTYxLjZjNDQuMSw0NywxMDUuOCw3My41LDE2MS42LDczLjVDNTA2MC41LTIyMC4yLDUxMjIuMi0yNDYuNyw1MTY2LjMtMjkzLjZ6Ii8+PC9nPjwvZz4NCjwvc3ZnPg==
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.group: "Timer"
+  labels:
+    camel.apache.org/kamelet.type: source
+    camel.apache.org/kamelet.verified: "true"
+spec:
+  definition:
+    title: Timer Source
+    description: Produces periodic events with a custom payload.
+    required:
+      - message
+    type: object
+    properties:
+      period:
+        title: Period
+        description: The interval between two events in milliseconds
+        type: integer
+        default: 1000
+      message:
+        title: Message
+        description: The message to generate
+        type: string
+        example: hello world
+      contentType:
+        title: Content Type
+        description: The content type of the message being generated
+        type: string
+        default: text/plain
+  dependencies:
+    - "camel:core"
+    - "camel:timer"
+    - "camel:kamelet"
+  flow:
+    from:
+      uri: timer:tick
+      parameters:
+        period: "{{period}}"
+      steps:
+        - set-body:
+            constant: "{{message}}"
+        - set-header:
+            name: "Content-Type"
+            constant: "{{contentType}}"
+        - to: kamelet:sink
diff --git a/e2e/yaks/common/kamelet-binding-property-encoding/yaks-config.yaml b/e2e/yaks/common/kamelet-binding-property-encoding/yaks-config.yaml
new file mode 100644
index 000000000..650df12bc
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-property-encoding/yaks-config.yaml
@@ -0,0 +1,27 @@
+# ---------------------------------------------------------------------------
+# 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 timer-source.kamelet.yaml -n $YAKS_NAMESPACE
+    kubectl apply -f properties-binding.yaml -n $YAKS_NAMESPACE
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index 1d9192fc6..e15354d82 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -154,18 +154,6 @@ func TestRunLabelWrongFormatFlag(t *testing.T) {
 	assert.NotNil(t, err)
 }
 
-func TestRunLoggingLevelFlag(t *testing.T) {
-	runCmdOptions, rootCmd, _ := initializeRunCmdOptions(t)
-	_, err := test.ExecuteCommand(rootCmd, cmdRun,
-		"--logging-level", "lev1",
-		"--logging-level", "lev2",
-		integrationSource)
-	assert.Nil(t, err)
-	assert.Len(t, runCmdOptions.LoggingLevels, 2)
-	assert.Equal(t, "lev1", runCmdOptions.LoggingLevels[0])
-	assert.Equal(t, "lev2", runCmdOptions.LoggingLevels[1])
-}
-
 func TestRunLogsFlag(t *testing.T) {
 	runCmdOptions, rootCmd, _ := initializeRunCmdOptions(t)
 	_, err := test.ExecuteCommand(rootCmd, cmdRun, "--logs", integrationSource)
@@ -245,10 +233,13 @@ func TestRunPropertyFileFlagMissingFileExtension(t *testing.T) {
 
 const TestPropertyFileContent = `
 a=b
-c\=d=e
-d=c\=e
+# There's an issue in the properties lib: https://github.com/magiconair/properties/issues/59
+# so the following cases have been commented. Btw, we don't use equal sign in keys
+#c\=d=e
+#d=c\=e
 #ignore=me
-f=g\:h
+f=g:h
+i=j\nk
 `
 
 func TestAddPropertyFile(t *testing.T) {
@@ -265,11 +256,12 @@ func TestAddPropertyFile(t *testing.T) {
 	properties, err := extractProperties("file:" + tmpFile.Name())
 	assert.Nil(t, err)
 	assert.Nil(t, addIntegrationProperties(properties, &spec))
-	assert.Equal(t, 4, len(spec.Configuration))
-	assert.Equal(t, `a=b`, spec.Configuration[0].Value)
-	assert.Equal(t, `c\=d=e`, spec.Configuration[1].Value)
-	assert.Equal(t, `d=c\=e`, spec.Configuration[2].Value)
-	assert.Equal(t, `f=g\:h`, spec.Configuration[3].Value)
+	assert.Equal(t, 3, len(spec.Configuration))
+	assert.Equal(t, `a = b`, spec.Configuration[0].Value)
+	//assert.Equal(t, `c\=d = e`, spec.Configuration[1].Value)
+	//assert.Equal(t, `d = c\=e`, spec.Configuration[2].Value)
+	assert.Equal(t, `f = g:h`, spec.Configuration[1].Value)
+	assert.Equal(t, `i = j\nk`, spec.Configuration[2].Value)
 }
 
 func TestRunPropertyFileFlag(t *testing.T) {
@@ -291,6 +283,15 @@ func TestRunPropertyFileFlag(t *testing.T) {
 	assert.Equal(t, tmpFile.Name(), runCmdOptions.PropertyFiles[0])
 }
 
+func TestRunProperty(t *testing.T) {
+	spec := v1.IntegrationSpec{}
+	properties, err := extractProperties(`key=value\nnewline`)
+	assert.Nil(t, err)
+	assert.Nil(t, addIntegrationProperties(properties, &spec))
+	assert.Equal(t, 1, len(spec.Configuration))
+	assert.Equal(t, `key = value\nnewline`, spec.Configuration[0].Value)
+}
+
 func TestRunResourceFlag(t *testing.T) {
 	runCmdOptions, rootCmd, _ := initializeRunCmdOptions(t)
 	_, err := test.ExecuteCommand(rootCmd, cmdRun,
diff --git a/pkg/trait/trait_types_test.go b/pkg/trait/trait_types_test.go
index 137f0f91d..2a68853ae 100644
--- a/pkg/trait/trait_types_test.go
+++ b/pkg/trait/trait_types_test.go
@@ -18,6 +18,9 @@ limitations under the License.
 package trait
 
 import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
 	serving "knative.dev/serving/pkg/apis/serving/v1"
 
 	appsv1 "k8s.io/api/apps/v1"
@@ -29,6 +32,19 @@ import (
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 )
 
+func TestMultilinePropertiesHandled(t *testing.T) {
+	e := Environment{
+		ApplicationProperties: map[string]string{
+			"prop": "multi\nline",
+		},
+		Integration: &v1.Integration{},
+	}
+	cm, err := e.computeApplicationProperties()
+	assert.NoError(t, err)
+	assert.NotNil(t, cm)
+	assert.Equal(t, "prop = multi\\nline\n", cm.Data["application.properties"])
+}
+
 func createNominalDeploymentTraitTest() (*Environment, *appsv1.Deployment) {
 	deployment := &appsv1.Deployment{
 		ObjectMeta: metav1.ObjectMeta{
diff --git a/pkg/util/property/property_test.go b/pkg/util/property/property_test.go
new file mode 100644
index 000000000..ea705a8b8
--- /dev/null
+++ b/pkg/util/property/property_test.go
@@ -0,0 +1,50 @@
+/*
+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 property
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPropertyEncoding(t *testing.T) {
+	enc, err := EncodePropertyFileEntry("a", "b")
+	assert.NoError(t, err)
+	assert.Equal(t, "a = b", enc)
+	enc, err = EncodePropertyFileEntry("c", "d\ne")
+	assert.NoError(t, err)
+	assert.Equal(t, "c = d\\ne", enc)
+}
+
+func TestPropertyFileEncoding(t *testing.T) {
+	props := map[string]string{
+		"c": "d\ne",
+		"a": "b",
+	}
+	enc, err := EncodePropertyFile(props)
+	assert.NoError(t, err)
+	assert.Equal(t, "a = b\nc = d\\ne\n", enc)
+}
+
+func TestSplitPropertyEntry(t *testing.T) {
+	entry := "c = d\\ne"
+	k, v := SplitPropertyFileEntry(entry)
+	assert.Equal(t, "c", k)
+	assert.Equal(t, "d\\ne", v)
+}