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

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

commit 0314d28c0435f54e9f848bc701792a1cce801db5
Author: nicolaferraro <[email protected]>
AuthorDate: Wed Apr 7 15:20:09 2021 +0200

    Fix #2083: add steps and query-like parameters
---
 pkg/cmd/bind.go                      | 68 +++++++++++++++++++++++++++++++-----
 pkg/cmd/root.go                      | 10 +++---
 pkg/cmd/run.go                       |  2 +-
 pkg/util/reference/reference.go      | 44 +++++++++++++++++++++--
 pkg/util/reference/reference_test.go | 36 +++++++++++++++++++
 5 files changed, 142 insertions(+), 18 deletions(-)

diff --git a/pkg/cmd/bind.go b/pkg/cmd/bind.go
index 320ffb4..2771899 100644
--- a/pkg/cmd/bind.go
+++ b/pkg/cmd/bind.go
@@ -57,15 +57,17 @@ func newCmdBind(rootCmdOptions *RootCmdOptions) 
(*cobra.Command, *bindCmdOptions
 
        cmd.Flags().String("name", "", "Name for the binding")
        cmd.Flags().StringP("output", "o", "", "Output format. One of: 
json|yaml")
-       cmd.Flags().StringArrayP("property", "p", nil, `Add a binding property 
in the form of "source.<key>=<value>" or "sink.<key>=<value>"`)
+       cmd.Flags().StringArrayP("property", "p", nil, `Add a binding property 
in the form of "source.<key>=<value>", "sink.<key>=<value>" or 
"step-<n>.<key>=<value>"`)
        cmd.Flags().Bool("skip-checks", false, "Do not verify the binding for 
compliance with Kamelets and other Kubernetes resources")
+       cmd.Flags().StringArray("step", nil, `Add binding steps as Kubernetes 
resources, such as Kamelets. Endpoints are expected in the format 
"[[apigroup/]version:]kind:[namespace/]name" or plain Camel URIs.`)
 
        return &cmd, &options
 }
 
 const (
-       sourceKey = "source"
-       sinkKey   = "sink"
+       sourceKey     = "source"
+       sinkKey       = "sink"
+       stepKeyPrefix = "step-"
 )
 
 type bindCmdOptions struct {
@@ -74,6 +76,7 @@ type bindCmdOptions struct {
        OutputFormat string   `mapstructure:"output" yaml:",omitempty"`
        Properties   []string `mapstructure:"properties" yaml:",omitempty"`
        SkipChecks   bool     `mapstructure:"skip-checks" yaml:",omitempty"`
+       Steps        []string `mapstructure:"steps" yaml:",omitempty"`
 }
 
 func (o *bindCmdOptions) validate(cmd *cobra.Command, args []string) error {
@@ -105,6 +108,17 @@ func (o *bindCmdOptions) validate(cmd *cobra.Command, args 
[]string) error {
                if err := o.checkCompliance(cmd, sink); err != nil {
                        return err
                }
+
+               for idx, stepDesc := range o.Steps {
+                       stepKey := fmt.Sprintf("%s%d", stepKeyPrefix, idx)
+                       step, err := o.decode(stepDesc, stepKey)
+                       if err != nil {
+                               return err
+                       }
+                       if err := o.checkCompliance(cmd, step); err != nil {
+                               return err
+                       }
+               }
        }
 
        return nil
@@ -134,6 +148,18 @@ func (o *bindCmdOptions) run(args []string) error {
                },
        }
 
+       if len(o.Steps) > 0 {
+               binding.Spec.Steps = make([]v1alpha1.Endpoint, 0)
+               for idx, stepDesc := range o.Steps {
+                       stepKey := fmt.Sprintf("%s%d", stepKeyPrefix, idx)
+                       step, err := o.decode(stepDesc, stepKey)
+                       if err != nil {
+                               return err
+                       }
+                       binding.Spec.Steps = append(binding.Spec.Steps, step)
+               }
+       }
+
        switch o.OutputFormat {
        case "":
                // continue..
@@ -183,7 +209,8 @@ func (o *bindCmdOptions) run(args []string) error {
 func (o *bindCmdOptions) decode(res string, key string) (v1alpha1.Endpoint, 
error) {
        refConverter := reference.NewConverter(reference.KameletPrefix)
        endpoint := v1alpha1.Endpoint{}
-       props, err := o.asEndpointProperties(o.getProperties(key))
+       explicitProps := o.getProperties(key)
+       props, err := o.asEndpointProperties(explicitProps)
        if err != nil {
                return endpoint, err
        }
@@ -201,6 +228,26 @@ func (o *bindCmdOptions) decode(res string, key string) 
(v1alpha1.Endpoint, erro
        if endpoint.Ref.Namespace == "" {
                endpoint.Ref.Namespace = o.Namespace
        }
+       embeddedProps, err := refConverter.PropertiesFromString(res)
+       if err != nil {
+               return endpoint, err
+       }
+       if len(embeddedProps) > 0 {
+               allProps := make(map[string]string)
+               for k, v := range explicitProps {
+                       allProps[k] = v
+               }
+               for k, v := range embeddedProps {
+                       allProps[k] = v
+               }
+
+               props, err := o.asEndpointProperties(allProps)
+               if err != nil {
+                       return endpoint, err
+               }
+               endpoint.Properties = props
+       }
+
        return endpoint, nil
 }
 
@@ -254,14 +301,17 @@ func (o *bindCmdOptions) getProperties(refType string) 
map[string]string {
 func (o *bindCmdOptions) parseProperty(prop string) (string, string, string, 
error) {
        parts := strings.SplitN(prop, "=", 2)
        if len(parts) != 2 {
-               return "", "", "", fmt.Errorf(`property %q does not follow 
format "[source|sink].<key>=<value>"`, prop)
+               return "", "", "", fmt.Errorf(`property %q does not follow 
format "[source|sink|step-<n>].<key>=<value>"`, prop)
        }
        keyParts := strings.SplitN(parts[0], ".", 2)
        if len(keyParts) != 2 {
-               return "", "", "", fmt.Errorf(`property key %q does not follow 
format "[source|sink].<key>"`, parts[0])
+               return "", "", "", fmt.Errorf(`property key %q does not follow 
format "[source|sink|step-<n>].<key>"`, parts[0])
        }
-       if keyParts[0] != sourceKey && keyParts[0] != sinkKey {
-               return "", "", "", fmt.Errorf(`property key %q does not start 
with "source." or "sink."`, parts[0])
+       isSource := keyParts[0] == sourceKey
+       isSink := keyParts[0] == sinkKey
+       isStep := strings.HasPrefix(keyParts[0], stepKeyPrefix)
+       if !isSource && !isSink && !isStep {
+               return "", "", "", fmt.Errorf(`property key %q does not start 
with "source.", "sink." or "step-<n>."`, parts[0])
        }
        return keyParts[0], keyParts[1], parts[1], nil
 }
@@ -280,7 +330,7 @@ func (o *bindCmdOptions) checkCompliance(cmd 
*cobra.Command, endpoint v1alpha1.E
                if err := c.Get(o.Context, key, &kamelet); err != nil {
                        if k8serrors.IsNotFound(err) {
                                // Kamelet may be in the operator namespace, 
but we currently don't have a way to determine it: we just warn
-                               fmt.Fprintf(cmd.OutOrStderr(), "Warning: 
Kamelet %q not found in namespace %q\n", key.Name, key.Namespace)
+                               fmt.Fprintf(cmd.ErrOrStderr(), "Warning: 
Kamelet %q not found in namespace %q\n", key.Name, key.Namespace)
                                return nil
                        }
                        return err
diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go
index d3de5c7..06a775b 100644
--- a/pkg/cmd/root.go
+++ b/pkg/cmd/root.go
@@ -194,24 +194,24 @@ func (command *RootCmdOptions) preRun(cmd *cobra.Command, 
_ []string) error {
                // Furthermore, there can be any incompatibilities, as the 
install command deploys
                // the operator version it's compatible with.
                if cmd.Use != installCommand && cmd.Use != operatorCommand {
-                       checkAndShowCompatibilityWarning(command.Context, c, 
command.Namespace)
+                       checkAndShowCompatibilityWarning(cmd, command.Context, 
c, command.Namespace)
                }
        }
 
        return nil
 }
 
-func checkAndShowCompatibilityWarning(ctx context.Context, c client.Client, 
namespace string) {
+func checkAndShowCompatibilityWarning(cmd *cobra.Command, ctx context.Context, 
c client.Client, namespace string) {
        operatorVersion, err := operatorVersion(ctx, c, namespace)
        if err != nil {
                if k8serrors.IsNotFound(err) {
-                       fmt.Printf("No IntegrationPlatform resource in %s 
namespace\n", namespace)
+                       fmt.Fprintf(cmd.ErrOrStderr(), "No IntegrationPlatform 
resource in %s namespace\n", namespace)
                } else {
-                       fmt.Printf("Unable to retrieve the operator version: 
%s\n", err.Error())
+                       fmt.Fprintf(cmd.ErrOrStderr(), "Unable to retrieve the 
operator version: %s\n", err.Error())
                }
        } else {
                if operatorVersion != "" && 
!compatibleVersions(operatorVersion, defaults.Version) {
-                       fmt.Printf("You're using Camel K %s client with a %s 
cluster operator, it's recommended to use the same version to improve 
compatibility.\n\n", defaults.Version, operatorVersion)
+                       fmt.Fprintf(cmd.ErrOrStderr(), "You're using Camel K %s 
client with a %s cluster operator, it's recommended to use the same version to 
improve compatibility.\n\n", defaults.Version, operatorVersion)
                }
        }
 }
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 0b6bf6e..255feaf 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -414,7 +414,7 @@ func (o *runCmdOptions) syncIntegration(cmd *cobra.Command, 
c client.Client, sou
                                }
                        }()
                } else {
-                       fmt.Printf("Warning: the following URL will not be 
watched for changes: %s\n", s)
+                       fmt.Fprintf(cmd.ErrOrStderr(), "Warning: the following 
URL will not be watched for changes: %s\n", s)
                }
        }
 
diff --git a/pkg/util/reference/reference.go b/pkg/util/reference/reference.go
index dc79cd5..5af887e 100644
--- a/pkg/util/reference/reference.go
+++ b/pkg/util/reference/reference.go
@@ -18,12 +18,14 @@ limitations under the License.
 package reference
 
 import (
-       "errors"
        "fmt"
+       "net/url"
        "regexp"
+       "strings"
        "unicode"
 
        camelv1alpha1 "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+       "github.com/pkg/errors"
        corev1 "k8s.io/api/core/v1"
        eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
        messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1"
@@ -35,8 +37,9 @@ const (
 )
 
 var (
-       simpleNameRegexp = 
regexp.MustCompile(`^(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)$`)
-       fullNameRegexp   = 
regexp.MustCompile(`^(?:(?P<apiVersion>(?:[a-z0-9-.]+/)?(?:[a-z0-9-.]+)):)?(?P<kind>[A-Za-z0-9-.]+):(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)$`)
+       simpleNameRegexp = 
regexp.MustCompile(`^(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)(?:$|[?].*$)`)
+       fullNameRegexp   = 
regexp.MustCompile(`^(?:(?P<apiVersion>(?:[a-z0-9-.]+/)?(?:[a-z0-9-.]+)):)?(?P<kind>[A-Za-z0-9-.]+):(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)(?:$|[?].*$)`)
+       queryRegexp      = regexp.MustCompile(`^[^?]*[?](?P<query>.*)$`)
 
        templates = map[string]corev1.ObjectReference{
                "kamelet": corev1.ObjectReference{
@@ -81,6 +84,41 @@ func (c *Converter) FromString(str string) 
(corev1.ObjectReference, error) {
        return ref, nil
 }
 
+func (c *Converter) PropertiesFromString(str string) (map[string]string, 
error) {
+       if queryRegexp.MatchString(str) {
+               groupNames := queryRegexp.SubexpNames()
+               res := make(map[string]string)
+               var query string
+               for _, match := range queryRegexp.FindAllStringSubmatch(str, 
-1) {
+                       for idx, text := range match {
+                               groupName := groupNames[idx]
+                               switch groupName {
+                               case "query":
+                                       query = text
+                               }
+                       }
+               }
+               parts := strings.Split(query, "&")
+               for _, part := range parts {
+                       kv := strings.SplitN(part, "=", 2)
+                       if len(kv) != 2 {
+                               return nil, fmt.Errorf("invalid key=value 
format for string %q", part)
+                       }
+                       k, errkey := url.QueryUnescape(kv[0])
+                       if errkey != nil {
+                               return nil, errors.Wrapf(errkey, "cannot 
unescape key %q", kv[0])
+                       }
+                       v, errval := url.QueryUnescape(kv[1])
+                       if errval != nil {
+                               return nil, errors.Wrapf(errval, "cannot 
unescape value %q", kv[1])
+                       }
+                       res[k] = v
+               }
+               return res, nil
+       }
+       return nil, nil
+}
+
 func (c *Converter) expandReference(ref *corev1.ObjectReference) {
        if template, ok := templates[ref.Kind]; ok {
                if template.Kind != "" {
diff --git a/pkg/util/reference/reference_test.go 
b/pkg/util/reference/reference_test.go
index f13c5d8..ca5be0b 100644
--- a/pkg/util/reference/reference_test.go
+++ b/pkg/util/reference/reference_test.go
@@ -33,6 +33,7 @@ func TestExpressions(t *testing.T) {
                error         bool
                ref           corev1.ObjectReference
                stringRef     string
+               properties    map[string]string
        }{
                {
                        name:  "lowercase:source",
@@ -123,6 +124,37 @@ func TestExpressions(t *testing.T) {
                        },
                        stringRef: "postgres.org/v1alpha1:PostgreSQL:ns1/db",
                },
+               {
+                       name: 
"postgres.org/v1alpha1:PostgreSQL:ns1/db?user=user1&password=pwd2&host=192.168.2.2&special=%201&special2=a=1",
+                       ref: corev1.ObjectReference{
+                               APIVersion: "postgres.org/v1alpha1",
+                               Kind:       "PostgreSQL",
+                               Namespace:  "ns1",
+                               Name:       "db",
+                       },
+                       stringRef: "postgres.org/v1alpha1:PostgreSQL:ns1/db",
+                       properties: map[string]string{
+                               "user":     "user1",
+                               "password": "pwd2",
+                               "host":     "192.168.2.2",
+                               "special":  " 1",
+                               "special2": "a=1",
+                       },
+               },
+               {
+                       name: "source?a=b&b=c&d=e",
+                       ref: corev1.ObjectReference{
+                               Kind:       "Kamelet",
+                               APIVersion: "camel.apache.org/v1alpha1",
+                               Name:       "source",
+                       },
+                       stringRef: "camel.apache.org/v1alpha1:Kamelet:source",
+                       properties: map[string]string{
+                               "a": "b",
+                               "b": "c",
+                               "d": "e",
+                       },
+               },
        }
 
        for i, tc := range tests {
@@ -143,6 +175,10 @@ func TestExpressions(t *testing.T) {
                                asString, err2 := converter.ToString(ref)
                                assert.NoError(t, err2)
 
+                               props, err3 := 
converter.PropertiesFromString(tc.name)
+                               assert.NoError(t, err3)
+                               assert.Equal(t, tc.properties, props)
+
                                assert.NoError(t, err)
                                assert.Equal(t, tc.ref, ref)
                                assert.Equal(t, tc.stringRef, asString)

Reply via email to