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

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

commit 8f5248aa88de5701ec59535ee859cc3d68dfd214
Author: nicolaferraro <ni.ferr...@gmail.com>
AuthorDate: Mon Jun 7 15:23:12 2021 +0200

    Fix #2361: allow multiline properties by correctly encoding them
---
 .../properties-binding.yaml                        | 19 ++++++
 .../properties.feature                             | 26 ++++++++
 .../timer-source.kamelet.yaml                      | 69 ++++++++++++++++++++++
 .../yaks-config.yaml                               | 27 +++++++++
 go.mod                                             |  2 +-
 go.sum                                             |  2 +
 pkg/cmd/run.go                                     | 33 ++++++++---
 pkg/cmd/run_test.go                                | 18 +++---
 pkg/trait/builder.go                               |  2 +-
 pkg/trait/container.go                             | 12 +++-
 pkg/trait/trait_types.go                           | 22 ++++---
 pkg/trait/trait_types_test.go                      | 16 +++++
 12 files changed, 220 insertions(+), 28 deletions(-)

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 0000000..f482ece
--- /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 0000000..1d7f795
--- /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 0000000..a9342e2
--- /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+DQo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm9
 [...]
+    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 0000000..650df12
--- /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/go.mod b/go.mod
index 9312782..8c2e45f 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 b633fdf..5185b94 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 3d7c1eb..1d292ff 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -18,6 +18,7 @@ limitations under the License.
 package cmd
 
 import (
+       "bytes"
        "context"
        "encoding/json"
        "fmt"
@@ -584,9 +585,13 @@ func (o *runCmdOptions) updateIntegrationCode(c 
client.Client, sources []string,
                        return nil, err
                }
                for _, k := range props.Keys() {
-                       v, ok := props.Get(k)
+                       _, ok := props.Get(k)
                        if ok {
-                               o.Traits = append(o.Traits, 
fmt.Sprintf("builder.properties=%s", 
escapePropertyFileItem(k)+"="+escapePropertyFileItem(v)))
+                               entry, err := toPropertyEntry(props, k)
+                               if err != nil {
+                                       return nil, err
+                               }
+                               o.Traits = append(o.Traits, 
fmt.Sprintf("builder.properties=%s", entry))
                        } else {
                                return nil, err
                        }
@@ -788,10 +793,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 := toPropertyEntry(props, k)
+               if err != nil {
+                       return err
+               }
                spec.AddConfiguration(
                        "property",
-                       escapePropertyFileItem(k)+"="+escapePropertyFileItem(v),
+                       entry,
                )
        }
        return nil
@@ -809,10 +817,19 @@ 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 toPropertyEntry(props *properties.Properties, key string) (string, error) 
{
+       value, _ := props.Get(key)
+       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
 }
 
 func resolvePodTemplate(ctx context.Context, templateSrc string, spec 
*v1.IntegrationSpec) (err error) {
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index 1d9192f..36242de 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -245,10 +245,12 @@ 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
 `
 
 func TestAddPropertyFile(t *testing.T) {
@@ -265,11 +267,11 @@ 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, 2, 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)
 }
 
 func TestRunPropertyFileFlag(t *testing.T) {
diff --git a/pkg/trait/builder.go b/pkg/trait/builder.go
index a7e354a..639a55c 100644
--- a/pkg/trait/builder.go
+++ b/pkg/trait/builder.go
@@ -156,7 +156,7 @@ 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, "=")
+                       split := strings.SplitN(v, "=", 2)
                        if len(split) != 2 {
                                return nil, fmt.Errorf("maven property must 
have key=value format, it was %v", v)
                        }
diff --git a/pkg/trait/container.go b/pkg/trait/container.go
index 65781bb..60bac5a 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 2ca1095..af884d5 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -18,6 +18,7 @@ limitations under the License.
 package trait
 
 import (
+       "bytes"
        "context"
        "fmt"
        "path"
@@ -25,6 +26,8 @@ import (
        "strconv"
        "strings"
 
+       "github.com/magiconair/properties"
+       "github.com/pkg/errors"
        appsv1 "k8s.io/api/apps/v1"
        "k8s.io/api/batch/v1beta1"
        corev1 "k8s.io/api/core/v1"
@@ -363,13 +366,17 @@ 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)
+       props := properties.LoadMap(e.ApplicationProperties)
+       props.DisableExpansion = true
+       props.Sort()
+       buf := new(bytes.Buffer)
+       _, err := props.Write(buf, properties.UTF8)
+       if err != nil {
+               return nil, errors.Wrapf(err, "could not compute application 
properties")
        }
+       applicationProperties := buf.String()
 
        if applicationProperties != "" {
                return &corev1.ConfigMap{
@@ -388,10 +395,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 +410,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/trait_types_test.go b/pkg/trait/trait_types_test.go
index 137f0f9..2a68853 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{

Reply via email to