From 6037a768d4c7055b443da5af33d538be392315c7 Mon Sep 17 00:00:00 2001 From: Pasquale Congiusti Date: Fri, 30 Aug 2024 10:12:34 +0200 Subject: [PATCH 1/3] feat(api): Kamelet version parameter --- addons/keda/keda_test.go | 56 +- .../ROOT/partials/apis/camel-k-crds.adoc | 41 +- e2e/support/test_support.go | 8 +- helm/camel-k/crds/camel-k-crds.yaml | 590 ++++++++++++++++++ pkg/apis/camel/v1/kamelet_types.go | 7 + pkg/apis/camel/v1/zz_generated.deepcopy.go | 29 +- .../camel/v1/kameletspec.go | 22 +- .../camel/v1/kameletspecbase.go | 108 ++++ pkg/client/camel/applyconfiguration/utils.go | 2 + pkg/controller/pipe/initialize_test.go | 42 +- .../crd/bases/camel.apache.org_kamelets.yaml | 590 ++++++++++++++++++ pkg/trait/kamelets_support_test.go | 46 +- pkg/trait/kamelets_test.go | 202 +++--- 13 files changed, 1570 insertions(+), 173 deletions(-) create mode 100644 pkg/client/camel/applyconfiguration/camel/v1/kameletspecbase.go diff --git a/addons/keda/keda_test.go b/addons/keda/keda_test.go index b017df262a..4b4be7d7e5 100644 --- a/addons/keda/keda_test.go +++ b/addons/keda/keda_test.go @@ -129,21 +129,23 @@ func TestKameletAutoDetection(t *testing.T) { }, }, Spec: camelv1.KameletSpec{ - Definition: &camelv1.JSONSchemaProps{ - Properties: map[string]camelv1.JSONSchemaProp{ - "a": { - XDescriptors: []string{ - "urn:keda:metadata:a", + KameletSpecBase: camelv1.KameletSpecBase{ + Definition: &camelv1.JSONSchemaProps{ + Properties: map[string]camelv1.JSONSchemaProp{ + "a": { + XDescriptors: []string{ + "urn:keda:metadata:a", + }, }, - }, - "b": { - XDescriptors: []string{ - "urn:keda:metadata:bb", + "b": { + XDescriptors: []string{ + "urn:keda:metadata:bb", + }, }, - }, - "c": { - XDescriptors: []string{ - "urn:keda:authentication:cc", + "c": { + XDescriptors: []string{ + "urn:keda:authentication:cc", + }, }, }, }, @@ -245,21 +247,23 @@ func TestPipeAutoDetection(t *testing.T) { }, }, Spec: camelv1.KameletSpec{ - Definition: &camelv1.JSONSchemaProps{ - Properties: map[string]camelv1.JSONSchemaProp{ - "a": { - XDescriptors: []string{ - "urn:keda:metadata:a", + KameletSpecBase: camelv1.KameletSpecBase{ + Definition: &camelv1.JSONSchemaProps{ + Properties: map[string]camelv1.JSONSchemaProp{ + "a": { + XDescriptors: []string{ + "urn:keda:metadata:a", + }, }, - }, - "b": { - XDescriptors: []string{ - "urn:keda:metadata:bb", + "b": { + XDescriptors: []string{ + "urn:keda:metadata:bb", + }, }, - }, - "c": { - XDescriptors: []string{ - "urn:keda:authentication:cc", + "c": { + XDescriptors: []string{ + "urn:keda:authentication:cc", + }, }, }, }, diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc index 3b4e6e41b0..abf88aac53 100644 --- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc +++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc @@ -1756,7 +1756,7 @@ the expected schema for the data type *Appears on:* -* <<#_camel_apache_org_v1_KameletSpec, KameletSpec>> +* <<#_camel_apache_org_v1_KameletSpecBase, KameletSpecBase>> DataTypesSpec represents the specification for a set of data types. @@ -2005,7 +2005,7 @@ ErrorHandlerType a type of error handler (ie, sink). *Appears on:* -* <<#_camel_apache_org_v1_KameletSpec, KameletSpec>> +* <<#_camel_apache_org_v1_KameletSpecBase, KameletSpecBase>> EventTypeSpec represents a specification for an event type. Deprecated: In favor of using DataTypeSpec. @@ -3864,7 +3864,7 @@ XDescriptors is a list of extended properties that trigger a custom behavior in * <<#_camel_apache_org_v1_DataTypeSpec, DataTypeSpec>> * <<#_camel_apache_org_v1_EventTypeSpec, EventTypeSpec>> -* <<#_camel_apache_org_v1_KameletSpec, KameletSpec>> +* <<#_camel_apache_org_v1_KameletSpecBase, KameletSpecBase>> JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/). @@ -4129,6 +4129,37 @@ KameletSpec specifies the configuration required to execute a Kamelet. |Field |Description +|`KameletSpecBase` + +*xref:#_camel_apache_org_v1_KameletSpecBase[KameletSpecBase]* +|(Members of `KameletSpecBase` are embedded into this type.) + + + + +|`versions` + +*xref:#_camel_apache_org_v1_KameletSpecBase[map[string\]github.com/apache/camel-k/v2/pkg/apis/camel/v1.KameletSpecBase]* +| + + +the optional versions available for this Kamelet. + + +|=== + +[#_camel_apache_org_v1_KameletSpecBase] +=== KameletSpecBase + +*Appears on:* + +* <<#_camel_apache_org_v1_KameletSpec, KameletSpec>> + +KameletSpecBase specifies the base configuration of a Kamelet. + +[cols="2,2a",options="header"] +|=== +|Field +|Description + |`definition` + *xref:#_camel_apache_org_v1_JSONSchemaProps[JSONSchemaProps]* | @@ -5474,7 +5505,7 @@ string * <<#_camel_apache_org_v1_IntegrationKitSpec, IntegrationKitSpec>> * <<#_camel_apache_org_v1_IntegrationSpec, IntegrationSpec>> * <<#_camel_apache_org_v1_IntegrationStatus, IntegrationStatus>> -* <<#_camel_apache_org_v1_KameletSpec, KameletSpec>> +* <<#_camel_apache_org_v1_KameletSpecBase, KameletSpecBase>> SourceSpec defines the configuration for one or more routes to be executed in a certain Camel DSL language. @@ -5687,7 +5718,7 @@ a JibTask, for Jib strategy *Appears on:* -* <<#_camel_apache_org_v1_KameletSpec, KameletSpec>> +* <<#_camel_apache_org_v1_KameletSpecBase, KameletSpecBase>> Template is an unstructured object representing a Kamelet template in YAML/JSON DSL. diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go index 48cab1b691..ba8d6e9f05 100644 --- a/e2e/support/test_support.go +++ b/e2e/support/test_support.go @@ -2666,10 +2666,12 @@ func CreateKameletWithID(t *testing.T, operatorID string, ctx context.Context, n Labels: labels, }, Spec: v1.KameletSpec{ - Definition: &v1.JSONSchemaProps{ - Properties: properties, + KameletSpecBase: v1.KameletSpecBase{ + Definition: &v1.JSONSchemaProps{ + Properties: properties, + }, + Template: asTemplate(t, template), }, - Template: asTemplate(t, template), }, } diff --git a/helm/camel-k/crds/camel-k-crds.yaml b/helm/camel-k/crds/camel-k-crds.yaml index c3a49865e0..e6fdf9788c 100644 --- a/helm/camel-k/crds/camel-k-crds.yaml +++ b/helm/camel-k/crds/camel-k-crds.yaml @@ -29257,6 +29257,596 @@ spec: data specification types for the events consumed/produced by the Kamelet Deprecated: In favor of using DataTypes type: object + versions: + additionalProperties: + description: KameletSpecBase specifies the base configuration of + a Kamelet. + properties: + dataTypes: + additionalProperties: + description: DataTypesSpec represents the specification for + a set of data types. + properties: + default: + description: the default data type for this Kamelet + type: string + headers: + additionalProperties: + description: HeaderSpec represents the specification + for a header used in the Kamelet. + properties: + default: + type: string + description: + type: string + required: + type: boolean + title: + type: string + type: + type: string + type: object + description: one to many header specifications + type: object + types: + additionalProperties: + description: DataTypeSpec represents the specification + for a data type. + properties: + dependencies: + description: the list of Camel or Maven dependencies + required by the data type + items: + type: string + type: array + description: + description: optional description + type: string + format: + description: the data type format name + type: string + headers: + additionalProperties: + description: HeaderSpec represents the specification + for a header used in the Kamelet. + properties: + default: + type: string + description: + type: string + required: + type: boolean + title: + type: string + type: + type: string + type: object + description: one to many header specifications + type: object + mediaType: + description: media type as expected for HTTP media + types (ie, application/json) + type: string + schema: + description: the expected schema for the data type + properties: + $schema: + description: JSONSchemaURL represents a schema + url. + type: string + description: + type: string + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + externalDocs: + description: ExternalDocumentation allows referencing + an external resource for extended documentation. + properties: + description: + type: string + url: + type: string + type: object + id: + type: string + properties: + additionalProperties: + properties: + default: + description: default is a default value + for undefined object fields. + x-kubernetes-preserve-unknown-fields: true + deprecated: + type: boolean + description: + type: string + enum: + items: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + type: array + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + exclusiveMaximum: + type: boolean + exclusiveMinimum: + type: boolean + format: + description: |- + format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated: + + + - bsonobjectid: a bson object ID, i.e. a 24 characters hex string + - uri: an URI as parsed by Golang net/url.ParseRequestURI + - email: an email address as parsed by Golang net/mail.ParseAddress + - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034]. + - ipv4: an IPv4 IP as parsed by Golang net.ParseIP + - ipv6: an IPv6 IP as parsed by Golang net.ParseIP + - cidr: a CIDR as parsed by Golang net.ParseCIDR + - mac: a MAC address as parsed by Golang net.ParseMAC + - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041" + - isbn10: an ISBN10 number string like "0321751043" + - isbn13: an ISBN13 number string like "978-0321751041" + - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in + - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$ + - hexcolor: an hexadecimal color code like "#FFFFFF" following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ + - rgbcolor: an RGB color code like rgb like "rgb(255,255,255)" + - byte: base64 encoded binary data + - password: any kind of string + - date: a date string like "2006-01-02" as defined by full-date in RFC3339 + - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format + - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339. + type: string + id: + type: string + maxItems: + format: int64 + type: integer + maxLength: + format: int64 + type: integer + maxProperties: + format: int64 + type: integer + maximum: + description: A Number represents a JSON + number literal. + type: string + minItems: + format: int64 + type: integer + minLength: + format: int64 + type: integer + minProperties: + format: int64 + type: integer + minimum: + description: A Number represents a JSON + number literal. + type: string + multipleOf: + description: A Number represents a JSON + number literal. + type: string + nullable: + type: boolean + pattern: + type: string + title: + type: string + type: + type: string + uniqueItems: + type: boolean + x-descriptors: + description: XDescriptors is a list of + extended properties that trigger a custom + behavior in external systems + items: + type: string + type: array + type: object + type: object + required: + items: + type: string + type: array + title: + type: string + type: + type: string + type: object + scheme: + description: the data type component scheme + type: string + type: object + description: one to many data type specifications + type: object + type: object + description: data specification types for the events consumed/produced + by the Kamelet + type: object + definition: + description: defines the formal configuration of the Kamelet + properties: + $schema: + description: JSONSchemaURL represents a schema url. + type: string + description: + type: string + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + externalDocs: + description: ExternalDocumentation allows referencing an + external resource for extended documentation. + properties: + description: + type: string + url: + type: string + type: object + id: + type: string + properties: + additionalProperties: + properties: + default: + description: default is a default value for undefined + object fields. + x-kubernetes-preserve-unknown-fields: true + deprecated: + type: boolean + description: + type: string + enum: + items: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + type: array + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + exclusiveMaximum: + type: boolean + exclusiveMinimum: + type: boolean + format: + description: |- + format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated: + + + - bsonobjectid: a bson object ID, i.e. a 24 characters hex string + - uri: an URI as parsed by Golang net/url.ParseRequestURI + - email: an email address as parsed by Golang net/mail.ParseAddress + - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034]. + - ipv4: an IPv4 IP as parsed by Golang net.ParseIP + - ipv6: an IPv6 IP as parsed by Golang net.ParseIP + - cidr: a CIDR as parsed by Golang net.ParseCIDR + - mac: a MAC address as parsed by Golang net.ParseMAC + - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041" + - isbn10: an ISBN10 number string like "0321751043" + - isbn13: an ISBN13 number string like "978-0321751041" + - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in + - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$ + - hexcolor: an hexadecimal color code like "#FFFFFF" following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ + - rgbcolor: an RGB color code like rgb like "rgb(255,255,255)" + - byte: base64 encoded binary data + - password: any kind of string + - date: a date string like "2006-01-02" as defined by full-date in RFC3339 + - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format + - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339. + type: string + id: + type: string + maxItems: + format: int64 + type: integer + maxLength: + format: int64 + type: integer + maxProperties: + format: int64 + type: integer + maximum: + description: A Number represents a JSON number literal. + type: string + minItems: + format: int64 + type: integer + minLength: + format: int64 + type: integer + minProperties: + format: int64 + type: integer + minimum: + description: A Number represents a JSON number literal. + type: string + multipleOf: + description: A Number represents a JSON number literal. + type: string + nullable: + type: boolean + pattern: + type: string + title: + type: string + type: + type: string + uniqueItems: + type: boolean + x-descriptors: + description: XDescriptors is a list of extended properties + that trigger a custom behavior in external systems + items: + type: string + type: array + type: object + type: object + required: + items: + type: string + type: array + title: + type: string + type: + type: string + type: object + dependencies: + description: Camel dependencies needed by the Kamelet + items: + type: string + type: array + sources: + description: sources in any Camel DSL supported + items: + description: SourceSpec defines the configuration for one + or more routes to be executed in a certain Camel DSL language. + properties: + compression: + description: if the content is compressed (base64 encrypted) + type: boolean + content: + description: the source code (plain text) + type: string + contentKey: + description: the confimap key holding the source content + type: string + contentRef: + description: the confimap reference holding the source + content + type: string + contentType: + description: the content type (tipically text or binary) + type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean + interceptors: + description: |- + Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader + uses to pre/post process sources + items: + type: string + type: array + language: + description: specify which is the language (Camel DSL) + used to interpret this source code + type: string + loader: + description: |- + Loader is an optional id of the org.apache.camel.k.RoutesLoader that will + interpret this source at runtime + type: string + name: + description: the name of the specification + type: string + path: + description: the path where the file is stored + type: string + property-names: + description: List of property names defined in the source + (e.g. if type is "template") + items: + type: string + type: array + rawContent: + description: the source code (binary) + format: byte + type: string + type: + description: Type defines the kind of source described + by this object + type: string + type: object + type: array + template: + description: the main source in YAML DSL + type: object + x-kubernetes-preserve-unknown-fields: true + types: + additionalProperties: + description: |- + EventTypeSpec represents a specification for an event type. + Deprecated: In favor of using DataTypeSpec. + properties: + mediaType: + description: media type as expected for HTTP media types + (ie, application/json) + type: string + schema: + description: the expected schema for the event + properties: + $schema: + description: JSONSchemaURL represents a schema url. + type: string + description: + type: string + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + externalDocs: + description: ExternalDocumentation allows referencing + an external resource for extended documentation. + properties: + description: + type: string + url: + type: string + type: object + id: + type: string + properties: + additionalProperties: + properties: + default: + description: default is a default value for + undefined object fields. + x-kubernetes-preserve-unknown-fields: true + deprecated: + type: boolean + description: + type: string + enum: + items: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + type: array + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + exclusiveMaximum: + type: boolean + exclusiveMinimum: + type: boolean + format: + description: |- + format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated: + + + - bsonobjectid: a bson object ID, i.e. a 24 characters hex string + - uri: an URI as parsed by Golang net/url.ParseRequestURI + - email: an email address as parsed by Golang net/mail.ParseAddress + - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034]. + - ipv4: an IPv4 IP as parsed by Golang net.ParseIP + - ipv6: an IPv6 IP as parsed by Golang net.ParseIP + - cidr: a CIDR as parsed by Golang net.ParseCIDR + - mac: a MAC address as parsed by Golang net.ParseMAC + - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041" + - isbn10: an ISBN10 number string like "0321751043" + - isbn13: an ISBN13 number string like "978-0321751041" + - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in + - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$ + - hexcolor: an hexadecimal color code like "#FFFFFF" following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ + - rgbcolor: an RGB color code like rgb like "rgb(255,255,255)" + - byte: base64 encoded binary data + - password: any kind of string + - date: a date string like "2006-01-02" as defined by full-date in RFC3339 + - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format + - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339. + type: string + id: + type: string + maxItems: + format: int64 + type: integer + maxLength: + format: int64 + type: integer + maxProperties: + format: int64 + type: integer + maximum: + description: A Number represents a JSON number + literal. + type: string + minItems: + format: int64 + type: integer + minLength: + format: int64 + type: integer + minProperties: + format: int64 + type: integer + minimum: + description: A Number represents a JSON number + literal. + type: string + multipleOf: + description: A Number represents a JSON number + literal. + type: string + nullable: + type: boolean + pattern: + type: string + title: + type: string + type: + type: string + uniqueItems: + type: boolean + x-descriptors: + description: XDescriptors is a list of extended + properties that trigger a custom behavior + in external systems + items: + type: string + type: array + type: object + type: object + required: + items: + type: string + type: array + title: + type: string + type: + type: string + type: object + type: object + description: |- + data specification types for the events consumed/produced by the Kamelet + Deprecated: In favor of using DataTypes + type: object + type: object + description: the optional versions available for this Kamelet. + type: object type: object status: default: diff --git a/pkg/apis/camel/v1/kamelet_types.go b/pkg/apis/camel/v1/kamelet_types.go index 43a8404fe4..7bc28652a4 100644 --- a/pkg/apis/camel/v1/kamelet_types.go +++ b/pkg/apis/camel/v1/kamelet_types.go @@ -76,6 +76,13 @@ type Kamelet struct { // KameletSpec specifies the configuration required to execute a Kamelet. type KameletSpec struct { + KameletSpecBase `json:",inline"` + // the optional versions available for this Kamelet. + Versions map[string]KameletSpecBase `json:"versions,omitempty"` +} + +// KameletSpecBase specifies the base configuration of a Kamelet. +type KameletSpecBase struct { // defines the formal configuration of the Kamelet Definition *JSONSchemaProps `json:"definition,omitempty"` // sources in any Camel DSL supported diff --git a/pkg/apis/camel/v1/zz_generated.deepcopy.go b/pkg/apis/camel/v1/zz_generated.deepcopy.go index 181d1c91c1..275a98449f 100644 --- a/pkg/apis/camel/v1/zz_generated.deepcopy.go +++ b/pkg/apis/camel/v1/zz_generated.deepcopy.go @@ -2157,6 +2157,29 @@ func (in *KameletRepositorySpec) DeepCopy() *KameletRepositorySpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KameletSpec) DeepCopyInto(out *KameletSpec) { + *out = *in + in.KameletSpecBase.DeepCopyInto(&out.KameletSpecBase) + if in.Versions != nil { + in, out := &in.Versions, &out.Versions + *out = make(map[string]KameletSpecBase, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KameletSpec. +func (in *KameletSpec) DeepCopy() *KameletSpec { + if in == nil { + return nil + } + out := new(KameletSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KameletSpecBase) DeepCopyInto(out *KameletSpecBase) { *out = *in if in.Definition != nil { in, out := &in.Definition, &out.Definition @@ -2196,12 +2219,12 @@ func (in *KameletSpec) DeepCopyInto(out *KameletSpec) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KameletSpec. -func (in *KameletSpec) DeepCopy() *KameletSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KameletSpecBase. +func (in *KameletSpecBase) DeepCopy() *KameletSpecBase { if in == nil { return nil } - out := new(KameletSpec) + out := new(KameletSpecBase) in.DeepCopyInto(out) return out } diff --git a/pkg/client/camel/applyconfiguration/camel/v1/kameletspec.go b/pkg/client/camel/applyconfiguration/camel/v1/kameletspec.go index bf1ac56b6c..eebfdce5f1 100644 --- a/pkg/client/camel/applyconfiguration/camel/v1/kameletspec.go +++ b/pkg/client/camel/applyconfiguration/camel/v1/kameletspec.go @@ -26,12 +26,8 @@ import ( // KameletSpecApplyConfiguration represents an declarative configuration of the KameletSpec type for use // with apply. type KameletSpecApplyConfiguration struct { - Definition *JSONSchemaPropsApplyConfiguration `json:"definition,omitempty"` - Sources []SourceSpecApplyConfiguration `json:"sources,omitempty"` - Template *TemplateApplyConfiguration `json:"template,omitempty"` - Types map[camelv1.TypeSlot]EventTypeSpecApplyConfiguration `json:"types,omitempty"` - DataTypes map[camelv1.TypeSlot]DataTypesSpecApplyConfiguration `json:"dataTypes,omitempty"` - Dependencies []string `json:"dependencies,omitempty"` + KameletSpecBaseApplyConfiguration `json:",inline"` + Versions map[string]KameletSpecBaseApplyConfiguration `json:"versions,omitempty"` } // KameletSpecApplyConfiguration constructs an declarative configuration of the KameletSpec type for use with @@ -106,3 +102,17 @@ func (b *KameletSpecApplyConfiguration) WithDependencies(values ...string) *Kame } return b } + +// WithVersions puts the entries into the Versions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Versions field, +// overwriting an existing map entries in Versions field with the same key. +func (b *KameletSpecApplyConfiguration) WithVersions(entries map[string]KameletSpecBaseApplyConfiguration) *KameletSpecApplyConfiguration { + if b.Versions == nil && len(entries) > 0 { + b.Versions = make(map[string]KameletSpecBaseApplyConfiguration, len(entries)) + } + for k, v := range entries { + b.Versions[k] = v + } + return b +} diff --git a/pkg/client/camel/applyconfiguration/camel/v1/kameletspecbase.go b/pkg/client/camel/applyconfiguration/camel/v1/kameletspecbase.go new file mode 100644 index 0000000000..5f30df08f4 --- /dev/null +++ b/pkg/client/camel/applyconfiguration/camel/v1/kameletspecbase.go @@ -0,0 +1,108 @@ +/* +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + camelv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" +) + +// KameletSpecBaseApplyConfiguration represents an declarative configuration of the KameletSpecBase type for use +// with apply. +type KameletSpecBaseApplyConfiguration struct { + Definition *JSONSchemaPropsApplyConfiguration `json:"definition,omitempty"` + Sources []SourceSpecApplyConfiguration `json:"sources,omitempty"` + Template *TemplateApplyConfiguration `json:"template,omitempty"` + Types map[camelv1.TypeSlot]EventTypeSpecApplyConfiguration `json:"types,omitempty"` + DataTypes map[camelv1.TypeSlot]DataTypesSpecApplyConfiguration `json:"dataTypes,omitempty"` + Dependencies []string `json:"dependencies,omitempty"` +} + +// KameletSpecBaseApplyConfiguration constructs an declarative configuration of the KameletSpecBase type for use with +// apply. +func KameletSpecBase() *KameletSpecBaseApplyConfiguration { + return &KameletSpecBaseApplyConfiguration{} +} + +// WithDefinition sets the Definition field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Definition field is set to the value of the last call. +func (b *KameletSpecBaseApplyConfiguration) WithDefinition(value *JSONSchemaPropsApplyConfiguration) *KameletSpecBaseApplyConfiguration { + b.Definition = value + return b +} + +// WithSources adds the given value to the Sources field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Sources field. +func (b *KameletSpecBaseApplyConfiguration) WithSources(values ...*SourceSpecApplyConfiguration) *KameletSpecBaseApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithSources") + } + b.Sources = append(b.Sources, *values[i]) + } + return b +} + +// WithTemplate sets the Template field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Template field is set to the value of the last call. +func (b *KameletSpecBaseApplyConfiguration) WithTemplate(value *TemplateApplyConfiguration) *KameletSpecBaseApplyConfiguration { + b.Template = value + return b +} + +// WithTypes puts the entries into the Types field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Types field, +// overwriting an existing map entries in Types field with the same key. +func (b *KameletSpecBaseApplyConfiguration) WithTypes(entries map[camelv1.TypeSlot]EventTypeSpecApplyConfiguration) *KameletSpecBaseApplyConfiguration { + if b.Types == nil && len(entries) > 0 { + b.Types = make(map[camelv1.TypeSlot]EventTypeSpecApplyConfiguration, len(entries)) + } + for k, v := range entries { + b.Types[k] = v + } + return b +} + +// WithDataTypes puts the entries into the DataTypes field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the DataTypes field, +// overwriting an existing map entries in DataTypes field with the same key. +func (b *KameletSpecBaseApplyConfiguration) WithDataTypes(entries map[camelv1.TypeSlot]DataTypesSpecApplyConfiguration) *KameletSpecBaseApplyConfiguration { + if b.DataTypes == nil && len(entries) > 0 { + b.DataTypes = make(map[camelv1.TypeSlot]DataTypesSpecApplyConfiguration, len(entries)) + } + for k, v := range entries { + b.DataTypes[k] = v + } + return b +} + +// WithDependencies adds the given value to the Dependencies field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Dependencies field. +func (b *KameletSpecBaseApplyConfiguration) WithDependencies(values ...string) *KameletSpecBaseApplyConfiguration { + for i := range values { + b.Dependencies = append(b.Dependencies, values[i]) + } + return b +} diff --git a/pkg/client/camel/applyconfiguration/utils.go b/pkg/client/camel/applyconfiguration/utils.go index 626858470c..835f37e452 100644 --- a/pkg/client/camel/applyconfiguration/utils.go +++ b/pkg/client/camel/applyconfiguration/utils.go @@ -168,6 +168,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &camelv1.KameletRepositorySpecApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("KameletSpec"): return &camelv1.KameletSpecApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("KameletSpecBase"): + return &camelv1.KameletSpecBaseApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("KameletStatus"): return &camelv1.KameletStatusApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("KanikoTask"): diff --git a/pkg/controller/pipe/initialize_test.go b/pkg/controller/pipe/initialize_test.go index f12bf73863..315d899fef 100644 --- a/pkg/controller/pipe/initialize_test.go +++ b/pkg/controller/pipe/initialize_test.go @@ -120,18 +120,20 @@ func TestNewPipeWithKameletsCreating(t *testing.T) { v1.AnnotationIcon: "my-source-icon-base64", } source.Spec = v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", - "steps": []interface{}{ - map[string]interface{}{ - "to": "kamelet:sink", + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + "steps": []interface{}{ + map[string]interface{}{ + "to": "kamelet:sink", + }, }, }, + }), + Dependencies: []string{ + "camel:timer", }, - }), - Dependencies: []string{ - "camel:timer", }, } sink := v1.NewKamelet("ns", "my-sink") @@ -139,20 +141,22 @@ func TestNewPipeWithKameletsCreating(t *testing.T) { v1.AnnotationIcon: "my-sink-icon-base64", } sink.Spec = v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "kamelet:source", - "steps": []interface{}{ - map[string]interface{}{ - "to": map[string]interface{}{ - "uri": "log:info", + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "kamelet:source", + "steps": []interface{}{ + map[string]interface{}{ + "to": map[string]interface{}{ + "uri": "log:info", + }, }, }, }, + }), + Dependencies: []string{ + "camel:log", }, - }), - Dependencies: []string{ - "camel:log", }, } pipe := &v1.Pipe{ diff --git a/pkg/resources/config/crd/bases/camel.apache.org_kamelets.yaml b/pkg/resources/config/crd/bases/camel.apache.org_kamelets.yaml index e008ec06d8..0318ec8910 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_kamelets.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_kamelets.yaml @@ -654,6 +654,596 @@ spec: data specification types for the events consumed/produced by the Kamelet Deprecated: In favor of using DataTypes type: object + versions: + additionalProperties: + description: KameletSpecBase specifies the base configuration of + a Kamelet. + properties: + dataTypes: + additionalProperties: + description: DataTypesSpec represents the specification for + a set of data types. + properties: + default: + description: the default data type for this Kamelet + type: string + headers: + additionalProperties: + description: HeaderSpec represents the specification + for a header used in the Kamelet. + properties: + default: + type: string + description: + type: string + required: + type: boolean + title: + type: string + type: + type: string + type: object + description: one to many header specifications + type: object + types: + additionalProperties: + description: DataTypeSpec represents the specification + for a data type. + properties: + dependencies: + description: the list of Camel or Maven dependencies + required by the data type + items: + type: string + type: array + description: + description: optional description + type: string + format: + description: the data type format name + type: string + headers: + additionalProperties: + description: HeaderSpec represents the specification + for a header used in the Kamelet. + properties: + default: + type: string + description: + type: string + required: + type: boolean + title: + type: string + type: + type: string + type: object + description: one to many header specifications + type: object + mediaType: + description: media type as expected for HTTP media + types (ie, application/json) + type: string + schema: + description: the expected schema for the data type + properties: + $schema: + description: JSONSchemaURL represents a schema + url. + type: string + description: + type: string + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + externalDocs: + description: ExternalDocumentation allows referencing + an external resource for extended documentation. + properties: + description: + type: string + url: + type: string + type: object + id: + type: string + properties: + additionalProperties: + properties: + default: + description: default is a default value + for undefined object fields. + x-kubernetes-preserve-unknown-fields: true + deprecated: + type: boolean + description: + type: string + enum: + items: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + type: array + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + exclusiveMaximum: + type: boolean + exclusiveMinimum: + type: boolean + format: + description: |- + format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated: + + + - bsonobjectid: a bson object ID, i.e. a 24 characters hex string + - uri: an URI as parsed by Golang net/url.ParseRequestURI + - email: an email address as parsed by Golang net/mail.ParseAddress + - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034]. + - ipv4: an IPv4 IP as parsed by Golang net.ParseIP + - ipv6: an IPv6 IP as parsed by Golang net.ParseIP + - cidr: a CIDR as parsed by Golang net.ParseCIDR + - mac: a MAC address as parsed by Golang net.ParseMAC + - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041" + - isbn10: an ISBN10 number string like "0321751043" + - isbn13: an ISBN13 number string like "978-0321751041" + - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in + - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$ + - hexcolor: an hexadecimal color code like "#FFFFFF" following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ + - rgbcolor: an RGB color code like rgb like "rgb(255,255,255)" + - byte: base64 encoded binary data + - password: any kind of string + - date: a date string like "2006-01-02" as defined by full-date in RFC3339 + - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format + - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339. + type: string + id: + type: string + maxItems: + format: int64 + type: integer + maxLength: + format: int64 + type: integer + maxProperties: + format: int64 + type: integer + maximum: + description: A Number represents a JSON + number literal. + type: string + minItems: + format: int64 + type: integer + minLength: + format: int64 + type: integer + minProperties: + format: int64 + type: integer + minimum: + description: A Number represents a JSON + number literal. + type: string + multipleOf: + description: A Number represents a JSON + number literal. + type: string + nullable: + type: boolean + pattern: + type: string + title: + type: string + type: + type: string + uniqueItems: + type: boolean + x-descriptors: + description: XDescriptors is a list of + extended properties that trigger a custom + behavior in external systems + items: + type: string + type: array + type: object + type: object + required: + items: + type: string + type: array + title: + type: string + type: + type: string + type: object + scheme: + description: the data type component scheme + type: string + type: object + description: one to many data type specifications + type: object + type: object + description: data specification types for the events consumed/produced + by the Kamelet + type: object + definition: + description: defines the formal configuration of the Kamelet + properties: + $schema: + description: JSONSchemaURL represents a schema url. + type: string + description: + type: string + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + externalDocs: + description: ExternalDocumentation allows referencing an + external resource for extended documentation. + properties: + description: + type: string + url: + type: string + type: object + id: + type: string + properties: + additionalProperties: + properties: + default: + description: default is a default value for undefined + object fields. + x-kubernetes-preserve-unknown-fields: true + deprecated: + type: boolean + description: + type: string + enum: + items: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + type: array + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + exclusiveMaximum: + type: boolean + exclusiveMinimum: + type: boolean + format: + description: |- + format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated: + + + - bsonobjectid: a bson object ID, i.e. a 24 characters hex string + - uri: an URI as parsed by Golang net/url.ParseRequestURI + - email: an email address as parsed by Golang net/mail.ParseAddress + - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034]. + - ipv4: an IPv4 IP as parsed by Golang net.ParseIP + - ipv6: an IPv6 IP as parsed by Golang net.ParseIP + - cidr: a CIDR as parsed by Golang net.ParseCIDR + - mac: a MAC address as parsed by Golang net.ParseMAC + - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041" + - isbn10: an ISBN10 number string like "0321751043" + - isbn13: an ISBN13 number string like "978-0321751041" + - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in + - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$ + - hexcolor: an hexadecimal color code like "#FFFFFF" following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ + - rgbcolor: an RGB color code like rgb like "rgb(255,255,255)" + - byte: base64 encoded binary data + - password: any kind of string + - date: a date string like "2006-01-02" as defined by full-date in RFC3339 + - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format + - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339. + type: string + id: + type: string + maxItems: + format: int64 + type: integer + maxLength: + format: int64 + type: integer + maxProperties: + format: int64 + type: integer + maximum: + description: A Number represents a JSON number literal. + type: string + minItems: + format: int64 + type: integer + minLength: + format: int64 + type: integer + minProperties: + format: int64 + type: integer + minimum: + description: A Number represents a JSON number literal. + type: string + multipleOf: + description: A Number represents a JSON number literal. + type: string + nullable: + type: boolean + pattern: + type: string + title: + type: string + type: + type: string + uniqueItems: + type: boolean + x-descriptors: + description: XDescriptors is a list of extended properties + that trigger a custom behavior in external systems + items: + type: string + type: array + type: object + type: object + required: + items: + type: string + type: array + title: + type: string + type: + type: string + type: object + dependencies: + description: Camel dependencies needed by the Kamelet + items: + type: string + type: array + sources: + description: sources in any Camel DSL supported + items: + description: SourceSpec defines the configuration for one + or more routes to be executed in a certain Camel DSL language. + properties: + compression: + description: if the content is compressed (base64 encrypted) + type: boolean + content: + description: the source code (plain text) + type: string + contentKey: + description: the confimap key holding the source content + type: string + contentRef: + description: the confimap reference holding the source + content + type: string + contentType: + description: the content type (tipically text or binary) + type: string + from-kamelet: + description: True if the spec is generated from a Kamelet + type: boolean + interceptors: + description: |- + Interceptors are optional identifiers the org.apache.camel.k.RoutesLoader + uses to pre/post process sources + items: + type: string + type: array + language: + description: specify which is the language (Camel DSL) + used to interpret this source code + type: string + loader: + description: |- + Loader is an optional id of the org.apache.camel.k.RoutesLoader that will + interpret this source at runtime + type: string + name: + description: the name of the specification + type: string + path: + description: the path where the file is stored + type: string + property-names: + description: List of property names defined in the source + (e.g. if type is "template") + items: + type: string + type: array + rawContent: + description: the source code (binary) + format: byte + type: string + type: + description: Type defines the kind of source described + by this object + type: string + type: object + type: array + template: + description: the main source in YAML DSL + type: object + x-kubernetes-preserve-unknown-fields: true + types: + additionalProperties: + description: |- + EventTypeSpec represents a specification for an event type. + Deprecated: In favor of using DataTypeSpec. + properties: + mediaType: + description: media type as expected for HTTP media types + (ie, application/json) + type: string + schema: + description: the expected schema for the event + properties: + $schema: + description: JSONSchemaURL represents a schema url. + type: string + description: + type: string + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + externalDocs: + description: ExternalDocumentation allows referencing + an external resource for extended documentation. + properties: + description: + type: string + url: + type: string + type: object + id: + type: string + properties: + additionalProperties: + properties: + default: + description: default is a default value for + undefined object fields. + x-kubernetes-preserve-unknown-fields: true + deprecated: + type: boolean + description: + type: string + enum: + items: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + type: array + example: + description: |- + JSON represents any valid JSON value. + These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + x-kubernetes-preserve-unknown-fields: true + exclusiveMaximum: + type: boolean + exclusiveMinimum: + type: boolean + format: + description: |- + format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated: + + + - bsonobjectid: a bson object ID, i.e. a 24 characters hex string + - uri: an URI as parsed by Golang net/url.ParseRequestURI + - email: an email address as parsed by Golang net/mail.ParseAddress + - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034]. + - ipv4: an IPv4 IP as parsed by Golang net.ParseIP + - ipv6: an IPv6 IP as parsed by Golang net.ParseIP + - cidr: a CIDR as parsed by Golang net.ParseCIDR + - mac: a MAC address as parsed by Golang net.ParseMAC + - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041" + - isbn10: an ISBN10 number string like "0321751043" + - isbn13: an ISBN13 number string like "978-0321751041" + - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in + - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$ + - hexcolor: an hexadecimal color code like "#FFFFFF" following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ + - rgbcolor: an RGB color code like rgb like "rgb(255,255,255)" + - byte: base64 encoded binary data + - password: any kind of string + - date: a date string like "2006-01-02" as defined by full-date in RFC3339 + - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format + - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339. + type: string + id: + type: string + maxItems: + format: int64 + type: integer + maxLength: + format: int64 + type: integer + maxProperties: + format: int64 + type: integer + maximum: + description: A Number represents a JSON number + literal. + type: string + minItems: + format: int64 + type: integer + minLength: + format: int64 + type: integer + minProperties: + format: int64 + type: integer + minimum: + description: A Number represents a JSON number + literal. + type: string + multipleOf: + description: A Number represents a JSON number + literal. + type: string + nullable: + type: boolean + pattern: + type: string + title: + type: string + type: + type: string + uniqueItems: + type: boolean + x-descriptors: + description: XDescriptors is a list of extended + properties that trigger a custom behavior + in external systems + items: + type: string + type: array + type: object + type: object + required: + items: + type: string + type: array + title: + type: string + type: + type: string + type: object + type: object + description: |- + data specification types for the events consumed/produced by the Kamelet + Deprecated: In favor of using DataTypes + type: object + type: object + description: the optional versions available for this Kamelet. + type: object type: object status: default: diff --git a/pkg/trait/kamelets_support_test.go b/pkg/trait/kamelets_support_test.go index 5435960756..26188f5306 100644 --- a/pkg/trait/kamelets_support_test.go +++ b/pkg/trait/kamelets_support_test.go @@ -86,29 +86,31 @@ func TestKameletBundleMultiKameletsMultiConfigmap(t *testing.T) { func kamelet(ns, name string) *v1.Kamelet { kamelet := v1.NewKamelet(ns, name) kamelet.Spec = v1.KameletSpec{ - Sources: []v1.SourceSpec{ - { - DataSpec: v1.DataSpec{ - Name: "mykamelet.groovy", - Content: `from("timer1").to("log:info") - from("timer2").to("log:info") - from("timer3").to("log:info") - from("timer4").to("log:info") - from("timer5").to("log:info") - from("timer6").to("log:info") - from("timer7").to("log:info") - from("timer8").to("log:info") - from("timer9").to("log:info") - from("timer10").to("log:info") - from("timer11").to("log:info") - from("timer12").to("log:info") - from("timer13").to("log:info") - from("timer14").to("log:info") - from("timer15").to("log:info") - from("timer16").to("log:info") - from("timer17").to("log:info")`, + KameletSpecBase: v1.KameletSpecBase{ + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "mykamelet.groovy", + Content: `from("timer1").to("log:info") + from("timer2").to("log:info") + from("timer3").to("log:info") + from("timer4").to("log:info") + from("timer5").to("log:info") + from("timer6").to("log:info") + from("timer7").to("log:info") + from("timer8").to("log:info") + from("timer9").to("log:info") + from("timer10").to("log:info") + from("timer11").to("log:info") + from("timer12").to("log:info") + from("timer13").to("log:info") + from("timer14").to("log:info") + from("timer15").to("log:info") + from("timer16").to("log:info") + from("timer17").to("log:info")`, + }, + Type: v1.SourceTypeTemplate, }, - Type: v1.SourceTypeTemplate, }, }, } diff --git a/pkg/trait/kamelets_test.go b/pkg/trait/kamelets_test.go index d59e56d797..41620ff7a9 100644 --- a/pkg/trait/kamelets_test.go +++ b/pkg/trait/kamelets_test.go @@ -81,14 +81,16 @@ func TestKameletLookup(t *testing.T) { Name: "timer", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + Dependencies: []string{ + "camel:timer", + "camel:log", }, - }), - Dependencies: []string{ - "camel:timer", - "camel:log", }, }, }) @@ -125,18 +127,20 @@ func TestKameletSecondarySourcesLookup(t *testing.T) { Name: "timer", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", - }, - }), - Sources: []v1.SourceSpec{ - { - DataSpec: v1.DataSpec{ - Name: "support.groovy", - Content: "from('xxx:xxx').('to:log:info')", + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "support.groovy", + Content: "from('xxx:xxx').('to:log:info')", + }, + Language: v1.LanguageGroovy, }, - Language: v1.LanguageGroovy, }, }, }, @@ -181,13 +185,15 @@ func TestNonYAMLKameletLookup(t *testing.T) { Name: "timer", }, Spec: v1.KameletSpec{ - Sources: []v1.SourceSpec{ - { - DataSpec: v1.DataSpec{ - Name: "mykamelet.groovy", - Content: `from("timer").to("log:info")`, + KameletSpecBase: v1.KameletSpecBase{ + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "mykamelet.groovy", + Content: `from("timer").to("log:info")`, + }, + Type: v1.SourceTypeTemplate, }, - Type: v1.SourceTypeTemplate, }, }, }, @@ -224,23 +230,25 @@ func TestMultipleKamelets(t *testing.T) { Name: "timer", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", - }, - }), - Sources: []v1.SourceSpec{ - { - DataSpec: v1.DataSpec{ - Name: "support.groovy", - Content: "from('xxx:xxx').('to:log:info')", + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "support.groovy", + Content: "from('xxx:xxx').('to:log:info')", + }, + Language: v1.LanguageGroovy, }, - Language: v1.LanguageGroovy, }, - }, - Dependencies: []string{ - "camel:timer", - "camel:xxx", + Dependencies: []string{ + "camel:timer", + "camel:xxx", + }, }, }, }, &v1.Kamelet{ @@ -249,21 +257,23 @@ func TestMultipleKamelets(t *testing.T) { Name: "logger", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "tbd:endpoint", - "steps": []interface{}{ - map[string]interface{}{ - "to": map[string]interface{}{ - "uri": "log:info", + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "tbd:endpoint", + "steps": []interface{}{ + map[string]interface{}{ + "to": map[string]interface{}{ + "uri": "log:info", + }, }, }, }, + }), + Dependencies: []string{ + "camel:log", + "camel:tbd", }, - }), - Dependencies: []string{ - "camel:log", - "camel:tbd", }, }, }) @@ -318,14 +328,16 @@ func TestKameletConfigLookup(t *testing.T) { Name: "timer", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + Dependencies: []string{ + "camel:timer", + "camel:log", }, - }), - Dependencies: []string{ - "camel:timer", - "camel:log", }, }, }, &corev1.Secret{ @@ -373,14 +385,16 @@ func TestKameletNamedConfigLookup(t *testing.T) { Name: "timer", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + Dependencies: []string{ + "camel:timer", + "camel:log", }, - }), - Dependencies: []string{ - "camel:timer", - "camel:log", }, }, }, &corev1.Secret{ @@ -432,11 +446,13 @@ func TestKameletConditionFalse(t *testing.T) { Name: "timer", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", - }, - }), + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + }, }, }) @@ -471,11 +487,13 @@ func TestKameletConditionTrue(t *testing.T) { Name: "timer", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", - }, - }), + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + }, }, }, &v1.Kamelet{ @@ -484,11 +502,13 @@ func TestKameletConditionTrue(t *testing.T) { Name: "none", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", - }, - }), + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + }, }, }) @@ -568,11 +588,13 @@ func TestKameletSyntheticKitConditionTrue(t *testing.T) { Name: "timer-source", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", - }, - }), + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + }, }, }) environment.CamelCatalog = nil @@ -611,11 +633,13 @@ func TestKameletSyntheticKitAutoConditionFalse(t *testing.T) { Name: "timer-source", }, Spec: v1.KameletSpec{ - Template: templateOrFail(map[string]interface{}{ - "from": map[string]interface{}{ - "uri": "timer:tick", - }, - }), + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + }, }, }) environment.Integration.Spec.Sources = nil From 8b0981d2961807eabc1eb78d6081aa61b61dd587 Mon Sep 17 00:00:00 2001 From: Pasquale Congiusti Date: Mon, 2 Sep 2024 16:00:33 +0200 Subject: [PATCH 2/3] feat(trait): support kamelet versions in Integration --- .../ROOT/pages/kamelets/kamelets-dev.adoc | 44 +++++++ .../ROOT/pages/kamelets/kamelets-user.adoc | 15 +++ e2e/common/traits/files/kamelet-it-main.yaml | 4 + e2e/common/traits/files/kamelet-it-v1.yaml | 4 + .../traits/files/my-timer-source.kamelet.yaml | 49 +++++++ e2e/common/traits/kamelet_test.go | 25 +++- pkg/apis/camel/v1/kamelet_types_support.go | 28 ++-- .../camel/v1alpha1/kamelet_types_support.go | 10 -- pkg/trait/kamelets.go | 74 +++++++---- pkg/trait/kamelets_test.go | 122 ++++++++++++------ pkg/util/source/kamelet.go | 6 + pkg/util/source/kamelet_test.go | 34 +++++ 12 files changed, 327 insertions(+), 88 deletions(-) create mode 100644 e2e/common/traits/files/kamelet-it-main.yaml create mode 100644 e2e/common/traits/files/kamelet-it-v1.yaml create mode 100644 e2e/common/traits/files/my-timer-source.kamelet.yaml create mode 100644 pkg/util/source/kamelet_test.go diff --git a/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc b/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc index ab5e533e62..da18d483ff 100644 --- a/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc +++ b/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc @@ -331,6 +331,50 @@ If everything goes right, you should get some tweets in the logs after the integ Refer to the xref:kamelets/kamelets-user.adoc[Kamelets User Guide] for more information on how to use it in different contexts (like Knative, Kafka, etc.). +== Kamelet versions + +The catalog containing a set of Kamelets is generally developed in order to be used with a given Camel version (see the Apache Camel Kamelets catalog). However, when publishing the Kamelet to the cluster you may want to maintain more than one version for any reason (ie, to use a different dependency and be able to support multiple runtimes). You can therefore use the `.spec.versions` parameter to optionally maintain a set of alternative versions beside the main (and default) one. + +.my-timer-source.yaml +[source,yaml] +---- +apiVersion: camel.apache.org/v1 +kind: Kamelet +metadata: + name: my-timer-source +spec: + definition: + title: "Timer Example" + types: + out: + mediaType: text/plain + template: + from: + uri: timer:tick + steps: + - setBody: + constant: "Kamelet Main" + - to: "kamelet:sink" + versions: + v2: + definition: + title: "Timer Example 2" + types: + out: + mediaType: text/plain + template: + from: + uri: timer:tick + steps: + - setBody: + constant: "Kamelet V2" + - to: "kamelet:sink" +---- + +NOTE: make sure the overall content fits into 1 MiB, which is the storage limit for a Custom Resource. + +This is a way to handle multiple version on Kubernetes and may not be supported out of the box by Camel core. If the Integration will require specifically to use `kamelet:my-timer-source?version=v2`, then, the operator will mount properly the specification on the running application. + == Kamelet data types A Kamelet usually encapsulates a specific functionality and serves a very opinionated use case with well-defined input parameters and outcome. diff --git a/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc b/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc index ae27db0230..5c84142192 100644 --- a/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc +++ b/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc @@ -218,6 +218,21 @@ You can now write an integration that uses the Kamelet with the named configurat You can run this integration without specifying other parameters, the Kamelet endpoint will be implicitly configured by the Camel K operator that will automatically mount the secret into the integration Pod. +=== 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: "timer:tick?version=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-usage-binding]] == Binding Kamelets in Pipes diff --git a/e2e/common/traits/files/kamelet-it-main.yaml b/e2e/common/traits/files/kamelet-it-main.yaml new file mode 100644 index 0000000000..cbda8d96c5 --- /dev/null +++ b/e2e/common/traits/files/kamelet-it-main.yaml @@ -0,0 +1,4 @@ +- from: + uri: "kamelet:my-timer-source" + steps: + - log: "${body}" \ No newline at end of file diff --git a/e2e/common/traits/files/kamelet-it-v1.yaml b/e2e/common/traits/files/kamelet-it-v1.yaml new file mode 100644 index 0000000000..8f4a0ab7b3 --- /dev/null +++ b/e2e/common/traits/files/kamelet-it-v1.yaml @@ -0,0 +1,4 @@ +- from: + uri: "kamelet:my-timer-source?version=v1" + steps: + - log: "${body}" \ No newline at end of file diff --git a/e2e/common/traits/files/my-timer-source.kamelet.yaml b/e2e/common/traits/files/my-timer-source.kamelet.yaml new file mode 100644 index 0000000000..f291e1a110 --- /dev/null +++ b/e2e/common/traits/files/my-timer-source.kamelet.yaml @@ -0,0 +1,49 @@ +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 Main" + - to: "kamelet:sink" + versions: + v1: + definition: + title: "Timer Example 1" + description: "Produces periodic events with a custom payload" + 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: "Produces periodic events with a custom payload" + types: + out: + mediaType: text/plain + template: + from: + uri: timer:tick + steps: + - setBody: + constant: "Kamelet V2" + - to: "kamelet:sink" diff --git a/e2e/common/traits/kamelet_test.go b/e2e/common/traits/kamelet_test.go index 72f636b8dd..257566471d 100644 --- a/e2e/common/traits/kamelet_test.go +++ b/e2e/common/traits/kamelet_test.go @@ -34,7 +34,7 @@ import ( v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" ) -func TestKameletTrait(t *testing.T) { +func TestKameletDiscoverCapabilities(t *testing.T) { t.Parallel() WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) { t.Run("discover kamelet capabilities", func(t *testing.T) { @@ -52,8 +52,8 @@ func TestKameletTrait(t *testing.T) { name := RandomizedSuffixName("webhook") g.Expect(KamelRun(t, ctx, ns, "files/webhook.yaml", "--name", name).Execute()).To(Succeed()) - g.Eventually(IntegrationPodPhase(t, ctx, ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning)) - g.Eventually(IntegrationConditionStatus(t, ctx, ns, name, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) + g.Eventually(IntegrationConditionStatus(t, ctx, ns, name, v1.IntegrationConditionReady), TestTimeoutMedium).Should(Equal(corev1.ConditionTrue)) + g.Eventually(IntegrationPodPhase(t, ctx, ns, name), TestTimeoutShort).Should(Equal(corev1.PodRunning)) g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("Started route1 (kamelet://capabilities-webhook-source)")) // Verify Integration capabilities g.Eventually(IntegrationStatusCapabilities(t, ctx, ns, name), TestTimeoutShort).Should(ContainElements("platform-http")) @@ -63,3 +63,22 @@ func TestKameletTrait(t *testing.T) { }) }) } + +func TestKameletMultiVersions(t *testing.T) { + t.Parallel() + WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) { + t.Run("multiple kamelet versions", func(t *testing.T) { + ExpectExecSucceed(t, g, Kubectl("apply", "-f", "files/my-timer-source.kamelet.yaml", "-n", ns)) + name := RandomizedSuffixName("multiversions") + g.Expect(KamelRun(t, ctx, ns, "files/kamelet-it-main.yaml", "--name", name).Execute()).To(Succeed()) + g.Eventually(IntegrationConditionStatus(t, ctx, ns, name, v1.IntegrationConditionReady), TestTimeoutMedium).Should(Equal(corev1.ConditionTrue)) + g.Eventually(IntegrationPodPhase(t, ctx, ns, name), TestTimeoutShort).Should(Equal(corev1.PodRunning)) + g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("Kamelet Main")) + // Switch to Kamelet V1 + g.Expect(KamelRun(t, ctx, ns, "files/kamelet-it-v1.yaml", "--name", name).Execute()).To(Succeed()) + g.Eventually(IntegrationConditionStatus(t, ctx, ns, name, v1.IntegrationConditionReady), TestTimeoutMedium).Should(Equal(corev1.ConditionTrue)) + g.Eventually(IntegrationPodPhase(t, ctx, ns, name), TestTimeoutShort).Should(Equal(corev1.PodRunning)) + g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("Kamelet V1")) + }) + }) +} diff --git a/pkg/apis/camel/v1/kamelet_types_support.go b/pkg/apis/camel/v1/kamelet_types_support.go index e8818cb9ef..bb22c998e8 100644 --- a/pkg/apis/camel/v1/kamelet_types_support.go +++ b/pkg/apis/camel/v1/kamelet_types_support.go @@ -18,6 +18,7 @@ limitations under the License. package v1 import ( + "fmt" "sort" corev1 "k8s.io/api/core/v1" @@ -181,16 +182,6 @@ func ValidKameletName(name string) bool { return !reservedKameletNames[name] } -func ValidKameletProperties(kamelet *Kamelet) bool { - if kamelet == nil || kamelet.Spec.Definition == nil || kamelet.Spec.Definition.Properties == nil { - return true - } - if _, idPresent := kamelet.Spec.Definition.Properties[KameletIDProperty]; idPresent { - return false - } - return true -} - // NewKamelet creates a new Kamelet. func NewKamelet(namespace string, name string) Kamelet { return Kamelet{ @@ -219,3 +210,20 @@ func NewKameletList() KameletList { func (k *Kamelet) SetOperatorID(operatorID string) { SetAnnotation(&k.ObjectMeta, OperatorIDAnnotation, operatorID) } + +// CloneWithVersion clones a Kamelet and set the main specification with any version provided. +// It also changes the name adding a suffix with the version provided. +func (k *Kamelet) CloneWithVersion(version string) (*Kamelet, error) { + clone := k.DeepCopy() + if version != "" { + kameletVersionSpec, ok := k.Spec.Versions[version] + if !ok { + return nil, fmt.Errorf("could not find version %s for Kamelet %s/%s", version, k.Namespace, k.Name) + } + clone.Spec.KameletSpecBase = kameletVersionSpec + } + // Remove any existing version + clone.Spec.Versions = nil + + return clone, nil +} diff --git a/pkg/apis/camel/v1alpha1/kamelet_types_support.go b/pkg/apis/camel/v1alpha1/kamelet_types_support.go index 55264bea6a..4181675ba3 100644 --- a/pkg/apis/camel/v1alpha1/kamelet_types_support.go +++ b/pkg/apis/camel/v1alpha1/kamelet_types_support.go @@ -184,16 +184,6 @@ func ValidKameletName(name string) bool { return !reservedKameletNames[name] } -func ValidKameletProperties(kamelet *Kamelet) bool { - if kamelet == nil || kamelet.Spec.Definition == nil || kamelet.Spec.Definition.Properties == nil { - return true - } - if _, idPresent := kamelet.Spec.Definition.Properties[KameletIDProperty]; idPresent { - return false - } - return true -} - // NewKamelet creates a new Kamelet. func NewKamelet(namespace string, name string) Kamelet { return Kamelet{ diff --git a/pkg/trait/kamelets.go b/pkg/trait/kamelets.go index 912bb63975..01a563034f 100644 --- a/pkg/trait/kamelets.go +++ b/pkg/trait/kamelets.go @@ -97,7 +97,7 @@ func (t *kameletsTrait) Configure(e *Environment) (bool, *TraitCondition, error) } } - return len(t.getKameletKeys()) > 0, nil, nil + return len(t.getKameletKeys(false)) > 0, nil, nil } func (t *kameletsTrait) Apply(e *Environment) error { @@ -109,6 +109,7 @@ func (t *kameletsTrait) Apply(e *Environment) error { return nil } +// 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()) if err != nil { @@ -119,18 +120,27 @@ func (t *kameletsTrait) collectKamelets(e *Environment) (map[string]*v1.Kamelet, missingKamelets := make([]string, 0) availableKamelets := make([]string, 0) - for _, key := range t.getKameletKeys() { - kamelet, err := repo.Get(e.Ctx, key) + for _, kml := range strings.Split(t.List, ",") { + name := getKameletKey(kml, false) + if !v1.ValidKameletName(name) { + // Skip kamelet sink and source id + continue + } + kamelet, err := repo.Get(e.Ctx, name) if err != nil { return nil, err } - if kamelet == nil { - missingKamelets = append(missingKamelets, key) + missingKamelets = append(missingKamelets, name) + continue } else { - availableKamelets = append(availableKamelets, key) - kamelets[key] = kamelet + availableKamelets = append(availableKamelets, name) + } + clonedKamelet, err := kamelet.CloneWithVersion(getKameletVersion(kml)) + if err != nil { + return nil, err } + kamelets[clonedKamelet.Name] = clonedKamelet } sort.Strings(availableKamelets) @@ -163,18 +173,16 @@ func (t *kameletsTrait) collectKamelets(e *Environment) (map[string]*v1.Kamelet, } func (t *kameletsTrait) addKamelets(e *Environment) error { - if len(t.getKameletKeys()) == 0 { + if len(t.getKameletKeys(false)) == 0 { return nil } - kamelets, err := t.collectKamelets(e) if err != nil { return err } kb := newKameletBundle() - for _, key := range t.getKameletKeys() { - kamelet := kamelets[key] - if err := t.addKameletAsSource(e, kamelet); err != nil { + for version, kamelet := range kamelets { + if err := t.addKameletAsSource(e, kamelet, version); err != nil { return err } // Adding dependencies from Kamelets @@ -210,23 +218,20 @@ func (t *kameletsTrait) addKamelets(e *Environment) error { // This func will add a Kamelet as a generated Integration source. The source included here is going to be used in order to parse the Kamelet // for any component or capability (ie, rest) which is included in the Kamelet spec itself. However, the generated source is marked as coming `FromKamelet`. // When mounting the sources, these generated sources won't be mounted as sources but as Kamelet instead. -func (t *kameletsTrait) addKameletAsSource(e *Environment, kamelet *v1.Kamelet) error { +func (t *kameletsTrait) addKameletAsSource(e *Environment, kamelet *v1.Kamelet, version string) error { sources := make([]v1.SourceSpec, 0) if kamelet.Spec.Template != nil { - template := kamelet.Spec.Template - flowData, err := dsl.TemplateToYamlDSL(*template, kamelet.Name) + flowData, err := dsl.TemplateToYamlDSL(*kamelet.Spec.Template, kamelet.Name) if err != nil { return err } - flowSource := v1.SourceSpec{ DataSpec: v1.DataSpec{ Name: fmt.Sprintf("%s.yaml", kamelet.Name), Content: string(flowData), }, - Language: v1.LanguageYaml, - FromKamelet: true, + Language: v1.LanguageYaml, } flowSource, err = integrationSourceFromKameletSource(e, kamelet, flowSource, fmt.Sprintf("%s-kamelet-%s-template", e.Integration.Name, kamelet.Name)) if err != nil { @@ -259,13 +264,10 @@ func (t *kameletsTrait) addKameletAsSource(e *Environment, kamelet *v1.Kamelet) return nil } -func (t *kameletsTrait) getKameletKeys() []string { +func (t *kameletsTrait) getKameletKeys(withVersion bool) []string { answer := make([]string, 0) for _, item := range strings.Split(t.List, ",") { - i := strings.Trim(item, " \t\"") - if strings.Contains(i, "/") { - i = strings.SplitN(i, "/", 2)[0] - } + i := getKameletKey(item, withVersion) if i != "" && v1.ValidKameletName(i) { util.StringSliceUniqueAdd(&answer, i) } @@ -274,6 +276,30 @@ func (t *kameletsTrait) getKameletKeys() []string { return answer } +func getKameletKey(item string, withVersion bool) string { + i := strings.Trim(item, " \t\"") + if strings.Contains(i, "/") { + i = strings.SplitN(i, "/", 2)[0] + } + if strings.Contains(i, "?version=") { + versionedKamelet := strings.SplitN(i, "?version=", 2) + if withVersion { + i = fmt.Sprintf("%s-%s", versionedKamelet[0], versionedKamelet[1]) + } else { + i = versionedKamelet[0] + } + } + return i +} + +func getKameletVersion(item string) string { + if strings.Contains(item, "?version=") { + versionedKamelet := strings.SplitN(item, "?version=", 2) + return versionedKamelet[1] + } + return "" +} + func integrationSourceFromKameletSource(e *Environment, kamelet *v1.Kamelet, source v1.SourceSpec, name string) (v1.SourceSpec, error) { if source.Type == v1.SourceTypeTemplate { // Kamelets must be named ".extension" @@ -281,6 +307,8 @@ func integrationSourceFromKameletSource(e *Environment, kamelet *v1.Kamelet, sou source.Name = fmt.Sprintf("%s.%s", kamelet.Name, string(language)) } + source.FromKamelet = true + if source.DataSpec.ContentRef != "" { return source, nil } diff --git a/pkg/trait/kamelets_test.go b/pkg/trait/kamelets_test.go index 41620ff7a9..b8937aa6d5 100644 --- a/pkg/trait/kamelets_test.go +++ b/pkg/trait/kamelets_test.go @@ -54,6 +54,7 @@ func TestConfigurationWithKamelets(t *testing.T) { uri: kamelet:c1 steps: - to: kamelet:c2 + - to: kamelet:c3?version=v1 - to: telegram:bots - to: kamelet://c0?prop=x - to: kamelet://complex-.-.-1a?prop=x&prop2 @@ -66,7 +67,8 @@ func TestConfigurationWithKamelets(t *testing.T) { require.NoError(t, err) assert.True(t, enabled) assert.Nil(t, condition) - assert.Equal(t, []string{"c0", "c1", "c2", "complex-.-.-1a", "complex-.-.-1b", "complex-.-.-1c"}, trait.getKameletKeys()) + 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)) } func TestKameletLookup(t *testing.T) { @@ -98,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()) + assert.Equal(t, []string{"timer"}, trait.getKameletKeys(false)) err = trait.Apply(environment) require.NoError(t, err) @@ -149,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()) + assert.Equal(t, []string{"timer"}, trait.getKameletKeys(false)) err = trait.Apply(environment) require.NoError(t, err) @@ -202,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()) + assert.Equal(t, []string{"timer"}, trait.getKameletKeys(false)) err = trait.Apply(environment) require.NoError(t, err) @@ -220,10 +222,9 @@ func TestNonYAMLKameletLookup(t *testing.T) { func TestMultipleKamelets(t *testing.T) { trait, environment := createKameletsTestEnvironment(` - from: - uri: kamelet:timer + uri: kamelet:timer?version=v1 steps: - to: kamelet:logger - - to: kamelet:logger `, &v1.Kamelet{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", @@ -236,20 +237,28 @@ func TestMultipleKamelets(t *testing.T) { "uri": "timer:tick", }, }), - Sources: []v1.SourceSpec{ - { - DataSpec: v1.DataSpec{ - Name: "support.groovy", - Content: "from('xxx:xxx').('to:log:info')", - }, - Language: v1.LanguageGroovy, - }, - }, Dependencies: []string{ "camel:timer", "camel:xxx", }, }, + Versions: map[string]v1.KameletSpecBase{ + "v1": { + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "support.groovy", + Content: "from('xxx:xxx').('to:log:info')", + }, + Language: v1.LanguageGroovy, + }, + }, + Dependencies: []string{ + "camel:timer", + "camel:xxx-2", + }, + }, + }, }, }, &v1.Kamelet{ ObjectMeta: metav1.ObjectMeta{ @@ -264,7 +273,7 @@ func TestMultipleKamelets(t *testing.T) { "steps": []interface{}{ map[string]interface{}{ "to": map[string]interface{}{ - "uri": "log:info", + "uri": "log:info?option=main", }, }, }, @@ -275,45 +284,74 @@ func TestMultipleKamelets(t *testing.T) { "camel:tbd", }, }, + Versions: map[string]v1.KameletSpecBase{ + "v2": { + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "tbd:endpoint", + "steps": []interface{}{ + map[string]interface{}{ + "to": map[string]interface{}{ + "uri": "log:info?option=version2", + }, + }, + }, + }, + }), + Dependencies: []string{ + "camel:log", + "camel:tbd-2", + }, + }, + }, }, }) enabled, condition, err := trait.Configure(environment) require.NoError(t, err) assert.True(t, enabled) assert.Nil(t, condition) - assert.Equal(t, []string{"logger", "timer"}, trait.getKameletKeys()) + assert.Equal(t, "logger,timer?version=v1", trait.List) + assert.Equal(t, []string{"logger", "timer"}, trait.getKameletKeys(false)) + assert.Equal(t, []string{"logger", "timer-v1"}, trait.getKameletKeys(true)) err = trait.Apply(environment) require.NoError(t, err) - cmFlow := environment.Resources.GetConfigMap(func(c *corev1.ConfigMap) bool { return c.Name == "it-kamelet-timer-template" }) - assert.NotNil(t, cmFlow) - cmRes := environment.Resources.GetConfigMap(func(c *corev1.ConfigMap) bool { return c.Name == "it-kamelet-timer-000" }) - assert.NotNil(t, cmRes) - cmFlow2 := environment.Resources.GetConfigMap(func(c *corev1.ConfigMap) bool { return c.Name == "it-kamelet-logger-template" }) - assert.NotNil(t, cmFlow2) + cmFlowTimerSource := environment.Resources.GetConfigMap(func(c *corev1.ConfigMap) bool { return c.Name == "it-kamelet-timer-000" }) + assert.NotNil(t, cmFlowTimerSource) + assert.Contains(t, cmFlowTimerSource.Data[contentKey], "from('xxx:xxx').('to:log:info')") + cmFlowMissing := environment.Resources.GetConfigMap(func(c *corev1.ConfigMap) bool { return c.Name == "it-kamelet-timer-template" }) + assert.Nil(t, cmFlowMissing) + cmFlowLoggerTemplateMain := environment.Resources.GetConfigMap(func(c *corev1.ConfigMap) bool { return c.Name == "it-kamelet-logger-template" }) + assert.NotNil(t, cmFlowLoggerTemplateMain) + assert.Contains(t, cmFlowLoggerTemplateMain.Data[contentKey], "log:info?option=main") - assert.Len(t, environment.Integration.Status.GeneratedSources, 3) + assert.Len(t, environment.Integration.Status.GeneratedSources, 2) - flowSource2 := environment.Integration.Status.GeneratedSources[0] - assert.Equal(t, "logger.yaml", flowSource2.Name) - assert.Equal(t, "", string(flowSource2.Type)) - assert.Equal(t, "it-kamelet-logger-template", flowSource2.ContentRef) - assert.Equal(t, "content", flowSource2.ContentKey) + expectedFlowSourceTimerV1 := v1.SourceSpec{ + DataSpec: v1.DataSpec{ + Name: "support.groovy", + ContentRef: "it-kamelet-timer-000", + ContentKey: "content", + }, + Language: v1.LanguageGroovy, + FromKamelet: true, + } - flowSource := environment.Integration.Status.GeneratedSources[1] - assert.Equal(t, "timer.yaml", flowSource.Name) - assert.Equal(t, "", string(flowSource.Type)) - assert.Equal(t, "it-kamelet-timer-template", flowSource.ContentRef) - assert.Equal(t, "content", flowSource.ContentKey) + expectedFlowSinkLoggerMain := v1.SourceSpec{ + DataSpec: v1.DataSpec{ + Name: "logger.yaml", + ContentRef: "it-kamelet-logger-template", + ContentKey: "content", + }, + Language: v1.LanguageYaml, + FromKamelet: true, + } - supportSource := environment.Integration.Status.GeneratedSources[2] - assert.Equal(t, "support.groovy", supportSource.Name) - assert.Equal(t, "", string(supportSource.Type)) - assert.Equal(t, "it-kamelet-timer-000", supportSource.ContentRef) - assert.Equal(t, "content", supportSource.ContentKey) + assert.Contains(t, environment.Integration.Status.GeneratedSources, expectedFlowSourceTimerV1, expectedFlowSinkLoggerMain) - assert.Equal(t, []string{"camel:log", "camel:tbd", "camel:timer", "camel:xxx"}, environment.Integration.Status.Dependencies) + assert.Contains(t, environment.Integration.Status.Dependencies, + "camel:log", "camel:tbd", "camel:timer", "camel:xxx", "camel:xxx-2") } func TestKameletConfigLookup(t *testing.T) { @@ -370,7 +408,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()) + assert.Equal(t, []string{"timer"}, trait.getKameletKeys(false)) } func TestKameletNamedConfigLookup(t *testing.T) { @@ -428,7 +466,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()) + assert.Equal(t, []string{"timer"}, trait.getKameletKeys(false)) } func TestKameletConditionFalse(t *testing.T) { diff --git a/pkg/util/source/kamelet.go b/pkg/util/source/kamelet.go index a9a155efac..50a0729395 100644 --- a/pkg/util/source/kamelet.go +++ b/pkg/util/source/kamelet.go @@ -18,10 +18,12 @@ limitations under the License. package source import ( + "fmt" "regexp" ) var kameletNameRegexp = regexp.MustCompile("kamelet:(?://)?([a-z0-9-.]+(/[a-z0-9-.]+)?)(?:$|[^a-z0-9-.].*)") +var kameletVersionRegexp = regexp.MustCompile("version=([a-z0-9-.]+)") func ExtractKamelets(uris []string) []string { var kamelets []string @@ -37,6 +39,10 @@ func ExtractKamelets(uris []string) []string { 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) + } return matches[1] } return "" diff --git a/pkg/util/source/kamelet_test.go b/pkg/util/source/kamelet_test.go new file mode 100644 index 0000000000..ad9beace0a --- /dev/null +++ b/pkg/util/source/kamelet_test.go @@ -0,0 +1,34 @@ +/* +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 source + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExtractKamelets(t *testing.T) { + assert.Equal(t, "", ExtractKamelet("fail")) + assert.Equal(t, "my-test", ExtractKamelet("kamelet:my-test")) + assert.Equal(t, "my-test", ExtractKamelet("kamelet:my-test?")) + assert.Equal(t, "my-test", ExtractKamelet("kamelet:my-test?option=1")) + assert.Equal(t, "my-test", ExtractKamelet("kamelet:my-test?option=1&opt2=2")) + assert.Equal(t, "my-test?version=v1", ExtractKamelet("kamelet:my-test?option=1&opt2=2&version=v1")) + assert.Equal(t, "my-test?version=v1", ExtractKamelet("kamelet:my-test?version=v1")) +} From 90739db1f8a652167372ba8d5d08895faa554677 Mon Sep 17 00:00:00 2001 From: Pasquale Congiusti Date: Mon, 2 Sep 2024 16:37:19 +0200 Subject: [PATCH 3/3] feat: support kamelet version in Pipe --- .../ROOT/pages/kamelets/kamelets-dev.adoc | 4 ++- .../ROOT/pages/kamelets/kamelets-user.adoc | 2 +- .../ROOT/partials/apis/camel-k-crds.adoc | 4 ++- .../.camel-jbang/camel-jbang-run.properties | 25 ++++++++++++++++++ e2e/common/traits/files/kamelet-it-main.yaml | 19 ++++++++++++++ e2e/common/traits/files/kamelet-it-v1.yaml | 21 ++++++++++++++- .../traits/files/my-timer-source.kamelet.yaml | 17 ++++++++++++ helm/camel-k/crds/camel-k-crds.yaml | 5 +++- pkg/apis/camel/v1/kamelet_types.go | 6 ++++- .../crd/bases/camel.apache.org_kamelets.yaml | 5 +++- pkg/trait/kamelets.go | 16 +++++++----- pkg/trait/kamelets_test.go | 6 ++--- pkg/util/bindings/bindings_test.go | 18 +++++++++++++ pkg/util/bindings/kamelet.go | 26 +++++++++++++------ pkg/util/source/kamelet.go | 4 ++- pkg/util/source/kamelet_test.go | 4 +-- 16 files changed, 154 insertions(+), 28 deletions(-) create mode 100644 e2e/common/traits/files/.camel-jbang/camel-jbang-run.properties diff --git a/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc b/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc index da18d483ff..c617c47806 100644 --- a/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc +++ b/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc @@ -373,7 +373,9 @@ spec: NOTE: make sure the overall content fits into 1 MiB, which is the storage limit for a Custom Resource. -This is a way to handle multiple version on Kubernetes and may not be supported out of the box by Camel core. If the Integration will require specifically to use `kamelet:my-timer-source?version=v2`, then, the operator will mount properly the specification on the running application. +This is a way to handle multiple version on Kubernetes and may not be supported out of the box by Camel core. If the Integration will require specifically to use `kamelet:my-timer-source?kameletVersion=v2`, then, the operator will mount properly the specification on the running application. + +The `.spec.versions` field may not be necessarily supported by the core as it's meant to provide a way to handle versioning on the cluster only. The runtime must be provided with a materialized Kamelet file with the chosen spec (the operator is in charge of that). == Kamelet data types diff --git a/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc b/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc index 5c84142192..d39814ced0 100644 --- a/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc +++ b/docs/modules/ROOT/pages/kamelets/kamelets-user.adoc @@ -226,7 +226,7 @@ Kamelets provided in a catalog are generally meant to work with a given runtime .kamlet-namedconfig-route.yaml ---- - from: - uri: "timer:tick?version=v2" + uri: "timer:tick?kameletVersion=v2" steps: - to: "log:info" ---- diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc index abf88aac53..4761b27477 100644 --- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc +++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc @@ -4141,7 +4141,9 @@ KameletSpec specifies the configuration required to execute a Kamelet. | -the optional versions available for this Kamelet. +the optional versions available for this Kamelet. This field may not be taken in account by Camel core and is meant to support +any user defined versioning model on cluster only. If the user wants to use any given version, she must materialize a file with the given version spec +as the `main` Kamelet spec on the runtime. |=== diff --git a/e2e/common/traits/files/.camel-jbang/camel-jbang-run.properties b/e2e/common/traits/files/.camel-jbang/camel-jbang-run.properties new file mode 100644 index 0000000000..8b86781dca --- /dev/null +++ b/e2e/common/traits/files/.camel-jbang/camel-jbang-run.properties @@ -0,0 +1,25 @@ +loggingLevel=info +loggingColor=true +loggingJson=false +camel.jbang.localKameletDir=file\:. +camel.main.modeline=true +camel.jbang.compileWorkDir=.camel-jbang/compile +camel.jbang.health=false +camel.jbang.metrics=false +camel.jbang.console=false +camel.jbang.verbose=false +camel.jbang.camel-version=4.7.0 +camel.jbang.springBootVersion=3.3.1 +camel.jbang.quarkusVersion=3.12.0 +camel.jbang.kameletsVersion=4.7.0 +camel.main.name=kamelet-it-v1 +camel.main.routesIncludePattern=file\:kamelet-it-v1.yaml +dependency=mvn\:org.apache.camel\:camel-kamelet\:4.7.0 +dependency=mvn\:org.apache.camel\:camel-yaml-dsl\:4.7.0 +dependency=mvn\:org.apache.camel\:camel-core-languages\:4.7.0 +dependency=mvn\:org.apache.camel\:camel-rest\:4.7.0 +dependency=mvn\:org.apache.camel\:camel-rest-openapi\:4.7.0 +dependency=mvn\:org.apache.camel\:camel-platform-http\:4.7.0 +camel.jbang.platform-http.port=8080 +kamelet=my-timer-source +dependency=mvn\:org.apache.camel\:camel-timer\:4.7.0 diff --git a/e2e/common/traits/files/kamelet-it-main.yaml b/e2e/common/traits/files/kamelet-it-main.yaml index cbda8d96c5..6dc2d08f2e 100644 --- a/e2e/common/traits/files/kamelet-it-main.yaml +++ b/e2e/common/traits/files/kamelet-it-main.yaml @@ -1,3 +1,22 @@ +# 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: uri: "kamelet:my-timer-source" steps: diff --git a/e2e/common/traits/files/kamelet-it-v1.yaml b/e2e/common/traits/files/kamelet-it-v1.yaml index 8f4a0ab7b3..a9a824ce9a 100644 --- a/e2e/common/traits/files/kamelet-it-v1.yaml +++ b/e2e/common/traits/files/kamelet-it-v1.yaml @@ -1,4 +1,23 @@ +# 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: - uri: "kamelet:my-timer-source?version=v1" + uri: "kamelet:my-timer-source?kameletVersion=v1" steps: - log: "${body}" \ No newline at end of file diff --git a/e2e/common/traits/files/my-timer-source.kamelet.yaml b/e2e/common/traits/files/my-timer-source.kamelet.yaml index f291e1a110..c389d36487 100644 --- a/e2e/common/traits/files/my-timer-source.kamelet.yaml +++ b/e2e/common/traits/files/my-timer-source.kamelet.yaml @@ -1,3 +1,20 @@ +# --------------------------------------------------------------------------- +# 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: diff --git a/helm/camel-k/crds/camel-k-crds.yaml b/helm/camel-k/crds/camel-k-crds.yaml index e6fdf9788c..632193ad51 100644 --- a/helm/camel-k/crds/camel-k-crds.yaml +++ b/helm/camel-k/crds/camel-k-crds.yaml @@ -29845,7 +29845,10 @@ spec: Deprecated: In favor of using DataTypes type: object type: object - description: the optional versions available for this Kamelet. + description: |- + the optional versions available for this Kamelet. This field may not be taken in account by Camel core and is meant to support + any user defined versioning model on cluster only. If the user wants to use any given version, she must materialize a file with the given version spec + as the `main` Kamelet spec on the runtime. type: object type: object status: diff --git a/pkg/apis/camel/v1/kamelet_types.go b/pkg/apis/camel/v1/kamelet_types.go index 7bc28652a4..3a571d4621 100644 --- a/pkg/apis/camel/v1/kamelet_types.go +++ b/pkg/apis/camel/v1/kamelet_types.go @@ -49,6 +49,8 @@ var ( reservedKameletNames = map[string]bool{"source": true, "sink": true} // KameletIDProperty used to identify. KameletIDProperty = "id" + // KameletVersionProperty used to specify the version to use. + KameletVersionProperty = "kameletVersion" ) // +genclient @@ -77,7 +79,9 @@ type Kamelet struct { // KameletSpec specifies the configuration required to execute a Kamelet. type KameletSpec struct { KameletSpecBase `json:",inline"` - // the optional versions available for this Kamelet. + // the optional versions available for this Kamelet. This field may not be taken in account by Camel core and is meant to support + // any user defined versioning model on cluster only. If the user wants to use any given version, she must materialize a file with the given version spec + // as the `main` Kamelet spec on the runtime. Versions map[string]KameletSpecBase `json:"versions,omitempty"` } diff --git a/pkg/resources/config/crd/bases/camel.apache.org_kamelets.yaml b/pkg/resources/config/crd/bases/camel.apache.org_kamelets.yaml index 0318ec8910..777b9e0e11 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_kamelets.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_kamelets.yaml @@ -1242,7 +1242,10 @@ spec: Deprecated: In favor of using DataTypes type: object type: object - description: the optional versions available for this Kamelet. + description: |- + the optional versions available for this Kamelet. This field may not be taken in account by Camel core and is meant to support + any user defined versioning model on cluster only. If the user wants to use any given version, she must materialize a file with the given version spec + as the `main` Kamelet spec on the runtime. type: object type: object status: diff --git a/pkg/trait/kamelets.go b/pkg/trait/kamelets.go index 01a563034f..8e861c5d33 100644 --- a/pkg/trait/kamelets.go +++ b/pkg/trait/kamelets.go @@ -53,6 +53,8 @@ const ( kameletMountPointAnnotation = "camel.apache.org/kamelet.mount-point" ) +var kameletVersionProperty = fmt.Sprintf("?%s=", v1.KameletVersionProperty) + type kameletsTrait struct { BaseTrait traitv1.KameletsTrait `property:",squash"` @@ -181,8 +183,8 @@ func (t *kameletsTrait) addKamelets(e *Environment) error { return err } kb := newKameletBundle() - for version, kamelet := range kamelets { - if err := t.addKameletAsSource(e, kamelet, version); err != nil { + for _, kamelet := range kamelets { + if err := t.addKameletAsSource(e, kamelet); err != nil { return err } // Adding dependencies from Kamelets @@ -218,7 +220,7 @@ func (t *kameletsTrait) addKamelets(e *Environment) error { // This func will add a Kamelet as a generated Integration source. The source included here is going to be used in order to parse the Kamelet // for any component or capability (ie, rest) which is included in the Kamelet spec itself. However, the generated source is marked as coming `FromKamelet`. // When mounting the sources, these generated sources won't be mounted as sources but as Kamelet instead. -func (t *kameletsTrait) addKameletAsSource(e *Environment, kamelet *v1.Kamelet, version string) error { +func (t *kameletsTrait) addKameletAsSource(e *Environment, kamelet *v1.Kamelet) error { sources := make([]v1.SourceSpec, 0) if kamelet.Spec.Template != nil { @@ -281,8 +283,8 @@ func getKameletKey(item string, withVersion bool) string { if strings.Contains(i, "/") { i = strings.SplitN(i, "/", 2)[0] } - if strings.Contains(i, "?version=") { - versionedKamelet := strings.SplitN(i, "?version=", 2) + if strings.Contains(i, kameletVersionProperty) { + versionedKamelet := strings.SplitN(i, kameletVersionProperty, 2) if withVersion { i = fmt.Sprintf("%s-%s", versionedKamelet[0], versionedKamelet[1]) } else { @@ -293,8 +295,8 @@ func getKameletKey(item string, withVersion bool) string { } func getKameletVersion(item string) string { - if strings.Contains(item, "?version=") { - versionedKamelet := strings.SplitN(item, "?version=", 2) + if strings.Contains(item, fmt.Sprintf("?%s=", v1.KameletVersionProperty)) { + versionedKamelet := strings.SplitN(item, kameletVersionProperty, 2) return versionedKamelet[1] } return "" diff --git a/pkg/trait/kamelets_test.go b/pkg/trait/kamelets_test.go index b8937aa6d5..161ac3714f 100644 --- a/pkg/trait/kamelets_test.go +++ b/pkg/trait/kamelets_test.go @@ -54,7 +54,7 @@ func TestConfigurationWithKamelets(t *testing.T) { uri: kamelet:c1 steps: - to: kamelet:c2 - - to: kamelet:c3?version=v1 + - to: kamelet:c3?kameletVersion=v1 - to: telegram:bots - to: kamelet://c0?prop=x - to: kamelet://complex-.-.-1a?prop=x&prop2 @@ -222,7 +222,7 @@ func TestNonYAMLKameletLookup(t *testing.T) { func TestMultipleKamelets(t *testing.T) { trait, environment := createKameletsTestEnvironment(` - from: - uri: kamelet:timer?version=v1 + uri: kamelet:timer?kameletVersion=v1 steps: - to: kamelet:logger `, &v1.Kamelet{ @@ -310,7 +310,7 @@ func TestMultipleKamelets(t *testing.T) { require.NoError(t, err) assert.True(t, enabled) assert.Nil(t, condition) - assert.Equal(t, "logger,timer?version=v1", trait.List) + 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)) diff --git a/pkg/util/bindings/bindings_test.go b/pkg/util/bindings/bindings_test.go index f1e06b7a04..eeff83b805 100644 --- a/pkg/util/bindings/bindings_test.go +++ b/pkg/util/bindings/bindings_test.go @@ -163,6 +163,24 @@ func TestBindings(t *testing.T) { "camel.kamelet.mykamelet.sink.mymessage": "myval", }, }, + { + endpointType: v1.EndpointTypeSink, + endpoint: v1.Endpoint{ + Ref: &corev1.ObjectReference{ + Kind: "Kamelet", + APIVersion: "camel.apache.org/v1any1", + Name: "mykamelet", + }, + Properties: asEndpointProperties(map[string]string{ + "mymessage": "myval", + "kameletVersion": "v1", + }), + }, + uri: "kamelet:mykamelet/sink?kameletVersion=v1", + props: map[string]string{ + "camel.kamelet.mykamelet.sink.mymessage": "myval", + }, + }, { endpoint: v1.Endpoint{ Ref: &corev1.ObjectReference{ diff --git a/pkg/util/bindings/kamelet.go b/pkg/util/bindings/kamelet.go index 300207bad9..d1001c87ed 100644 --- a/pkg/util/bindings/kamelet.go +++ b/pkg/util/bindings/kamelet.go @@ -41,8 +41,6 @@ func (k BindingConverter) ID() string { } // Translate --. -// -//nolint:dupl func (k BindingConverter) Translate(ctx BindingContext, endpointCtx EndpointContext, e v1.Endpoint) (*Binding, error) { if e.Ref == nil { // works only on refs @@ -71,6 +69,12 @@ func (k BindingConverter) Translate(ctx BindingContext, endpointCtx EndpointCont } else { id = endpointCtx.GenerateID() } + version, versionPresent := props[v1.KameletVersionProperty] + if versionPresent { + delete(props, v1.KameletVersionProperty) + } + + kameletTranslated := getKameletName(kameletName, id, version) binding := Binding{} binding.ApplicationProperties = make(map[string]string) @@ -97,7 +101,7 @@ func (k BindingConverter) Translate(ctx BindingContext, endpointCtx EndpointCont steps = append(steps, map[string]interface{}{ "kamelet": map[string]interface{}{ - "name": fmt.Sprintf("%s/%s", kameletName, url.PathEscape(id)), + "name": kameletTranslated, }, }) @@ -126,7 +130,7 @@ func (k BindingConverter) Translate(ctx BindingContext, endpointCtx EndpointCont } } - binding.URI = fmt.Sprintf("kamelet:%s/%s", kameletName, url.PathEscape(id)) + binding.URI = fmt.Sprintf("kamelet:%s", kameletTranslated) case v1.EndpointTypeSink: if in, applicationProperties := k.DataTypeStep(e, id, v1.TypeSlotIn, dataTypeActionKamelet); in != nil { binding.Step = in @@ -135,14 +139,22 @@ func (k BindingConverter) Translate(ctx BindingContext, endpointCtx EndpointCont } } - binding.URI = fmt.Sprintf("kamelet:%s/%s", kameletName, url.PathEscape(id)) + binding.URI = fmt.Sprintf("kamelet:%s", kameletTranslated) default: - binding.URI = fmt.Sprintf("kamelet:%s/%s", kameletName, url.PathEscape(id)) + binding.URI = fmt.Sprintf("kamelet:%s", kameletTranslated) } return &binding, nil } +func getKameletName(name, id, version string) string { + kamelet := fmt.Sprintf("%s/%s", name, url.PathEscape(id)) + if version != "" { + kamelet = fmt.Sprintf("%s?%s=%s", kamelet, v1.KameletVersionProperty, version) + } + return kamelet +} + // DataTypeStep --. func (k BindingConverter) DataTypeStep(e v1.Endpoint, id string, typeSlot v1.TypeSlot, dataTypeActionKamelet string) (map[string]interface{}, map[string]string) { if e.DataTypes == nil { @@ -193,8 +205,6 @@ func (k V1alpha1BindingConverter) ID() string { // Translate -- . // Deprecated. -// -//nolint:dupl func (k V1alpha1BindingConverter) Translate(ctx V1alpha1BindingContext, endpointCtx V1alpha1EndpointContext, e v1alpha1.Endpoint) (*Binding, error) { if e.Ref == nil { // works only on refs diff --git a/pkg/util/source/kamelet.go b/pkg/util/source/kamelet.go index 50a0729395..d44f542a2c 100644 --- a/pkg/util/source/kamelet.go +++ b/pkg/util/source/kamelet.go @@ -20,10 +20,12 @@ package source import ( "fmt" "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("version=([a-z0-9-.]+)") +var kameletVersionRegexp = regexp.MustCompile(v1.KameletVersionProperty + "=([a-z0-9-.]+)") func ExtractKamelets(uris []string) []string { var kamelets []string diff --git a/pkg/util/source/kamelet_test.go b/pkg/util/source/kamelet_test.go index ad9beace0a..173183c2d3 100644 --- a/pkg/util/source/kamelet_test.go +++ b/pkg/util/source/kamelet_test.go @@ -29,6 +29,6 @@ func TestExtractKamelets(t *testing.T) { assert.Equal(t, "my-test", ExtractKamelet("kamelet:my-test?")) assert.Equal(t, "my-test", ExtractKamelet("kamelet:my-test?option=1")) assert.Equal(t, "my-test", ExtractKamelet("kamelet:my-test?option=1&opt2=2")) - assert.Equal(t, "my-test?version=v1", ExtractKamelet("kamelet:my-test?option=1&opt2=2&version=v1")) - assert.Equal(t, "my-test?version=v1", ExtractKamelet("kamelet:my-test?version=v1")) + assert.Equal(t, "my-test?kameletVersion=v1", ExtractKamelet("kamelet:my-test?option=1&opt2=2&kameletVersion=v1")) + assert.Equal(t, "my-test?kameletVersion=v1", ExtractKamelet("kamelet:my-test?kameletVersion=v1")) }