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 19b66cffb432f98f8e29ca0bf71e6112f375c989 Author: nicolaferraro <[email protected]> AuthorDate: Thu Oct 1 18:04:54 2020 +0200 chore(binding): refactor binding mechanism and add many more tests --- deploy/crd-kamelet-binding.yaml | 80 ++++---- .../kameletbindings.camel.apache.org.crd.yaml | 80 ++++---- .../display.groovy} | 9 +- e2e/yaks/kamelet-binding/kamelet.feature | 5 + .../logger-sink-binding.yaml} | 15 +- .../logger-sink.kamelet.yaml} | 13 +- .../messages-channel.yaml | 0 .../timer-source-binding-display.yaml} | 10 +- .../timer-source-binding.yaml | 0 .../timer-source.kamelet.yaml | 0 .../{kamelets => kamelet-binding}/yaks-config.yaml | 10 +- .../{kamelets => kamelet}/echo-sink.kamelet.yaml | 0 e2e/yaks/kamelet/kamelet.feature | 5 + e2e/yaks/{kamelets => kamelet}/source-sink.groovy | 3 +- .../timer-source.kamelet.yaml | 0 e2e/yaks/{kamelets => kamelet}/yaks-config.yaml | 6 - e2e/yaks/kamelets/kamelet.feature | 9 - helm/camel-k/crds/crd-kamelet-binding.yaml | 80 ++++---- pkg/apis/camel/v1alpha1/kamelet_binding_types.go | 9 +- .../v1alpha1/kamelet_binding_types_support.go | 21 ++ pkg/controller/kameletbinding/initialize.go | 85 +------- pkg/util/bindings/api.go | 48 +++++ pkg/util/bindings/bindings_test.go | 222 +++++++++++++++++++++ pkg/util/bindings/camel_uri.go | 58 ++++++ pkg/util/bindings/catalog.go | 61 ++++++ pkg/util/bindings/kamelet.go | 68 +++++++ pkg/util/bindings/knative_ref.go | 116 +++++++++++ pkg/util/bindings/knative_uri.go | 102 ++++++++++ pkg/util/knative/apis.go | 30 +++ 29 files changed, 915 insertions(+), 230 deletions(-) diff --git a/deploy/crd-kamelet-binding.yaml b/deploy/crd-kamelet-binding.yaml index 74575d8..53bf707 100644 --- a/deploy/crd-kamelet-binding.yaml +++ b/deploy/crd-kamelet-binding.yaml @@ -189,31 +189,35 @@ spec: apiVersion: description: API version of the referent. type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean + fieldPath: + description: 'If referring to a piece of an object instead of + an entire object, this string should contain a valid JSON/Go + field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within + a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" + (container with index 2 in this pod). This syntax is chosen + only to have some well-defined way of referencing a part of + an object. TODO: this design is not final and this field is + subject to change in the future.' + type: string kind: description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference + is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' type: string uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string - required: - - apiVersion - - kind - - name - - uid type: object uri: description: URI can alternatively be used to specify the (Camel) @@ -235,31 +239,35 @@ spec: apiVersion: description: API version of the referent. type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean + fieldPath: + description: 'If referring to a piece of an object instead of + an entire object, this string should contain a valid JSON/Go + field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within + a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" + (container with index 2 in this pod). This syntax is chosen + only to have some well-defined way of referencing a part of + an object. TODO: this design is not final and this field is + subject to change in the future.' + type: string kind: description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference + is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' type: string uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string - required: - - apiVersion - - kind - - name - - uid type: object uri: description: URI can alternatively be used to specify the (Camel) diff --git a/deploy/olm-catalog/camel-k-dev/1.2.0-snapshot/kameletbindings.camel.apache.org.crd.yaml b/deploy/olm-catalog/camel-k-dev/1.2.0-snapshot/kameletbindings.camel.apache.org.crd.yaml index 74575d8..53bf707 100644 --- a/deploy/olm-catalog/camel-k-dev/1.2.0-snapshot/kameletbindings.camel.apache.org.crd.yaml +++ b/deploy/olm-catalog/camel-k-dev/1.2.0-snapshot/kameletbindings.camel.apache.org.crd.yaml @@ -189,31 +189,35 @@ spec: apiVersion: description: API version of the referent. type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean + fieldPath: + description: 'If referring to a piece of an object instead of + an entire object, this string should contain a valid JSON/Go + field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within + a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" + (container with index 2 in this pod). This syntax is chosen + only to have some well-defined way of referencing a part of + an object. TODO: this design is not final and this field is + subject to change in the future.' + type: string kind: description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference + is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' type: string uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string - required: - - apiVersion - - kind - - name - - uid type: object uri: description: URI can alternatively be used to specify the (Camel) @@ -235,31 +239,35 @@ spec: apiVersion: description: API version of the referent. type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean + fieldPath: + description: 'If referring to a piece of an object instead of + an entire object, this string should contain a valid JSON/Go + field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within + a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" + (container with index 2 in this pod). This syntax is chosen + only to have some well-defined way of referencing a part of + an object. TODO: this design is not final and this field is + subject to change in the future.' + type: string kind: description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference + is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' type: string uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string - required: - - apiVersion - - kind - - name - - uid type: object uri: description: URI can alternatively be used to specify the (Camel) diff --git a/e2e/yaks/kamelets/logger.groovy b/e2e/yaks/kamelet-binding/display.groovy old mode 100755 new mode 100644 similarity index 84% rename from e2e/yaks/kamelets/logger.groovy rename to e2e/yaks/kamelet-binding/display.groovy index a28afe3..b3ba363 --- a/e2e/yaks/kamelets/logger.groovy +++ b/e2e/yaks/kamelet-binding/display.groovy @@ -1,5 +1,4 @@ -// camel-k: language=groovy dependency=mvn:org.apache.camel.k:camel-kamelet:1.5.1-SNAPSHOT - +// camel-k: language=groovy /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -17,5 +16,7 @@ * limitations under the License. */ -from('knative:channel/messages') - .log('${body}') +from('knative:endpoint/display') + .log('${body}') + .setBody().header('CE-Type') + .log('type: ${body}'); diff --git a/e2e/yaks/kamelet-binding/kamelet.feature b/e2e/yaks/kamelet-binding/kamelet.feature new file mode 100644 index 0000000..2d04ce1 --- /dev/null +++ b/e2e/yaks/kamelet-binding/kamelet.feature @@ -0,0 +1,5 @@ +Feature: Camel K can bind Kamelets + + Scenario: Running integration using a simple Kamelet with KameletBinding + Given integration logger-sink-binding is running + Then integration logger-sink-binding should print message: Hello Kamelets diff --git a/e2e/yaks/kamelets/timer-source-binding.yaml b/e2e/yaks/kamelet-binding/logger-sink-binding.yaml similarity index 69% copy from e2e/yaks/kamelets/timer-source-binding.yaml copy to e2e/yaks/kamelet-binding/logger-sink-binding.yaml index 32aeefb..bcdd95e 100644 --- a/e2e/yaks/kamelets/timer-source-binding.yaml +++ b/e2e/yaks/kamelet-binding/logger-sink-binding.yaml @@ -1,18 +1,15 @@ apiVersion: camel.apache.org/v1alpha1 kind: KameletBinding metadata: - name: timer-source-binding + name: logger-sink-binding spec: source: ref: - kind: Kamelet - apiVersion: camel.apache.org/v1alpha1 - name: timer-source - properties: - message: Hello Kamelets - period: 1000 - sink: - ref: kind: InMemoryChannel apiVersion: messaging.knative.dev/v1beta1 name: messages + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1alpha1 + name: logger-sink diff --git a/e2e/yaks/kamelets/echo-sink.kamelet.yaml b/e2e/yaks/kamelet-binding/logger-sink.kamelet.yaml similarity index 57% copy from e2e/yaks/kamelets/echo-sink.kamelet.yaml copy to e2e/yaks/kamelet-binding/logger-sink.kamelet.yaml index c21ea6b..a28fc94 100644 --- a/e2e/yaks/kamelets/echo-sink.kamelet.yaml +++ b/e2e/yaks/kamelet-binding/logger-sink.kamelet.yaml @@ -1,19 +1,19 @@ apiVersion: camel.apache.org/v1alpha1 kind: Kamelet metadata: - name: echo-sink + name: logger-sink label: camel.apache.org/kamelet.type: "sink" spec: definition: - title: "Echo" - description: "Replies with an echo message to each incoming event" + title: "Logger" + description: "Logs the received payload of each incoming event" properties: prefix: title: Prefix - description: The prefix to prepend to the incoming event + description: The prefix to prepend to the logged message type: string - default: "echo: " + default: "message: " types: in: mediaType: text/plain @@ -23,5 +23,4 @@ spec: from: uri: "direct:{{routeId}}" steps: - - set-body: - simple: "{{prefix}}${body}" + - log: "{{prefix}}${body}" diff --git a/e2e/yaks/kamelets/messages-channel.yaml b/e2e/yaks/kamelet-binding/messages-channel.yaml similarity index 100% rename from e2e/yaks/kamelets/messages-channel.yaml rename to e2e/yaks/kamelet-binding/messages-channel.yaml diff --git a/e2e/yaks/kamelets/timer-source-binding.yaml b/e2e/yaks/kamelet-binding/timer-source-binding-display.yaml similarity index 52% copy from e2e/yaks/kamelets/timer-source-binding.yaml copy to e2e/yaks/kamelet-binding/timer-source-binding-display.yaml index 32aeefb..b217445 100644 --- a/e2e/yaks/kamelets/timer-source-binding.yaml +++ b/e2e/yaks/kamelet-binding/timer-source-binding-display.yaml @@ -1,7 +1,7 @@ apiVersion: camel.apache.org/v1alpha1 kind: KameletBinding metadata: - name: timer-source-binding + name: timer-source-binding-display spec: source: ref: @@ -9,10 +9,6 @@ spec: apiVersion: camel.apache.org/v1alpha1 name: timer-source properties: - message: Hello Kamelets - period: 1000 + message: Hello sink: - ref: - kind: InMemoryChannel - apiVersion: messaging.knative.dev/v1beta1 - name: messages + uri: http://display.{namespace}.svc.cluster.local diff --git a/e2e/yaks/kamelets/timer-source-binding.yaml b/e2e/yaks/kamelet-binding/timer-source-binding.yaml similarity index 100% rename from e2e/yaks/kamelets/timer-source-binding.yaml rename to e2e/yaks/kamelet-binding/timer-source-binding.yaml diff --git a/e2e/yaks/kamelets/timer-source.kamelet.yaml b/e2e/yaks/kamelet-binding/timer-source.kamelet.yaml similarity index 100% copy from e2e/yaks/kamelets/timer-source.kamelet.yaml copy to e2e/yaks/kamelet-binding/timer-source.kamelet.yaml diff --git a/e2e/yaks/kamelets/yaks-config.yaml b/e2e/yaks/kamelet-binding/yaks-config.yaml similarity index 72% copy from e2e/yaks/kamelets/yaks-config.yaml copy to e2e/yaks/kamelet-binding/yaks-config.yaml index 8679b79..59fec0a 100644 --- a/e2e/yaks/kamelets/yaks-config.yaml +++ b/e2e/yaks/kamelet-binding/yaks-config.yaml @@ -26,10 +26,14 @@ pre: kubectl apply -f messages-channel.yaml -n $YAKS_NAMESPACE kubectl apply -f timer-source.kamelet.yaml -n $YAKS_NAMESPACE - kubectl apply -f echo-sink.kamelet.yaml -n $YAKS_NAMESPACE + kubectl apply -f logger-sink.kamelet.yaml -n $YAKS_NAMESPACE kubectl apply -f timer-source-binding.yaml -n $YAKS_NAMESPACE + kubectl apply -f logger-sink-binding.yaml -n $YAKS_NAMESPACE + kubectl wait kameletbinding timer-source-binding --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE + kubectl wait kameletbinding logger-sink-binding --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE - kamel run logger.groovy -w -n $YAKS_NAMESPACE - kamel run source-sink.groovy -w -n $YAKS_NAMESPACE + kamel run display.groovy -w -n $YAKS_NAMESPACE + cat timer-source-binding-display.yaml | sed 's/{namespace}/'"${YAKS_NAMESPACE}"'/' | kubectl apply -f - + kubectl wait kameletbinding timer-source-binding-display --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE diff --git a/e2e/yaks/kamelets/echo-sink.kamelet.yaml b/e2e/yaks/kamelet/echo-sink.kamelet.yaml similarity index 100% rename from e2e/yaks/kamelets/echo-sink.kamelet.yaml rename to e2e/yaks/kamelet/echo-sink.kamelet.yaml diff --git a/e2e/yaks/kamelet/kamelet.feature b/e2e/yaks/kamelet/kamelet.feature new file mode 100644 index 0000000..01aca6f --- /dev/null +++ b/e2e/yaks/kamelet/kamelet.feature @@ -0,0 +1,5 @@ +Feature: Camel K can run Kamelets + + Scenario: Integrations can use multiple kamelets + Given integration source-sink is running + Then integration source-sink should print nice echo: Camel K diff --git a/e2e/yaks/kamelets/source-sink.groovy b/e2e/yaks/kamelet/source-sink.groovy similarity index 94% rename from e2e/yaks/kamelets/source-sink.groovy rename to e2e/yaks/kamelet/source-sink.groovy index 3666bad..e607a9f 100755 --- a/e2e/yaks/kamelets/source-sink.groovy +++ b/e2e/yaks/kamelet/source-sink.groovy @@ -17,8 +17,7 @@ * limitations under the License. */ -from('timer:tick') - .setBody().constant('Camel K') +from('kamelet:timer-source?message=Camel+K') .to("kamelet:echo-sink") .to("kamelet:echo-sink?prefix=nice+") .log('${body}') diff --git a/e2e/yaks/kamelets/timer-source.kamelet.yaml b/e2e/yaks/kamelet/timer-source.kamelet.yaml similarity index 100% rename from e2e/yaks/kamelets/timer-source.kamelet.yaml rename to e2e/yaks/kamelet/timer-source.kamelet.yaml diff --git a/e2e/yaks/kamelets/yaks-config.yaml b/e2e/yaks/kamelet/yaks-config.yaml similarity index 81% rename from e2e/yaks/kamelets/yaks-config.yaml rename to e2e/yaks/kamelet/yaks-config.yaml index 8679b79..6ff2355 100644 --- a/e2e/yaks/kamelets/yaks-config.yaml +++ b/e2e/yaks/kamelet/yaks-config.yaml @@ -23,13 +23,7 @@ pre: run: | kamel install -n $YAKS_NAMESPACE - kubectl apply -f messages-channel.yaml -n $YAKS_NAMESPACE - kubectl apply -f timer-source.kamelet.yaml -n $YAKS_NAMESPACE kubectl apply -f echo-sink.kamelet.yaml -n $YAKS_NAMESPACE - kubectl apply -f timer-source-binding.yaml -n $YAKS_NAMESPACE - kubectl wait kameletbinding timer-source-binding --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE - - kamel run logger.groovy -w -n $YAKS_NAMESPACE kamel run source-sink.groovy -w -n $YAKS_NAMESPACE diff --git a/e2e/yaks/kamelets/kamelet.feature b/e2e/yaks/kamelets/kamelet.feature deleted file mode 100644 index 2a4d932..0000000 --- a/e2e/yaks/kamelets/kamelet.feature +++ /dev/null @@ -1,9 +0,0 @@ -Feature: Camel K can run Kamelets and bind them - - Scenario: Running integration using a simple Kamelet with KameletBinding - Given integration logger is running - Then integration logger should print Hello Kamelets - - Scenario: Integrations can use multiple kamelets - Given integration source-sink is running - Then integration source-sink should print nice echo: Camel K diff --git a/helm/camel-k/crds/crd-kamelet-binding.yaml b/helm/camel-k/crds/crd-kamelet-binding.yaml index 74575d8..53bf707 100644 --- a/helm/camel-k/crds/crd-kamelet-binding.yaml +++ b/helm/camel-k/crds/crd-kamelet-binding.yaml @@ -189,31 +189,35 @@ spec: apiVersion: description: API version of the referent. type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean + fieldPath: + description: 'If referring to a piece of an object instead of + an entire object, this string should contain a valid JSON/Go + field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within + a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" + (container with index 2 in this pod). This syntax is chosen + only to have some well-defined way of referencing a part of + an object. TODO: this design is not final and this field is + subject to change in the future.' + type: string kind: description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference + is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' type: string uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string - required: - - apiVersion - - kind - - name - - uid type: object uri: description: URI can alternatively be used to specify the (Camel) @@ -235,31 +239,35 @@ spec: apiVersion: description: API version of the referent. type: string - blockOwnerDeletion: - description: If true, AND if the owner has the "foregroundDeletion" - finalizer, then the owner cannot be deleted from the key-value - store until this reference is removed. Defaults to false. - To set this field, a user needs "delete" permission of the - owner, otherwise 422 (Unprocessable Entity) will be returned. - type: boolean - controller: - description: If true, this reference points to the managing - controller. - type: boolean + fieldPath: + description: 'If referring to a piece of an object instead of + an entire object, this string should contain a valid JSON/Go + field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within + a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" + (container with index 2 in this pod). This syntax is chosen + only to have some well-defined way of referencing a part of + an object. TODO: this design is not final and this field is + subject to change in the future.' + type: string kind: description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string name: - description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference + is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' type: string uid: - description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string - required: - - apiVersion - - kind - - name - - uid type: object uri: description: URI can alternatively be used to specify the (Camel) diff --git a/pkg/apis/camel/v1alpha1/kamelet_binding_types.go b/pkg/apis/camel/v1alpha1/kamelet_binding_types.go index 64702c2..4214074 100644 --- a/pkg/apis/camel/v1alpha1/kamelet_binding_types.go +++ b/pkg/apis/camel/v1alpha1/kamelet_binding_types.go @@ -38,13 +38,20 @@ type KameletBindingSpec struct { // Endpoint represents a source/sink external entity type Endpoint struct { // Ref can be used to declare a Kubernetes resource as source/sink endpoint - Ref *metav1.OwnerReference `json:"ref,omitempty"` + Ref *corev1.ObjectReference `json:"ref,omitempty"` // URI can alternatively be used to specify the (Camel) endpoint explicitly URI *string `json:"uri,omitempty"` // Properties are a key value representation of endpoint properties Properties EndpointProperties `json:"properties,omitempty"` } +type EndpointType string + +const ( + EndpointTypeSource EndpointType = "source" + EndpointTypeSink EndpointType = "sink" +) + // EndpointProperties is a key/value struct represented as JSON raw to allow numeric/boolean values // +kubebuilder:validation:Type=object type EndpointProperties struct { diff --git a/pkg/apis/camel/v1alpha1/kamelet_binding_types_support.go b/pkg/apis/camel/v1alpha1/kamelet_binding_types_support.go index f630013..d470dc6 100644 --- a/pkg/apis/camel/v1alpha1/kamelet_binding_types_support.go +++ b/pkg/apis/camel/v1alpha1/kamelet_binding_types_support.go @@ -18,6 +18,9 @@ limitations under the License. package v1alpha1 import ( + "encoding/json" + "fmt" + v1 "github.com/apache/camel-k/pkg/apis/camel/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -137,6 +140,24 @@ func (in *KameletBindingStatus) RemoveCondition(condType KameletBindingCondition in.Conditions = newConditions } +// GetPropertyMap returns the EndpointProperties as map +func (p EndpointProperties) GetPropertyMap() (map[string]string, error) { + if len(p.RawMessage) == 0 { + return nil, nil + } + + // Convert json property values to objects before getting their string representation + var props map[string]interface{} + if err := json.Unmarshal(p.RawMessage, &props); err != nil { + return nil, err + } + stringProps := make(map[string]string, len(props)) + for k, v := range props { + stringProps[k] = fmt.Sprintf("%v", v) + } + return stringProps, nil +} + // NewKameletBinding -- func NewKameletBinding(namespace string, name string) KameletBinding { return KameletBinding{ diff --git a/pkg/controller/kameletbinding/initialize.go b/pkg/controller/kameletbinding/initialize.go index d047ae6..1054c3f 100644 --- a/pkg/controller/kameletbinding/initialize.go +++ b/pkg/controller/kameletbinding/initialize.go @@ -20,14 +20,11 @@ package kameletbinding import ( "context" "encoding/json" - "fmt" - "net/url" - "strings" v1 "github.com/apache/camel-k/pkg/apis/camel/v1" "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/util/bindings" "github.com/apache/camel-k/pkg/util/kubernetes" - "github.com/apache/camel-k/pkg/util/uri" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -61,40 +58,33 @@ func (action *initializeAction) Handle(ctx context.Context, kameletbinding *v1al it.Spec = *kameletbinding.Spec.Integration.DeepCopy() } - fromURI, err := getEndpointURI(kameletbinding.Spec.Source) + from, err := bindings.Translate(v1alpha1.EndpointTypeSource, kameletbinding.Spec.Source) if err != nil { return nil, errors.Wrap(err, "could not determine source URI") } - toURI, err := getEndpointURI(kameletbinding.Spec.Sink) + to, err := bindings.Translate(v1alpha1.EndpointTypeSink, kameletbinding.Spec.Sink) if err != nil { return nil, errors.Wrap(err, "could not determine sink URI") } - // TODO remove this after making sinkbinding the default (https://github.com/apache/camel-k/issues/1654) - if strings.HasPrefix(toURI, "knative:") { - knativeConfig := map[string]interface{}{ - "sinkBinding": true, - } - knativeConfigJSON, err := json.Marshal(knativeConfig) - if err != nil { - return nil, err - } + if len(from.Traits) > 0 || len(to.Traits) > 0 { if it.Spec.Traits == nil { it.Spec.Traits = make(map[string]v1.TraitSpec) } - it.Spec.Traits["knative"] = v1.TraitSpec{ - Configuration: v1.TraitConfiguration{ - RawMessage: knativeConfigJSON, - }, + for k, v := range from.Traits { + it.Spec.Traits[k] = v + } + for k, v := range to.Traits { + it.Spec.Traits[k] = v } } flow := map[string]interface{}{ "from": map[string]interface{}{ - "uri": fromURI, + "uri": from.URI, "steps": []map[string]interface{}{ { - "to": toURI, + "to": to.URI, }, }, }, @@ -113,56 +103,3 @@ func (action *initializeAction) Handle(ctx context.Context, kameletbinding *v1al target.Status.Phase = v1alpha1.KameletBindingPhaseCreating return target, nil } - -func getEndpointURI(e v1alpha1.Endpoint) (string, error) { - baseURI, err := getEndpointBaseURI(e) - if err != nil { - return baseURI, err - } - - // Convert json properties to string before using them in URI - if len(e.Properties.RawMessage) > 0 { - var props map[string]interface{} - if err := json.Unmarshal(e.Properties.RawMessage, &props); err != nil { - return "", err - } - stringProps := make(map[string]string, len(props)) - for k, v := range props { - stringProps[k] = fmt.Sprintf("%v", v) - } - return uri.AppendParameters(baseURI, stringProps), nil - } - - return baseURI, nil -} - -func getEndpointBaseURI(e v1alpha1.Endpoint) (string, error) { - if err := validateEndpoint(e); err != nil { - return "", err - } - - // return the URI if explicitly stated - if e.URI != nil { - return *e.URI, nil - } - - // Kamelets are a known type - if e.Ref.Kind == v1alpha1.KameletKind { - return fmt.Sprintf("kamelet:%s", url.PathEscape(e.Ref.Name)), nil - } - - // assume we're using Knative for the time being (Kafka resources may be added in the future) - return uri.AppendParameters(fmt.Sprintf("knative:endpoint/%s", url.PathEscape(e.Ref.Name)), map[string]string{ - "apiVersion": e.Ref.APIVersion, - "kind": e.Ref.Kind, - }), nil -} - -func validateEndpoint(e v1alpha1.Endpoint) error { - if e.Ref == nil && e.URI == nil { - return errors.New("no ref or URI specified in endpoint") - } else if e.Ref != nil && e.URI != nil { - return errors.New("cannot use both ref and URI to specify an endpoint: only one of them should be used") - } - return nil -} diff --git a/pkg/util/bindings/api.go b/pkg/util/bindings/api.go new file mode 100644 index 0000000..c9daf72 --- /dev/null +++ b/pkg/util/bindings/api.go @@ -0,0 +1,48 @@ +/* +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 bindings provides APIs to transform Kubernetes objects into Camel URIs equivalents +package bindings + +import ( + v1 "github.com/apache/camel-k/pkg/apis/camel/v1" + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" +) + +const ( + OrderFirst = 0 + OrderStandard = 50 + OrderLast = 100 +) + +// Binding represents how a Kubernetes object is represented in Camel K resources +type Binding struct { + // URI is the Camel URI equivalent + URI string + // Traits is a partial trait specification that should be merged into the integration + Traits map[string]v1.TraitSpec +} + +// BindingProvider maps a KameletBinding endpoint into Camel K resources +type BindingProvider interface { + // ID returns the name of the binding provider + ID() string + // Translate does the actual mapping + Translate(endpointType v1alpha1.EndpointType, endpoint v1alpha1.Endpoint) (*Binding, error) + // Order returns the relative order of execution of the binding provider + Order() int +} diff --git a/pkg/util/bindings/bindings_test.go b/pkg/util/bindings/bindings_test.go new file mode 100644 index 0000000..1f7696a --- /dev/null +++ b/pkg/util/bindings/bindings_test.go @@ -0,0 +1,222 @@ +/* +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 bindings + +import ( + "encoding/json" + "fmt" + "net/url" + "testing" + + camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1" + knativeapis "github.com/apache/camel-k/pkg/apis/camel/v1/knative" + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" +) + +func TestBindings(t *testing.T) { + testcases := []struct { + endpointType v1alpha1.EndpointType + endpoint v1alpha1.Endpoint + uri string + traits map[string]camelv1.TraitSpec + }{ + { + endpointType: v1alpha1.EndpointTypeSink, + endpoint: v1alpha1.Endpoint{ + Ref: &corev1.ObjectReference{ + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + Name: "myservice", + }, + }, + uri: "knative:endpoint/myservice?apiVersion=serving.knative.dev%2Fv1&kind=Service", + traits: asTraitSpec("knative", map[string]interface{}{ + "sinkBinding": true, + }), + }, + { + endpointType: v1alpha1.EndpointTypeSink, + endpoint: v1alpha1.Endpoint{ + Ref: &corev1.ObjectReference{ + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + Name: "myservice", + }, + Properties: asEndpointProperties(map[string]string{ + "ce.override.ce-type": "mytype", + }), + }, + uri: "knative:endpoint/myservice?apiVersion=serving.knative.dev%2Fv1&ce.override.ce-type=mytype&kind=Service", + traits: asTraitSpec("knative", map[string]interface{}{ + "sinkBinding": true, + }), + }, + { + endpointType: v1alpha1.EndpointTypeSink, + endpoint: v1alpha1.Endpoint{ + Ref: &corev1.ObjectReference{ + Kind: "Channel", + APIVersion: "messaging.knative.dev/v1", + Name: "mychannel", + }, + }, + uri: "knative:channel/mychannel?apiVersion=messaging.knative.dev%2Fv1&kind=Channel", + traits: asTraitSpec("knative", map[string]interface{}{ + "sinkBinding": true, + }), + }, + { + endpointType: v1alpha1.EndpointTypeSource, + endpoint: v1alpha1.Endpoint{ + Ref: &corev1.ObjectReference{ + Kind: "Channel", + APIVersion: "messaging.knative.dev/v1", + Name: "mychannel", + }, + }, + uri: "knative:channel/mychannel?apiVersion=messaging.knative.dev%2Fv1&kind=Channel", + }, + { + endpointType: v1alpha1.EndpointTypeSource, + endpoint: v1alpha1.Endpoint{ + Ref: &corev1.ObjectReference{ + Kind: "KafkaChannel", + APIVersion: "messaging.knative.dev/v1beta1", + Name: "mychannel", + }, + }, + uri: "knative:channel/mychannel?apiVersion=messaging.knative.dev%2Fv1beta1&kind=KafkaChannel", + }, + { + endpointType: v1alpha1.EndpointTypeSource, + endpoint: v1alpha1.Endpoint{ + Ref: &corev1.ObjectReference{ + Kind: "Broker", + APIVersion: "eventing.knative.dev/v1beta1", + Name: "default", + }, + Properties: asEndpointProperties(map[string]string{ + "type": "myeventtype", + }), + }, + uri: "knative:event/myeventtype?apiVersion=eventing.knative.dev%2Fv1beta1&kind=Broker", + }, + { + endpoint: v1alpha1.Endpoint{ + Ref: &corev1.ObjectReference{ + Kind: "Kamelet", + APIVersion: "camel.apache.org/v1any1", + Name: "mykamelet", + }, + }, + uri: "kamelet:mykamelet", + }, + { + endpoint: v1alpha1.Endpoint{ + Ref: &corev1.ObjectReference{ + Kind: "Kamelet", + APIVersion: "camel.apache.org/v1any1", + Name: "mykamelet", + }, + Properties: asEndpointProperties(map[string]string{ + "mymessage": "myval", + "encodedkey?": "encoded=val", + }), + }, + uri: "kamelet:mykamelet?encodedkey%3F=encoded%3Dval&mymessage=myval", + }, + { + endpointType: v1alpha1.EndpointTypeSink, + endpoint: v1alpha1.Endpoint{ + URI: asStringPointer("https://myurl/hey"), + Properties: asEndpointProperties(map[string]string{ + "ce.override.ce-type": "mytype", + }), + }, + uri: "knative:endpoint/sink?ce.override.ce-type=mytype", + traits: asTraitSpec("knative", map[string]interface{}{ + "configuration": asKnativeConfig("https://myurl/hey"), + }), + }, + { + endpointType: v1alpha1.EndpointTypeSink, + endpoint: v1alpha1.Endpoint{ + URI: asStringPointer("docker://xxx"), + }, + uri: "docker://xxx", + }, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("test-%d-%s", i, tc.uri), func(t *testing.T) { + binding, err := Translate(tc.endpointType, tc.endpoint) + assert.NoError(t, err) + assert.NotNil(t, binding) + assert.Equal(t, tc.uri, binding.URI) + assert.Equal(t, tc.traits, binding.Traits) + }) + } +} + +func asEndpointProperties(props map[string]string) v1alpha1.EndpointProperties { + serialized, err := json.Marshal(props) + if err != nil { + panic(err) + } + return v1alpha1.EndpointProperties{ + RawMessage: serialized, + } +} + +func asTraitSpec(key string, data map[string]interface{}) map[string]camelv1.TraitSpec { + res := make(map[string]camelv1.TraitSpec) + serialized, err := json.Marshal(data) + if err != nil { + panic(err) + } + res[key] = camelv1.TraitSpec{ + Configuration: camelv1.TraitConfiguration{ + RawMessage: serialized, + }, + } + return res +} + +func asStringPointer(str string) *string { + return &str +} + +func asKnativeConfig(endpointURL string) string { + serviceURL, err := url.Parse(endpointURL) + if err != nil { + panic(err) + } + def, err := knativeapis.BuildCamelServiceDefinition("sink", knativeapis.CamelEndpointKindSink, knativeapis.CamelServiceTypeEndpoint, *serviceURL, "", "") + if err != nil { + panic(err) + } + env := knativeapis.NewCamelEnvironment() + env.Services = append(env.Services, def) + serialized, err := json.Marshal(env) + if err != nil { + panic(err) + } + return string(serialized) +} diff --git a/pkg/util/bindings/camel_uri.go b/pkg/util/bindings/camel_uri.go new file mode 100644 index 0000000..6dfea5f --- /dev/null +++ b/pkg/util/bindings/camel_uri.go @@ -0,0 +1,58 @@ +/* +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 bindings + +import ( + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/util/uri" +) + +// CamelURIBindingProvider converts an explicit URI into a Camel endpoint. +// It's used as fallback if the URI scheme is not known by other providers. +type CamelURIBindingProvider struct{} + +func (k CamelURIBindingProvider) ID() string { + return "camel-uri" +} + +func (k CamelURIBindingProvider) Translate(endpointType v1alpha1.EndpointType, e v1alpha1.Endpoint) (*Binding, error) { + if e.URI == nil { + // works only on uris + return nil, nil + } + + endpointURI := *e.URI + props, err := e.Properties.GetPropertyMap() + if err != nil { + return nil, err + } + endpointURI = uri.AppendParameters(endpointURI, props) + + return &Binding{ + URI: endpointURI, + }, nil +} + +func (k CamelURIBindingProvider) Order() int { + // Using it as fallback + return OrderLast +} + +func init() { + RegisterBindingProvider(CamelURIBindingProvider{}) +} diff --git a/pkg/util/bindings/catalog.go b/pkg/util/bindings/catalog.go new file mode 100644 index 0000000..0fa42ee --- /dev/null +++ b/pkg/util/bindings/catalog.go @@ -0,0 +1,61 @@ +/* +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 bindings + +import ( + "errors" + "sort" + + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" +) + +var bindingProviders []BindingProvider + +func RegisterBindingProvider(bp BindingProvider) { + bindingProviders = append(bindingProviders, bp) + sort.Slice(bindingProviders, func(i, j int) bool { + bi := bindingProviders[i] + bj := bindingProviders[j] + return (bi.Order() < bj.Order()) || + (bi.Order() == bj.Order() && bi.ID() < bj.ID()) + }) +} + +// Translate execute all chained binding providers, returning the first success or the first error +func Translate(endpointType v1alpha1.EndpointType, endpoint v1alpha1.Endpoint) (*Binding, error) { + if err := validateEndpoint(endpoint); err != nil { + return nil, err + } + + for _, bp := range bindingProviders { + b, err := bp.Translate(endpointType, endpoint) + if b != nil || err != nil { + return b, err + } + } + return nil, nil +} + +func validateEndpoint(e v1alpha1.Endpoint) error { + if e.Ref == nil && e.URI == nil { + return errors.New("no ref or URI specified in endpoint") + } else if e.Ref != nil && e.URI != nil { + return errors.New("cannot use both ref and URI to specify an endpoint: only one of them should be used") + } + return nil +} diff --git a/pkg/util/bindings/kamelet.go b/pkg/util/bindings/kamelet.go new file mode 100644 index 0000000..7e8d166 --- /dev/null +++ b/pkg/util/bindings/kamelet.go @@ -0,0 +1,68 @@ +/* +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 bindings + +import ( + "fmt" + "net/url" + + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/util/uri" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// KameletBindingProvider converts a reference to a Kamelet into a Camel URI +type KameletBindingProvider struct{} + +func (k KameletBindingProvider) ID() string { + return "kamelet" +} + +func (k KameletBindingProvider) Translate(endpointType v1alpha1.EndpointType, e v1alpha1.Endpoint) (*Binding, error) { + if e.Ref == nil { + // works only on refs + return nil, nil + } + gv, err := schema.ParseGroupVersion(e.Ref.APIVersion) + if err != nil { + return nil, err + } + // it translates only Kamelet refs + if e.Ref.Kind == v1alpha1.KameletKind && gv.Group == v1alpha1.SchemeGroupVersion.Group { + kameletURI := fmt.Sprintf("kamelet:%s", url.PathEscape(e.Ref.Name)) + + props, err := e.Properties.GetPropertyMap() + if err != nil { + return nil, err + } + kameletURI = uri.AppendParameters(kameletURI, props) + + return &Binding{ + URI: kameletURI, + }, nil + } + return nil, nil +} + +func (k KameletBindingProvider) Order() int { + return OrderStandard +} + +func init() { + RegisterBindingProvider(KameletBindingProvider{}) +} diff --git a/pkg/util/bindings/knative_ref.go b/pkg/util/bindings/knative_ref.go new file mode 100644 index 0000000..bd7f7a3 --- /dev/null +++ b/pkg/util/bindings/knative_ref.go @@ -0,0 +1,116 @@ +/* +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 bindings + +import ( + "encoding/json" + "fmt" + "net/url" + + v1 "github.com/apache/camel-k/pkg/apis/camel/v1" + knativeapis "github.com/apache/camel-k/pkg/apis/camel/v1/knative" + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/util/knative" + "github.com/apache/camel-k/pkg/util/uri" +) + +// KnativeRefBindingProvider converts a reference to a Kubernetes object into a Camel URI. +// It's used as fallback if no other providers can decode the object reference. +type KnativeRefBindingProvider struct{} + +func (k KnativeRefBindingProvider) ID() string { + return "knative-ref" +} + +func (k KnativeRefBindingProvider) Translate(endpointType v1alpha1.EndpointType, e v1alpha1.Endpoint) (*Binding, error) { + if e.Ref == nil { + // works only on refs + return nil, nil + } + + serviceType, err := knative.GetServiceType(*e.Ref) + if err != nil { + return nil, err + } + + if serviceType == nil { + endpointType := knativeapis.CamelServiceTypeEndpoint + serviceType = &endpointType + } + + props, err := e.Properties.GetPropertyMap() + if err != nil { + return nil, err + } + if props == nil { + props = make(map[string]string) + } + if props["apiVersion"] == "" { + props["apiVersion"] = e.Ref.APIVersion + } + if props["kind"] == "" { + props["kind"] = e.Ref.Kind + } + + var serviceURI string + if *serviceType == knativeapis.CamelServiceTypeEvent { + if eventType, ok := props["type"]; ok { + // consume prop + delete(props, "type") + serviceURI = fmt.Sprintf("knative:%s/%s", *serviceType, eventType) + } else { + serviceURI = fmt.Sprintf("knative:%s", *serviceType) + } + } else { + serviceURI = fmt.Sprintf("knative:%s/%s", *serviceType, url.PathEscape(e.Ref.Name)) + } + + serviceURI = uri.AppendParameters(serviceURI, props) + + var traits map[string]v1.TraitSpec + if endpointType == v1alpha1.EndpointTypeSink { + knativeConfig := make(map[string]interface{}) + // TODO remove this after making sinkbinding the default (https://github.com/apache/camel-k/issues/1654) + knativeConfig["sinkBinding"] = true + knativeConfigJSON, err := json.Marshal(knativeConfig) + if err != nil { + return nil, err + } + traits = map[string]v1.TraitSpec{ + "knative": { + Configuration: v1.TraitConfiguration{ + RawMessage: knativeConfigJSON, + }, + }, + } + } + + return &Binding{ + URI: serviceURI, + Traits: traits, + }, nil +} + +func (k KnativeRefBindingProvider) Order() int { + // Executes as last, as it can be used as fallback for all unknown object references + return OrderLast +} + +func init() { + RegisterBindingProvider(KnativeRefBindingProvider{}) +} diff --git a/pkg/util/bindings/knative_uri.go b/pkg/util/bindings/knative_uri.go new file mode 100644 index 0000000..0f43cfa --- /dev/null +++ b/pkg/util/bindings/knative_uri.go @@ -0,0 +1,102 @@ +/* +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 bindings + +import ( + "encoding/json" + "net/url" + "strings" + + v1 "github.com/apache/camel-k/pkg/apis/camel/v1" + knativeapis "github.com/apache/camel-k/pkg/apis/camel/v1/knative" + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/util/uri" +) + +// KnativeURIBindingProvider converts a HTTP/HTTPS URI into a Camel Knative endpoint (to call it via CloudEvents). +type KnativeURIBindingProvider struct{} + +func (k KnativeURIBindingProvider) ID() string { + return "knative-uri" +} + +func (k KnativeURIBindingProvider) Translate(endpointType v1alpha1.EndpointType, e v1alpha1.Endpoint) (*Binding, error) { + if e.URI == nil { + // works only on uris + return nil, nil + } + if !strings.HasPrefix(*e.URI, "http:") && !strings.HasPrefix(*e.URI, "https:") { + // only translates http/https uri to Knative calls + return nil, nil + } + if endpointType == v1alpha1.EndpointTypeSource { + // HTTP/HTTPS uri are translated to Knative endpoints only when used as sinks + return nil, nil + } + + knativeConfig := make(map[string]interface{}) + originalURI, err := url.Parse(*e.URI) + if err != nil { + return nil, err + } + env := knativeapis.NewCamelEnvironment() + svc, err := knativeapis.BuildCamelServiceDefinition("sink", + knativeapis.CamelEndpointKindSink, + knativeapis.CamelServiceTypeEndpoint, + *originalURI, "", "") + if err != nil { + return nil, err + } + env.Services = append(env.Services, svc) + config, err := env.Serialize() + if err != nil { + return nil, err + } + knativeConfig["configuration"] = config + knativeConfigJSON, err := json.Marshal(knativeConfig) + if err != nil { + return nil, err + } + + // Rewrite URI to match the service definition + serviceURI := "knative:endpoint/sink" + props, err := e.Properties.GetPropertyMap() + if err != nil { + return nil, err + } + serviceURI = uri.AppendParameters(serviceURI, props) + + return &Binding{ + URI: serviceURI, + Traits: map[string]v1.TraitSpec{ + "knative": { + Configuration: v1.TraitConfiguration{ + RawMessage: knativeConfigJSON, + }, + }, + }, + }, nil +} + +func (k KnativeURIBindingProvider) Order() int { + return OrderStandard +} + +func init() { + RegisterBindingProvider(KnativeURIBindingProvider{}) +} diff --git a/pkg/util/knative/apis.go b/pkg/util/knative/apis.go index 72607f1..67bf5b2 100644 --- a/pkg/util/knative/apis.go +++ b/pkg/util/knative/apis.go @@ -176,6 +176,36 @@ func FillMissingReferenceData(serviceType knativev1.CamelServiceType, ref v1.Obj return refs } +func GetServiceType(ref v1.ObjectReference) (*knativev1.CamelServiceType, error) { + refGV, err := schema.ParseGroupVersion(ref.APIVersion) + if err != nil { + return nil, err + } + + for _, c := range KnownChannelKinds { + if c.Group == refGV.Group && c.Kind == ref.Kind { + channelType := knativev1.CamelServiceTypeChannel + return &channelType, nil + } + } + + for _, c := range KnownBrokerKinds { + if c.Group == refGV.Group && c.Kind == ref.Kind { + eventType := knativev1.CamelServiceTypeEvent + return &eventType, nil + } + } + + for _, c := range KnownEndpointKinds { + if c.Group == refGV.Group && c.Kind == ref.Kind { + endpointType := knativev1.CamelServiceTypeEndpoint + return &endpointType, nil + } + } + + return nil, nil +} + // nolint: gocritic func fillMissingReferenceDataWith(serviceTypes []GroupVersionKindResource, ref v1.ObjectReference) []v1.ObjectReference { list := make([]v1.ObjectReference, 0)
