You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2021/03/25 14:08:16 UTC
[camel-k] 01/03: Fix #2158: set a convention for object references
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...@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)
+ }
+ })
+ }
+
+}