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 a134e488f7de828ea62cad32f02afdf438aa3ef2
Author: nicolaferraro <ni.ferr...@gmail.com>
AuthorDate: Wed Mar 24 17:03:39 2021 +0100

    Fix #2158: set a convention for object references
---
 pkg/cmd/run.go                       |   2 +-
 pkg/trait/service_binding.go         |  55 ++++++---------
 pkg/util/reference/reference.go      | 128 ++++++++++++++++++++++++++++++++++
 pkg/util/reference/reference_test.go | 131 +++++++++++++++++++++++++++++++++++
 4 files changed, 281 insertions(+), 35 deletions(-)

diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index a77afa5..ca2c09f 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -73,7 +73,7 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) 
(*cobra.Command, *runCmdOptions)
        }
 
        cmd.Flags().String("name", "", "The integration name")
-       cmd.Flags().StringArrayP("connect", "c", nil, "A ServiceBinding or 
Provisioned Service that the integration should bind to specified as 
KIND.VERSION.GROUP/NAME[/NAMESPACE]")
+       cmd.Flags().StringArrayP("connect", "c", nil, "A ServiceBinding or 
Provisioned Service that the integration should bind to, specified as 
[[apigroup/]version:]kind:[namespace/]name")
        cmd.Flags().StringArrayP("dependency", "d", nil, "An external library 
that should be included. E.g. for Maven dependencies \"mvn:org.my/app:1.0\"")
        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")
diff --git a/pkg/trait/service_binding.go b/pkg/trait/service_binding.go
index d8ebb3a..10ff8ce 100644
--- a/pkg/trait/service_binding.go
+++ b/pkg/trait/service_binding.go
@@ -19,8 +19,8 @@ package trait
 
 import (
        "fmt"
-       "strings"
 
+       "github.com/apache/camel-k/pkg/util/reference"
        corev1 "k8s.io/api/core/v1"
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -38,7 +38,7 @@ import (
 // +camel-k:trait=service-binding
 type serviceBindingTrait struct {
        BaseTrait `property:",squash"`
-       // List of Provisioned Services and ServiceBindings in the form 
KIND.VERSION.GROUP/NAME[/NAMESPACE]
+       // List of Provisioned Services and ServiceBindings in the form 
[[apigroup/]version:]kind:[namespace/]name
        ServiceBindings []string `property:"service-bindings" 
json:"serviceBindings,omitempty"`
 }
 
@@ -173,33 +173,23 @@ func (t *serviceBindingTrait) getServiceBinding(e 
*Environment, name string) (sb
 
 func (t *serviceBindingTrait) parseProvisionedServices(e *Environment) 
([]sb.Service, error) {
        services := make([]sb.Service, 0)
+       converter := reference.NewConverter("")
        for _, s := range t.ServiceBindings {
-               seg := strings.Split(s, "/")
-               if !(len(seg) == 3 || len(seg) == 2) {
-                       return nil, fmt.Errorf("ServiceBinding: %s should be 
specified in the form KIND.VERSION.GROUP/NAME[/NAMESPACE]", s)
+               ref, err := converter.FromString(s)
+               if err != nil {
+                       return services, err
                }
-               gvk := seg[0]
-               index := strings.Index(gvk, ".")
-               kind := seg[0][0:index]
-               if kind == "ServiceBinding" {
-                       continue
-               }
-               vg := seg[0][index+1 : len(gvk)]
-               index = strings.Index(vg, ".")
-               version := vg[0:index]
-               group := vg[index+1 : len(vg)]
-               name := seg[1]
                namespace := e.Integration.Namespace
-               if len(seg) == 3 {
-                       namespace = seg[2]
+               if ref.Namespace != "" {
+                       namespace = ref.Namespace
                }
                service := sb.Service{
                        NamespacedRef: sb.NamespacedRef{
                                Ref: sb.Ref{
-                                       Group:   group,
-                                       Version: version,
-                                       Kind:    kind,
-                                       Name:    name,
+                                       Group:   ref.GroupVersionKind().Group,
+                                       Version: ref.GroupVersionKind().Version,
+                                       Kind:    ref.Kind,
+                                       Name:    ref.Name,
                                },
                                Namespace: &namespace,
                        },
@@ -211,23 +201,20 @@ func (t *serviceBindingTrait) parseProvisionedServices(e 
*Environment) ([]sb.Ser
 
 func (t *serviceBindingTrait) parseServiceBindings(e *Environment) ([]string, 
error) {
        serviceBindings := make([]string, 0)
+       converter := reference.NewConverter("")
        for _, s := range t.ServiceBindings {
-               seg := strings.Split(s, "/")
-               if !(len(seg) == 3 || len(seg) == 2) {
-                       return nil, fmt.Errorf("ServiceBinding: %s should be 
specified in the form KIND.VERSION.GROUP/NAME[/NAMESPACE]", s)
+               ref, err := converter.FromString(s)
+               if err != nil {
+                       return serviceBindings, err
                }
-               gvk := seg[0]
-               index := strings.Index(gvk, ".")
-               kind := seg[0][0:index]
-               if kind == "ServiceBinding" {
-                       vg := seg[0][index+1 : len(gvk)]
-                       if vg != "v1alpha1.binding.operators.coreos.com" {
-                               return nil, fmt.Errorf("ServiceBinding: %s 
VERSION.GROUP should be v1alpha1.binding.operators.coreos.com", s)
+               if ref.Kind == "ServiceBinding" {
+                       if ref.GroupVersionKind().String() != 
sb.GroupVersion.String() {
+                               return nil, fmt.Errorf("ServiceBinding: %q api 
version should be %q", s, sb.GroupVersion.String())
                        }
-                       if len(seg) == 3 && seg[2] != e.Integration.Namespace {
+                       if ref.Namespace != e.Integration.Namespace {
                                return nil, fmt.Errorf("ServiceBinding: %s 
should be in the same namespace %s as the integration", s, 
e.Integration.Namespace)
                        }
-                       serviceBindings = append(serviceBindings, seg[1])
+                       serviceBindings = append(serviceBindings, ref.Name)
                }
        }
        return serviceBindings, nil
diff --git a/pkg/util/reference/reference.go b/pkg/util/reference/reference.go
new file mode 100644
index 0000000..0458a89
--- /dev/null
+++ b/pkg/util/reference/reference.go
@@ -0,0 +1,128 @@
+/*
+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 reference
+
+import (
+       "fmt"
+       "regexp"
+       "unicode"
+
+       camelv1alpha1 "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+       corev1 "k8s.io/api/core/v1"
+       eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
+       messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1"
+       servingv1 "knative.dev/serving/pkg/apis/serving/v1"
+)
+
+const (
+       KameletPrefix = "kamelet:"
+)
+
+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-.]+)$`)
+
+       templates = map[string]corev1.ObjectReference{
+               "kamelet": corev1.ObjectReference{
+                       Kind:       "Kamelet",
+                       APIVersion: camelv1alpha1.SchemeGroupVersion.String(),
+               },
+               "channel": corev1.ObjectReference{
+                       Kind:       "Channel",
+                       APIVersion: messagingv1.SchemeGroupVersion.String(),
+               },
+               "broker": corev1.ObjectReference{
+                       Kind:       "Broker",
+                       APIVersion: eventingv1.SchemeGroupVersion.String(),
+               },
+               "ksvc": corev1.ObjectReference{
+                       Kind:       "Service",
+                       APIVersion: servingv1.SchemeGroupVersion.String(),
+               },
+       }
+)
+
+type Converter struct {
+       defaultPrefix string
+}
+
+func NewConverter(defaultPrefix string) *Converter {
+       return &Converter{
+               defaultPrefix: defaultPrefix,
+       }
+}
+
+func (c *Converter) FromString(str string) (corev1.ObjectReference, error) {
+       ref, err := c.simpleDecodeString(str)
+       if err != nil {
+               return ref, err
+       }
+       c.expandReference(&ref)
+
+       if ref.Kind == "" || !unicode.IsUpper([]rune(ref.Kind)[0]) {
+               return corev1.ObjectReference{}, fmt.Errorf("invalid kind: %q", 
ref.Kind)
+       }
+       return ref, nil
+}
+
+func (c *Converter) expandReference(ref *corev1.ObjectReference) {
+       if template, ok := templates[ref.Kind]; ok {
+               if template.Kind != "" {
+                       ref.Kind = template.Kind
+               }
+               if ref.APIVersion == "" && template.APIVersion != "" {
+                       ref.APIVersion = template.APIVersion
+               }
+       }
+}
+
+func (c *Converter) simpleDecodeString(str string) (corev1.ObjectReference, 
error) {
+       fullName := str
+       if simpleNameRegexp.MatchString(str) {
+               fullName = c.defaultPrefix + str
+       }
+
+       if fullNameRegexp.MatchString(fullName) {
+               groupNames := fullNameRegexp.SubexpNames()
+               ref := corev1.ObjectReference{}
+               for _, match := range 
fullNameRegexp.FindAllStringSubmatch(fullName, -1) {
+                       for idx, text := range match {
+                               groupName := groupNames[idx]
+                               switch groupName {
+                               case "apiVersion":
+                                       ref.APIVersion = text
+                               case "namespace":
+                                       ref.Namespace = text
+                               case "kind":
+                                       ref.Kind = text
+                               case "name":
+                                       ref.Name = text
+                               }
+                       }
+               }
+               return ref, nil
+       }
+       if c.defaultPrefix != "" {
+               return corev1.ObjectReference{}, fmt.Errorf(`name %q does not 
match either "[[apigroup/]version:]kind:[namespace/]name" or 
"[namespace/]name"`, str)
+       }
+       return corev1.ObjectReference{}, fmt.Errorf(`name %q does not match 
format "[[apigroup/]version:]kind:[namespace/]name"`, str)
+}
+
+func (c *Converter) ToString(ref corev1.ObjectReference) (string, error) {
+       return "", nil
+}
diff --git a/pkg/util/reference/reference_test.go 
b/pkg/util/reference/reference_test.go
new file mode 100644
index 0000000..5539602
--- /dev/null
+++ b/pkg/util/reference/reference_test.go
@@ -0,0 +1,131 @@
+/*
+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 reference
+
+import (
+       "fmt"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+       corev1 "k8s.io/api/core/v1"
+)
+
+func TestExpressions(t *testing.T) {
+       emptyPrefix := ""
+       tests := []struct {
+               defaultPrefix *string
+               name          string
+               error         bool
+               ref           corev1.ObjectReference
+       }{
+               {
+                       name:  "lowercase:source",
+                       error: true,
+               },
+               {
+                       name:  "PostgreSQL/ns/name",
+                       error: true,
+               },
+               {
+                       defaultPrefix: &emptyPrefix,
+                       name:          "source",
+                       error:         true,
+               },
+               {
+                       name: "source",
+                       ref: corev1.ObjectReference{
+                               Kind:       "Kamelet",
+                               APIVersion: "camel.apache.org/v1alpha1",
+                               Name:       "source",
+                       },
+               },
+               {
+                       name: "ns1/source",
+                       ref: corev1.ObjectReference{
+                               Kind:       "Kamelet",
+                               APIVersion: "camel.apache.org/v1alpha1",
+                               Namespace:  "ns1",
+                               Name:       "source",
+                       },
+               },
+               {
+                       name: "ksvc:service",
+                       ref: corev1.ObjectReference{
+                               Kind:       "Service",
+                               APIVersion: "serving.knative.dev/v1",
+                               Name:       "service",
+                       },
+               },
+               {
+                       name: "channel:ns3/ch2",
+                       ref: corev1.ObjectReference{
+                               Kind:       "Channel",
+                               APIVersion: "messaging.knative.dev/v1",
+                               Namespace:  "ns3",
+                               Name:       "ch2",
+                       },
+               },
+               {
+                       name: "broker:default",
+                       ref: corev1.ObjectReference{
+                               Kind:       "Broker",
+                               APIVersion: "eventing.knative.dev/v1",
+                               Name:       "default",
+                       },
+               },
+               {
+                       name: "PostgreSQL:ns1/db",
+                       ref: corev1.ObjectReference{
+                               Kind:      "PostgreSQL",
+                               Namespace: "ns1",
+                               Name:      "db",
+                       },
+               },
+               {
+                       name: "postgres.org/v1alpha1:PostgreSQL:ns1/db",
+                       ref: corev1.ObjectReference{
+                               APIVersion: "postgres.org/v1alpha1",
+                               Kind:       "PostgreSQL",
+                               Namespace:  "ns1",
+                               Name:       "db",
+                       },
+               },
+       }
+
+       for i, tc := range tests {
+               t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) {
+
+                       var converter *Converter
+                       if tc.defaultPrefix != nil {
+                               converter = NewConverter(*tc.defaultPrefix)
+                       } else {
+                               // Using kamelet: prefix by default in the tests
+                               converter = NewConverter(KameletPrefix)
+                       }
+
+                       ref, err := converter.FromString(tc.name)
+                       if tc.error {
+                               assert.Error(t, err)
+                       } else {
+                               assert.NoError(t, err)
+                               assert.Equal(t, tc.ref, ref)
+                       }
+               })
+       }
+
+}

Reply via email to