This is an automated email from the ASF dual-hosted git repository. nfilotto pushed a commit to branch 3002/properties-from-configmap in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit bbf97a99c8c0dc5d26ebfac5c43642f40d72b51e Author: Nicolas Filotto <nfilo...@talend.com> AuthorDate: Fri Sep 9 20:43:27 2022 +0200 feat(cmd/run): secret/configmap as runtime/build-time properties --- .../pages/configuration/build-time-properties.adoc | 60 ++++++++++++ .../pages/configuration/runtime-properties.adoc | 60 ++++++++++++ e2e/global/common/config/config_test.go | 102 ++++++++++++++++++++- pkg/cmd/run.go | 61 +++++++++--- pkg/cmd/run_help.go | 66 +++++++------ pkg/cmd/run_help_test.go | 63 ------------- pkg/cmd/run_test.go | 69 +++++++++++++- 7 files changed, 374 insertions(+), 107 deletions(-) diff --git a/docs/modules/ROOT/pages/configuration/build-time-properties.adoc b/docs/modules/ROOT/pages/configuration/build-time-properties.adoc index 713c3f182..e02d330dd 100644 --- a/docs/modules/ROOT/pages/configuration/build-time-properties.adoc +++ b/docs/modules/ROOT/pages/configuration/build-time-properties.adoc @@ -50,6 +50,66 @@ kamel run --build-property=file:quarkus.properties build-property-route.groovy The property file is parsed and its properties configured on the `Integration`. As soon as the application starts, you will see the log with the expected configuration. +[[build-time-configmap]] +== Property from ConfigMap/Secret + +In case some build-time properties are stored into a `Configmap` or a `Secret`, you can use the `--build-property` flag with a value of type respectively _configmap:name-of-configmap_ or _secret:name-of-secret_ to refer to the specific resource to use as build-time properties. + +As an example, let's create a `Configmap` named _my-cm-bp_ containing the build-time properties to load. You can alternatively use any `Configmap` you've already stored in your cluster: + +---- +kubectl create configmap my-cm-bp --from-literal=quarkus.application.name="my-great-application" --from-literal=quarkus.banner.enabled="true" +---- + +Here, as an example we have create a configmap with 2 `Quarkus` properties. + +[source,groovy] +.build-property-route.groovy +---- +from('timer:build-property') + .log('The application name: {{quarkus.application.name}}') +---- + +The `quarkus.banner.enabled` is configured to show the banner during the `Integration` startup. Let's use `--build-property` flag in conjunction with file: + +---- +kamel run --build-property=configmap:my-cm-bp build-property-route.groovy +---- + +The key-value pairs of the `ConfigMap` are loaded and used as build-time properties of the `Integration`. As soon as the application starts, you will see the log with the expected configuration. + +[[build-time-configmap-as-file]] +== Property from ConfigMap/Secret as file + +When you have a lot of key-value pairs to store into a given `ConfigMap`/`Secret`, you may consider storing some build-time properties as a file into a specific key-value pair for the sake of simplicity. + +The only constraint is to use `.properties` as a suffix of the key to indicate that the value is actually a property file, not a simple value. + +As an example, let's use the same `Integration` as the previous section but with a `ConfigMap` that contains all the properties into the same key-value pair. + +For this we need a properties file as next: + +[source,properties] +.quarkus.properties +---- +quarkus.application.name = my-super-application +quarkus.banner.enabled = true +---- + +That we will load into a specific `ConfigMap` using the following command: + +---- +kubectl create configmap my-cm-bps --from-file=quarkus.properties +---- + +Then we launch the `run` command with the `--build-property` flag whose value matches with the appropriate syntax to refer to `my-cm-bps`: + +---- +kamel run --build-property configmap:my-cm-bps build-property-route.groovy +---- + +The value of the key-value of the `ConfigMap` is loaded as a property file and used as build-time properties of the `Integration`. you will see the log with the expected configuration. + [[build-time-props-file-precedence]] == Property collision priority diff --git a/docs/modules/ROOT/pages/configuration/runtime-properties.adoc b/docs/modules/ROOT/pages/configuration/runtime-properties.adoc index c65b5efb2..3c6cc36d7 100644 --- a/docs/modules/ROOT/pages/configuration/runtime-properties.adoc +++ b/docs/modules/ROOT/pages/configuration/runtime-properties.adoc @@ -67,6 +67,66 @@ kamel run --property file:my.properties property-route.groovy The property file is parsed and its properties configured on the `Integration`. As soon as the application starts, you will see the log with the expected configuration. +[[runtime-configmap]] +== Property from ConfigMap/Secret + +In case some runtime properties are stored into a `Configmap` or a `Secret`, you can use the `--property` flag with a value of type respectively _configmap:name-of-configmap_ or _secret:name-of-secret_ to refer to the specific resource to use as runtime properties. + +As an example, let's create a `Configmap` named _my-cm-rp_ containing the runtime properties to load. You can alternatively use any `Configmap` you've already stored in your cluster: + +---- +kubectl create configmap my-cm-rp --from-literal=name="Will Smith" --from-literal=period="2000" +---- + +In our `Integration` we can simply refer to the properties defined in the `ConfigMap` as we'd do with any other property: + +[source,groovy] +.property-configmap-route.groovy +---- +from('timer:property?period={{period}}') + .log('Hello {{name}}!') +---- + +Then we launch the `run` command with the `--property` flag whose value matches with the appropriate syntax to refer to `my-cm-rp`: + +---- +kamel run --property configmap:my-cm-rp property-configmap-route.groovy +---- + +The key-value pairs of the `ConfigMap` are loaded and used as runtime properties of the `Integration`. As soon as the application starts, you will see the log with the expected message. + +[[runtime-configmap-as-file]] +== Property from ConfigMap/Secret as file + +When you have a lot of key-value pairs to store into a given `ConfigMap`/`Secret`, you may consider storing some runtime properties as a file into a specific key-value pair for the sake of simplicity. + +The only constraint is to use `.properties` as a suffix of the key to indicate that the value is actually a property file, not a simple value. + +As an example, let's use the same `Integration` as the previous section but with a `ConfigMap` that contains all the properties into the same key-value pair. + +For this we need a properties file as next: + +[source,text] +.some.properties +---- +name=John Smith +period=2000 +---- + +That we will load into a specific `ConfigMap` using the following command: + +---- +kubectl create configmap my-cm-rps --from-file=some.properties +---- + +Then we launch the `run` command with the `--property` flag whose value matches with the appropriate syntax to refer to `my-cm-rps`: + +---- +kamel run --property configmap:my-cm-rps property-configmap-route.groovy +---- + +The value of the key-value of the `ConfigMap` is loaded as a property file and used as runtime properties of the `Integration`. As soon as the application starts, you will see the log with the expected message. + [[runtime-props-file-precedence]] == Property collision priority diff --git a/e2e/global/common/config/config_test.go b/e2e/global/common/config/config_test.go index 9363fb391..5b5dfe125 100644 --- a/e2e/global/common/config/config_test.go +++ b/e2e/global/common/config/config_test.go @@ -23,7 +23,6 @@ limitations under the License. package resources import ( - "fmt" "io/ioutil" "testing" @@ -39,8 +38,10 @@ import ( func TestRunConfigExamples(t *testing.T) { WithNewTestNamespace(t, func(ns string) { - operatorID := fmt.Sprintf("camel-k-%s", ns) - Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed()) + ns = "operator-test" + operatorID := "camel-k" + //operatorID := fmt.Sprintf("camel-k-%s", ns) + //Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed()) // Properties @@ -68,6 +69,54 @@ func TestRunConfigExamples(t *testing.T) { Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) }) + t.Run("Property from ConfigMap", func(t *testing.T) { + var cmData = make(map[string]string) + cmData["my.message"] = "my-configmap-property-value" + CreatePlainTextConfigmap(ns, "my-cm-test-property", cmData) + + Expect(KamelRunWithID(operatorID, ns, "./files/property-route.groovy", "-p", "configmap:my-cm-test-property").Execute()).To(Succeed()) + Eventually(IntegrationPodPhase(ns, "property-route"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + Eventually(IntegrationConditionStatus(ns, "property-route", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "property-route"), TestTimeoutShort).Should(ContainSubstring("my-configmap-property-value")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) + }) + + t.Run("Property from ConfigMap as property file", func(t *testing.T) { + var cmData = make(map[string]string) + cmData["my.properties"] = "my.message=my-configmap-property-entry" + CreatePlainTextConfigmap(ns, "my-cm-test-properties", cmData) + + Expect(KamelRunWithID(operatorID, ns, "./files/property-route.groovy", "-p", "configmap:my-cm-test-properties").Execute()).To(Succeed()) + Eventually(IntegrationPodPhase(ns, "property-route"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + Eventually(IntegrationConditionStatus(ns, "property-route", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "property-route"), TestTimeoutShort).Should(ContainSubstring("my-configmap-property-entry")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) + }) + + t.Run("Property from Secret", func(t *testing.T) { + var secData = make(map[string]string) + secData["my.message"] = "my-secret-property-value" + CreatePlainTextSecret(ns, "my-sec-test-property", secData) + + Expect(KamelRunWithID(operatorID, ns, "./files/property-route.groovy", "-p", "secret:my-sec-test-property").Execute()).To(Succeed()) + Eventually(IntegrationPodPhase(ns, "property-route"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + Eventually(IntegrationConditionStatus(ns, "property-route", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "property-route"), TestTimeoutShort).Should(ContainSubstring("my-secret-property-value")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) + }) + + t.Run("Property from Secret as property file", func(t *testing.T) { + var secData = make(map[string]string) + secData["my.properties"] = "my.message=my-secret-property-entry" + CreatePlainTextSecret(ns, "my-sec-test-properties", secData) + + Expect(KamelRunWithID(operatorID, ns, "./files/property-route.groovy", "-p", "secret:my-sec-test-properties").Execute()).To(Succeed()) + Eventually(IntegrationPodPhase(ns, "property-route"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + Eventually(IntegrationConditionStatus(ns, "property-route", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "property-route"), TestTimeoutShort).Should(ContainSubstring("my-secret-property-entry")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) + }) + // Configmap // Store a configmap on the cluster @@ -274,5 +323,52 @@ func TestRunConfigExamples(t *testing.T) { Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) }) + t.Run("Build time property from ConfigMap", func(t *testing.T) { + var cmData = make(map[string]string) + cmData["quarkus.application.name"] = "my-cool-application" + CreatePlainTextConfigmap(ns, "my-cm-test-build-property", cmData) + + Expect(KamelRunWithID(operatorID, ns, "./files/build-property-file-route.groovy", "--build-property", "configmap:my-cm-test-build-property").Execute()).To(Succeed()) + Eventually(IntegrationPodPhase(ns, "build-property-file-route"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + Eventually(IntegrationConditionStatus(ns, "build-property-file-route", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "build-property-file-route"), TestTimeoutShort).Should(ContainSubstring("my-cool-application")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) + }) + + t.Run("Build time property from ConfigMap as property file", func(t *testing.T) { + var cmData = make(map[string]string) + cmData["my.properties"] = "quarkus.application.name=my-super-cool-application" + CreatePlainTextConfigmap(ns, "my-cm-test-build-properties", cmData) + + Expect(KamelRunWithID(operatorID, ns, "./files/build-property-file-route.groovy", "--build-property", "configmap:my-cm-test-build-properties").Execute()).To(Succeed()) + Eventually(IntegrationPodPhase(ns, "build-property-file-route"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + Eventually(IntegrationConditionStatus(ns, "build-property-file-route", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "build-property-file-route"), TestTimeoutShort).Should(ContainSubstring("my-super-cool-application")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) + }) + + t.Run("Build time property from Secret", func(t *testing.T) { + var secData = make(map[string]string) + secData["quarkus.application.name"] = "my-great-application" + CreatePlainTextSecret(ns, "my-sec-test-build-property", secData) + + Expect(KamelRunWithID(operatorID, ns, "./files/build-property-file-route.groovy", "--build-property", "secret:my-sec-test-build-property").Execute()).To(Succeed()) + Eventually(IntegrationPodPhase(ns, "build-property-file-route"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + Eventually(IntegrationConditionStatus(ns, "build-property-file-route", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "build-property-file-route"), TestTimeoutShort).Should(ContainSubstring("my-great-application")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) + }) + + t.Run("Build time property from Secret as property file", func(t *testing.T) { + var secData = make(map[string]string) + secData["my.properties"] = "quarkus.application.name=my-awsome-application" + CreatePlainTextSecret(ns, "my-sec-test-build-properties", secData) + + Expect(KamelRunWithID(operatorID, ns, "./files/build-property-file-route.groovy", "--build-property", "secret:my-sec-test-build-properties").Execute()).To(Succeed()) + Eventually(IntegrationPodPhase(ns, "build-property-file-route"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + Eventually(IntegrationConditionStatus(ns, "build-property-file-route", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "build-property-file-route"), TestTimeoutShort).Should(ContainSubstring("my-awsome-application")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) + }) }) } diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index ac4174ff4..301aa05b3 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -101,8 +101,8 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *runCmdOptions) cmd.Flags().StringArrayP("dependency", "d", nil, usageDependency) 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 runtime property or properties file (syntax: [my-key=my-value|file:/path/to/my-conf.properties])") - cmd.Flags().StringArray("build-property", nil, "Add a build time property or properties file (syntax: [my-key=my-value|file:/path/to/my-conf.properties])") + cmd.Flags().StringArrayP("property", "p", nil, "Add a runtime property or properties file from a path, a config map or a secret (syntax: [my-key=my-value|file:/path/to/my-conf.properties|[configmap|secret]:name])") + cmd.Flags().StringArray("build-property", nil, "Add a build time property or properties file from a path, a config map or a secret (syntax: [my-key=my-value|file:/path/to/my-conf.properties|[configmap|secret]:name]])") cmd.Flags().StringArray("config", nil, "Add a runtime configuration from a Configmap, a Secret or a file (syntax: [configmap|secret|file]:name[/key], where name represents the local file path or the configmap/secret name and key optionally represents the configmap/secret key to be filtered)") cmd.Flags().StringArray("resource", nil, "Add a runtime resource from a Configmap, a Secret or a file (syntax: [configmap|secret|file]:name[/key][@path], where name represents the local file path or the configmap/secret name, key optionally represents the configmap/secret key to be filtered and path represents the destination path)") cmd.Flags().StringArray("maven-repository", nil, "Add a maven repository") @@ -694,11 +694,11 @@ func (o *runCmdOptions) convertOptionsToTraits(cmd *cobra.Command, c client.Clie return err } - if err := o.applyProperties(); err != nil { + if err := o.applyProperties(c); err != nil { return err } - if err := o.applyBuildProperties(); err != nil { + if err := o.applyBuildProperties(c); err != nil { return err } @@ -738,14 +738,14 @@ func convertToTrait(value, traitParameter string) string { return fmt.Sprintf("%s=%s", traitParameter, value) } -func (o *runCmdOptions) applyProperties() error { - props, err := mergePropertiesWithPrecedence(o.Properties) +func (o *runCmdOptions) applyProperties(c client.Client) error { + props, err := o.mergePropertiesWithPrecedence(c, o.Properties) if err != nil { return err } for _, key := range props.Keys() { kv := fmt.Sprintf("%s=%s", key, props.GetString(key, "")) - propsTraits, err := convertToTraitParameter(kv, "camel.properties") + propsTraits, err := o.convertToTraitParameter(c, kv, "camel.properties") if err != nil { return err } @@ -755,15 +755,15 @@ func (o *runCmdOptions) applyProperties() error { return nil } -func (o *runCmdOptions) applyBuildProperties() error { +func (o *runCmdOptions) applyBuildProperties(c client.Client) error { // convert each build configuration to a builder trait property - buildProps, err := mergePropertiesWithPrecedence(o.BuildProperties) + buildProps, err := o.mergePropertiesWithPrecedence(c, o.BuildProperties) if err != nil { return err } for _, key := range buildProps.Keys() { kv := fmt.Sprintf("%s=%s", key, buildProps.GetString(key, "")) - buildPropsTraits, err := convertToTraitParameter(kv, "builder.properties") + buildPropsTraits, err := o.convertToTraitParameter(c, kv, "builder.properties") if err != nil { return err } @@ -773,9 +773,9 @@ func (o *runCmdOptions) applyBuildProperties() error { return nil } -func convertToTraitParameter(value, traitParameter string) ([]string, error) { +func (o *runCmdOptions) convertToTraitParameter(c client.Client, value, traitParameter string) ([]string, error) { traits := make([]string, 0) - props, err := extractProperties(value) + props, err := o.extractProperties(c, value) if err != nil { return nil, err } @@ -852,6 +852,43 @@ func (o *runCmdOptions) GetIntegrationName(sources []string) string { return name } +func (o *runCmdOptions) mergePropertiesWithPrecedence(c client.Client, items []string) (*properties.Properties, error) { + loPrecedenceProps := properties.NewProperties() + hiPrecedenceProps := properties.NewProperties() + for _, item := range items { + prop, err := o.extractProperties(c, item) + if err != nil { + return nil, err + } + // We consider file, secret and config map props to have a lower priority versus single properties + if strings.HasPrefix(item, "file:") || strings.HasPrefix(item, "secret:") || strings.HasPrefix(item, "configmap:") { + loPrecedenceProps.Merge(prop) + } else { + hiPrecedenceProps.Merge(prop) + } + } + // Any property contained in both collections will be merged + // giving precedence to the ones in hiPrecedenceProps + loPrecedenceProps.Merge(hiPrecedenceProps) + return loPrecedenceProps, nil +} + +// The function parse the value and if it is a file (file:/path/), it will parse as property file +// otherwise return a single property built from the item passed as `key=value`. +func (o *runCmdOptions) extractProperties(c client.Client, value string) (*properties.Properties, error) { + switch { + case strings.HasPrefix(value, "file:"): + // we already validated the existence of files during validate() + return loadPropertyFile(strings.Replace(value, "file:", "", 1)) + case strings.HasPrefix(value, "secret:"): + return loadPropertiesFromSecret(o.Context, c, o.Namespace, strings.Replace(value, "secret:", "", 1)) + case strings.HasPrefix(value, "configmap:"): + return loadPropertiesFromConfigMap(o.Context, c, o.Namespace, strings.Replace(value, "configmap:", "", 1)) + default: + return keyValueProps(value) + } +} + func loadPropertyFile(fileName string) (*properties.Properties, error) { file, err := util.ReadFile(fileName) if err != nil { diff --git a/pkg/cmd/run_help.go b/pkg/cmd/run_help.go index c885e4151..328e9be32 100644 --- a/pkg/cmd/run_help.go +++ b/pkg/cmd/run_help.go @@ -21,6 +21,7 @@ import ( "context" "fmt" "path" + "reflect" "strings" v1 "github.com/apache/camel-k/pkg/apis/camel/v1" @@ -115,37 +116,48 @@ func filterFileLocation(maybeFileLocations []string) []string { return filteredOptions } -func mergePropertiesWithPrecedence(items []string) (*properties.Properties, error) { - loPrecedenceProps := properties.NewProperties() - hiPrecedenceProps := properties.NewProperties() - for _, item := range items { - prop, err := extractProperties(item) - if err != nil { - return nil, err - } - // We consider file props to have a lower priority versus single properties - if strings.HasPrefix(item, "file:") { - loPrecedenceProps.Merge(prop) - } else { - hiPrecedenceProps.Merge(prop) - } +func keyValueProps(value string) (*properties.Properties, error) { + return properties.Load([]byte(value), properties.UTF8) +} + +func loadPropertiesFromSecret(ctx context.Context, c client.Client, ns string, name string) (*properties.Properties, error) { + secret := kubernetes.LookupSecret(ctx, c, ns, name) + if secret == nil { + return nil, fmt.Errorf("%s secret not found in %s namespace, make sure to provide it before the Integration can run", name, ns) } - // Any property contained in both collections will be merged - // giving precedence to the ones in hiPrecedenceProps - loPrecedenceProps.Merge(hiPrecedenceProps) - return loPrecedenceProps, nil + return fromMapToProperties(secret.Data, + func(v reflect.Value) string { return string(v.Bytes()) }, + func(v reflect.Value) (*properties.Properties, error) { + return properties.Load(v.Bytes(), properties.UTF8) + }) } -// The function parse the value and if it is a file (file:/path/), it will parse as property file -// otherwise return a single property built from the item passed as `key=value`. -func extractProperties(value string) (*properties.Properties, error) { - if !strings.HasPrefix(value, "file:") { - return keyValueProps(value) +func loadPropertiesFromConfigMap(ctx context.Context, c client.Client, ns string, name string) (*properties.Properties, error) { + cm := kubernetes.LookupConfigmap(ctx, c, ns, name) + if cm == nil { + return nil, fmt.Errorf("%s configmap not found in %s namespace, make sure to provide it before the Integration can run", name, ns) } - // we already validated the existence of files during validate() - return loadPropertyFile(strings.Replace(value, "file:", "", 1)) + return fromMapToProperties(cm.Data, + func(v reflect.Value) string { return v.String() }, + func(v reflect.Value) (*properties.Properties, error) { return keyValueProps(v.String()) }) } -func keyValueProps(value string) (*properties.Properties, error) { - return properties.Load([]byte(value), properties.UTF8) +func fromMapToProperties(data interface{}, toString func(reflect.Value) string, loadProperties func(reflect.Value) (*properties.Properties, error)) (*properties.Properties, error) { + result := properties.NewProperties() + m := reflect.ValueOf(data) + for _, k := range m.MapKeys() { + key := k.String() + value := m.MapIndex(k) + if strings.HasSuffix(key, ".properties") { + p, err := loadProperties(value) + if err == nil { + result.Merge(p) + } else if _, _, err = result.Set(key, toString(value)); err != nil { + return nil, fmt.Errorf("cannot assign %s to %s", value, key) + } + } else if _, _, err := result.Set(key, toString(value)); err != nil { + return nil, fmt.Errorf("cannot assign %s to %s", value, key) + } + } + return result, nil } diff --git a/pkg/cmd/run_help_test.go b/pkg/cmd/run_help_test.go index 0fa7c9a96..175d4da93 100644 --- a/pkg/cmd/run_help_test.go +++ b/pkg/cmd/run_help_test.go @@ -18,8 +18,6 @@ limitations under the License. package cmd import ( - "io/ioutil" - "os" "testing" "github.com/stretchr/testify/assert" @@ -41,64 +39,3 @@ func TestFilterFileLocation(t *testing.T) { assert.Equal(t, "app.properties", filteredOptions[1]) assert.Equal(t, "/validfile", filteredOptions[2]) } - -func TestExtractProperties_SingleKeyValue(t *testing.T) { - correctValues := []string{"key=val", "key = val", "key= val", " key = val"} - for _, val := range correctValues { - prop, err := extractProperties(val) - assert.Nil(t, err) - value, ok := prop.Get("key") - assert.True(t, ok) - assert.Equal(t, "val", value) - } -} - -func TestExtractProperties_FromFile(t *testing.T) { - var tmpFile1 *os.File - var err error - if tmpFile1, err = ioutil.TempFile("", "camel-k-*.properties"); err != nil { - t.Error(err) - } - - assert.Nil(t, tmpFile1.Close()) - assert.Nil(t, ioutil.WriteFile(tmpFile1.Name(), []byte(` - key=value - #key2=value2 - my.key=value - `), 0o400)) - - props, err := extractProperties("file:" + tmpFile1.Name()) - assert.Nil(t, err) - assert.Equal(t, 2, props.Len()) - for _, prop := range props.Keys() { - value, ok := props.Get(prop) - assert.True(t, ok) - assert.Equal(t, "value", value) - } -} - -func TestExtractPropertiesFromFileAndSingleValue(t *testing.T) { - var tmpFile1 *os.File - var err error - if tmpFile1, err = ioutil.TempFile("", "camel-k-*.properties"); err != nil { - t.Error(err) - } - - assert.Nil(t, tmpFile1.Close()) - assert.Nil(t, ioutil.WriteFile(tmpFile1.Name(), []byte(` - key=value - #key2=value2 - my.key=value - `), 0o400)) - - properties := []string{"key=override", "file:" + tmpFile1.Name(), "my.key = override"} - props, err := mergePropertiesWithPrecedence(properties) - assert.Nil(t, err) - assert.Equal(t, 2, props.Len()) - val, ok := props.Get("key") - assert.True(t, ok) - assert.Equal(t, "override", val) - val, ok = props.Get("my.key") - assert.True(t, ok) - assert.Equal(t, "override", val) -} diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go index b02f37698..2b7144071 100644 --- a/pkg/cmd/run_test.go +++ b/pkg/cmd/run_test.go @@ -274,6 +274,69 @@ f=g:h i=j\nk ` +func TestExtractProperties_SingleKeyValue(t *testing.T) { + correctValues := []string{"key=val", "key = val", "key= val", " key = val"} + runCmdOptions, _, _ := initializeRunCmdOptionsWithOutput(t) + for _, val := range correctValues { + prop, err := runCmdOptions.extractProperties(nil, val) + assert.Nil(t, err) + value, ok := prop.Get("key") + assert.True(t, ok) + assert.Equal(t, "val", value) + } +} + +func TestExtractProperties_FromFile(t *testing.T) { + var tmpFile1 *os.File + var err error + if tmpFile1, err = ioutil.TempFile("", "camel-k-*.properties"); err != nil { + t.Error(err) + } + + assert.Nil(t, tmpFile1.Close()) + assert.Nil(t, ioutil.WriteFile(tmpFile1.Name(), []byte(` + key=value + #key2=value2 + my.key=value + `), 0o400)) + runCmdOptions, _, _ := initializeRunCmdOptionsWithOutput(t) + props, err := runCmdOptions.extractProperties(nil, "file:"+tmpFile1.Name()) + assert.Nil(t, err) + assert.Equal(t, 2, props.Len()) + for _, prop := range props.Keys() { + value, ok := props.Get(prop) + assert.True(t, ok) + assert.Equal(t, "value", value) + } +} + +func TestExtractPropertiesFromFileAndSingleValue(t *testing.T) { + var tmpFile1 *os.File + var err error + if tmpFile1, err = ioutil.TempFile("", "camel-k-*.properties"); err != nil { + t.Error(err) + } + + assert.Nil(t, tmpFile1.Close()) + assert.Nil(t, ioutil.WriteFile(tmpFile1.Name(), []byte(` + key=value + #key2=value2 + my.key=value + `), 0o400)) + + properties := []string{"key=override", "file:" + tmpFile1.Name(), "my.key = override"} + runCmdOptions, _, _ := initializeRunCmdOptionsWithOutput(t) + props, err := runCmdOptions.mergePropertiesWithPrecedence(nil, properties) + assert.Nil(t, err) + assert.Equal(t, 2, props.Len()) + val, ok := props.Get("key") + assert.True(t, ok) + assert.Equal(t, "override", val) + val, ok = props.Get("my.key") + assert.True(t, ok) + assert.Equal(t, "override", val) +} + func TestAddPropertyFile(t *testing.T) { var tmpFile *os.File var err error @@ -284,7 +347,8 @@ func TestAddPropertyFile(t *testing.T) { assert.Nil(t, tmpFile.Close()) assert.Nil(t, ioutil.WriteFile(tmpFile.Name(), []byte(TestPropertyFileContent), 0o400)) - properties, err := convertToTraitParameter("file:"+tmpFile.Name(), "trait.properties") + runCmdOptions, _, _ := initializeRunCmdOptionsWithOutput(t) + properties, err := runCmdOptions.convertToTraitParameter(nil, "file:"+tmpFile.Name(), "trait.properties") assert.Nil(t, err) assert.Equal(t, 3, len(properties)) assert.Equal(t, `trait.properties=a = b`, properties[0]) @@ -293,7 +357,8 @@ func TestAddPropertyFile(t *testing.T) { } func TestRunProperty(t *testing.T) { - properties, err := convertToTraitParameter(`key=value\nnewline`, "trait.properties") + runCmdOptions, _, _ := initializeRunCmdOptionsWithOutput(t) + properties, err := runCmdOptions.convertToTraitParameter(nil, `key=value\nnewline`, "trait.properties") assert.Nil(t, err) assert.Equal(t, 1, len(properties)) assert.Equal(t, `trait.properties=key = value\nnewline`, properties[0])