This is an automated email from the ASF dual-hosted git repository. tsato pushed a commit to branch release-1.9.x in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit ecef065064dab2cc03efd4c079845de494baadb6 Author: Tadayoshi Sato <[email protected]> AuthorDate: Tue May 17 15:18:18 2022 +0900 chore(cmd/trait): refactor cmd & trait packages (cherry picked from commit d9f10e963213b6dec74abe594e77270145c0b40e) --- pkg/cmd/trait_help.go | 107 ---------------- pkg/cmd/trait_support.go | 136 +++++++++++++++++++++ pkg/trait/{trait_types_test.go => test_support.go} | 16 --- pkg/trait/trait_types_test.go | 133 +++++++++----------- pkg/trait/util_test.go | 79 ------------ 5 files changed, 194 insertions(+), 277 deletions(-) diff --git a/pkg/cmd/trait_help.go b/pkg/cmd/trait_help.go index b56e8c69e..0ef7bc9af 100644 --- a/pkg/cmd/trait_help.go +++ b/pkg/cmd/trait_help.go @@ -26,14 +26,12 @@ import ( "strings" "github.com/fatih/structs" - "github.com/mitchellh/mapstructure" "github.com/spf13/cobra" "gopkg.in/yaml.v2" v1 "github.com/apache/camel-k/pkg/apis/camel/v1" "github.com/apache/camel-k/pkg/resources" "github.com/apache/camel-k/pkg/trait" - "github.com/apache/camel-k/pkg/util" "github.com/apache/camel-k/pkg/util/indentedwriter" ) @@ -263,108 +261,3 @@ func outputTraits(descriptions []*traitDescription) (string, error) { return nil }) } - -func validateTraits(catalog *trait.Catalog, traits []string) error { - tp := catalog.ComputeTraitsProperties() - for _, t := range traits { - kv := strings.SplitN(t, "=", 2) - prefix := kv[0] - if strings.Contains(prefix, "[") { - prefix = prefix[0:strings.Index(prefix, "[")] - } - if !util.StringSliceExists(tp, prefix) { - return fmt.Errorf("%s is not a valid trait property", t) - } - } - - return nil -} - -func configureTraits(options []string, catalog trait.Finder) (map[string]v1.TraitSpec, error) { - traits := make(map[string]map[string]interface{}) - - for _, option := range options { - parts := traitConfigRegexp.FindStringSubmatch(option) - if len(parts) < 4 { - return nil, errors.New("unrecognized config format (expected \"<trait>.<prop>=<value>\"): " + option) - } - id := parts[1] - fullProp := parts[2][1:] - value := parts[3] - if _, ok := traits[id]; !ok { - traits[id] = make(map[string]interface{}) - } - - propParts := util.ConfigTreePropertySplit(fullProp) - var current = traits[id] - if len(propParts) > 1 { - c, err := util.NavigateConfigTree(current, propParts[0:len(propParts)-1]) - if err != nil { - return nil, err - } - if cc, ok := c.(map[string]interface{}); ok { - current = cc - } else { - return nil, errors.New("trait configuration cannot end with a slice") - } - } - - prop := propParts[len(propParts)-1] - switch v := current[prop].(type) { - case []string: - current[prop] = append(v, value) - case string: - // Aggregate multiple occurrences of the same option into a string array, to emulate POSIX conventions. - // This enables executing: - // $ kamel run -t <trait>.<property>=<value_1> ... -t <trait>.<property>=<value_N> - // Or: - // $ kamel run --trait <trait>.<property>=<value_1>,...,<trait>.<property>=<value_N> - current[prop] = []string{v, value} - case nil: - current[prop] = value - } - } - - specs := make(map[string]v1.TraitSpec) - for id, config := range traits { - t := catalog.GetTrait(id) - if t != nil { - // let's take a clone to prevent default values set at runtime from being serialized - zero := reflect.New(reflect.TypeOf(t)).Interface() - err := configureTrait(config, zero) - if err != nil { - return nil, err - } - data, err := json.Marshal(zero) - if err != nil { - return nil, err - } - var spec v1.TraitSpec - err = json.Unmarshal(data, &spec.Configuration) - if err != nil { - return nil, err - } - specs[id] = spec - } - } - - return specs, nil -} - -func configureTrait(config map[string]interface{}, trait interface{}) error { - md := mapstructure.Metadata{} - - decoder, err := mapstructure.NewDecoder( - &mapstructure.DecoderConfig{ - Metadata: &md, - WeaklyTypedInput: true, - TagName: "property", - Result: &trait, - }, - ) - if err != nil { - return err - } - - return decoder.Decode(config) -} diff --git a/pkg/cmd/trait_support.go b/pkg/cmd/trait_support.go new file mode 100644 index 000000000..63fa23b9c --- /dev/null +++ b/pkg/cmd/trait_support.go @@ -0,0 +1,136 @@ +/* +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 cmd + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" + + v1 "github.com/apache/camel-k/pkg/apis/camel/v1" + "github.com/apache/camel-k/pkg/trait" + "github.com/apache/camel-k/pkg/util" + "github.com/mitchellh/mapstructure" +) + +func validateTraits(catalog *trait.Catalog, traits []string) error { + tp := catalog.ComputeTraitsProperties() + for _, t := range traits { + kv := strings.SplitN(t, "=", 2) + prefix := kv[0] + if strings.Contains(prefix, "[") { + prefix = prefix[0:strings.Index(prefix, "[")] + } + if !util.StringSliceExists(tp, prefix) { + return fmt.Errorf("%s is not a valid trait property", t) + } + } + + return nil +} + +func configureTraits(options []string, catalog trait.Finder) (map[string]v1.TraitSpec, error) { + traits := make(map[string]map[string]interface{}) + + for _, option := range options { + parts := traitConfigRegexp.FindStringSubmatch(option) + if len(parts) < 4 { + return nil, errors.New("unrecognized config format (expected \"<trait>.<prop>=<value>\"): " + option) + } + id := parts[1] + fullProp := parts[2][1:] + value := parts[3] + if _, ok := traits[id]; !ok { + traits[id] = make(map[string]interface{}) + } + + propParts := util.ConfigTreePropertySplit(fullProp) + var current = traits[id] + if len(propParts) > 1 { + c, err := util.NavigateConfigTree(current, propParts[0:len(propParts)-1]) + if err != nil { + return nil, err + } + if cc, ok := c.(map[string]interface{}); ok { + current = cc + } else { + return nil, errors.New("trait configuration cannot end with a slice") + } + } + + prop := propParts[len(propParts)-1] + switch v := current[prop].(type) { + case []string: + current[prop] = append(v, value) + case string: + // Aggregate multiple occurrences of the same option into a string array, to emulate POSIX conventions. + // This enables executing: + // $ kamel run -t <trait>.<property>=<value_1> ... -t <trait>.<property>=<value_N> + // Or: + // $ kamel run --trait <trait>.<property>=<value_1>,...,<trait>.<property>=<value_N> + current[prop] = []string{v, value} + case nil: + current[prop] = value + } + } + + specs := make(map[string]v1.TraitSpec) + for id, config := range traits { + t := catalog.GetTrait(id) + if t != nil { + // let's take a clone to prevent default values set at runtime from being serialized + zero := reflect.New(reflect.TypeOf(t)).Interface() + err := configureTrait(config, zero) + if err != nil { + return nil, err + } + data, err := json.Marshal(zero) + if err != nil { + return nil, err + } + var spec v1.TraitSpec + err = json.Unmarshal(data, &spec.Configuration) + if err != nil { + return nil, err + } + specs[id] = spec + } + } + + return specs, nil +} + +func configureTrait(config map[string]interface{}, trait interface{}) error { + md := mapstructure.Metadata{} + + decoder, err := mapstructure.NewDecoder( + &mapstructure.DecoderConfig{ + Metadata: &md, + WeaklyTypedInput: true, + TagName: "property", + Result: &trait, + }, + ) + if err != nil { + return err + } + + return decoder.Decode(config) +} diff --git a/pkg/trait/trait_types_test.go b/pkg/trait/test_support.go similarity index 88% copy from pkg/trait/trait_types_test.go copy to pkg/trait/test_support.go index 2a68853ae..137f0f91d 100644 --- a/pkg/trait/trait_types_test.go +++ b/pkg/trait/test_support.go @@ -18,9 +18,6 @@ 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" @@ -32,19 +29,6 @@ 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/trait/trait_types_test.go b/pkg/trait/trait_types_test.go index 2a68853ae..9d3509de9 100644 --- a/pkg/trait/trait_types_test.go +++ b/pkg/trait/trait_types_test.go @@ -21,15 +21,8 @@ import ( "testing" "github.com/stretchr/testify/assert" - serving "knative.dev/serving/pkg/apis/serving/v1" - - appsv1 "k8s.io/api/apps/v1" - "k8s.io/api/batch/v1beta1" - corev1 "k8s.io/api/core/v1" - 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/kubernetes" ) func TestMultilinePropertiesHandled(t *testing.T) { @@ -45,89 +38,79 @@ func TestMultilinePropertiesHandled(t *testing.T) { assert.Equal(t, "prop = multi\\nline\n", cm.Data["application.properties"]) } -func createNominalDeploymentTraitTest() (*Environment, *appsv1.Deployment) { - deployment := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "integration-name", - }, - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{}, - }, - } - - environment := &Environment{ +func TestCollectConfigurationValues(t *testing.T) { + e := Environment{ Integration: &v1.Integration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "integration-name", - }, - Status: v1.IntegrationStatus{ - Phase: v1.IntegrationPhaseDeploying, + Spec: v1.IntegrationSpec{ + Configuration: []v1.ConfigurationSpec{ + {Type: "configmap", Value: "my-cm-integration"}, + {Type: "env", Value: "my-env-integration"}, + }, }, }, - Resources: kubernetes.NewCollection(deployment), - } - - return environment, deployment -} - -func createNominalMissingDeploymentTraitTest() *Environment { - environment := &Environment{ - Integration: &v1.Integration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "integration-name", + IntegrationKit: &v1.IntegrationKit{ + Spec: v1.IntegrationKitSpec{ + Configuration: []v1.ConfigurationSpec{ + {Type: "configmap", Value: "my-cm-kit"}, + {Type: "property", Value: "my-p-kit"}, + }, }, - Status: v1.IntegrationStatus{ - Phase: v1.IntegrationPhaseDeploying, + }, + Platform: &v1.IntegrationPlatform{ + Spec: v1.IntegrationPlatformSpec{ + Configuration: []v1.ConfigurationSpec{ + {Type: "configmap", Value: "my-cm-platform"}, + {Type: "secret", Value: "my-secret-platform"}, + {Type: "property", Value: "my-p-platform"}, + {Type: "env", Value: "my-env-platform"}, + }, }, }, - Resources: kubernetes.NewCollection(), } + e.Platform.ResyncStatusFullConfig() - return environment + assert.Contains(t, e.collectConfigurationValues("configmap"), "my-cm-integration") + assert.Contains(t, e.collectConfigurationValues("secret"), "my-secret-platform") + assert.Contains(t, e.collectConfigurationValues("property"), "my-p-kit") + assert.Contains(t, e.collectConfigurationValues("env"), "my-env-integration") } -func createNominalKnativeServiceTraitTest() (*Environment, *serving.Service) { - knativeService := &serving.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "integration-name", - }, - Spec: serving.ServiceSpec{}, - } - - environment := &Environment{ +func TestCollectConfigurationPairs(t *testing.T) { + e := Environment{ Integration: &v1.Integration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "integration-name", - }, - Status: v1.IntegrationStatus{ - Phase: v1.IntegrationPhaseDeploying, + Spec: v1.IntegrationSpec{ + Configuration: []v1.ConfigurationSpec{ + {Type: "property", Value: "p1=integration"}, + {Type: "property", Value: "p4=integration"}, + }, }, }, - Resources: kubernetes.NewCollection(knativeService), - } - - return environment, knativeService -} - -func createNominalCronJobTraitTest() (*Environment, *v1beta1.CronJob) { - cronJob := &v1beta1.CronJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "integration-name", - }, - Spec: v1beta1.CronJobSpec{}, - } - - environment := &Environment{ - Integration: &v1.Integration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "integration-name", + IntegrationKit: &v1.IntegrationKit{ + Spec: v1.IntegrationKitSpec{ + Configuration: []v1.ConfigurationSpec{ + {Type: "property", Value: "p1=kit"}, + {Type: "property", Value: "p2=kit"}, + }, }, - Status: v1.IntegrationStatus{ - Phase: v1.IntegrationPhaseDeploying, + }, + Platform: &v1.IntegrationPlatform{ + Spec: v1.IntegrationPlatformSpec{ + Configuration: []v1.ConfigurationSpec{ + {Type: "property", Value: "p1=platform"}, + {Type: "property", Value: "p2=platform"}, + {Type: "property", Value: "p3=platform"}, + {Type: "property", Value: "p4=platform"}, + }, }, }, - Resources: kubernetes.NewCollection(cronJob), } - - return environment, cronJob + e.Platform.ResyncStatusFullConfig() + + pairs := e.collectConfigurationPairs("property") + assert.Equal(t, pairs, []variable{ + {Name: "p1", Value: "integration"}, + {Name: "p2", Value: "kit"}, + {Name: "p3", Value: "platform"}, + {Name: "p4", Value: "integration"}, + }) } diff --git a/pkg/trait/util_test.go b/pkg/trait/util_test.go index d1488ad41..da4cd9589 100644 --- a/pkg/trait/util_test.go +++ b/pkg/trait/util_test.go @@ -21,87 +21,8 @@ import ( "testing" "github.com/stretchr/testify/assert" - - v1 "github.com/apache/camel-k/pkg/apis/camel/v1" ) -func TestCollectConfigurationValues(t *testing.T) { - e := Environment{ - Integration: &v1.Integration{ - Spec: v1.IntegrationSpec{ - Configuration: []v1.ConfigurationSpec{ - {Type: "configmap", Value: "my-cm-integration"}, - {Type: "env", Value: "my-env-integration"}, - }, - }, - }, - IntegrationKit: &v1.IntegrationKit{ - Spec: v1.IntegrationKitSpec{ - Configuration: []v1.ConfigurationSpec{ - {Type: "configmap", Value: "my-cm-kit"}, - {Type: "property", Value: "my-p-kit"}, - }, - }, - }, - Platform: &v1.IntegrationPlatform{ - Spec: v1.IntegrationPlatformSpec{ - Configuration: []v1.ConfigurationSpec{ - {Type: "configmap", Value: "my-cm-platform"}, - {Type: "secret", Value: "my-secret-platform"}, - {Type: "property", Value: "my-p-platform"}, - {Type: "env", Value: "my-env-platform"}, - }, - }, - }, - } - e.Platform.ResyncStatusFullConfig() - - assert.Contains(t, e.collectConfigurationValues("configmap"), "my-cm-integration") - assert.Contains(t, e.collectConfigurationValues("secret"), "my-secret-platform") - assert.Contains(t, e.collectConfigurationValues("property"), "my-p-kit") - assert.Contains(t, e.collectConfigurationValues("env"), "my-env-integration") -} - -func TestCollectConfigurationPairs(t *testing.T) { - e := Environment{ - Integration: &v1.Integration{ - Spec: v1.IntegrationSpec{ - Configuration: []v1.ConfigurationSpec{ - {Type: "property", Value: "p1=integration"}, - {Type: "property", Value: "p4=integration"}, - }, - }, - }, - IntegrationKit: &v1.IntegrationKit{ - Spec: v1.IntegrationKitSpec{ - Configuration: []v1.ConfigurationSpec{ - {Type: "property", Value: "p1=kit"}, - {Type: "property", Value: "p2=kit"}, - }, - }, - }, - Platform: &v1.IntegrationPlatform{ - Spec: v1.IntegrationPlatformSpec{ - Configuration: []v1.ConfigurationSpec{ - {Type: "property", Value: "p1=platform"}, - {Type: "property", Value: "p2=platform"}, - {Type: "property", Value: "p3=platform"}, - {Type: "property", Value: "p4=platform"}, - }, - }, - }, - } - e.Platform.ResyncStatusFullConfig() - - pairs := e.collectConfigurationPairs("property") - assert.Equal(t, pairs, []variable{ - {Name: "p1", Value: "integration"}, - {Name: "p2", Value: "kit"}, - {Name: "p3", Value: "platform"}, - {Name: "p4", Value: "integration"}, - }) -} - func TestBoolPointerFunctions(t *testing.T) { trueP := BoolP(true) falseP := BoolP(false)
