This is an automated email from the ASF dual-hosted git repository.
pcongiusti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git
The following commit(s) were added to refs/heads/main by this push:
new ce2d16e14 feat(trait): cross namespace Kamelets
ce2d16e14 is described below
commit ce2d16e14bde4884e58125536e08cb4f4fa7657c
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Sat Oct 11 09:17:53 2025 +0200
feat(trait): cross namespace Kamelets
Allow the Integration to run any Kamelet available in any namespace
---
docs/modules/ROOT/nav.adoc | 6 +-
.../kamelets.adoc => kamelets/architecture.adoc} | 6 +-
.../modules/ROOT/pages/kamelets/configuration.adoc | 247 +++++++++++++++++++++
...amelets-distribution.adoc => distribution.adoc} | 8 +-
.../modules/ROOT/pages/kamelets/kamelets-user.adoc | 191 ----------------
docs/modules/ROOT/pages/kamelets/kamelets.adoc | 2 +-
.../ROOT/pages/troubleshooting/debugging.adoc | 9 -
e2e/common/traits/files/kamelet-it-ns.yaml | 24 ++
.../traits/files/my-timer-source-ns.kamelet.yaml | 36 +++
e2e/common/traits/kamelet_test.go | 52 ++++-
pkg/apis/camel/v1/kamelet_types.go | 6 +-
pkg/trait/kamelets.go | 102 ++++++---
pkg/trait/kamelets_test.go | 159 ++++++++++++-
pkg/util/source/kamelet.go | 31 ++-
14 files changed, 628 insertions(+), 251 deletions(-)
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index b9c649026..ee3ec7c4b 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -80,8 +80,9 @@
** xref:traits:toleration.adoc[Toleration]
// End of autogenerated code - DO NOT EDIT! (trait-nav)
* xref:kamelets/kamelets.adoc[Kamelets]
-** xref:kamelets/kamelets-distribution.adoc[Distribution]
-** xref:kamelets/kamelets-user.adoc[Configuration]
+** xref:kamelets/architecture.adoc[Architecture]
+** xref:kamelets/distribution.adoc[Distribution]
+** xref:kamelets/configuration.adoc[Configuration]
* xref:pipeline/pipeline.adoc[Pipelines]
** xref:pipeline/external.adoc[External CICD]
* Scaling
@@ -106,7 +107,6 @@
*** xref:architecture/cr/camel-catalog.adoc[CamelCatalog]
** xref:architecture/runtime.adoc[Runtime]
** xref:architecture/traits.adoc[Traits]
-** xref:architecture/kamelets.adoc[Kamelets]
** xref:architecture/incremental-image.adoc[Incremental Image]
* API
** xref:apis/camel-k.adoc[Camel K v1]
diff --git a/docs/modules/ROOT/pages/architecture/kamelets.adoc
b/docs/modules/ROOT/pages/kamelets/architecture.adoc
similarity index 65%
rename from docs/modules/ROOT/pages/architecture/kamelets.adoc
rename to docs/modules/ROOT/pages/kamelets/architecture.adoc
index 6efa7cd8d..1561ad402 100644
--- a/docs/modules/ROOT/pages/architecture/kamelets.adoc
+++ b/docs/modules/ROOT/pages/kamelets/architecture.adoc
@@ -1,8 +1,8 @@
= Kamelets architecture in Camel K
-xref:kamelets/kamelets.adoc[Kamelets] are a technology which were originally
developed as a Camel K side resource but moved into Camel framework as Kamelet
component. From an design point of view, a Kamelet is a specification that is
provided into the cluster and which can be used at any point by an Integration
or a Pipe, in order to reuse the connector style approach.
+Kamelets are a technology which were originally developed as a Camel K side
resource but moved into Camel framework as Kamelet component. From an design
point of view, a Kamelet is a specification that is provided into the cluster
and which can be used at any point by an Integration or a Pipe, in order to
reuse the connector style approach.
-In Camel framework, a Kamelet is nothing than a component which can be used as
any other component with the `kamelet` uri scheme. This is translated to one or
more Route Templates. What's important for Camel runtime is to have the Kamelet
spec available somewhere when running the application making reference to it.
+In Camel framework, a Kamelet is a component which can be used as any other
component with the `kamelet` uri scheme. This is translated to one or more
Route Templates. What's important for Camel runtime is to have the Kamelet spec
available somewhere when running the application making reference to it (by
default in any `/kamelet` folder available in the classpath).
[[deployment-model]]
== Deployment model
@@ -15,6 +15,8 @@ The operator creates a ConfigMap in order to bundle all the
Kamelets which are e
NOTE: as the Configmap resource is limited to 1 MiB, the operator may split
into more than a single Configmap bundle.
+In resume, the operator is responsible to pick the Kamelet spec available in
the cluster and transform into a format that the runtime will be able to
execute.
+
[[kamelet-parsing]]
=== Parsing capabilities defined in a Kamelet
diff --git a/docs/modules/ROOT/pages/kamelets/configuration.adoc
b/docs/modules/ROOT/pages/kamelets/configuration.adoc
new file mode 100644
index 000000000..7e1b866d9
--- /dev/null
+++ b/docs/modules/ROOT/pages/kamelets/configuration.adoc
@@ -0,0 +1,247 @@
+= How to configure a Kamelet
+
+Speaking technically, a Kamelet is a resource that can be installed on any
Kubernetes cluster or used as a plain yaml configuration in Apache Camel
runtimes. The following is an example of a Kamelet source which can be
programmed to trigger events with a `timer`. This is coming directly from
Apache Camel Kamelets catalog:
+
+.timer-source.kamelet.yaml
+[source,yaml]
+----
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+ name: timer-source
+ annotations:
+ camel.apache.org/kamelet.support.level: "Stable"
+ camel.apache.org/catalog.version: "4.16.0-SNAPSHOT"
+ camel.apache.org/kamelet.icon:
data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4w[...]
+ camel.apache.org/provider: "Apache Software Foundation"
+ camel.apache.org/kamelet.group: "Timer"
+ camel.apache.org/kamelet.namespace: "Scheduling"
+ labels:
+ camel.apache.org/kamelet.type: "source"
+ camel.apache.org/kamelet.verified: "true"
+spec:
+ definition:
+ title: "Timer Source"
+ description: Produces periodic messages with a custom payload.
+ required:
+ - message
+ type: object
+ properties:
+ period:
+ title: Period
+ description: "The interval (in milliseconds) to wait between producing
the next message."
+ type: integer
+ default: 1000
+ message:
+ title: Message
+ description: The message to generate.
+ type: string
+ example: hello world
+ contentType:
+ title: Content Type
+ description: The content type of the generated message.
+ type: string
+ default: text/plain
+ repeatCount:
+ title: Repeat Count
+ description: Specifies a maximum limit of number of fires
+ type: integer
+ dependencies:
+ - "camel:core"
+ - "camel:timer"
+ - "camel:kamelet"
+ template:
+ from:
+ uri: timer:tick
+ parameters:
+ period: "{{period}}"
+ repeatCount: "{{?repeatCount}}"
+ steps:
+ - setBody:
+ constant: "{{message}}"
+ - setHeader:
+ name: "Content-Type"
+ constant: "{{contentType}}"
+ - to: kamelet:sink
+----
+
+From a user perspective it's important to understand the parameter exposed
(`.spec.definition.properties`) and the execution flow (`.spec.template`).
+
+The flow defines the business logic and generally use the parameter that you,
as a user have to provide in your integration.
+
+The Kamelet can be installed on the cluster as a Kamelet custom resource, and
the operator is already in charge to preinstall the Kamelets coming from the
Apache Camel Kamelets bundled catalog. If you create your own Kamelet you can
install it on the cluster in the following way:
+
+[source,shell]
+----
+kubectl apply -f timer-source.kamelet.yaml
+----
+
+Kamelets are standard YAML files, but their common extension is
`.kamelet.yaml` to help IDEs to recognize them and possibly provide
auto-completion.
+
+[[kamelets-usage-integration]]
+== Using Kamelets in Integrations
+
+Kamelets can be used in integrations **as if they were standard Camel
components**. For example, suppose that you've created the `timer-source`
Kamelet in the `default` namespace on Kubernetes, then you can write the
following integration to use the Kamelet:
+
+[source,yaml]
+.kamlet-route.yaml
+----
+- from:
+ uri: "kamelet:timer-source?message=Hello!"
+ steps:
+ - to: "log:info"
+----
+
+NOTE: URI properties ("message") match the corresponding parameters in the
Kamelet definition.
+
+Kamelets can also be used multiple times in the same route definition. This
happens usually with sink Kamelets. Suppose that you've defined a Kamelet named
"my-company-log-sink" in your Kubernetes namespace, then you can write a route
like this:
+
+[source,yaml]
+.kamlet-multi-route.yaml
+----
+- from:
+ uri: "kamelet:timer-source?message=Hello!"
+ steps:
+ - to: "kamelet:my-company-log-sink?bucket=general"
+ - filter:
+ simple: '${body} contains "Camel"'
+ - to: "kamelet:my-company-log-sink?bucket=special"
+----
+
+The "my-company-log-sink" will obviously define what it means to write a log
in the enterprise system and what is concretely a "bucket".
+
+[[kamelets-usage-pipe]]
+== Binding Kamelets into a Pipe
+
+Kamelets are the main reason why we have introduced the `Pipe` custom
resource. They provide a very easy connector style approach as, each Kamelet,
can be defined naturally as an event source or event sink. Usage of a Kamelet
from a Pipe would be like:
+
+[source,yaml]
+.pipe.yaml
+----
+apiVersion: camel.apache.org/v1
+kind: Pipe
+metadata:
+ name: timer-to-log
+spec:
+ source:
+ ref:
+ kind: Kamelet
+ apiVersion: camel.apache.org/v1
+ name: timer-source
+ properties:
+ message: Hello pipe!
+ sink:
+ ref:
+ kind: Kamelet
+ apiVersion: camel.apache.org/v1
+ name: log-sink
+----
+
+=== Configuration
+
+When using a Kamelet, the instance parameters (e.g. "message", "bucket") can
be passed explicitly in the URI or you can use Camel properties. For example:
+
+[source,yaml]
+.kamlet-route.yaml
+----
+- from:
+ uri: "kamelet:timer-source?message={{my-message}}"
+ steps:
+ - to: "log:info"
+----
+
+The application should run with Camel properties set, such as
`my-message=Hello!`.
+
+=== Kamelet versioning
+
+Kamelets provided in a catalog are generally meant to work with a given
runtime version (the same for which they are released). However, when you
create a Kamelet and publish to a cluster, you may want to store and use
different versions. If the Kamelet is provided with more than the `main`
version, then, you can specify which version to use in your Integration by
adding the version parameter.
+
+NOTE: multiple version Kamelets is an exclusive feature of Camel K.
+
+For instance, take the following multi version Kamelet:
+
+[source,yaml]
+.kamlet-namedconfig-route.yaml
+----
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+ name: my-source
+ labels:
+ camel.apache.org/kamelet.type: "source"
+spec:
+ definition:
+ title: "Timer Example"
+ description: "Emit Kamelet Main body"
+ types:
+ out:
+ mediaType: text/plain
+ template:
+ from:
+ uri: timer:tick
+ steps:
+ - setBody:
+ constant: "Kamelet Main"
+ - to: "kamelet:sink"
+ versions:
+ v1:
+ definition:
+ title: "Timer Example 1"
+ description: "Emit Kamelet V1 body"
+ types:
+ out:
+ mediaType: text/plain
+ template:
+ from:
+ uri: timer:tick
+ steps:
+ - setBody:
+ constant: "Kamelet V1"
+ - to: "kamelet:sink"
+ v2:
+ definition:
+ title: "Timer Example 2"
+ description: "Emit Kamelet V2 body"
+ types:
+ out:
+ mediaType: text/plain
+ template:
+ from:
+ uri: timer:tick
+ steps:
+ - setBody:
+ constant: "Kamelet V2"
+ - to: "kamelet:sink"
+----
+
+You can see it specifies the `main` specification, which it is the default to
use. It also specifies two additional versions, `v1` and `v2`. This is nice if
you want to provide versioning changes maintaining the same resource. When you
use it, you will therefore need to specify the version you want to adopt by
setting the `kameletVersion` parameter. For example, you want to use the `v2`
version:
+
+[source,yaml]
+.kamlet-namedconfig-route.yaml
+----
+- from:
+ uri: "kamelet:my-source?kameletVersion=v2"
+ steps:
+ - to: "log:info"
+----
+
+The operator will be able to automatically pick the right version and use it
at runtime. If no version is specified, then you will use the default one.
+
+=== Kamelet namespace
+
+A Kamelet can be installed in any cluster namespace. By default, the operator
will expect the Kamelet to be in the same namespace of the Integration (or
Pipe), the operator namespace (where the bundled kamelets are stored) or any
other repository defined in the `IntegrationPlatform`. If you want to use a
Kamelet stored in another namespace, you will need to use the
`kameletNamespace` parameter. For example, say you have a dedicated namespace
called `kamelets` where you're installing your [...]
+
+NOTE: namespace Kamelets is an exclusive feature of Camel K.
+
+Now, you can instruct the operator to find the Kamelet you're using into such
namespace, for example:
+
+[source,yaml]
+.kamlet-namedconfig-route.yaml
+----
+- from:
+ uri: "kamelet:my-source?kameletNamespace=kamelets"
+ steps:
+ - to: "log:info"
+----
+
+The operator will be therefore loading the Kamelets from that namespace,
unless the same Kamelet exists in the same Integration namespace or the
operator namespace.
diff --git a/docs/modules/ROOT/pages/kamelets/kamelets-distribution.adoc
b/docs/modules/ROOT/pages/kamelets/distribution.adoc
similarity index 77%
rename from docs/modules/ROOT/pages/kamelets/kamelets-distribution.adoc
rename to docs/modules/ROOT/pages/kamelets/distribution.adoc
index a2e9f0e5e..deeadee93 100644
--- a/docs/modules/ROOT/pages/kamelets/kamelets-distribution.adoc
+++ b/docs/modules/ROOT/pages/kamelets/distribution.adoc
@@ -7,6 +7,12 @@ NOTE: the version we bundle depends directly on the default
Camel version used.
As development of Kamelet is very fast, make sure to use some version which is
compatible with the Camel runtime you're going to use.
+== Release Kamelets on the cluster
+
+Since Kamelets are Kubernetes custom resources, the Camel K opinionated way of
Kamelets distribution is to expect them available on the cluster. You tipically
develop a Kamelet and then release it in the Integration namespace or the
operator namespace. Alternatively you can also deliver them into any other
namespace.
+
+There is not a prescribed way how to release the Kamelets on the cluster. It
can vary based on each company process. What's important for the operator, is
that they are available when running the Integrations.
+
[[kamelets-own-catalog]]
== Provide your own catalog
@@ -25,7 +31,7 @@ With this approach you can dynamically include any repository
where your Kamelet
[[kamelets-as-dependency]]
== Kamelets as a dependency
-The Camel K has an opinionated way to use Kamelets which is the one exposed
above. Here the Kamelet spec resource is expected to be available in the
cluster.
+The Camel K has an opinionated way to use Kamelets which is the usage of
cluster custom resources. Here the Kamelet spec resource is expected to be
available in the cluster.
However, you may find situations where you want to bundle a Kamelet in a
dependency (ie, some external catalog containing all Kamelets spec). As
Kamelets are a Camel thing, then, you can use such dependency and let the
runtime use the Kamelets available in the classpath.
diff --git a/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc
b/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc
deleted file mode 100644
index 773c970ca..000000000
--- a/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc
+++ /dev/null
@@ -1,191 +0,0 @@
-= How to configure a Kamelet
-
-Speaking technically, a Kamelet is a resource that can be installed on any
Kubernetes cluster or used as a plain yaml configuration in Apache Camel
runtimes. The following is an example of a Kamelet that we'll use to discuss
the various parts:
-
-.telegram-text-source.kamelet.yaml
-[source,yaml]
-----
-apiVersion: camel.apache.org/v1
-kind: Kamelet
-metadata:
- name: telegram-text-source # <1>
- annotations: # <2>
- camel.apache.org/kamelet.icon: "data:image/svg+xml;base64,PD94bW..."
- labels: # <3>
- camel.apache.org/kamelet.type: "source"
-spec:
- definition: # <4>
- title: "Telegram Text Source"
- description: |-
- Receive all text messages that people send to your telegram bot.
-
- # Instructions
- Description can include Markdown and guide the final user to configure
the Kamelet parameters.
- required:
- - botToken
- properties:
- botToken:
- title: Token
- description: The token to access your bot on Telegram
- type: string
- x-descriptors:
- - urn:alm:descriptor:com.tectonic.ui:password
-
- dataTypes: # <5>
- out:
- default: text
- types:
- text:
- mediaType: text/plain
- # schema:
- template: # <6>
- from:
- uri: telegram:bots
- parameters:
- authorizationToken: "#property:botToken"
- steps:
- - convert-body-to:
- type: "java.lang.String"
- type-class: "java.lang.String"
- charset: "UTF8"
- - filter:
- simple: "${body} != null"
- - log: "${body}"
- - to: "kamelet:sink"
-----
-<1> The Kamelet ID, to be used in integrations that want to leverage the
Kamelet
-<2> Annotations such as icon provide additional display features to the Kamelet
-<3> Labels allow users to query Kamelets e.g. by kind ("source" vs. "sink")
-<4> Description of the Kamelets and parameters in JSON-schema specification
format
-<5> The data type that the Kamelet produces. Data type specifications contain
the media type of the output and also may include a schema.
-<6> The route template defining the behavior of the Kamelet
-
-At a high level (more details are provided later), a Kamelet resource
describes:
-
-- A metadata section containing the ID (`metadata` -> `name`) of the Kamelet
and other information, such as the type of Kamelet (`source` or `sink`)
-- A JSON-schema specification (`definition`) containing a set of parameters
that you can use to configure the Kamelet
-- An optional section containing information about input and output expected
by the Kamelet (`types`)
-- A Camel flow in YAML DSL containing the implementation of the Kamelet
(`flow`)
-
-Once **installed on a Kubernetes namespace**, the Kamelet can be **used by any
Integration in that namespace**. Kamelets can be installed on a Kubernetes
namespace with a simple command:
-
-[source,shell]
-----
-kubectl apply -f telegram-text-source.kamelet.yaml
-----
-
-Kamelets are standard YAML files, but their common extension is
`.kamelet.yaml` to help IDEs to recognize them and possibly provide
auto-completion.
-
-[[kamelets-usage-integration]]
-== Using Kamelets in Integrations
-
-Kamelets can be used in integrations **as if they were standard Camel
components**. For example, suppose that you've created the
`telegram-text-source` Kamelet in the `default` namespace on Kubernetes, then
you can write the following integration to use the Kamelet:
-
-[source,yaml]
-.kamlet-route.yaml
-----
-- from:
- uri: "kamelet:telegram-text-source?botToken=XXXXYYYY"
- steps:
- - to: "log:info"
-----
-
-NOTE: URI properties ("botToken") match the corresponding parameters in the
Kamelet definition
-
-Kamelets can also be used multiple times in the same route definition. This
happens usually with sink Kamelets. Suppose that you've defined a Kamelet named
"my-company-log-sink" in your Kubernetes namespace, then you can write a route
like this:
-
-[source,yaml]
-.kamlet-multi-route.yaml
-----
-- from:
- uri: "kamelet:telegram-text-source?botToken=XXXXYYYY"
- steps:
- - to: "kamelet:my-company-log-sink?bucket=general"
- - filter:
- simple: '${body} contains "Camel"'
- - to: "kamelet:my-company-log-sink?bucket=special"
-----
-
-The "my-company-log-sink" will obviously define what it means to write a log
in the enterprise system and what is concretely a "bucket".
-
-=== Configuration
-
-When using a Kamelet, the instance parameters (e.g. "botToken", "bucket") can
be passed explicitly in the URI or you can use properties. Properties can be
also
-loaded implicitly by the operator from Kubernetes secrets (see below).
-
-==== URI based configuration
-
-You can configure the Kamelet by passing directly the configuration parameters
in the URI, as in:
-
-[source,yaml]
-----
-- from:
- uri: "kamelet:telegram-text-source?botToken=the-token-value"
-...
-----
-
-In this case, "the-token-value" is passed explicitly in the URI (you can also
pass a custom property placeholder as value).
-
-==== Property based configuration
-
-An alternative way to configure the Kamelet is to provide configuration
parameters as properties of the integration.
-
-Taking for example a different version of the integration above:
-
-[source,yaml]
-.kamelet-properties-route.yaml
-----
-- from:
- uri: "kamelet:telegram-text-source"
- steps:
- - to: "kamelet:my-company-log-sink"
- - filter:
- simple: '${body} contains "Camel"'
- - to: "kamelet:my-company-log-sink/mynamedconfig"
-----
-
-NOTE: The integration above does not contain URI query parameters and the last
URI ("kamelet:my-company-log-sink/mynamedconfig") contains a path parameter
with value "mynamedconfig"
-
-The integration above needs some configuration in order to run properly. The
configuration can be provided in a property file:
-
-[source,properties]
-.kamelet-example.properties
-----
-# Configuration for the Telegram source Kamelet
-camel.kamelet.telegram-text-source.botToken=the-token-value
-
-# General configuration for the Company Log Kamelet
-camel.kamelet.my-company-log-sink.bucket=general
-# camel.kamelet.my-company-log-sink.xxx=yyy
-
-# Specific configuration for the Company Log Kamelet corresponding to the
named configuration "mynamedconfig"
-camel.kamelet.my-company-log-sink.mynamedconfig.bucket=special
-# When using "kamelet:my-company-log-sink/mynamedconfig", the bucket will be
"special", not "general"
-----
-
-Then the integration can be run with the following command:
-
-[source,shell]
-----
-kamel run kamelet-properties-route.yaml --property
file:kamelet-example.properties
-----
-
-=== Kamelet versioning
-
-Kamelets provided in a catalog are generally meant to work with a given
runtime version (the same for which they are released). However, when you
create a Kamelet and publish to a cluster, you may want to store and use
different versions. If the Kamelet is provided with more than the `main`
version, then, you can specify which version to use in your Integration by
adding the version parameter. For instance:
-
-[source,yaml]
-.kamlet-namedconfig-route.yaml
-----
-- from:
- uri: "kamelet:my-source?kameletVersion=v2"
- steps:
- - to: "log:info"
-----
-
-The operator will be able to automatically pick the right version and use it
at runtime. If no version is specified, then you will use the default one.
-
-[[kamelets-troubleshooting]]
-== Troubleshooting
-
-A `Kamelet` is translated into a `Route` used from the `Integration`. In order
to troubleshoot any possible issue, you can have a look at the dedicated
xref:troubleshooting/debugging.adoc#debugging-kamelets[troubleshoot section].
diff --git a/docs/modules/ROOT/pages/kamelets/kamelets.adoc
b/docs/modules/ROOT/pages/kamelets/kamelets.adoc
index 2bea27233..bdba18535 100644
--- a/docs/modules/ROOT/pages/kamelets/kamelets.adoc
+++ b/docs/modules/ROOT/pages/kamelets/kamelets.adoc
@@ -29,4 +29,4 @@ Kamelets are also expected to be **rendered on visual tools**
that will provide
They are generic connectors that can be used in multiples ways, depending on
the context, so each UIs can use them
for its own purpose.
-Have a look at the xref:kamelets/kamelets-user.adoc[Kamelets User Guide] to
learn how to immediately use Kamelets in Camel K or check
xref:kamelets/kamelets-distribution.adoc[how to use your own Kamelet catalog].
+Have a look at the xref:kamelets/configuration.adoc[Kamelets User Guide] to
learn how to immediately use Kamelets in Camel K or check
xref:kamelets/distribution.adoc[how to use your own Kamelet catalog].
diff --git a/docs/modules/ROOT/pages/troubleshooting/debugging.adoc
b/docs/modules/ROOT/pages/troubleshooting/debugging.adoc
index 6e685b0e4..8eb59e57b 100644
--- a/docs/modules/ROOT/pages/troubleshooting/debugging.adoc
+++ b/docs/modules/ROOT/pages/troubleshooting/debugging.adoc
@@ -51,12 +51,3 @@ image::debugging/remote-debugger.png[Configuration of the
remote debugger in Int
Once you configure a debugger, you can **add breakpoints** to various part of
the code, then connect the debugger to trigger the JVM startup.
When the debugging session is done, hitting kbd:[Ctrl+c] on the terminal where
the kamel CLI is running will restore the integration to its original status.
-
-[[debugging-kamelets]]
-== Debugging Kamelets
-
-As we've seen in the previous section, all `Integration` created in Camel K
are finally bundled as a Java application, hence, the possibility to debug via
JVM debugger. Any `Kamelet` you will be using directly in your `Route`
definition or in a `Pipe` is automatically converted in a `yaml` route and
injected in the Camel Context to be executed. That means that you cannot
directly debug a `Kamelet` as you would do with a Java or any other JVM
language `Route`.
-
-However, you can troubleshoot individually each `Kamelet` definition by
focusing on the specification `Flow`. As an example, you can create a simple
`yaml` test `Route` substituting the `kamelet:source` or `kamelet:sink` with
any mock endpoint that can help you in debugging the single `Kamelet` flow.
Even using a `timer` and a `log` component may be enough for a basic check.
-
-NOTE: the same idea applies for a `Pipe` which translates to an `Integration`
type under the hood. If you need to debug a `Pipe` just apply the same
troubleshooting technique that you would apply on an `Integration`.
diff --git a/e2e/common/traits/files/kamelet-it-ns.yaml
b/e2e/common/traits/files/kamelet-it-ns.yaml
new file mode 100644
index 000000000..c4bd766ef
--- /dev/null
+++ b/e2e/common/traits/files/kamelet-it-ns.yaml
@@ -0,0 +1,24 @@
+# camel-k: language=yaml
+
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+- from:
+ # The test will take care to replace %%% with the testing namespace
+ uri: "kamelet:my-timer-source?kameletNamespace=%%%"
+ steps:
+ - log: "${body}"
\ No newline at end of file
diff --git a/e2e/common/traits/files/my-timer-source-ns.kamelet.yaml
b/e2e/common/traits/files/my-timer-source-ns.kamelet.yaml
new file mode 100644
index 000000000..dca31ad89
--- /dev/null
+++ b/e2e/common/traits/files/my-timer-source-ns.kamelet.yaml
@@ -0,0 +1,36 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+ name: my-timer-source
+ labels:
+ camel.apache.org/kamelet.type: "source"
+spec:
+ definition:
+ title: "Timer Example"
+ description: "Produces periodic events with a custom payload"
+ types:
+ out:
+ mediaType: text/plain
+ template:
+ from:
+ uri: timer:tick
+ steps:
+ - setBody:
+ constant: "Kamelet NS"
+ - to: "kamelet:sink"
diff --git a/e2e/common/traits/kamelet_test.go
b/e2e/common/traits/kamelet_test.go
index 257566471..d96ce6215 100644
--- a/e2e/common/traits/kamelet_test.go
+++ b/e2e/common/traits/kamelet_test.go
@@ -24,10 +24,12 @@ package common
import (
"context"
+ "os"
+ "path/filepath"
+ "strings"
"testing"
. "github.com/onsi/gomega"
-
corev1 "k8s.io/api/core/v1"
. "github.com/apache/camel-k/v2/e2e/support"
@@ -82,3 +84,51 @@ func TestKameletMultiVersions(t *testing.T) {
})
})
}
+
+func TestKameletNamespaced(t *testing.T) {
+ t.Parallel()
+ WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns1 string)
{
+ t.Run("store kamelet", func(t *testing.T) {
+ ExpectExecSucceed(t, g, Kubectl("apply", "-f",
"files/my-timer-source-ns.kamelet.yaml", "-n", ns1))
+ })
+
+ WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns2
string) {
+ t.Run("namespaced kamelet", func(t *testing.T) {
+ // Clone the resource in a temporary file as it
will require to be changed
+ routeFile := cloneAndReplaceNamespace(t,
"files/kamelet-it-ns.yaml", ns1)
+ name :=
RandomizedSuffixName("namespaced-kamelet")
+ g.Expect(KamelRun(t, ctx, ns2, routeFile,
"--name", name).Execute()).To(Succeed())
+ g.Eventually(IntegrationConditionStatus(t, ctx,
ns2, name, v1.IntegrationConditionReady), TestTimeoutMedium).
+ Should(Equal(corev1.ConditionTrue))
+ g.Eventually(IntegrationPodPhase(t, ctx, ns2,
name), TestTimeoutShort).Should(Equal(corev1.PodRunning))
+ g.Eventually(IntegrationLogs(t, ctx, ns2,
name), TestTimeoutShort).Should(ContainSubstring("Kamelet NS"))
+ })
+ })
+ })
+}
+
+// cloneAndReplaceNamespace clones and replace the content marked as %%% with
the namespace passed as parameter.
+func cloneAndReplaceNamespace(t *testing.T, srcPath, namespace string) string {
+ t.Helper()
+
+ tempDir := t.TempDir()
+ tempPath := filepath.Join(tempDir, filepath.Base(srcPath))
+
+ dstFile, err := os.Create(tempPath)
+ if err != nil {
+ t.Fatalf("failed to create temp file: %v", err)
+ }
+ defer dstFile.Close()
+
+ content, err := os.ReadFile(srcPath)
+ if err != nil {
+ t.Fatalf("failed to read src file: %v", err)
+ }
+ updated := strings.ReplaceAll(string(content), "%%%", namespace)
+ err = os.WriteFile(tempPath, []byte(updated), 0644)
+ if err != nil {
+ t.Fatalf("failed to write dst file: %v", err)
+ }
+
+ return tempPath
+}
diff --git a/pkg/apis/camel/v1/kamelet_types.go
b/pkg/apis/camel/v1/kamelet_types.go
index be31072c3..0e8146af2 100644
--- a/pkg/apis/camel/v1/kamelet_types.go
+++ b/pkg/apis/camel/v1/kamelet_types.go
@@ -50,8 +50,12 @@ var (
reservedKameletNames = map[string]bool{"source": true, "sink": true}
// KameletIDProperty used to identify.
KameletIDProperty = "id"
- // KameletVersionProperty used to specify the version to use.
+ // KameletVersionProperty used to specify the version to use. NOTE:
this parameter is exclusive use of the operator
+ // and should not be used by final users.
KameletVersionProperty = "kameletVersion"
+ // KameletNamespaceProperty used to specify the namespace to use. NOTE:
this parameter is exclusive use of the operator
+ // and should not be used by final users.
+ KameletNamespaceProperty = "kameletNamespace"
)
// +genclient
diff --git a/pkg/trait/kamelets.go b/pkg/trait/kamelets.go
index 0cb2f3a15..091788677 100644
--- a/pkg/trait/kamelets.go
+++ b/pkg/trait/kamelets.go
@@ -20,6 +20,7 @@ package trait
import (
"errors"
"fmt"
+ "net/url"
"path/filepath"
"sort"
"strconv"
@@ -51,8 +52,6 @@ const (
kameletMountPointAnnotation = "camel.apache.org/kamelet.mount-point"
)
-var kameletVersionProperty = fmt.Sprintf("?%s=", v1.KameletVersionProperty)
-
type kameletsTrait struct {
BaseTrait
traitv1.KameletsTrait `property:",squash"`
@@ -89,7 +88,7 @@ func (t *kameletsTrait) Configure(e *Environment) (bool,
*TraitCondition, error)
}
}
- return len(t.getKameletKeys(false)) > 0, nil, nil
+ return len(t.getKameletKeys()) > 0, nil, nil
}
func (t *kameletsTrait) Apply(e *Environment) error {
@@ -103,18 +102,22 @@ func (t *kameletsTrait) Apply(e *Environment) error {
// collectKamelets load a Kamelet specification setting the specific version
specification.
func (t *kameletsTrait) collectKamelets(e *Environment)
(map[string]*v1.Kamelet, error) {
- repo, err := repository.NewForPlatform(e.Ctx, e.Client, e.Platform,
e.Integration.Namespace, platform.GetOperatorNamespace())
+ namespaces, err := t.calculateNamespaces(e.Integration.Namespace,
platform.GetOperatorNamespace())
+ if err != nil {
+ return nil, err
+ }
+ repo, err := repository.NewForPlatform(e.Ctx, e.Client, e.Platform,
namespaces...)
if err != nil {
return nil, err
}
kamelets := make(map[string]*v1.Kamelet)
- missingKamelets := make([]string, 0)
- availableKamelets := make([]string, 0)
- bundledKamelets := make([]string, 0)
+ var missingKamelets []string
+ var availableKamelets []string
+ var bundledKamelets []string
for _, kml := range strings.Split(t.List, ",") {
- name := getKameletKey(kml, false)
+ name := getKameletKey(kml)
if !v1.ValidKameletName(name) {
// Skip kamelet sink and source id
continue
@@ -133,7 +136,11 @@ func (t *kameletsTrait) collectKamelets(e *Environment)
(map[string]*v1.Kamelet,
bundledKamelets = append(bundledKamelets, name)
}
// We control which version to use (if any is specified)
- clonedKamelet, err :=
kamelet.CloneWithVersion(getKameletVersion(kml))
+ version, err := getKameletVersion(kml)
+ if err != nil {
+ return nil, fmt.Errorf("could not parse kamelet
version: %w", err)
+ }
+ clonedKamelet, err := kamelet.CloneWithVersion(version)
if err != nil {
return nil, err
}
@@ -186,8 +193,38 @@ func (t *kameletsTrait) collectKamelets(e *Environment)
(map[string]*v1.Kamelet,
return kamelets, nil
}
+// calculateNamespaces is in charge to scan the kamelets specification and
provide a list of
+// namespaces where to look for Kamelets.
+func (t *kameletsTrait) calculateNamespaces(defaultNamespaces ...string)
([]string, error) {
+ return calculateNamespaces(strings.Split(t.List, ","),
defaultNamespaces...)
+}
+
+func calculateNamespaces(kamelets []string, defaultNamespaces ...string)
([]string, error) {
+ namespaces := defaultNamespaces
+ for _, kml := range kamelets {
+ ns, err := getKameletNamespace(kml)
+ if err != nil {
+ return nil, fmt.Errorf("could not parse kamelet
namespace: %w", err)
+ }
+ if ns != "" {
+ addNs := true
+ loop:
+ for _, addedNs := range namespaces {
+ if addedNs == ns {
+ addNs = false
+ break loop
+ }
+ }
+ if addNs {
+ namespaces = append(namespaces, ns)
+ }
+ }
+ }
+ return namespaces, nil
+}
+
func (t *kameletsTrait) addKamelets(e *Environment) error {
- if len(t.getKameletKeys(false)) == 0 {
+ if len(t.getKameletKeys()) == 0 {
return nil
}
kamelets, err := t.collectKamelets(e)
@@ -280,10 +317,10 @@ func (t *kameletsTrait) addKameletAsSource(e
*Environment, kamelet *v1.Kamelet)
return nil
}
-func (t *kameletsTrait) getKameletKeys(withVersion bool) []string {
+func (t *kameletsTrait) getKameletKeys() []string {
answer := make([]string, 0)
for _, item := range strings.Split(t.List, ",") {
- i := getKameletKey(item, withVersion)
+ i := getKameletKey(item)
if i != "" && v1.ValidKameletName(i) {
util.StringSliceUniqueAdd(&answer, i)
}
@@ -300,28 +337,35 @@ func (t *kameletsTrait) getMountPoint() string {
return t.MountPoint
}
-func getKameletKey(item string, withVersion bool) string {
- i := strings.Trim(item, " \t\"")
- if strings.Contains(i, "/") {
- i = strings.SplitN(i, "/", 2)[0]
+// getKameletKey remove any params from the kamelet, eg
my-kamelet/abc?param1=1 will return the uri path filtered (my-kamelet).
+func getKameletKey(item string) string {
+ parsedURL, err := url.Parse(item)
+ if err != nil {
+ return ""
}
- if strings.Contains(i, kameletVersionProperty) {
- versionedKamelet := strings.SplitN(i, kameletVersionProperty, 2)
- if withVersion {
- i = fmt.Sprintf("%s-%s", versionedKamelet[0],
versionedKamelet[1])
- } else {
- i = versionedKamelet[0]
- }
+ parts := strings.Split(parsedURL.Path, "/")
+ if len(parts) > 0 {
+ return parts[0]
}
- return i
+ return ""
}
-func getKameletVersion(item string) string {
- if strings.Contains(item, fmt.Sprintf("?%s=",
v1.KameletVersionProperty)) {
- versionedKamelet := strings.SplitN(item,
kameletVersionProperty, 2)
- return versionedKamelet[1]
+func getKameletVersion(item string) (string, error) {
+ return getKameletParam(item, v1.KameletVersionProperty)
+}
+
+func getKameletNamespace(item string) (string, error) {
+ return getKameletParam(item, v1.KameletNamespaceProperty)
+}
+
+func getKameletParam(uri, param string) (string, error) {
+ parsedURL, err := url.Parse(uri)
+ if err != nil {
+ return "", err
}
- return ""
+
+ queryParams := parsedURL.Query()
+ return queryParams.Get(param), nil
}
func integrationSourceFromKameletSource(e *Environment, kamelet *v1.Kamelet,
source v1.SourceSpec, name string) (v1.SourceSpec, error) {
diff --git a/pkg/trait/kamelets_test.go b/pkg/trait/kamelets_test.go
index bc59b47be..87c24cda8 100644
--- a/pkg/trait/kamelets_test.go
+++ b/pkg/trait/kamelets_test.go
@@ -68,8 +68,7 @@ func TestConfigurationWithKamelets(t *testing.T) {
require.NoError(t, err)
assert.True(t, enabled)
assert.Nil(t, condition)
- assert.Equal(t, []string{"c0", "c1", "c2", "c3", "complex-.-.-1a",
"complex-.-.-1b", "complex-.-.-1c"}, trait.getKameletKeys(false))
- assert.Equal(t, []string{"c0", "c1", "c2", "c3-v1", "complex-.-.-1a",
"complex-.-.-1b", "complex-.-.-1c"}, trait.getKameletKeys(true))
+ assert.Equal(t, []string{"c0", "c1", "c2", "c3", "complex-.-.-1a",
"complex-.-.-1b", "complex-.-.-1c"}, trait.getKameletKeys())
}
func TestKameletLookup(t *testing.T) {
@@ -101,7 +100,7 @@ func TestKameletLookup(t *testing.T) {
require.NoError(t, err)
assert.True(t, enabled)
assert.Nil(t, condition)
- assert.Equal(t, []string{"timer"}, trait.getKameletKeys(false))
+ assert.Equal(t, []string{"timer"}, trait.getKameletKeys())
err = trait.Apply(environment)
require.NoError(t, err)
@@ -152,7 +151,7 @@ func TestKameletSecondarySourcesLookup(t *testing.T) {
require.NoError(t, err)
assert.True(t, enabled)
assert.Nil(t, condition)
- assert.Equal(t, []string{"timer"}, trait.getKameletKeys(false))
+ assert.Equal(t, []string{"timer"}, trait.getKameletKeys())
err = trait.Apply(environment)
require.NoError(t, err)
@@ -205,7 +204,7 @@ func TestNonYAMLKameletLookup(t *testing.T) {
require.NoError(t, err)
assert.True(t, enabled)
assert.Nil(t, condition)
- assert.Equal(t, []string{"timer"}, trait.getKameletKeys(false))
+ assert.Equal(t, []string{"timer"}, trait.getKameletKeys())
err = trait.Apply(environment)
require.NoError(t, err)
@@ -312,8 +311,7 @@ func TestMultipleKamelets(t *testing.T) {
assert.True(t, enabled)
assert.Nil(t, condition)
assert.Equal(t, "logger,timer?kameletVersion=v1", trait.List)
- assert.Equal(t, []string{"logger", "timer"},
trait.getKameletKeys(false))
- assert.Equal(t, []string{"logger", "timer-v1"},
trait.getKameletKeys(true))
+ assert.Equal(t, []string{"logger", "timer"}, trait.getKameletKeys())
err = trait.Apply(environment)
require.NoError(t, err)
@@ -409,7 +407,7 @@ func TestKameletConfigLookup(t *testing.T) {
require.NoError(t, err)
assert.True(t, enabled)
assert.Nil(t, condition)
- assert.Equal(t, []string{"timer"}, trait.getKameletKeys(false))
+ assert.Equal(t, []string{"timer"}, trait.getKameletKeys())
}
func TestKameletNamedConfigLookup(t *testing.T) {
@@ -467,7 +465,7 @@ func TestKameletNamedConfigLookup(t *testing.T) {
require.NoError(t, err)
assert.True(t, enabled)
assert.Nil(t, condition)
- assert.Equal(t, []string{"timer"}, trait.getKameletKeys(false))
+ assert.Equal(t, []string{"timer"}, trait.getKameletKeys())
}
func TestKameletConditionFalse(t *testing.T) {
@@ -753,3 +751,146 @@ func TestKameletAuto(t *testing.T) {
)
assert.Equal(t, "false",
environment.ApplicationProperties[KameletErrorHandler])
}
+
+func TestKameletVersionParameter(t *testing.T) {
+ v1, err := getKameletVersion("my-kamelet?kameletVersion=v2")
+ require.NoError(t, err)
+ assert.Equal(t, "v2", v1)
+ v2, err := getKameletVersion("my-kamelet?prop1=1&kameletVersion=v2")
+ require.NoError(t, err)
+ assert.Equal(t, "v2", v2)
+ v3, err := getKameletVersion("my-kamelet")
+ require.NoError(t, err)
+ assert.Equal(t, "", v3)
+}
+
+func TestKameletNamespaceParameter(t *testing.T) {
+ v2, err :=
getKameletNamespace("my-kamelet?prop1=1&kameletNamespace=my-ns")
+ require.NoError(t, err)
+ assert.Equal(t, "my-ns", v2)
+}
+
+func TestCalculateKameletNamespaces(t *testing.T) {
+ namespaces, err := calculateNamespaces(
+ []string{"my-kamelet", "my-kamelet?kameletNamespace=ns1",
"my-kamelet?kameletVersion=v2&kameletNamespace=ns2"},
+ "default", "camel-k",
+ )
+ require.NoError(t, err)
+ assert.Len(t, namespaces, 4)
+ assert.Contains(t, namespaces, "ns1")
+ assert.Contains(t, namespaces, "ns2")
+ assert.Contains(t, namespaces, "default")
+ assert.Contains(t, namespaces, "camel-k")
+}
+
+func TestKameletMultiNamespace(t *testing.T) {
+ flow := `
+- from:
+ uri: kamelet:timer
+ steps:
+ - to: kamelet:extra?kameletNamespace=ns1
+`
+ trait, environment := createKameletsTestEnvironment(
+ flow,
+ &v1.Kamelet{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "test",
+ Name: "timer",
+ },
+ Spec: v1.KameletSpec{
+ KameletSpecBase: v1.KameletSpecBase{
+ Template:
templateOrFail(map[string]interface{}{
+ "from": map[string]interface{}{
+ "uri": "timer:tick",
+ },
+ }),
+ },
+ },
+ },
+ &v1.Kamelet{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "ns1",
+ Name: "extra",
+ },
+ Spec: v1.KameletSpec{
+ KameletSpecBase: v1.KameletSpecBase{
+ Template:
templateOrFail(map[string]interface{}{
+ "from": map[string]interface{}{
+ "uri": "timer:tick",
+ },
+ }),
+ },
+ },
+ })
+
+ enabled, condition, err := trait.Configure(environment)
+ require.NoError(t, err)
+ assert.True(t, enabled)
+ assert.Nil(t, condition)
+
+ err = trait.Apply(environment)
+ require.NoError(t, err)
+ assert.Equal(t, "extra?kameletNamespace=ns1,timer", trait.List)
+ assert.Equal(t,
+ corev1.ConditionTrue,
+
environment.Integration.Status.GetCondition(v1.IntegrationConditionKameletsAvailable).Status)
+ assert.Equal(t,
+ "kamelets [extra,timer] found in (Kubernetes[namespace=test],
Kubernetes[namespace=ns1], Empty[]) repositories",
+
environment.Integration.Status.GetCondition(v1.IntegrationConditionKameletsAvailable).Message)
+}
+
+func TestKameletMultiNamespaceMissing(t *testing.T) {
+ flow := `
+- from:
+ uri: kamelet:timer
+ steps:
+ - to: kamelet:missing?kameletNamespace=ns1
+`
+ trait, environment := createKameletsTestEnvironment(
+ flow,
+ &v1.Kamelet{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "test",
+ Name: "timer",
+ },
+ Spec: v1.KameletSpec{
+ KameletSpecBase: v1.KameletSpecBase{
+ Template:
templateOrFail(map[string]interface{}{
+ "from": map[string]interface{}{
+ "uri": "timer:tick",
+ },
+ }),
+ },
+ },
+ },
+ &v1.Kamelet{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "another-namespace",
+ Name: "missing",
+ },
+ Spec: v1.KameletSpec{
+ KameletSpecBase: v1.KameletSpecBase{
+ Template:
templateOrFail(map[string]interface{}{
+ "from": map[string]interface{}{
+ "uri": "timer:tick",
+ },
+ }),
+ },
+ },
+ })
+
+ enabled, condition, err := trait.Configure(environment)
+ require.NoError(t, err)
+ assert.True(t, enabled)
+ assert.Nil(t, condition)
+ assert.Equal(t, "missing?kameletNamespace=ns1,timer", trait.List)
+
+ err = trait.Apply(environment)
+ require.Error(t, err)
+ assert.Equal(t,
+ corev1.ConditionFalse,
+
environment.Integration.Status.GetCondition(v1.IntegrationConditionKameletsAvailable).Status)
+ assert.Equal(t,
+ "kamelets [missing] not found in (Kubernetes[namespace=test],
Kubernetes[namespace=ns1], Empty[]) repositories",
+ err.Error())
+}
diff --git a/pkg/util/source/kamelet.go b/pkg/util/source/kamelet.go
index d220bc5ba..82579aead 100644
--- a/pkg/util/source/kamelet.go
+++ b/pkg/util/source/kamelet.go
@@ -19,20 +19,32 @@ package source
import (
"fmt"
+ "net/url"
"regexp"
v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
)
var kameletNameRegexp =
regexp.MustCompile("kamelet:(?://)?([a-z0-9-.]+(/[a-z0-9-.]+)?)(?:$|[^a-z0-9-.].*)")
-var kameletVersionRegexp = regexp.MustCompile(v1.KameletVersionProperty +
"=([a-z0-9-.]+)")
+//nolint:nestif
func ExtractKamelet(uri string) string {
matches := kameletNameRegexp.FindStringSubmatch(uri)
if len(matches) > 1 {
- version := kameletVersionRegexp.FindString(uri)
- if version != "" {
- return fmt.Sprintf("%s?%s", matches[1], version)
+ version := getKameletParam(uri, v1.KameletVersionProperty)
+ namespace := getKameletParam(uri, v1.KameletNamespaceProperty)
+ if version != "" || namespace != "" {
+ var querystring string
+ if version != "" {
+ querystring = v1.KameletVersionProperty + "=" +
version
+ }
+ if namespace != "" {
+ if querystring != "" {
+ querystring += "&"
+ }
+ querystring += v1.KameletNamespaceProperty +
"=" + namespace
+ }
+ return fmt.Sprintf("%s?%s", matches[1], querystring)
}
return matches[1]
}
@@ -44,3 +56,14 @@ func AddKamelet(meta *Metadata, content string) {
meta.Kamelets = append(meta.Kamelets, maybeKamelet)
}
}
+
+// getKameletParam parses the URI and return the query parameter or an empty
value if not found.
+func getKameletParam(uri, param string) string {
+ parsedURL, err := url.Parse(uri)
+ if err != nil {
+ return ""
+ }
+
+ queryParams := parsedURL.Query()
+ return queryParams.Get(param)
+}