From 2ecb2b2059fbdebd7be7fffc78f16302a6b22938 Mon Sep 17 00:00:00 2001 From: Pasquale Congiusti Date: Wed, 18 Sep 2024 08:18:43 +0200 Subject: [PATCH 1/2] feat(api): store executed traits in status --- addons/master/master.go | 82 +- addons/master/master_test.go | 78 + addons/resume/resume.go | 50 +- .../ROOT/partials/apis/camel-k-crds.adoc | 8 + docs/modules/traits/pages/resume.adoc | 1 + helm/camel-k/crds/camel-k-crds.yaml | 1693 +++++++++++++++++ pkg/apis/camel/v1/integration_types.go | 2 + pkg/apis/camel/v1/zz_generated.deepcopy.go | 5 + .../camel/v1/integrationstatus.go | 9 + .../bases/camel.apache.org_integrations.yaml | 1693 +++++++++++++++++ pkg/trait/affinity.go | 4 - pkg/trait/builder_test.go | 9 +- pkg/trait/camel.go | 10 +- pkg/trait/container.go | 147 +- pkg/trait/container_probes_test.go | 12 +- pkg/trait/container_test.go | 34 +- pkg/trait/cron.go | 29 +- pkg/trait/cron_test.go | 87 +- pkg/trait/environment.go | 3 - pkg/trait/environment_test.go | 15 +- pkg/trait/health.go | 35 +- pkg/trait/health_test.go | 4 +- pkg/trait/ingress.go | 34 +- pkg/trait/ingress_test.go | 2 +- pkg/trait/istio.go | 17 +- pkg/trait/istio_test.go | 12 +- pkg/trait/jolokia.go | 15 +- pkg/trait/jolokia_test.go | 5 +- pkg/trait/jvm.go | 4 +- pkg/trait/kamelets.go | 15 +- pkg/trait/kamelets_test.go | 53 + pkg/trait/knative.go | 72 +- pkg/trait/knative_service.go | 28 +- pkg/trait/knative_service_test.go | 86 +- pkg/trait/knative_test.go | 83 +- pkg/trait/logging.go | 23 +- pkg/trait/logging_test.go | 9 +- pkg/trait/mount_test.go | 12 +- pkg/trait/platform.go | 29 +- pkg/trait/prometheus.go | 5 +- pkg/trait/route.go | 3 - pkg/trait/route_test.go | 36 +- pkg/trait/security_context.go | 28 +- pkg/trait/security_context_test.go | 15 +- pkg/trait/service.go | 3 +- pkg/trait/service_test.go | 82 +- pkg/trait/trait.go | 16 +- pkg/trait/trait_catalog.go | 65 +- pkg/trait/trait_catalog_test.go | 89 + pkg/trait/trait_test.go | 204 +- pkg/trait/trait_types.go | 7 +- pkg/trait/trait_types_test.go | 6 +- pkg/trait/util.go | 4 - 53 files changed, 4691 insertions(+), 381 deletions(-) create mode 100644 pkg/trait/trait_catalog_test.go diff --git a/addons/master/master.go b/addons/master/master.go index f893e19bc3..242c7ee40b 100644 --- a/addons/master/master.go +++ b/addons/master/master.go @@ -66,7 +66,7 @@ type Trait struct { type masterTrait struct { trait.BaseTrait Trait `property:",squash"` - delegateDependencies []string `json:"-"` + delegateDependencies []string } // NewMasterTrait --. @@ -105,7 +105,7 @@ func (t *masterTrait) Configure(e *trait.Environment) (bool, *trait.TraitConditi } } if found { - if t.IncludeDelegateDependencies == nil || *t.IncludeDelegateDependencies { + if ptr.Deref(t.IncludeDelegateDependencies, true) { t.delegateDependencies = findAdditionalDependencies(e, meta) } } @@ -117,28 +117,17 @@ func (t *masterTrait) Configure(e *trait.Environment) (bool, *trait.TraitConditi return false, nil, err } if enabled { + t.Enabled = ptr.To(enabled) if t.ResourceName == nil { val := e.Integration.Name + "-lock" t.ResourceName = &val } - - if t.ResourceType == nil { - t.ResourceType = ptr.To(leaseResourceType) - } - - if t.LabelKey == nil { - val := v1.IntegrationLabel - t.LabelKey = &val - } - if t.LabelValue == nil { t.LabelValue = &e.Integration.Name } - - return true, nil, nil } - return false, nil, nil + return enabled, nil, nil } func (t *masterTrait) Apply(e *trait.Environment) error { @@ -172,49 +161,50 @@ func (t *masterTrait) setCustomizerConfiguration(e *trait.Environment) { e.Integration.Status.Configuration = append(e.Integration.Status.Configuration, v1.ConfigurationSpec{Type: "property", Value: "customizer.master.enabled=true"}, ) - if t.ResourceName != nil { - resourceName := t.ResourceName - e.Integration.Status.Configuration = append(e.Integration.Status.Configuration, - v1.ConfigurationSpec{Type: "property", Value: "customizer.master.kubernetesResourceNames=" + *resourceName}, - ) - } - if t.ResourceType != nil { - e.Integration.Status.Configuration = append(e.Integration.Status.Configuration, - v1.ConfigurationSpec{Type: "property", Value: "customizer.master.leaseResourceType" + *t.ResourceType}, - ) - } - if t.LabelKey != nil { - e.Integration.Status.Configuration = append(e.Integration.Status.Configuration, - v1.ConfigurationSpec{Type: "property", Value: "customizer.master.labelKey=" + *t.LabelKey}, - ) - } - if t.LabelValue != nil { - e.Integration.Status.Configuration = append(e.Integration.Status.Configuration, - v1.ConfigurationSpec{Type: "property", Value: "customizer.master.labelValue=" + *t.LabelValue}, - ) - } + e.Integration.Status.Configuration = append(e.Integration.Status.Configuration, + v1.ConfigurationSpec{Type: "property", Value: "customizer.master.kubernetesResourceNames=" + *t.ResourceName}, + ) + e.Integration.Status.Configuration = append(e.Integration.Status.Configuration, + v1.ConfigurationSpec{Type: "property", Value: "customizer.master.leaseResourceType" + t.getResourceKey()}, + ) + e.Integration.Status.Configuration = append(e.Integration.Status.Configuration, + v1.ConfigurationSpec{Type: "property", Value: "customizer.master.labelKey=" + t.getLabelKey()}, + ) + e.Integration.Status.Configuration = append(e.Integration.Status.Configuration, + v1.ConfigurationSpec{Type: "property", Value: "customizer.master.labelValue=" + *t.LabelValue}, + ) } func (t *masterTrait) setCatalogConfiguration(e *trait.Environment) { if e.ApplicationProperties == nil { e.ApplicationProperties = make(map[string]string) } - if t.ResourceName != nil { - e.ApplicationProperties["camel.k.master.resourceName"] = *t.ResourceName - } - if t.ResourceType != nil { - e.ApplicationProperties["camel.k.master.resourceType"] = *t.ResourceType - } - if t.LabelKey != nil && t.LabelValue != nil { - e.ApplicationProperties["camel.k.master.labelKey"] = *t.LabelKey - e.ApplicationProperties["camel.k.master.labelValue"] = *t.LabelValue - } + e.ApplicationProperties["camel.k.master.resourceName"] = *t.ResourceName + e.ApplicationProperties["camel.k.master.resourceType"] = t.getResourceKey() + e.ApplicationProperties["camel.k.master.labelKey"] = t.getLabelKey() + e.ApplicationProperties["camel.k.master.labelValue"] = *t.LabelValue for _, cp := range e.CamelCatalog.Runtime.Capabilities["master"].RuntimeProperties { e.ApplicationProperties[trait.CapabilityPropertyKey(cp.Key, e.ApplicationProperties)] = cp.Value } } +func (t *masterTrait) getResourceKey() string { + if t.ResourceType == nil { + return leaseResourceType + } + + return *t.ResourceType +} + +func (t *masterTrait) getLabelKey() string { + if t.LabelKey == nil { + return v1.IntegrationLabel + } + + return *t.LabelKey +} + func findAdditionalDependencies(e *trait.Environment, meta metadata.IntegrationMetadata) []string { var dependencies []string for _, endpoint := range meta.FromURIs { diff --git a/addons/master/master_test.go b/addons/master/master_test.go index c80b2abd6d..0754331b8f 100644 --- a/addons/master/master_test.go +++ b/addons/master/master_test.go @@ -21,6 +21,7 @@ import ( "testing" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" "github.com/apache/camel-k/v2/pkg/trait" "github.com/apache/camel-k/v2/pkg/util/camel" "github.com/apache/camel-k/v2/pkg/util/kubernetes" @@ -29,6 +30,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" ) func TestMasterOn(t *testing.T) { @@ -184,3 +186,79 @@ func TestMasterOff(t *testing.T) { assert.Empty(t, conditions) assert.False(t, configured) } + +func TestMasterAuto(t *testing.T) { + catalog, err := camel.DefaultCatalog() + require.NoError(t, err) + + client, err := test.NewFakeClient() + require.NoError(t, err) + traitCatalog := trait.NewCatalog(nil) + + environment := trait.Environment{ + CamelCatalog: catalog, + Catalog: traitCatalog, + Client: client, + Integration: &v1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "ns", + }, + Status: v1.IntegrationStatus{ + Phase: v1.IntegrationPhaseInitialization, + }, + Spec: v1.IntegrationSpec{ + Profile: v1.TraitProfileKnative, + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "Master.java", + Content: `from("master:lock:timer:tick").to("log:test")`, + }, + Language: v1.LanguageJavaSource, + }, + }, + }, + }, + Platform: &v1.IntegrationPlatform{ + Spec: v1.IntegrationPlatformSpec{ + Cluster: v1.IntegrationPlatformClusterOpenShift, + Build: v1.IntegrationPlatformBuildSpec{ + PublishStrategy: v1.IntegrationPlatformBuildPublishStrategyS2I, + Registry: v1.RegistrySpec{Address: "registry"}, + RuntimeVersion: catalog.Runtime.Version, + }, + Profile: v1.TraitProfileKnative, + }, + Status: v1.IntegrationPlatformStatus{ + Phase: v1.IntegrationPlatformPhaseReady, + }, + }, + EnvVars: make([]corev1.EnvVar, 0), + ExecutedTraits: make([]trait.Trait, 0), + Resources: kubernetes.NewCollection(), + } + environment.Platform.ResyncStatusFullConfig() + + mt := NewMasterTrait() + mt.InjectClient(client) + trait, _ := mt.(*masterTrait) + // Initialization phase + configured, conditions, err := trait.Configure(&environment) + require.NoError(t, err) + assert.Empty(t, conditions) + assert.True(t, configured) + err = trait.Apply(&environment) + require.NoError(t, err) + + expectedTrait := &masterTrait{ + Trait: Trait{ + Trait: traitv1.Trait{ + Enabled: ptr.To(true), + }, + ResourceName: ptr.To("test-lock"), + LabelValue: ptr.To("test"), + }, + } + assert.Equal(t, expectedTrait.Trait, trait.Trait) +} diff --git a/addons/resume/resume.go b/addons/resume/resume.go index 45d56756c7..36cb9fdded 100644 --- a/addons/resume/resume.go +++ b/addons/resume/resume.go @@ -20,10 +20,8 @@ package resume import ( v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" - "github.com/apache/camel-k/v2/pkg/metadata" "github.com/apache/camel-k/v2/pkg/trait" "github.com/apache/camel-k/v2/pkg/util" - "github.com/apache/camel-k/v2/pkg/util/log" "k8s.io/utils/ptr" ) @@ -46,6 +44,7 @@ import ( type Trait struct { traitv1.Trait `property:",squash"` // Enables automatic configuration of the trait. + // Deprecated: not in use. Auto *bool `property:"auto" json:"auto,omitempty"` // The type of the resume strategy to use ResumeStrategy string `property:"resume-strategy,omitempty"` @@ -64,8 +63,8 @@ type resumeTrait struct { } const ( - KafkaSingle = "org.apache.camel.processor.resume.kafka.SingleNodeKafkaResumeStrategy" - StrategyPath = "camel-k-offsets" + kafkaSingle = "org.apache.camel.processor.resume.kafka.SingleNodeKafkaResumeStrategy" + strategyPath = "camel-k-offsets" ) func NewResumeTrait() trait.Trait { @@ -82,28 +81,7 @@ func (r *resumeTrait) Configure(environment *trait.Environment) (bool, *trait.Tr return false, nil, nil } - if ptr.Deref(r.Auto, true) { - _, err := environment.ConsumeMeta(func(meta metadata.IntegrationMetadata) bool { - for _, endpoint := range meta.FromURIs { - log.Infof("Processing component %s", endpoint) - } - - return true - }) - if err != nil { - return false, nil, err - } - - if r.ResumeStrategy == "" { - r.ResumeStrategy = KafkaSingle - } - - if r.ResumePath == "" { - r.ResumePath = StrategyPath - } - } - - return r.Enabled != nil && *r.Enabled, nil, nil + return ptr.Deref(r.Enabled, false), nil, nil } func (r *resumeTrait) Apply(environment *trait.Environment) error { @@ -113,11 +91,27 @@ func (r *resumeTrait) Apply(environment *trait.Environment) error { if environment.IntegrationInRunningPhases() { environment.ApplicationProperties["customizer.resume.enabled"] = "true" - environment.ApplicationProperties["customizer.resume.resumeStrategy"] = r.ResumeStrategy - environment.ApplicationProperties["customizer.resume.resumePath"] = r.ResumePath + environment.ApplicationProperties["customizer.resume.resumeStrategy"] = r.getResumeStrategy() + environment.ApplicationProperties["customizer.resume.resumePath"] = r.getResumePath() environment.ApplicationProperties["customizer.resume.resumeServer"] = r.ResumeServer environment.ApplicationProperties["customizer.resume.cacheFillPolicy"] = r.CacheFillPolicy } return nil } + +func (r *resumeTrait) getResumeStrategy() string { + if r.ResumeStrategy == "" { + return kafkaSingle + } + + return r.ResumeStrategy +} + +func (r *resumeTrait) getResumePath() string { + if r.ResumePath == "" { + return strategyPath + } + + return r.ResumePath +} diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc index 77c844a218..a9199401aa 100644 --- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc +++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc @@ -3533,6 +3533,13 @@ a list of dependencies needed by the application the profile needed to run this Integration +|`traits` + +*xref:#_camel_apache_org_v1_Traits[Traits]* +| + + +the traits executed for the Integration + |`integrationKit` + *https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectreference-v1-core[Kubernetes core/v1.ObjectReference]* | @@ -5817,6 +5824,7 @@ TraitConfiguration parameters configuration * <<#_camel_apache_org_v1_IntegrationPlatformSpec, IntegrationPlatformSpec>> * <<#_camel_apache_org_v1_IntegrationProfileSpec, IntegrationProfileSpec>> * <<#_camel_apache_org_v1_IntegrationSpec, IntegrationSpec>> +* <<#_camel_apache_org_v1_IntegrationStatus, IntegrationStatus>> Traits represents the collection of trait configurations. diff --git a/docs/modules/traits/pages/resume.adoc b/docs/modules/traits/pages/resume.adoc index 894fe4d12c..779b9f1af7 100644 --- a/docs/modules/traits/pages/resume.adoc +++ b/docs/modules/traits/pages/resume.adoc @@ -43,6 +43,7 @@ The following configuration options are available: | resume.auto | bool | Enables automatic configuration of the trait. +Deprecated: not in use. | resume.resume-strategy,omitempty | string diff --git a/helm/camel-k/crds/camel-k-crds.yaml b/helm/camel-k/crds/camel-k-crds.yaml index 449de8c308..ee85c65f6b 100644 --- a/helm/camel-k/crds/camel-k-crds.yaml +++ b/helm/camel-k/crds/camel-k-crds.yaml @@ -19892,6 +19892,1699 @@ spec: selector: description: label selector type: string + traits: + description: the traits executed for the Integration + properties: + 3scale: + description: 'Deprecated: for backward compatibility.' + properties: + configuration: + description: TraitConfiguration parameters configuration + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configuration + type: object + addons: + additionalProperties: + description: AddonTrait represents the configuration of an addon + trait. + type: object + x-kubernetes-preserve-unknown-fields: true + description: The extension point with addon traits + type: object + affinity: + description: The configuration of Affinity trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + nodeAffinityLabels: + description: Defines a set of nodes the integration pod(s) + are eligible to be scheduled on, based on labels on the + node. + items: + type: string + type: array + podAffinity: + description: Always co-locates multiple replicas of the integration + in the same node (default `false`). + type: boolean + podAffinityLabels: + description: |- + Defines a set of pods (namely those matching the label selector, relative to the given namespace) that the + integration pod(s) should be co-located with. + items: + type: string + type: array + podAntiAffinity: + description: Never co-locates multiple replicas of the integration + in the same node (default `false`). + type: boolean + podAntiAffinityLabels: + description: |- + Defines a set of pods (namely those matching the label selector, relative to the given namespace) that the + integration pod(s) should not be co-located with. + items: + type: string + type: array + type: object + builder: + description: The configuration of Builder trait + properties: + annotations: + additionalProperties: + type: string + description: When using `pod` strategy, annotation to use + for the builder pod. + type: object + baseImage: + description: |- + Specify a base image. In order to have the application working properly it must be a container image which has a Java JDK + installed and ready to use on path (ie `/usr/bin/java`). + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + incrementalImageBuild: + description: Use the incremental image build option, to reuse + existing containers (default `true`) + type: boolean + limitCPU: + description: |- + When using `pod` strategy, the maximum amount of CPU required by the pod builder. + Deprecated: use TasksRequestCPU instead with task name `builder`. + type: string + limitMemory: + description: |- + When using `pod` strategy, the maximum amount of memory required by the pod builder. + Deprecated: use TasksRequestCPU instead with task name `builder`. + type: string + mavenProfiles: + description: |- + A list of references pointing to configmaps/secrets that contains a maven profile. + This configmap/secret is a resource of the IntegrationKit created, therefore it needs to be present in the namespace where the operator is going to create the IntegrationKit. + The content of the maven profile is expected to be a text containing a valid maven profile starting with `` and ending with `` that will be integrated as an inline profile in the POM. + Syntax: [configmap|secret]:name[/key], where name represents the resource name, key optionally represents the resource key to be filtered (default key value = profile.xml). + items: + type: string + type: array + nodeSelector: + additionalProperties: + type: string + description: Defines a set of nodes the builder pod is eligible + to be scheduled on, based on labels on the node. + type: object + orderStrategy: + description: The build order strategy to use, either `dependencies`, + `fifo` or `sequential` (default is the platform default) + enum: + - dependencies + - fifo + - sequential + type: string + platforms: + description: The list of manifest platforms to use to build + a container image (default `linux/amd64`). + items: + type: string + type: array + properties: + description: A list of properties to be provided to the build + task + items: + type: string + type: array + requestCPU: + description: |- + When using `pod` strategy, the minimum amount of CPU required by the pod builder. + Deprecated: use TasksRequestCPU instead with task name `builder`. + type: string + requestMemory: + description: |- + When using `pod` strategy, the minimum amount of memory required by the pod builder. + Deprecated: use TasksRequestCPU instead with task name `builder`. + type: string + strategy: + description: The strategy to use, either `pod` or `routine` + (default `routine`) + enum: + - pod + - routine + type: string + tasks: + description: A list of tasks to be executed (available only + when using `pod` strategy) with format `;;`. + items: + type: string + type: array + tasksFilter: + description: |- + A list of tasks sorted by the order of execution in a csv format, ie, `,,...`. + Mind that you must include also the operator tasks (`builder`, `quarkus-native`, `package`, `jib`, `s2i`) + if you need to execute them. Useful only with `pod` strategy. + type: string + tasksLimitCPU: + description: A list of limit cpu configuration for the specific + task with format `:`. + items: + type: string + type: array + tasksLimitMemory: + description: A list of limit memory configuration for the + specific task with format `:`. + items: + type: string + type: array + tasksRequestCPU: + description: A list of request cpu configuration for the specific + task with format `:`. + items: + type: string + type: array + tasksRequestMemory: + description: A list of request memory configuration for the + specific task with format `:`. + items: + type: string + type: array + verbose: + description: |- + Enable verbose logging on build components that support it (e.g. Kaniko build pod). + Deprecated no longer in use + type: boolean + type: object + camel: + description: The configuration of Camel trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + properties: + description: A list of properties to be provided to the Integration + runtime + items: + type: string + type: array + runtimeVersion: + description: |- + The camel-k-runtime version to use for the integration. It overrides the default version set in the Integration Platform. + You can use a fixed version (for example "3.2.3") or a semantic version (for example "3.x") which will try to resolve + to the best matching Catalog existing on the cluster. + type: string + type: object + container: + description: The configuration of Container trait + properties: + allowPrivilegeEscalation: + description: Security Context AllowPrivilegeEscalation configuration + (default false). + type: boolean + auto: + description: To automatically enable the trait + type: boolean + capabilitiesAdd: + description: Security Context Capabilities Add configuration + (default none). + items: + description: Capability represent POSIX capabilities type + type: string + type: array + capabilitiesDrop: + description: Security Context Capabilities Drop configuration + (default ALL). + items: + description: Capability represent POSIX capabilities type + type: string + type: array + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + expose: + description: Can be used to enable/disable exposure via kubernetes + Service. + type: boolean + image: + description: |- + The main container image to use for the Integration. When using this parameter the operator will create a synthetic IntegrationKit which + won't be able to execute traits requiring CamelCatalog. If the container image you're using is coming from an IntegrationKit, use instead + Integration `.spec.integrationKit` parameter. If you're moving the Integration across environments, you will also need to create an "external" IntegrationKit. + type: string + imagePullPolicy: + description: 'The pull policy: Always|Never|IfNotPresent' + enum: + - Always + - Never + - IfNotPresent + type: string + limitCPU: + description: The maximum amount of CPU to be provided (default + 500 millicores). + type: string + limitMemory: + description: The maximum amount of memory to be provided (default + 512 Mi). + type: string + name: + description: The main container name. It's named `integration` + by default. + type: string + port: + description: To configure a different port exposed by the + container (default `8080`). + type: integer + portName: + description: To configure a different port name for the port + exposed by the container. It defaults to `http` only when + the `expose` parameter is true. + type: string + requestCPU: + description: The minimum amount of CPU required (default 125 + millicores). + type: string + requestMemory: + description: The minimum amount of memory required (default + 128 Mi). + type: string + runAsNonRoot: + description: Security Context RunAsNonRoot configuration (default + false). + type: boolean + runAsUser: + description: 'Security Context RunAsUser configuration (default + none): this value is automatically retrieved in Openshift + clusters when not explicitly set.' + format: int64 + type: integer + seccompProfileType: + description: Security Context SeccompProfileType configuration + (default RuntimeDefault). + enum: + - Unconfined + - RuntimeDefault + type: string + servicePort: + description: To configure under which service port the container + port is to be exposed (default `80`). + type: integer + servicePortName: + description: To configure under which service port name the + container port is to be exposed (default `http`). + type: string + type: object + cron: + description: The configuration of Cron trait + properties: + activeDeadlineSeconds: + description: |- + Specifies the duration in seconds, relative to the start time, that the job + may be continuously active before it is considered to be failed. + It defaults to 60s. + format: int64 + type: integer + auto: + description: |- + Automatically deploy the integration as CronJob when all routes are + either starting from a periodic consumer (only `cron`, `timer` and `quartz` are supported) or a passive consumer (e.g. `direct` is a passive consumer). + + + It's required that all periodic consumers have the same period, and it can be expressed as cron schedule (e.g. `1m` can be expressed as `0/1 * * * *`, + while `35m` or `50s` cannot). + type: boolean + backoffLimit: + description: |- + Specifies the number of retries before marking the job failed. + It defaults to 2. + format: int32 + type: integer + components: + description: |- + A comma separated list of the Camel components that need to be customized in order for them to work when the schedule is triggered externally by Kubernetes. + A specific customizer is activated for each specified component. E.g. for the `timer` component, the `cron-timer` customizer is + activated (it's present in the `org.apache.camel.k:camel-k-cron` library). + + + Supported components are currently: `cron`, `timer` and `quartz`. + type: string + concurrencyPolicy: + description: |- + Specifies how to treat concurrent executions of a Job. + Valid values are: + - "Allow": allows CronJobs to run concurrently; + - "Forbid" (default): forbids concurrent runs, skipping next run if previous run hasn't finished yet; + - "Replace": cancels currently running job and replaces it with a new one + enum: + - Allow + - Forbid + - Replace + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + fallback: + description: |- + Use the default Camel implementation of the `cron` endpoint (`quartz`) instead of trying to materialize the integration + as Kubernetes CronJob. + type: boolean + schedule: + description: |- + The CronJob schedule for the whole integration. If multiple routes are declared, they must have the same schedule for this + mechanism to work correctly. + type: string + startingDeadlineSeconds: + description: |- + Optional deadline in seconds for starting the job if it misses scheduled + time for any reason. Missed jobs executions will be counted as failed ones. + format: int64 + type: integer + timeZone: + description: The timezone that the CronJob will run on + type: string + type: object + dependencies: + description: The configuration of Dependencies trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + type: object + deployer: + description: The configuration of Deployer trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + kind: + description: Allows to explicitly select the desired deployment + kind between `deployment`, `cron-job` or `knative-service` + when creating the resources for running the integration. + enum: + - deployment + - cron-job + - knative-service + type: string + useSSA: + description: |- + Deprecated: won't be able to enforce client side update in the future. + Use server-side apply to update the owned resources (default `true`). + Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. + type: boolean + type: object + deployment: + description: The configuration of Deployment trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + progressDeadlineSeconds: + description: |- + The maximum time in seconds for the deployment to make progress before it + is considered to be failed. It defaults to `60s`. + format: int32 + type: integer + rollingUpdateMaxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to `25%`. + x-kubernetes-int-or-string: true + rollingUpdateMaxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to `25%`. + x-kubernetes-int-or-string: true + strategy: + description: The deployment strategy to use to replace existing + pods with new ones. + enum: + - Recreate + - RollingUpdate + type: string + type: object + environment: + description: The configuration of Environment trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + containerMeta: + description: Enables injection of `NAMESPACE` and `POD_NAME` + environment variables (default `true`) + type: boolean + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + httpProxy: + description: Propagates the `HTTP_PROXY`, `HTTPS_PROXY` and + `NO_PROXY` environment variables (default `true`) + type: boolean + vars: + description: |- + A list of environment variables to be added to the integration container. + The syntax is either VAR=VALUE or VAR=[configmap|secret]:name/key, where name represents the resource name, + and key represents the resource key to be mapped as and environment variable. + These take precedence over any previously defined environment variables. + items: + type: string + type: array + type: object + error-handler: + description: The configuration of Error Handler trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + ref: + description: The error handler ref name provided or found + in application properties + type: string + type: object + gc: + description: The configuration of GC trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + discoveryCache: + description: |- + Discovery client cache to be used, either `disabled`, `disk` or `memory` (default `memory`). + Deprecated: to be removed from trait configuration. + enum: + - disabled + - disk + - memory + type: string + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + type: object + health: + description: The configuration of Health trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + livenessFailureThreshold: + description: Minimum consecutive failures for the liveness + probe to be considered failed after having succeeded. + format: int32 + type: integer + livenessInitialDelay: + description: Number of seconds after the container has started + before the liveness probe is initiated. + format: int32 + type: integer + livenessPeriod: + description: How often to perform the liveness probe. + format: int32 + type: integer + livenessProbe: + description: The liveness probe path to use (default provided + by the Catalog runtime used). + type: string + livenessProbeEnabled: + description: Configures the liveness probe for the integration + container (default `false`). + type: boolean + livenessScheme: + description: Scheme to use when connecting to the liveness + probe (default `HTTP`). + type: string + livenessSuccessThreshold: + description: Minimum consecutive successes for the liveness + probe to be considered successful after having failed. + format: int32 + type: integer + livenessTimeout: + description: Number of seconds after which the liveness probe + times out. + format: int32 + type: integer + readinessFailureThreshold: + description: Minimum consecutive failures for the readiness + probe to be considered failed after having succeeded. + format: int32 + type: integer + readinessInitialDelay: + description: Number of seconds after the container has started + before the readiness probe is initiated. + format: int32 + type: integer + readinessPeriod: + description: How often to perform the readiness probe. + format: int32 + type: integer + readinessProbe: + description: The readiness probe path to use (default provided + by the Catalog runtime used). + type: string + readinessProbeEnabled: + description: Configures the readiness probe for the integration + container (default `true`). + type: boolean + readinessScheme: + description: Scheme to use when connecting to the readiness + probe (default `HTTP`). + type: string + readinessSuccessThreshold: + description: Minimum consecutive successes for the readiness + probe to be considered successful after having failed. + format: int32 + type: integer + readinessTimeout: + description: Number of seconds after which the readiness probe + times out. + format: int32 + type: integer + startupFailureThreshold: + description: Minimum consecutive failures for the startup + probe to be considered failed after having succeeded. + format: int32 + type: integer + startupInitialDelay: + description: Number of seconds after the container has started + before the startup probe is initiated. + format: int32 + type: integer + startupPeriod: + description: How often to perform the startup probe. + format: int32 + type: integer + startupProbe: + description: The startup probe path to use (default provided + by the Catalog runtime used). + type: string + startupProbeEnabled: + description: Configures the startup probe for the integration + container (default `false`). + type: boolean + startupScheme: + description: Scheme to use when connecting to the startup + probe (default `HTTP`). + type: string + startupSuccessThreshold: + description: Minimum consecutive successes for the startup + probe to be considered successful after having failed. + format: int32 + type: integer + startupTimeout: + description: Number of seconds after which the startup probe + times out. + format: int32 + type: integer + type: object + ingress: + description: The configuration of Ingress trait + properties: + annotations: + additionalProperties: + type: string + description: |- + The annotations added to the ingress. + This can be used to set controller specific annotations, e.g., when using the NGINX Ingress controller: + See https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md + type: object + auto: + description: To automatically add an ingress whenever the + integration uses an HTTP endpoint consumer. + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + host: + description: To configure the host exposed by the ingress. + type: string + ingressClassName: + description: |- + The Ingress class name as defined by the Ingress spec + See https://kubernetes.io/docs/concepts/services-networking/ingress/ + type: string + path: + description: To configure the path exposed by the ingress + (default `/`). + type: string + pathType: + description: |- + To configure the path type exposed by the ingress. + One of `Exact`, `Prefix`, `ImplementationSpecific` (default to `Prefix`). + enum: + - Exact + - Prefix + - ImplementationSpecific + type: string + tlsHosts: + description: To configure tls hosts + items: + type: string + type: array + tlsSecretName: + description: To configure tls secret name + type: string + type: object + istio: + description: The configuration of Istio trait + properties: + allow: + description: Configures a (comma-separated) list of CIDR subnets + that should not be intercepted by the Istio proxy (`10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` + by default). + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + inject: + description: Forces the value for labels `sidecar.istio.io/inject`. + By default the label is set to `true` on deployment and + not set on Knative Service. + type: boolean + type: object + jolokia: + description: The configuration of Jolokia trait + properties: + CACert: + description: |- + The PEM encoded CA certification file path, used to verify client certificates, + applicable when `protocol` is `https` and `use-ssl-client-authentication` is `true` + (default `/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt` for OpenShift). + type: string + clientPrincipal: + description: |- + The principal(s) which must be given in a client certificate to allow access to the Jolokia endpoint, + applicable when `protocol` is `https` and `use-ssl-client-authentication` is `true` + (default `clientPrincipal=cn=system:master-proxy`, `cn=hawtio-online.hawtio.svc` and `cn=fuse-console.fuse.svc` for OpenShift). + items: + type: string + type: array + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + discoveryEnabled: + description: Listen for multicast requests (default `false`) + type: boolean + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + extendedClientCheck: + description: |- + Mandate the client certificate contains a client flag in the extended key usage section, + applicable when `protocol` is `https` and `use-ssl-client-authentication` is `true` + (default `true` for OpenShift). + type: boolean + host: + description: |- + The Host address to which the Jolokia agent should bind to. If `"\*"` or `"0.0.0.0"` is given, + the servers binds to every network interface (default `"*"`). + type: string + options: + description: |- + A list of additional Jolokia options as defined + in https://jolokia.org/reference/html/agents.html#agent-jvm-config[JVM agent configuration options] + items: + type: string + type: array + password: + description: The password used for authentication, applicable + when the `user` option is set. + type: string + port: + description: The Jolokia endpoint port (default `8778`). + type: integer + protocol: + description: The protocol to use, either `http` or `https` + (default `https` for OpenShift) + type: string + useSSLClientAuthentication: + description: Whether client certificates should be used for + authentication (default `true` for OpenShift). + type: boolean + user: + description: The user to be used for authentication + type: string + type: object + jvm: + description: The configuration of JVM trait + properties: + classpath: + description: Additional JVM classpath (use `Linux` classpath + separator) + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + debug: + description: Activates remote debugging, so that a debugger + can be attached to the JVM, e.g., using port-forwarding + type: boolean + debugAddress: + description: Transport address at which to listen for the + newly launched JVM (default `*:5005`) + type: string + debugSuspend: + description: Suspends the target JVM immediately before the + main class is loaded + type: boolean + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + jar: + description: The Jar dependency which will run the application. + Leave it empty for managed Integrations. + type: string + options: + description: A list of JVM options + items: + type: string + type: array + printCommand: + description: |- + Prints the command used the start the JVM in the container logs (default `true`) + Deprecated: no longer in use. + type: boolean + type: object + kamelets: + description: The configuration of Kamelets trait + properties: + auto: + description: Automatically inject all referenced Kamelets + and their default configuration (enabled by default) + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + list: + description: Comma separated list of Kamelet names to load + into the current integration + type: string + mountPoint: + description: The directory where the application mounts and + reads Kamelet spec (default `/etc/camel/kamelets`) + type: string + type: object + keda: + description: 'Deprecated: for backward compatibility.' + properties: + configuration: + description: TraitConfiguration parameters configuration + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configuration + type: object + knative: + description: The configuration of Knative trait + properties: + auto: + description: Enable automatic discovery of all trait properties. + type: boolean + channelSinks: + description: |- + List of channels used as destination of integration routes. + Can contain simple channel names or full Camel URIs. + items: + type: string + type: array + channelSources: + description: |- + List of channels used as source of integration routes. + Can contain simple channel names or full Camel URIs. + items: + type: string + type: array + config: + description: Can be used to inject a Knative complete configuration + in JSON format. + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + endpointSinks: + description: |- + List of endpoints used as destination of integration routes. + Can contain simple endpoint names or full Camel URIs. + items: + type: string + type: array + endpointSources: + description: List of channels used as source of integration + routes. + items: + type: string + type: array + eventSinks: + description: |- + List of event types that the integration will produce. + Can contain simple event types or full Camel URIs (to use a specific broker). + items: + type: string + type: array + eventSources: + description: |- + List of event types that the integration will be subscribed to. + Can contain simple event types or full Camel URIs (to use a specific broker different from "default"). + items: + type: string + type: array + filterEventType: + description: |- + Enables the default filtering for the Knative trigger using the event type + If this is true, the created Knative trigger uses the event type as a filter on the event stream when no other filter criteria is given. (default: true) + type: boolean + filterSourceChannels: + description: |- + Enables filtering on events based on the header "ce-knativehistory". Since this header has been removed in newer versions of + Knative, filtering is disabled by default. + type: boolean + filters: + description: |- + Sets filter attributes on the event stream (such as event type, source, subject and so on). + A list of key-value pairs that represent filter attributes and its values. + The syntax is KEY=VALUE, e.g., `source="my.source"`. + Filter attributes get set on the Knative trigger that is being created as part of this integration. + items: + type: string + type: array + namespaceLabel: + description: |- + Enables the camel-k-operator to set the "bindings.knative.dev/include=true" label to the namespace + As Knative requires this label to perform injection of K_SINK URL into the service. + If this is false, the integration pod may start and fail, read the SinkBinding Knative documentation. (default: true) + type: boolean + sinkBinding: + description: |- + Allows binding the integration to a sink via a Knative SinkBinding resource. + This can be used when the integration targets a single sink. + It's enabled by default when the integration targets a single sink + (except when the integration is owned by a Knative source). + type: boolean + type: object + knative-service: + description: The configuration of Knative Service trait + properties: + annotations: + additionalProperties: + type: string + description: |- + The annotations added to route. + This can be used to set knative service specific annotations + CLI usage example: -t "knative-service.annotations.'haproxy.router.openshift.io/balance'=true" + type: object + auto: + description: |- + Automatically deploy the integration as Knative service when all conditions hold: + + + * Integration is using the Knative profile + * All routes are either starting from an HTTP based consumer or a passive consumer (e.g. `direct` is a passive consumer) + type: boolean + autoscalingMetric: + description: |- + Configures the Knative autoscaling metric property (e.g. to set `concurrency` based or `cpu` based autoscaling). + + + Refer to the Knative documentation for more information. + type: string + autoscalingTarget: + description: |- + Sets the allowed concurrency level or CPU percentage (depending on the autoscaling metric) for each Pod. + + + Refer to the Knative documentation for more information. + type: integer + class: + description: |- + Configures the Knative autoscaling class property (e.g. to set `hpa.autoscaling.knative.dev` or `kpa.autoscaling.knative.dev` autoscaling). + + + Refer to the Knative documentation for more information. + enum: + - kpa.autoscaling.knative.dev + - hpa.autoscaling.knative.dev + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + maxScale: + description: |- + An upper bound for the number of Pods that can be running in parallel for the integration. + Knative has its own cap value that depends on the installation. + + + Refer to the Knative documentation for more information. + type: integer + minScale: + description: |- + The minimum number of Pods that should be running at any time for the integration. It's **zero** by default, meaning that + the integration is scaled down to zero when not used for a configured amount of time. + + + Refer to the Knative documentation for more information. + type: integer + rolloutDuration: + description: |- + Enables to gradually shift traffic to the latest Revision and sets the rollout duration. + It's disabled by default and must be expressed as a Golang `time.Duration` string representation, + rounded to a second precision. + type: string + timeoutSeconds: + description: |- + The maximum duration in seconds that the request instance is allowed to respond to a request. + This field propagates to the integration pod's terminationGracePeriodSeconds + + + Refer to the Knative documentation for more information. + format: int64 + type: integer + visibility: + description: |- + Setting `cluster-local`, Knative service becomes a private service. + Specifically, this option applies the `networking.knative.dev/visibility` label to Knative service. + + + Refer to the Knative documentation for more information. + enum: + - cluster-local + type: string + type: object + logging: + description: The configuration of Logging trait + properties: + color: + description: Colorize the log output + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + format: + description: Logs message format + type: string + json: + description: Output the logs in JSON + type: boolean + jsonPrettyPrint: + description: Enable "pretty printing" of the JSON logs + type: boolean + level: + description: Adjust the logging level (defaults to `INFO`) + enum: + - FATAL + - WARN + - INFO + - DEBUG + - TRACE + type: string + type: object + master: + description: 'Deprecated: for backward compatibility.' + properties: + configuration: + description: TraitConfiguration parameters configuration + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configuration + type: object + mount: + description: The configuration of Mount trait + properties: + configs: + description: |- + A list of configuration pointing to configmap/secret. + The configuration are expected to be UTF-8 resources as they are processed by runtime Camel Context and tried to be parsed as property files. + They are also made available on the classpath in order to ease their usage directly from the Route. + Syntax: [configmap|secret]:name[/key], where name represents the resource name and key optionally represents the resource key to be filtered + items: + type: string + type: array + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + emptyDirs: + description: 'A list of EmptyDir volumes to be mounted. Syntax: + [name:/container/path]' + items: + type: string + type: array + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + hotReload: + description: |- + Enable "hot reload" when a secret/configmap mounted is edited (default `false`). The configmap/secret must be + marked with `camel.apache.org/integration` label to be taken in account. The resource will be watched for any kind change, also for + changes in metadata. + type: boolean + resources: + description: |- + A list of resources (text or binary content) pointing to configmap/secret. + The resources are expected to be any resource type (text or binary content). + The destination path can be either a default location or any path specified by the user. + Syntax: [configmap|secret]:name[/key][@path], where name represents the resource name, key optionally represents the resource key to be filtered and path represents the destination path + items: + type: string + type: array + scanKameletsImplicitLabelSecrets: + description: 'Deprecated: no longer available since version + 2.5.' + type: boolean + volumes: + description: 'A list of Persistent Volume Claims to be mounted. + Syntax: [pvcname:/container/path]' + items: + type: string + type: array + type: object + openapi: + description: The configuration of OpenAPI trait + properties: + configmaps: + description: The configmaps holding the spec of the OpenAPI + (compatible with > 3.0 spec only). + items: + type: string + type: array + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + type: object + owner: + description: The configuration of Owner trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + targetAnnotations: + description: The set of annotations to be transferred + items: + type: string + type: array + targetLabels: + description: The set of labels to be transferred + items: + type: string + type: array + type: object + pdb: + description: The configuration of PDB trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + maxUnavailable: + description: |- + The number of pods for the Integration that can be unavailable after an eviction. + It can be either an absolute number or a percentage (default `1` if `min-available` is also not set). + Only one of `max-unavailable` and `min-available` can be specified. + type: string + minAvailable: + description: |- + The number of pods for the Integration that must still be available after an eviction. + It can be either an absolute number or a percentage. + Only one of `min-available` and `max-unavailable` can be specified. + type: string + type: object + platform: + description: The configuration of Platform trait + properties: + auto: + description: |- + To automatically detect from the environment if a default platform can be created (it will be created on OpenShift or when a registry address is set). + Deprecated: Platform is auto generated by the operator install procedure - maintained for backward compatibility + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + createDefault: + description: |- + To create a default (empty) platform when the platform is missing. + Deprecated: Platform is auto generated by the operator install procedure - maintained for backward compatibility + type: boolean + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + global: + description: |- + Indicates if the platform should be created globally in the case of global operator (default true). + Deprecated: Platform is auto generated by the operator install procedure - maintained for backward compatibility + type: boolean + type: object + pod: + description: The configuration of Pod trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + type: object + prometheus: + description: The configuration of Prometheus trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + podMonitor: + description: Whether a `PodMonitor` resource is created (default + `true`). + type: boolean + podMonitorLabels: + description: The `PodMonitor` resource labels, applicable + when `pod-monitor` is `true`. + items: + type: string + type: array + type: object + pull-secret: + description: The configuration of Pull Secret trait + properties: + auto: + description: Automatically configures the platform registry + secret on the pod if it is of type `kubernetes.io/dockerconfigjson`. + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + imagePullerDelegation: + description: When using a global operator with a shared platform, + this enables delegation of the `system:image-puller` cluster + role on the operator namespace to the integration service + account. + type: boolean + secretName: + description: The pull secret name to set on the Pod. If left + empty this is automatically taken from the `IntegrationPlatform` + registry configuration. + type: string + type: object + quarkus: + description: The configuration of Quarkus trait + properties: + buildMode: + description: |- + The Quarkus mode to run: either `jvm` or `native` (default `jvm`). + In case both `jvm` and `native` are specified, two `IntegrationKit` resources are created, + with the `native` kit having precedence over the `jvm` one once ready. + items: + description: QuarkusMode is the type of Quarkus build packaging. + enum: + - jvm + - native + type: string + type: array + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + nativeBaseImage: + description: The base image to use when running a native build + (default `quay.io/quarkus/quarkus-micro-image:2.0`) + type: string + nativeBuilderImage: + description: The image containing the tooling required for + a native build (by default it will use the one provided + in the runtime catalog) + type: string + packageTypes: + description: |- + The Quarkus package types, `fast-jar` or `native` (default `fast-jar`). + In case both `fast-jar` and `native` are specified, two `IntegrationKit` resources are created, + with the native kit having precedence over the `fast-jar` one once ready. + The order influences the resolution of the current kit for the integration. + The kit corresponding to the first package type will be assigned to the + integration in case no existing kit that matches the integration exists. + Deprecated: use `build-mode` instead. + items: + description: |- + QuarkusPackageType is the type of Quarkus build packaging. + Deprecated: use `QuarkusMode` instead. + enum: + - fast-jar + - native + type: string + type: array + type: object + registry: + description: |- + The configuration of Registry trait (support removed since version 2.5.0). + Deprecated: use jvm trait or read documentation. + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + type: object + route: + description: The configuration of Route trait + properties: + annotations: + additionalProperties: + type: string + description: |- + The annotations added to route. + This can be used to set route specific annotations + For annotations options see https://docs.openshift.com/container-platform/3.11/architecture/networking/routes.html#route-specific-annotations + CLI usage example: -t "route.annotations.'haproxy.router.openshift.io/balance'=true" + type: object + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + host: + description: To configure the host exposed by the route. + type: string + tlsCACertificate: + description: |- + The TLS CA certificate contents. + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsCACertificateSecret: + description: |- + The secret name and key reference to the TLS CA certificate. The format is "secret-name[/key-name]", the value represents the secret name, if there is only one key in the secret it will be read, otherwise you can set a key name separated with a "/". + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsCertificate: + description: |- + The TLS certificate contents. + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsCertificateSecret: + description: |- + The secret name and key reference to the TLS certificate. The format is "secret-name[/key-name]", the value represents the secret name, if there is only one key in the secret it will be read, otherwise you can set a key name separated with a "/". + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsDestinationCACertificate: + description: |- + The destination CA certificate provides the contents of the ca certificate of the final destination. When using reencrypt + termination this file should be provided in order to have routers use it for health checks on the secure connection. + If this field is not specified, the router may provide its own destination CA and perform hostname validation using + the short service name (service.namespace.svc), which allows infrastructure generated certificates to automatically + verify. + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsDestinationCACertificateSecret: + description: |- + The secret name and key reference to the destination CA certificate. The format is "secret-name[/key-name]", the value represents the secret name, if there is only one key in the secret it will be read, otherwise you can set a key name separated with a "/". + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsInsecureEdgeTerminationPolicy: + description: |- + To configure how to deal with insecure traffic, e.g. `Allow`, `Disable` or `Redirect` traffic. + + + Refer to the OpenShift route documentation for additional information. + enum: + - None + - Allow + - Redirect + type: string + tlsKey: + description: |- + The TLS certificate key contents. + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsKeySecret: + description: |- + The secret name and key reference to the TLS certificate key. The format is "secret-name[/key-name]", the value represents the secret name, if there is only one key in the secret it will be read, otherwise you can set a key name separated with a "/". + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsTermination: + description: |- + The TLS termination type, like `edge`, `passthrough` or `reencrypt`. + + + Refer to the OpenShift route documentation for additional information. + enum: + - edge + - reencrypt + - passthrough + type: string + type: object + security-context: + description: The configuration of Security Context trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + runAsNonRoot: + description: Security Context RunAsNonRoot configuration (default + false). + type: boolean + runAsUser: + description: 'Security Context RunAsUser configuration (default + none): this value is automatically retrieved in Openshift + clusters when not explicitly set.' + format: int64 + type: integer + seccompProfileType: + description: Security Context SeccompProfileType configuration + (default RuntimeDefault). + enum: + - Unconfined + - RuntimeDefault + type: string + type: object + service: + description: The configuration of Service trait + properties: + auto: + description: To automatically detect from the code if a Service + needs to be created. + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + nodePort: + description: |- + Enable Service to be exposed as NodePort (default `false`). + Deprecated: Use service type instead. + type: boolean + type: + description: The type of service to be used, either 'ClusterIP', + 'NodePort' or 'LoadBalancer'. + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + type: object + service-binding: + description: The configuration of Service Binding trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + services: + description: List of Services in the form [[apigroup/]version:]kind:[namespace/]name + items: + type: string + type: array + type: object + strimzi: + description: 'Deprecated: for backward compatibility.' + properties: + configuration: + description: TraitConfiguration parameters configuration + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configuration + type: object + telemetry: + description: The configuration of Telemetry trait + properties: + auto: + description: Enables automatic configuration of the trait, + including automatic discovery of the telemetry endpoint. + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + endpoint: + description: The target endpoint of the Telemetry service + (automatically discovered by default) + type: string + sampler: + description: The sampler of the telemetry used for tracing + (default "on") + type: string + sampler-parent-based: + description: The sampler of the telemetry used for tracing + is parent based (default "true") + type: boolean + sampler-ratio: + description: The sampler ratio of the telemetry used for tracing + type: string + serviceName: + description: The name of the service that publishes telemetry + data (defaults to the integration name) + type: string + type: object + toleration: + description: The configuration of Toleration trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + taints: + description: The list of taints to tolerate, in the form `Key[=Value]:Effect[:Seconds]` + items: + type: string + type: array + type: object + tracing: + description: 'Deprecated: for backward compatibility.' + properties: + configuration: + description: TraitConfiguration parameters configuration + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configuration + type: object + type: object version: description: the operator version type: string diff --git a/pkg/apis/camel/v1/integration_types.go b/pkg/apis/camel/v1/integration_types.go index 96e429bd1a..d31db2cb27 100644 --- a/pkg/apis/camel/v1/integration_types.go +++ b/pkg/apis/camel/v1/integration_types.go @@ -97,6 +97,8 @@ type IntegrationStatus struct { Dependencies []string `json:"dependencies,omitempty"` // the profile needed to run this Integration Profile TraitProfile `json:"profile,omitempty"` + // the traits executed for the Integration + Traits *Traits `json:"traits,omitempty"` // the reference of the `IntegrationKit` which is used for this Integration IntegrationKit *corev1.ObjectReference `json:"integrationKit,omitempty"` // The IntegrationPlatform watching this Integration diff --git a/pkg/apis/camel/v1/zz_generated.deepcopy.go b/pkg/apis/camel/v1/zz_generated.deepcopy.go index 64fa10e2bd..a4362f7409 100644 --- a/pkg/apis/camel/v1/zz_generated.deepcopy.go +++ b/pkg/apis/camel/v1/zz_generated.deepcopy.go @@ -1838,6 +1838,11 @@ func (in *IntegrationStatus) DeepCopyInto(out *IntegrationStatus) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Traits != nil { + in, out := &in.Traits, &out.Traits + *out = new(Traits) + (*in).DeepCopyInto(*out) + } if in.IntegrationKit != nil { in, out := &in.IntegrationKit, &out.IntegrationKit *out = new(corev1.ObjectReference) diff --git a/pkg/client/camel/applyconfiguration/camel/v1/integrationstatus.go b/pkg/client/camel/applyconfiguration/camel/v1/integrationstatus.go index 851decd192..12a1980cb8 100644 --- a/pkg/client/camel/applyconfiguration/camel/v1/integrationstatus.go +++ b/pkg/client/camel/applyconfiguration/camel/v1/integrationstatus.go @@ -34,6 +34,7 @@ type IntegrationStatusApplyConfiguration struct { Image *string `json:"image,omitempty"` Dependencies []string `json:"dependencies,omitempty"` Profile *v1.TraitProfile `json:"profile,omitempty"` + Traits *TraitsApplyConfiguration `json:"traits,omitempty"` IntegrationKit *corev1.ObjectReference `json:"integrationKit,omitempty"` Platform *string `json:"platform,omitempty"` GeneratedSources []SourceSpecApplyConfiguration `json:"generatedSources,omitempty"` @@ -105,6 +106,14 @@ func (b *IntegrationStatusApplyConfiguration) WithProfile(value v1.TraitProfile) return b } +// WithTraits sets the Traits 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 Traits field is set to the value of the last call. +func (b *IntegrationStatusApplyConfiguration) WithTraits(value *TraitsApplyConfiguration) *IntegrationStatusApplyConfiguration { + b.Traits = value + return b +} + // WithIntegrationKit sets the IntegrationKit 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 IntegrationKit field is set to the value of the last call. diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml index 20be245607..a89e55bd13 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml @@ -8357,6 +8357,1699 @@ spec: selector: description: label selector type: string + traits: + description: the traits executed for the Integration + properties: + 3scale: + description: 'Deprecated: for backward compatibility.' + properties: + configuration: + description: TraitConfiguration parameters configuration + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configuration + type: object + addons: + additionalProperties: + description: AddonTrait represents the configuration of an addon + trait. + type: object + x-kubernetes-preserve-unknown-fields: true + description: The extension point with addon traits + type: object + affinity: + description: The configuration of Affinity trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + nodeAffinityLabels: + description: Defines a set of nodes the integration pod(s) + are eligible to be scheduled on, based on labels on the + node. + items: + type: string + type: array + podAffinity: + description: Always co-locates multiple replicas of the integration + in the same node (default `false`). + type: boolean + podAffinityLabels: + description: |- + Defines a set of pods (namely those matching the label selector, relative to the given namespace) that the + integration pod(s) should be co-located with. + items: + type: string + type: array + podAntiAffinity: + description: Never co-locates multiple replicas of the integration + in the same node (default `false`). + type: boolean + podAntiAffinityLabels: + description: |- + Defines a set of pods (namely those matching the label selector, relative to the given namespace) that the + integration pod(s) should not be co-located with. + items: + type: string + type: array + type: object + builder: + description: The configuration of Builder trait + properties: + annotations: + additionalProperties: + type: string + description: When using `pod` strategy, annotation to use + for the builder pod. + type: object + baseImage: + description: |- + Specify a base image. In order to have the application working properly it must be a container image which has a Java JDK + installed and ready to use on path (ie `/usr/bin/java`). + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + incrementalImageBuild: + description: Use the incremental image build option, to reuse + existing containers (default `true`) + type: boolean + limitCPU: + description: |- + When using `pod` strategy, the maximum amount of CPU required by the pod builder. + Deprecated: use TasksRequestCPU instead with task name `builder`. + type: string + limitMemory: + description: |- + When using `pod` strategy, the maximum amount of memory required by the pod builder. + Deprecated: use TasksRequestCPU instead with task name `builder`. + type: string + mavenProfiles: + description: |- + A list of references pointing to configmaps/secrets that contains a maven profile. + This configmap/secret is a resource of the IntegrationKit created, therefore it needs to be present in the namespace where the operator is going to create the IntegrationKit. + The content of the maven profile is expected to be a text containing a valid maven profile starting with `` and ending with `` that will be integrated as an inline profile in the POM. + Syntax: [configmap|secret]:name[/key], where name represents the resource name, key optionally represents the resource key to be filtered (default key value = profile.xml). + items: + type: string + type: array + nodeSelector: + additionalProperties: + type: string + description: Defines a set of nodes the builder pod is eligible + to be scheduled on, based on labels on the node. + type: object + orderStrategy: + description: The build order strategy to use, either `dependencies`, + `fifo` or `sequential` (default is the platform default) + enum: + - dependencies + - fifo + - sequential + type: string + platforms: + description: The list of manifest platforms to use to build + a container image (default `linux/amd64`). + items: + type: string + type: array + properties: + description: A list of properties to be provided to the build + task + items: + type: string + type: array + requestCPU: + description: |- + When using `pod` strategy, the minimum amount of CPU required by the pod builder. + Deprecated: use TasksRequestCPU instead with task name `builder`. + type: string + requestMemory: + description: |- + When using `pod` strategy, the minimum amount of memory required by the pod builder. + Deprecated: use TasksRequestCPU instead with task name `builder`. + type: string + strategy: + description: The strategy to use, either `pod` or `routine` + (default `routine`) + enum: + - pod + - routine + type: string + tasks: + description: A list of tasks to be executed (available only + when using `pod` strategy) with format `;;`. + items: + type: string + type: array + tasksFilter: + description: |- + A list of tasks sorted by the order of execution in a csv format, ie, `,,...`. + Mind that you must include also the operator tasks (`builder`, `quarkus-native`, `package`, `jib`, `s2i`) + if you need to execute them. Useful only with `pod` strategy. + type: string + tasksLimitCPU: + description: A list of limit cpu configuration for the specific + task with format `:`. + items: + type: string + type: array + tasksLimitMemory: + description: A list of limit memory configuration for the + specific task with format `:`. + items: + type: string + type: array + tasksRequestCPU: + description: A list of request cpu configuration for the specific + task with format `:`. + items: + type: string + type: array + tasksRequestMemory: + description: A list of request memory configuration for the + specific task with format `:`. + items: + type: string + type: array + verbose: + description: |- + Enable verbose logging on build components that support it (e.g. Kaniko build pod). + Deprecated no longer in use + type: boolean + type: object + camel: + description: The configuration of Camel trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + properties: + description: A list of properties to be provided to the Integration + runtime + items: + type: string + type: array + runtimeVersion: + description: |- + The camel-k-runtime version to use for the integration. It overrides the default version set in the Integration Platform. + You can use a fixed version (for example "3.2.3") or a semantic version (for example "3.x") which will try to resolve + to the best matching Catalog existing on the cluster. + type: string + type: object + container: + description: The configuration of Container trait + properties: + allowPrivilegeEscalation: + description: Security Context AllowPrivilegeEscalation configuration + (default false). + type: boolean + auto: + description: To automatically enable the trait + type: boolean + capabilitiesAdd: + description: Security Context Capabilities Add configuration + (default none). + items: + description: Capability represent POSIX capabilities type + type: string + type: array + capabilitiesDrop: + description: Security Context Capabilities Drop configuration + (default ALL). + items: + description: Capability represent POSIX capabilities type + type: string + type: array + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + expose: + description: Can be used to enable/disable exposure via kubernetes + Service. + type: boolean + image: + description: |- + The main container image to use for the Integration. When using this parameter the operator will create a synthetic IntegrationKit which + won't be able to execute traits requiring CamelCatalog. If the container image you're using is coming from an IntegrationKit, use instead + Integration `.spec.integrationKit` parameter. If you're moving the Integration across environments, you will also need to create an "external" IntegrationKit. + type: string + imagePullPolicy: + description: 'The pull policy: Always|Never|IfNotPresent' + enum: + - Always + - Never + - IfNotPresent + type: string + limitCPU: + description: The maximum amount of CPU to be provided (default + 500 millicores). + type: string + limitMemory: + description: The maximum amount of memory to be provided (default + 512 Mi). + type: string + name: + description: The main container name. It's named `integration` + by default. + type: string + port: + description: To configure a different port exposed by the + container (default `8080`). + type: integer + portName: + description: To configure a different port name for the port + exposed by the container. It defaults to `http` only when + the `expose` parameter is true. + type: string + requestCPU: + description: The minimum amount of CPU required (default 125 + millicores). + type: string + requestMemory: + description: The minimum amount of memory required (default + 128 Mi). + type: string + runAsNonRoot: + description: Security Context RunAsNonRoot configuration (default + false). + type: boolean + runAsUser: + description: 'Security Context RunAsUser configuration (default + none): this value is automatically retrieved in Openshift + clusters when not explicitly set.' + format: int64 + type: integer + seccompProfileType: + description: Security Context SeccompProfileType configuration + (default RuntimeDefault). + enum: + - Unconfined + - RuntimeDefault + type: string + servicePort: + description: To configure under which service port the container + port is to be exposed (default `80`). + type: integer + servicePortName: + description: To configure under which service port name the + container port is to be exposed (default `http`). + type: string + type: object + cron: + description: The configuration of Cron trait + properties: + activeDeadlineSeconds: + description: |- + Specifies the duration in seconds, relative to the start time, that the job + may be continuously active before it is considered to be failed. + It defaults to 60s. + format: int64 + type: integer + auto: + description: |- + Automatically deploy the integration as CronJob when all routes are + either starting from a periodic consumer (only `cron`, `timer` and `quartz` are supported) or a passive consumer (e.g. `direct` is a passive consumer). + + + It's required that all periodic consumers have the same period, and it can be expressed as cron schedule (e.g. `1m` can be expressed as `0/1 * * * *`, + while `35m` or `50s` cannot). + type: boolean + backoffLimit: + description: |- + Specifies the number of retries before marking the job failed. + It defaults to 2. + format: int32 + type: integer + components: + description: |- + A comma separated list of the Camel components that need to be customized in order for them to work when the schedule is triggered externally by Kubernetes. + A specific customizer is activated for each specified component. E.g. for the `timer` component, the `cron-timer` customizer is + activated (it's present in the `org.apache.camel.k:camel-k-cron` library). + + + Supported components are currently: `cron`, `timer` and `quartz`. + type: string + concurrencyPolicy: + description: |- + Specifies how to treat concurrent executions of a Job. + Valid values are: + - "Allow": allows CronJobs to run concurrently; + - "Forbid" (default): forbids concurrent runs, skipping next run if previous run hasn't finished yet; + - "Replace": cancels currently running job and replaces it with a new one + enum: + - Allow + - Forbid + - Replace + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + fallback: + description: |- + Use the default Camel implementation of the `cron` endpoint (`quartz`) instead of trying to materialize the integration + as Kubernetes CronJob. + type: boolean + schedule: + description: |- + The CronJob schedule for the whole integration. If multiple routes are declared, they must have the same schedule for this + mechanism to work correctly. + type: string + startingDeadlineSeconds: + description: |- + Optional deadline in seconds for starting the job if it misses scheduled + time for any reason. Missed jobs executions will be counted as failed ones. + format: int64 + type: integer + timeZone: + description: The timezone that the CronJob will run on + type: string + type: object + dependencies: + description: The configuration of Dependencies trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + type: object + deployer: + description: The configuration of Deployer trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + kind: + description: Allows to explicitly select the desired deployment + kind between `deployment`, `cron-job` or `knative-service` + when creating the resources for running the integration. + enum: + - deployment + - cron-job + - knative-service + type: string + useSSA: + description: |- + Deprecated: won't be able to enforce client side update in the future. + Use server-side apply to update the owned resources (default `true`). + Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. + type: boolean + type: object + deployment: + description: The configuration of Deployment trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + progressDeadlineSeconds: + description: |- + The maximum time in seconds for the deployment to make progress before it + is considered to be failed. It defaults to `60s`. + format: int32 + type: integer + rollingUpdateMaxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to `25%`. + x-kubernetes-int-or-string: true + rollingUpdateMaxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to `25%`. + x-kubernetes-int-or-string: true + strategy: + description: The deployment strategy to use to replace existing + pods with new ones. + enum: + - Recreate + - RollingUpdate + type: string + type: object + environment: + description: The configuration of Environment trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + containerMeta: + description: Enables injection of `NAMESPACE` and `POD_NAME` + environment variables (default `true`) + type: boolean + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + httpProxy: + description: Propagates the `HTTP_PROXY`, `HTTPS_PROXY` and + `NO_PROXY` environment variables (default `true`) + type: boolean + vars: + description: |- + A list of environment variables to be added to the integration container. + The syntax is either VAR=VALUE or VAR=[configmap|secret]:name/key, where name represents the resource name, + and key represents the resource key to be mapped as and environment variable. + These take precedence over any previously defined environment variables. + items: + type: string + type: array + type: object + error-handler: + description: The configuration of Error Handler trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + ref: + description: The error handler ref name provided or found + in application properties + type: string + type: object + gc: + description: The configuration of GC trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + discoveryCache: + description: |- + Discovery client cache to be used, either `disabled`, `disk` or `memory` (default `memory`). + Deprecated: to be removed from trait configuration. + enum: + - disabled + - disk + - memory + type: string + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + type: object + health: + description: The configuration of Health trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + livenessFailureThreshold: + description: Minimum consecutive failures for the liveness + probe to be considered failed after having succeeded. + format: int32 + type: integer + livenessInitialDelay: + description: Number of seconds after the container has started + before the liveness probe is initiated. + format: int32 + type: integer + livenessPeriod: + description: How often to perform the liveness probe. + format: int32 + type: integer + livenessProbe: + description: The liveness probe path to use (default provided + by the Catalog runtime used). + type: string + livenessProbeEnabled: + description: Configures the liveness probe for the integration + container (default `false`). + type: boolean + livenessScheme: + description: Scheme to use when connecting to the liveness + probe (default `HTTP`). + type: string + livenessSuccessThreshold: + description: Minimum consecutive successes for the liveness + probe to be considered successful after having failed. + format: int32 + type: integer + livenessTimeout: + description: Number of seconds after which the liveness probe + times out. + format: int32 + type: integer + readinessFailureThreshold: + description: Minimum consecutive failures for the readiness + probe to be considered failed after having succeeded. + format: int32 + type: integer + readinessInitialDelay: + description: Number of seconds after the container has started + before the readiness probe is initiated. + format: int32 + type: integer + readinessPeriod: + description: How often to perform the readiness probe. + format: int32 + type: integer + readinessProbe: + description: The readiness probe path to use (default provided + by the Catalog runtime used). + type: string + readinessProbeEnabled: + description: Configures the readiness probe for the integration + container (default `true`). + type: boolean + readinessScheme: + description: Scheme to use when connecting to the readiness + probe (default `HTTP`). + type: string + readinessSuccessThreshold: + description: Minimum consecutive successes for the readiness + probe to be considered successful after having failed. + format: int32 + type: integer + readinessTimeout: + description: Number of seconds after which the readiness probe + times out. + format: int32 + type: integer + startupFailureThreshold: + description: Minimum consecutive failures for the startup + probe to be considered failed after having succeeded. + format: int32 + type: integer + startupInitialDelay: + description: Number of seconds after the container has started + before the startup probe is initiated. + format: int32 + type: integer + startupPeriod: + description: How often to perform the startup probe. + format: int32 + type: integer + startupProbe: + description: The startup probe path to use (default provided + by the Catalog runtime used). + type: string + startupProbeEnabled: + description: Configures the startup probe for the integration + container (default `false`). + type: boolean + startupScheme: + description: Scheme to use when connecting to the startup + probe (default `HTTP`). + type: string + startupSuccessThreshold: + description: Minimum consecutive successes for the startup + probe to be considered successful after having failed. + format: int32 + type: integer + startupTimeout: + description: Number of seconds after which the startup probe + times out. + format: int32 + type: integer + type: object + ingress: + description: The configuration of Ingress trait + properties: + annotations: + additionalProperties: + type: string + description: |- + The annotations added to the ingress. + This can be used to set controller specific annotations, e.g., when using the NGINX Ingress controller: + See https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md + type: object + auto: + description: To automatically add an ingress whenever the + integration uses an HTTP endpoint consumer. + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + host: + description: To configure the host exposed by the ingress. + type: string + ingressClassName: + description: |- + The Ingress class name as defined by the Ingress spec + See https://kubernetes.io/docs/concepts/services-networking/ingress/ + type: string + path: + description: To configure the path exposed by the ingress + (default `/`). + type: string + pathType: + description: |- + To configure the path type exposed by the ingress. + One of `Exact`, `Prefix`, `ImplementationSpecific` (default to `Prefix`). + enum: + - Exact + - Prefix + - ImplementationSpecific + type: string + tlsHosts: + description: To configure tls hosts + items: + type: string + type: array + tlsSecretName: + description: To configure tls secret name + type: string + type: object + istio: + description: The configuration of Istio trait + properties: + allow: + description: Configures a (comma-separated) list of CIDR subnets + that should not be intercepted by the Istio proxy (`10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` + by default). + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + inject: + description: Forces the value for labels `sidecar.istio.io/inject`. + By default the label is set to `true` on deployment and + not set on Knative Service. + type: boolean + type: object + jolokia: + description: The configuration of Jolokia trait + properties: + CACert: + description: |- + The PEM encoded CA certification file path, used to verify client certificates, + applicable when `protocol` is `https` and `use-ssl-client-authentication` is `true` + (default `/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt` for OpenShift). + type: string + clientPrincipal: + description: |- + The principal(s) which must be given in a client certificate to allow access to the Jolokia endpoint, + applicable when `protocol` is `https` and `use-ssl-client-authentication` is `true` + (default `clientPrincipal=cn=system:master-proxy`, `cn=hawtio-online.hawtio.svc` and `cn=fuse-console.fuse.svc` for OpenShift). + items: + type: string + type: array + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + discoveryEnabled: + description: Listen for multicast requests (default `false`) + type: boolean + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + extendedClientCheck: + description: |- + Mandate the client certificate contains a client flag in the extended key usage section, + applicable when `protocol` is `https` and `use-ssl-client-authentication` is `true` + (default `true` for OpenShift). + type: boolean + host: + description: |- + The Host address to which the Jolokia agent should bind to. If `"\*"` or `"0.0.0.0"` is given, + the servers binds to every network interface (default `"*"`). + type: string + options: + description: |- + A list of additional Jolokia options as defined + in https://jolokia.org/reference/html/agents.html#agent-jvm-config[JVM agent configuration options] + items: + type: string + type: array + password: + description: The password used for authentication, applicable + when the `user` option is set. + type: string + port: + description: The Jolokia endpoint port (default `8778`). + type: integer + protocol: + description: The protocol to use, either `http` or `https` + (default `https` for OpenShift) + type: string + useSSLClientAuthentication: + description: Whether client certificates should be used for + authentication (default `true` for OpenShift). + type: boolean + user: + description: The user to be used for authentication + type: string + type: object + jvm: + description: The configuration of JVM trait + properties: + classpath: + description: Additional JVM classpath (use `Linux` classpath + separator) + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + debug: + description: Activates remote debugging, so that a debugger + can be attached to the JVM, e.g., using port-forwarding + type: boolean + debugAddress: + description: Transport address at which to listen for the + newly launched JVM (default `*:5005`) + type: string + debugSuspend: + description: Suspends the target JVM immediately before the + main class is loaded + type: boolean + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + jar: + description: The Jar dependency which will run the application. + Leave it empty for managed Integrations. + type: string + options: + description: A list of JVM options + items: + type: string + type: array + printCommand: + description: |- + Prints the command used the start the JVM in the container logs (default `true`) + Deprecated: no longer in use. + type: boolean + type: object + kamelets: + description: The configuration of Kamelets trait + properties: + auto: + description: Automatically inject all referenced Kamelets + and their default configuration (enabled by default) + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + list: + description: Comma separated list of Kamelet names to load + into the current integration + type: string + mountPoint: + description: The directory where the application mounts and + reads Kamelet spec (default `/etc/camel/kamelets`) + type: string + type: object + keda: + description: 'Deprecated: for backward compatibility.' + properties: + configuration: + description: TraitConfiguration parameters configuration + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configuration + type: object + knative: + description: The configuration of Knative trait + properties: + auto: + description: Enable automatic discovery of all trait properties. + type: boolean + channelSinks: + description: |- + List of channels used as destination of integration routes. + Can contain simple channel names or full Camel URIs. + items: + type: string + type: array + channelSources: + description: |- + List of channels used as source of integration routes. + Can contain simple channel names or full Camel URIs. + items: + type: string + type: array + config: + description: Can be used to inject a Knative complete configuration + in JSON format. + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + endpointSinks: + description: |- + List of endpoints used as destination of integration routes. + Can contain simple endpoint names or full Camel URIs. + items: + type: string + type: array + endpointSources: + description: List of channels used as source of integration + routes. + items: + type: string + type: array + eventSinks: + description: |- + List of event types that the integration will produce. + Can contain simple event types or full Camel URIs (to use a specific broker). + items: + type: string + type: array + eventSources: + description: |- + List of event types that the integration will be subscribed to. + Can contain simple event types or full Camel URIs (to use a specific broker different from "default"). + items: + type: string + type: array + filterEventType: + description: |- + Enables the default filtering for the Knative trigger using the event type + If this is true, the created Knative trigger uses the event type as a filter on the event stream when no other filter criteria is given. (default: true) + type: boolean + filterSourceChannels: + description: |- + Enables filtering on events based on the header "ce-knativehistory". Since this header has been removed in newer versions of + Knative, filtering is disabled by default. + type: boolean + filters: + description: |- + Sets filter attributes on the event stream (such as event type, source, subject and so on). + A list of key-value pairs that represent filter attributes and its values. + The syntax is KEY=VALUE, e.g., `source="my.source"`. + Filter attributes get set on the Knative trigger that is being created as part of this integration. + items: + type: string + type: array + namespaceLabel: + description: |- + Enables the camel-k-operator to set the "bindings.knative.dev/include=true" label to the namespace + As Knative requires this label to perform injection of K_SINK URL into the service. + If this is false, the integration pod may start and fail, read the SinkBinding Knative documentation. (default: true) + type: boolean + sinkBinding: + description: |- + Allows binding the integration to a sink via a Knative SinkBinding resource. + This can be used when the integration targets a single sink. + It's enabled by default when the integration targets a single sink + (except when the integration is owned by a Knative source). + type: boolean + type: object + knative-service: + description: The configuration of Knative Service trait + properties: + annotations: + additionalProperties: + type: string + description: |- + The annotations added to route. + This can be used to set knative service specific annotations + CLI usage example: -t "knative-service.annotations.'haproxy.router.openshift.io/balance'=true" + type: object + auto: + description: |- + Automatically deploy the integration as Knative service when all conditions hold: + + + * Integration is using the Knative profile + * All routes are either starting from an HTTP based consumer or a passive consumer (e.g. `direct` is a passive consumer) + type: boolean + autoscalingMetric: + description: |- + Configures the Knative autoscaling metric property (e.g. to set `concurrency` based or `cpu` based autoscaling). + + + Refer to the Knative documentation for more information. + type: string + autoscalingTarget: + description: |- + Sets the allowed concurrency level or CPU percentage (depending on the autoscaling metric) for each Pod. + + + Refer to the Knative documentation for more information. + type: integer + class: + description: |- + Configures the Knative autoscaling class property (e.g. to set `hpa.autoscaling.knative.dev` or `kpa.autoscaling.knative.dev` autoscaling). + + + Refer to the Knative documentation for more information. + enum: + - kpa.autoscaling.knative.dev + - hpa.autoscaling.knative.dev + type: string + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + maxScale: + description: |- + An upper bound for the number of Pods that can be running in parallel for the integration. + Knative has its own cap value that depends on the installation. + + + Refer to the Knative documentation for more information. + type: integer + minScale: + description: |- + The minimum number of Pods that should be running at any time for the integration. It's **zero** by default, meaning that + the integration is scaled down to zero when not used for a configured amount of time. + + + Refer to the Knative documentation for more information. + type: integer + rolloutDuration: + description: |- + Enables to gradually shift traffic to the latest Revision and sets the rollout duration. + It's disabled by default and must be expressed as a Golang `time.Duration` string representation, + rounded to a second precision. + type: string + timeoutSeconds: + description: |- + The maximum duration in seconds that the request instance is allowed to respond to a request. + This field propagates to the integration pod's terminationGracePeriodSeconds + + + Refer to the Knative documentation for more information. + format: int64 + type: integer + visibility: + description: |- + Setting `cluster-local`, Knative service becomes a private service. + Specifically, this option applies the `networking.knative.dev/visibility` label to Knative service. + + + Refer to the Knative documentation for more information. + enum: + - cluster-local + type: string + type: object + logging: + description: The configuration of Logging trait + properties: + color: + description: Colorize the log output + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + format: + description: Logs message format + type: string + json: + description: Output the logs in JSON + type: boolean + jsonPrettyPrint: + description: Enable "pretty printing" of the JSON logs + type: boolean + level: + description: Adjust the logging level (defaults to `INFO`) + enum: + - FATAL + - WARN + - INFO + - DEBUG + - TRACE + type: string + type: object + master: + description: 'Deprecated: for backward compatibility.' + properties: + configuration: + description: TraitConfiguration parameters configuration + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configuration + type: object + mount: + description: The configuration of Mount trait + properties: + configs: + description: |- + A list of configuration pointing to configmap/secret. + The configuration are expected to be UTF-8 resources as they are processed by runtime Camel Context and tried to be parsed as property files. + They are also made available on the classpath in order to ease their usage directly from the Route. + Syntax: [configmap|secret]:name[/key], where name represents the resource name and key optionally represents the resource key to be filtered + items: + type: string + type: array + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + emptyDirs: + description: 'A list of EmptyDir volumes to be mounted. Syntax: + [name:/container/path]' + items: + type: string + type: array + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + hotReload: + description: |- + Enable "hot reload" when a secret/configmap mounted is edited (default `false`). The configmap/secret must be + marked with `camel.apache.org/integration` label to be taken in account. The resource will be watched for any kind change, also for + changes in metadata. + type: boolean + resources: + description: |- + A list of resources (text or binary content) pointing to configmap/secret. + The resources are expected to be any resource type (text or binary content). + The destination path can be either a default location or any path specified by the user. + Syntax: [configmap|secret]:name[/key][@path], where name represents the resource name, key optionally represents the resource key to be filtered and path represents the destination path + items: + type: string + type: array + scanKameletsImplicitLabelSecrets: + description: 'Deprecated: no longer available since version + 2.5.' + type: boolean + volumes: + description: 'A list of Persistent Volume Claims to be mounted. + Syntax: [pvcname:/container/path]' + items: + type: string + type: array + type: object + openapi: + description: The configuration of OpenAPI trait + properties: + configmaps: + description: The configmaps holding the spec of the OpenAPI + (compatible with > 3.0 spec only). + items: + type: string + type: array + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + type: object + owner: + description: The configuration of Owner trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + targetAnnotations: + description: The set of annotations to be transferred + items: + type: string + type: array + targetLabels: + description: The set of labels to be transferred + items: + type: string + type: array + type: object + pdb: + description: The configuration of PDB trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + maxUnavailable: + description: |- + The number of pods for the Integration that can be unavailable after an eviction. + It can be either an absolute number or a percentage (default `1` if `min-available` is also not set). + Only one of `max-unavailable` and `min-available` can be specified. + type: string + minAvailable: + description: |- + The number of pods for the Integration that must still be available after an eviction. + It can be either an absolute number or a percentage. + Only one of `min-available` and `max-unavailable` can be specified. + type: string + type: object + platform: + description: The configuration of Platform trait + properties: + auto: + description: |- + To automatically detect from the environment if a default platform can be created (it will be created on OpenShift or when a registry address is set). + Deprecated: Platform is auto generated by the operator install procedure - maintained for backward compatibility + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + createDefault: + description: |- + To create a default (empty) platform when the platform is missing. + Deprecated: Platform is auto generated by the operator install procedure - maintained for backward compatibility + type: boolean + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + global: + description: |- + Indicates if the platform should be created globally in the case of global operator (default true). + Deprecated: Platform is auto generated by the operator install procedure - maintained for backward compatibility + type: boolean + type: object + pod: + description: The configuration of Pod trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + type: object + prometheus: + description: The configuration of Prometheus trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + podMonitor: + description: Whether a `PodMonitor` resource is created (default + `true`). + type: boolean + podMonitorLabels: + description: The `PodMonitor` resource labels, applicable + when `pod-monitor` is `true`. + items: + type: string + type: array + type: object + pull-secret: + description: The configuration of Pull Secret trait + properties: + auto: + description: Automatically configures the platform registry + secret on the pod if it is of type `kubernetes.io/dockerconfigjson`. + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + imagePullerDelegation: + description: When using a global operator with a shared platform, + this enables delegation of the `system:image-puller` cluster + role on the operator namespace to the integration service + account. + type: boolean + secretName: + description: The pull secret name to set on the Pod. If left + empty this is automatically taken from the `IntegrationPlatform` + registry configuration. + type: string + type: object + quarkus: + description: The configuration of Quarkus trait + properties: + buildMode: + description: |- + The Quarkus mode to run: either `jvm` or `native` (default `jvm`). + In case both `jvm` and `native` are specified, two `IntegrationKit` resources are created, + with the `native` kit having precedence over the `jvm` one once ready. + items: + description: QuarkusMode is the type of Quarkus build packaging. + enum: + - jvm + - native + type: string + type: array + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + nativeBaseImage: + description: The base image to use when running a native build + (default `quay.io/quarkus/quarkus-micro-image:2.0`) + type: string + nativeBuilderImage: + description: The image containing the tooling required for + a native build (by default it will use the one provided + in the runtime catalog) + type: string + packageTypes: + description: |- + The Quarkus package types, `fast-jar` or `native` (default `fast-jar`). + In case both `fast-jar` and `native` are specified, two `IntegrationKit` resources are created, + with the native kit having precedence over the `fast-jar` one once ready. + The order influences the resolution of the current kit for the integration. + The kit corresponding to the first package type will be assigned to the + integration in case no existing kit that matches the integration exists. + Deprecated: use `build-mode` instead. + items: + description: |- + QuarkusPackageType is the type of Quarkus build packaging. + Deprecated: use `QuarkusMode` instead. + enum: + - fast-jar + - native + type: string + type: array + type: object + registry: + description: |- + The configuration of Registry trait (support removed since version 2.5.0). + Deprecated: use jvm trait or read documentation. + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + type: object + route: + description: The configuration of Route trait + properties: + annotations: + additionalProperties: + type: string + description: |- + The annotations added to route. + This can be used to set route specific annotations + For annotations options see https://docs.openshift.com/container-platform/3.11/architecture/networking/routes.html#route-specific-annotations + CLI usage example: -t "route.annotations.'haproxy.router.openshift.io/balance'=true" + type: object + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + host: + description: To configure the host exposed by the route. + type: string + tlsCACertificate: + description: |- + The TLS CA certificate contents. + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsCACertificateSecret: + description: |- + The secret name and key reference to the TLS CA certificate. The format is "secret-name[/key-name]", the value represents the secret name, if there is only one key in the secret it will be read, otherwise you can set a key name separated with a "/". + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsCertificate: + description: |- + The TLS certificate contents. + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsCertificateSecret: + description: |- + The secret name and key reference to the TLS certificate. The format is "secret-name[/key-name]", the value represents the secret name, if there is only one key in the secret it will be read, otherwise you can set a key name separated with a "/". + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsDestinationCACertificate: + description: |- + The destination CA certificate provides the contents of the ca certificate of the final destination. When using reencrypt + termination this file should be provided in order to have routers use it for health checks on the secure connection. + If this field is not specified, the router may provide its own destination CA and perform hostname validation using + the short service name (service.namespace.svc), which allows infrastructure generated certificates to automatically + verify. + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsDestinationCACertificateSecret: + description: |- + The secret name and key reference to the destination CA certificate. The format is "secret-name[/key-name]", the value represents the secret name, if there is only one key in the secret it will be read, otherwise you can set a key name separated with a "/". + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsInsecureEdgeTerminationPolicy: + description: |- + To configure how to deal with insecure traffic, e.g. `Allow`, `Disable` or `Redirect` traffic. + + + Refer to the OpenShift route documentation for additional information. + enum: + - None + - Allow + - Redirect + type: string + tlsKey: + description: |- + The TLS certificate key contents. + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsKeySecret: + description: |- + The secret name and key reference to the TLS certificate key. The format is "secret-name[/key-name]", the value represents the secret name, if there is only one key in the secret it will be read, otherwise you can set a key name separated with a "/". + + + Refer to the OpenShift route documentation for additional information. + type: string + tlsTermination: + description: |- + The TLS termination type, like `edge`, `passthrough` or `reencrypt`. + + + Refer to the OpenShift route documentation for additional information. + enum: + - edge + - reencrypt + - passthrough + type: string + type: object + security-context: + description: The configuration of Security Context trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: 'Deprecated: no longer in use.' + type: boolean + runAsNonRoot: + description: Security Context RunAsNonRoot configuration (default + false). + type: boolean + runAsUser: + description: 'Security Context RunAsUser configuration (default + none): this value is automatically retrieved in Openshift + clusters when not explicitly set.' + format: int64 + type: integer + seccompProfileType: + description: Security Context SeccompProfileType configuration + (default RuntimeDefault). + enum: + - Unconfined + - RuntimeDefault + type: string + type: object + service: + description: The configuration of Service trait + properties: + auto: + description: To automatically detect from the code if a Service + needs to be created. + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + nodePort: + description: |- + Enable Service to be exposed as NodePort (default `false`). + Deprecated: Use service type instead. + type: boolean + type: + description: The type of service to be used, either 'ClusterIP', + 'NodePort' or 'LoadBalancer'. + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + type: object + service-binding: + description: The configuration of Service Binding trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + services: + description: List of Services in the form [[apigroup/]version:]kind:[namespace/]name + items: + type: string + type: array + type: object + strimzi: + description: 'Deprecated: for backward compatibility.' + properties: + configuration: + description: TraitConfiguration parameters configuration + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configuration + type: object + telemetry: + description: The configuration of Telemetry trait + properties: + auto: + description: Enables automatic configuration of the trait, + including automatic discovery of the telemetry endpoint. + type: boolean + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + endpoint: + description: The target endpoint of the Telemetry service + (automatically discovered by default) + type: string + sampler: + description: The sampler of the telemetry used for tracing + (default "on") + type: string + sampler-parent-based: + description: The sampler of the telemetry used for tracing + is parent based (default "true") + type: boolean + sampler-ratio: + description: The sampler ratio of the telemetry used for tracing + type: string + serviceName: + description: The name of the service that publishes telemetry + data (defaults to the integration name) + type: string + type: object + toleration: + description: The configuration of Toleration trait + properties: + configuration: + description: |- + Legacy trait configuration parameters. + Deprecated: for backward compatibility. + type: object + x-kubernetes-preserve-unknown-fields: true + enabled: + description: Can be used to enable or disable a trait. All + traits share this common property. + type: boolean + taints: + description: The list of taints to tolerate, in the form `Key[=Value]:Effect[:Seconds]` + items: + type: string + type: array + type: object + tracing: + description: 'Deprecated: for backward compatibility.' + properties: + configuration: + description: TraitConfiguration parameters configuration + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configuration + type: object + type: object version: description: the operator version type: string diff --git a/pkg/trait/affinity.go b/pkg/trait/affinity.go index caa9278350..43f0485237 100644 --- a/pkg/trait/affinity.go +++ b/pkg/trait/affinity.go @@ -44,10 +44,6 @@ type affinityTrait struct { func newAffinityTrait() Trait { return &affinityTrait{ BaseTrait: NewBaseTrait(affinityTraitID, affinityTraitOrder), - AffinityTrait: traitv1.AffinityTrait{ - PodAffinity: ptr.To(false), - PodAntiAffinity: ptr.To(false), - }, } } diff --git a/pkg/trait/builder_test.go b/pkg/trait/builder_test.go index a096c6e7c8..aaa30c00f7 100644 --- a/pkg/trait/builder_test.go +++ b/pkg/trait/builder_test.go @@ -67,10 +67,11 @@ func TestBuilderTraitNotAppliedBecauseOfNilPhase(t *testing.T) { e.IntegrationKit.Status.Phase = v1.IntegrationKitPhaseInitialization t.Run(string(e.Platform.Status.Cluster), func(t *testing.T) { - conditions, err := NewBuilderTestCatalog().apply(e) + conditions, traits, err := NewBuilderTestCatalog().apply(e) require.NoError(t, err) assert.NotEmpty(t, conditions) + assert.Empty(t, traits) assert.NotEmpty(t, e.ExecutedTraits) assert.Nil(t, e.GetTrait("builder")) assert.Empty(t, e.Pipeline) @@ -80,9 +81,10 @@ func TestBuilderTraitNotAppliedBecauseOfNilPhase(t *testing.T) { func TestS2IBuilderTrait(t *testing.T) { env := createBuilderTestEnv(v1.IntegrationPlatformClusterOpenShift, v1.IntegrationPlatformBuildPublishStrategyS2I, v1.BuildStrategyRoutine) - conditions, err := NewBuilderTestCatalog().apply(env) + conditions, traits, err := NewBuilderTestCatalog().apply(env) require.NoError(t, err) + assert.Empty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, env.ExecutedTraits) assert.NotNil(t, env.GetTrait("builder")) @@ -97,9 +99,10 @@ func TestS2IBuilderTrait(t *testing.T) { func TestJibBuilderTrait(t *testing.T) { env := createBuilderTestEnv(v1.IntegrationPlatformClusterOpenShift, v1.IntegrationPlatformBuildPublishStrategyJib, v1.BuildStrategyRoutine) - conditions, err := NewBuilderTestCatalog().apply(env) + conditions, traits, err := NewBuilderTestCatalog().apply(env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, env.ExecutedTraits) assert.NotNil(t, env.GetTrait("builder")) diff --git a/pkg/trait/camel.go b/pkg/trait/camel.go index 698f360514..df94312aec 100644 --- a/pkg/trait/camel.go +++ b/pkg/trait/camel.go @@ -42,6 +42,8 @@ const ( type camelTrait struct { BasePlatformTrait traitv1.CamelTrait `property:",squash"` + // private configuration used only internally + runtimeVersion string } func newCamelTrait() Trait { @@ -73,8 +75,10 @@ func (t *camelTrait) Configure(e *Environment) (bool, *TraitCondition, error) { if runtimeVersion, err := determineRuntimeVersion(e); err != nil { return false, nil, err } else { - t.RuntimeVersion = runtimeVersion + t.runtimeVersion = runtimeVersion } + } else { + t.runtimeVersion = t.RuntimeVersion } var cond *TraitCondition @@ -89,7 +93,7 @@ func (t *camelTrait) Configure(e *Environment) (bool, *TraitCondition, error) { traitConfigurationReason, fmt.Sprintf( "Operated with CamelCatalog version %s which may be different from the runtime used in the container", - t.RuntimeVersion, + t.runtimeVersion, ), ) } @@ -102,7 +106,7 @@ func (t *camelTrait) Apply(e *Environment) error { // expects a CamelCatalog to be loaded regardless it's a managed or // non managed build Integration if e.CamelCatalog == nil { - if err := t.loadOrCreateCatalog(e, t.RuntimeVersion); err != nil { + if err := t.loadOrCreateCatalog(e, t.runtimeVersion); err != nil { return err } } diff --git a/pkg/trait/container.go b/pkg/trait/container.go index 80c116991d..f3f24deb3b 100644 --- a/pkg/trait/container.go +++ b/pkg/trait/container.go @@ -66,20 +66,6 @@ type containerTrait struct { func newContainerTrait() Trait { return &containerTrait{ BasePlatformTrait: NewBasePlatformTrait(containerTraitID, containerTraitOrder), - ContainerTrait: traitv1.ContainerTrait{ - Port: defaultContainerPort, - ServicePort: defaultServicePort, - ServicePortName: defaultContainerPortName, - Name: defaultContainerName, - RunAsNonRoot: ptr.To(defaultContainerRunAsNonRoot), - SeccompProfileType: defaultContainerSeccompProfileType, - AllowPrivilegeEscalation: ptr.To(defaultContainerAllowPrivilegeEscalation), - CapabilitiesDrop: []corev1.Capability{defaultContainerCapabilitiesDrop}, - RequestCPU: defaultContainerResourceCPU, - RequestMemory: defaultContainerResourceMemory, - LimitCPU: defaultContainerLimitCPU, - LimitMemory: defaultContainerLimitMemory, - }, } } @@ -94,8 +80,9 @@ func (t *containerTrait) Configure(e *Environment) (bool, *TraitCondition, error if ptr.Deref(t.Auto, true) { if t.Expose == nil { - e := e.Resources.GetServiceForIntegration(e.Integration) != nil - t.Expose = &e + if e.Resources.GetServiceForIntegration(e.Integration) != nil { + t.Expose = ptr.To(true) + } } } @@ -138,7 +125,7 @@ func (t *containerTrait) configureContainer(e *Environment) error { e.ApplicationProperties = make(map[string]string) } container := corev1.Container{ - Name: t.Name, + Name: t.getContainerName(), Image: e.Integration.Status.Image, Env: make([]corev1.EnvVar, 0), } @@ -230,7 +217,7 @@ func (t *containerTrait) configureService(e *Environment, container *corev1.Cont } containerPort := corev1.ContainerPort{ Name: name, - ContainerPort: int32(t.Port), + ContainerPort: int32(t.getPort()), Protocol: corev1.ProtocolTCP, } if !isKnative { @@ -238,8 +225,8 @@ func (t *containerTrait) configureService(e *Environment, container *corev1.Cont service := e.Resources.GetServiceForIntegration(e.Integration) if service != nil { servicePort := corev1.ServicePort{ - Name: t.ServicePortName, - Port: int32(t.ServicePort), + Name: t.getServicePortName(), + Port: int32(t.getServicePort()), Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromString(name), } @@ -271,21 +258,21 @@ func (t *containerTrait) configureResources(container *corev1.Container) { limitsList = make(corev1.ResourceList) } - requestsList, err = kubernetes.ConfigureResource(t.RequestCPU, requestsList, corev1.ResourceCPU) + requestsList, err = kubernetes.ConfigureResource(t.getRequestCPU(), requestsList, corev1.ResourceCPU) if err != nil { - t.L.Error(err, "unable to parse quantity", "request-cpu", t.RequestCPU) + t.L.Error(err, "unable to parse quantity", "request-cpu", t.getRequestCPU()) } - requestsList, err = kubernetes.ConfigureResource(t.RequestMemory, requestsList, corev1.ResourceMemory) + requestsList, err = kubernetes.ConfigureResource(t.getRequestMemory(), requestsList, corev1.ResourceMemory) if err != nil { - t.L.Error(err, "unable to parse quantity", "request-memory", t.RequestMemory) + t.L.Error(err, "unable to parse quantity", "request-memory", t.getRequestMemory()) } - limitsList, err = kubernetes.ConfigureResource(t.LimitCPU, limitsList, corev1.ResourceCPU) + limitsList, err = kubernetes.ConfigureResource(t.getLimitCPU(), limitsList, corev1.ResourceCPU) if err != nil { - t.L.Error(err, "unable to parse quantity", "limit-cpu", t.LimitCPU) + t.L.Error(err, "unable to parse quantity", "limit-cpu", t.getLimitCPU()) } - limitsList, err = kubernetes.ConfigureResource(t.LimitMemory, limitsList, corev1.ResourceMemory) + limitsList, err = kubernetes.ConfigureResource(t.getLimitMemory(), limitsList, corev1.ResourceMemory) if err != nil { - t.L.Error(err, "unable to parse quantity", "limit-memory", t.LimitMemory) + t.L.Error(err, "unable to parse quantity", "limit-memory", t.getLimitMemory()) } container.Resources.Requests = requestsList @@ -300,12 +287,12 @@ func (t *containerTrait) configureCapabilities(e *Environment) { func (t *containerTrait) setSecurityContext(e *Environment, container *corev1.Container) error { sc := corev1.SecurityContext{ - RunAsNonRoot: t.RunAsNonRoot, + RunAsNonRoot: t.getRunAsNonRoot(), SeccompProfile: &corev1.SeccompProfile{ - Type: t.SeccompProfileType, + Type: t.getSeccompProfileType(), }, - AllowPrivilegeEscalation: t.AllowPrivilegeEscalation, - Capabilities: &corev1.Capabilities{Drop: t.CapabilitiesDrop, Add: t.CapabilitiesAdd}, + AllowPrivilegeEscalation: t.getAllowPrivilegeEscalation(), + Capabilities: &corev1.Capabilities{Drop: t.getCapabilitiesDrop(), Add: t.CapabilitiesAdd}, } runAsUser, err := t.getUser(e) @@ -342,3 +329,99 @@ func (t *containerTrait) getUser(e *Environment) (*int64, error) { return runAsUser, nil } + +func (t *containerTrait) getPort() int { + if t.Port == 0 { + return defaultContainerPort + } + + return t.Port +} + +func (t *containerTrait) getServicePort() int { + if t.ServicePort == 0 { + return defaultServicePort + } + + return t.ServicePort +} + +func (t *containerTrait) getServicePortName() string { + if t.ServicePortName == "" { + return defaultContainerPortName + } + + return t.ServicePortName +} + +func (t *containerTrait) getContainerName() string { + if t.Name == "" { + return defaultContainerName + } + + return t.Name +} + +func (t *containerTrait) getRunAsNonRoot() *bool { + if t.RunAsNonRoot == nil { + return ptr.To(defaultContainerRunAsNonRoot) + } + + return t.RunAsNonRoot +} + +func (t *containerTrait) getSeccompProfileType() corev1.SeccompProfileType { + if t.SeccompProfileType == "" { + return defaultContainerSeccompProfileType + } + + return t.SeccompProfileType +} + +func (t *containerTrait) getAllowPrivilegeEscalation() *bool { + if t.AllowPrivilegeEscalation == nil { + return ptr.To(defaultContainerAllowPrivilegeEscalation) + } + + return t.AllowPrivilegeEscalation +} + +func (t *containerTrait) getCapabilitiesDrop() []corev1.Capability { + if t.CapabilitiesDrop == nil { + return []corev1.Capability{defaultContainerCapabilitiesDrop} + } + + return t.CapabilitiesDrop +} + +func (t *containerTrait) getRequestCPU() string { + if t.RequestCPU == "" { + return defaultContainerResourceCPU + } + + return t.RequestCPU +} + +func (t *containerTrait) getRequestMemory() string { + if t.RequestMemory == "" { + return defaultContainerResourceMemory + } + + return t.RequestMemory +} + +func (t *containerTrait) getLimitCPU() string { + if t.LimitCPU == "" { + return defaultContainerLimitCPU + } + + return t.LimitCPU +} + +func (t *containerTrait) getLimitMemory() string { + if t.LimitMemory == "" { + return defaultContainerLimitMemory + } + + return t.LimitMemory +} diff --git a/pkg/trait/container_probes_test.go b/pkg/trait/container_probes_test.go index 4b128e9c2a..0d17162a3b 100644 --- a/pkg/trait/container_probes_test.go +++ b/pkg/trait/container_probes_test.go @@ -80,8 +80,9 @@ func TestProbesDependencies(t *testing.T) { env := newTestProbesEnv(t, integration) env.Integration.Status.Phase = v1.IntegrationPhaseInitialization - conditions, err := env.Catalog.apply(&env) + conditions, traits, err := env.Catalog.apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.Contains(t, env.Integration.Status.Dependencies, "mvn:org.apache.camel.quarkus:camel-quarkus-microprofile-health") } @@ -105,8 +106,9 @@ func TestProbesOnDeployment(t *testing.T) { env := newTestProbesEnv(t, integration) env.Integration.Status.Phase = v1.IntegrationPhaseDeploying - conditions, err := env.Catalog.apply(&env) + conditions, traits, err := env.Catalog.apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) container := env.GetIntegrationContainer() @@ -143,8 +145,9 @@ func TestProbesOnDeploymentWithCustomScheme(t *testing.T) { env := newTestProbesEnv(t, integration) env.Integration.Status.Phase = v1.IntegrationPhaseDeploying - conditions, err := env.Catalog.apply(&env) + conditions, traits, err := env.Catalog.apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) container := env.GetIntegrationContainer() @@ -200,8 +203,9 @@ func TestProbesOnKnativeService(t *testing.T) { "controller strategy: knative-service", ) - conditions, err := env.Catalog.apply(&env) + conditions, traits, err := env.Catalog.apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.Contains(t, conditions, ctrlStrategyCondition) assert.Contains(t, conditions, serviceOverrideCondition) diff --git a/pkg/trait/container_test.go b/pkg/trait/container_test.go index db41467a84..ff52d6e8b5 100644 --- a/pkg/trait/container_test.go +++ b/pkg/trait/container_test.go @@ -86,9 +86,10 @@ func TestContainerWithDefaults(t *testing.T) { } environment.Platform.ResyncStatusFullConfig() - conditions, err := traitCatalog.apply(&environment) + conditions, traits, err := traitCatalog.apply(&environment) require.NoError(t, err) + assert.Empty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("deployment")) @@ -167,9 +168,10 @@ func TestContainerWithOpenshift(t *testing.T) { } environment.Platform.ResyncStatusFullConfig() - conditions, err := traitCatalog.apply(&environment) + conditions, traits, err := traitCatalog.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("deployment")) @@ -235,9 +237,10 @@ func TestContainerWithCustomName(t *testing.T) { } environment.Platform.ResyncStatusFullConfig() - conditions, err := traitCatalog.apply(&environment) + conditions, traits, err := traitCatalog.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("deployment")) @@ -301,9 +304,10 @@ func TestContainerWithCustomImage(t *testing.T) { } environment.Platform.ResyncStatusFullConfig() - conditions, err := traitCatalog.apply(&environment) + conditions, traits, err := traitCatalog.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) for _, postAction := range environment.PostActions { @@ -370,8 +374,9 @@ func TestContainerWithCustomImageAndIntegrationKit(t *testing.T) { } environment.Platform.ResyncStatusFullConfig() - conditions, err := traitCatalog.apply(&environment) + conditions, traits, err := traitCatalog.apply(&environment) require.Error(t, err) + assert.Empty(t, traits) assert.NotEmpty(t, conditions) assert.Contains(t, err.Error(), "unsupported configuration: a container image has been set in conjunction with an IntegrationKit") } @@ -414,9 +419,10 @@ func TestContainerWithImagePullPolicy(t *testing.T) { environment.Integration.Status.Phase = v1.IntegrationPhaseDeploying environment.Platform.ResyncStatusFullConfig() - conditions, err := traitCatalog.apply(&environment) + conditions, traits, err := traitCatalog.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) container := environment.GetIntegrationContainer() @@ -479,7 +485,7 @@ func TestDeploymentContainerPorts(t *testing.T) { environment.Integration.Status.Phase = v1.IntegrationPhaseDeploying environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) container := environment.GetIntegrationContainer() assert.Len(t, container.Ports, 1) @@ -545,7 +551,7 @@ func TestKnativeServiceContainerPorts(t *testing.T) { environment.Integration.Status.Phase = v1.IntegrationPhaseDeploying environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) container := environment.GetIntegrationContainer() assert.Len(t, container.Ports, 1) @@ -557,9 +563,10 @@ func TestDefaultKubernetesSecurityContext(t *testing.T) { environment := createSettingContextEnvironment(t, v1.TraitProfileKubernetes) traitCatalog := NewCatalog(nil) - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.Empty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("deployment")) @@ -587,9 +594,10 @@ func TestDefaultKnativeSecurityContext(t *testing.T) { } traitCatalog := NewCatalog(nil) - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.Nil(t, environment.GetTrait("deployment")) @@ -625,9 +633,10 @@ func TestUserSecurityContext(t *testing.T) { } traitCatalog := NewCatalog(nil) - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("deployment")) @@ -648,9 +657,10 @@ func TestUserSecurityContext(t *testing.T) { func TestUserDefaultResources(t *testing.T) { environment := createSettingContextEnvironment(t, v1.TraitProfileKubernetes) traitCatalog := NewCatalog(nil) - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.Empty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("deployment")) diff --git a/pkg/trait/cron.go b/pkg/trait/cron.go index 7fbb35d9ea..80da757783 100644 --- a/pkg/trait/cron.go +++ b/pkg/trait/cron.go @@ -88,15 +88,6 @@ func (t *cronTrait) Configure(e *Environment) (bool, *TraitCondition, error) { if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization) && !e.IntegrationInRunningPhases() { return false, nil, nil } - if _, ok := e.CamelCatalog.Runtime.Capabilities[v1.CapabilityCron]; !ok { - return false, NewIntegrationCondition( - "Cron", - v1.IntegrationConditionCronJobAvailable, - corev1.ConditionFalse, - v1.IntegrationConditionCronJobNotAvailableReason, - "the runtime provider %s does not declare 'cron' capability", - ), nil - } if ptr.Deref(t.Auto, true) { err := t.autoConfigure(e) @@ -143,7 +134,11 @@ func (t *cronTrait) Configure(e *Environment) (bool, *TraitCondition, error) { return false, nil, nil } - return t.Schedule != "", nil, nil + if t.Schedule != "" { + t.Enabled = ptr.To(true) + } + + return ptr.Deref(t.Enabled, false), nil, nil } func (t *cronTrait) autoConfigure(e *Environment) error { @@ -164,10 +159,6 @@ func (t *cronTrait) autoConfigure(e *Environment) error { t.Components = strings.Join(configuredComponents, ",") } - if t.ConcurrencyPolicy == "" { - t.ConcurrencyPolicy = string(batchv1.ForbidConcurrent) - } - if (t.Schedule == "" && t.Components == "") && t.Fallback == nil { // If there's at least a `cron` endpoint, add a fallback implementation fromURIs, err := t.getSourcesFromURIs(e) @@ -254,7 +245,7 @@ func (t *cronTrait) getCronJobFor(e *Environment) *batchv1.CronJob { Spec: batchv1.CronJobSpec{ Schedule: t.Schedule, TimeZone: t.TimeZone, - ConcurrencyPolicy: batchv1.ConcurrencyPolicy(t.ConcurrencyPolicy), + ConcurrencyPolicy: t.getConcurrentPolicy(), StartingDeadlineSeconds: t.StartingDeadlineSeconds, JobTemplate: batchv1.JobTemplateSpec{ Spec: batchv1.JobSpec{ @@ -361,6 +352,14 @@ func (t *cronTrait) getSourcesFromURIs(e *Environment) ([]string, error) { return fromUris, nil } +func (t *cronTrait) getConcurrentPolicy() batchv1.ConcurrencyPolicy { + if t.ConcurrencyPolicy == "" { + return batchv1.ForbidConcurrent + } + + return batchv1.ConcurrencyPolicy(t.ConcurrencyPolicy) +} + func getCronForURIs(camelURIs []string) *cronInfo { var globalCron *cronInfo for _, camelURI := range camelURIs { diff --git a/pkg/trait/cron_test.go b/pkg/trait/cron_test.go index 178f22d082..e09eaeff21 100644 --- a/pkg/trait/cron_test.go +++ b/pkg/trait/cron_test.go @@ -285,9 +285,10 @@ func TestCronDeps(t *testing.T) { require.NoError(t, err) tc := NewCatalog(c) - conditions, err := tc.apply(&environment) + conditions, traits, err := tc.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) @@ -367,9 +368,10 @@ func TestCronMultipleScheduleFallback(t *testing.T) { assert.Nil(t, err) tc := NewCatalog(c) - conditions, err := tc.apply(&environment) + conditions, traits, err := tc.apply(&environment) assert.Nil(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) @@ -445,9 +447,10 @@ func TestCronDepsFallback(t *testing.T) { tc := NewCatalog(c) - conditions, err := tc.apply(&environment) + conditions, traits, err := tc.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) @@ -528,8 +531,9 @@ func TestCronWithActiveDeadline(t *testing.T) { "DeploymentAvailable", "controller strategy: cron-job", ) - conditions, err := tc.apply(&environment) + conditions, traits, err := tc.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.Contains(t, conditions, expectedCondition) assert.NotEmpty(t, environment.ExecutedTraits) @@ -617,8 +621,9 @@ func TestCronWithBackoffLimit(t *testing.T) { "DeploymentAvailable", "controller strategy: cron-job", ) - conditions, err := tc.apply(&environment) + conditions, traits, err := tc.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.Contains(t, conditions, expectedCondition) assert.NotEmpty(t, environment.ExecutedTraits) @@ -710,8 +715,9 @@ func TestCronWithTimeZone(t *testing.T) { "DeploymentAvailable", "controller strategy: cron-job", ) - conditions, err := tc.apply(&environment) + conditions, traits, err := tc.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.Contains(t, conditions, expectedCondition) assert.NotEmpty(t, environment.ExecutedTraits) @@ -726,3 +732,72 @@ func TestCronWithTimeZone(t *testing.T) { assert.NotNil(t, cronJob.Spec.TimeZone) assert.EqualValues(t, *cronJob.Spec.TimeZone, "America/Sao_Paulo") } + +func TestCronAuto(t *testing.T) { + catalog, err := camel.DefaultCatalog() + require.NoError(t, err) + + client, _ := test.NewFakeClient() + traitCatalog := NewCatalog(nil) + + environment := Environment{ + CamelCatalog: catalog, + Catalog: traitCatalog, + Client: client, + Integration: &v1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "ns", + }, + Status: v1.IntegrationStatus{ + Phase: v1.IntegrationPhaseDeploying, + }, + Spec: v1.IntegrationSpec{ + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "routes.java", + Content: `from("cron:tab?schedule=0 0/2 * * ?").to("log:test")`, + }, + Language: v1.LanguageJavaSource, + }, + }, + }, + }, + IntegrationKit: &v1.IntegrationKit{ + Status: v1.IntegrationKitStatus{ + Phase: v1.IntegrationKitPhaseReady, + }, + }, + Platform: &v1.IntegrationPlatform{ + Spec: v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + RuntimeVersion: catalog.Runtime.Version, + }, + }, + Status: v1.IntegrationPlatformStatus{ + Phase: v1.IntegrationPlatformPhaseReady, + }, + }, + EnvVars: make([]corev1.EnvVar, 0), + ExecutedTraits: make([]Trait, 0), + Resources: kubernetes.NewCollection(), + } + environment.Platform.ResyncStatusFullConfig() + + c, err := NewFakeClient("ns") + require.NoError(t, err) + + tc := NewCatalog(c) + _, traits, err := tc.apply(&environment) + require.NoError(t, err) + assert.NotEmpty(t, traits) + + assert.Equal(t, &traitv1.CronTrait{ + Trait: traitv1.Trait{ + Enabled: ptr.To(true), + }, + Schedule: "0 0/2 * * ?", + Components: "cron", + }, traits.Cron) +} diff --git a/pkg/trait/environment.go b/pkg/trait/environment.go index c1db2aec84..da49b26f76 100644 --- a/pkg/trait/environment.go +++ b/pkg/trait/environment.go @@ -59,9 +59,6 @@ const ( func newEnvironmentTrait() Trait { return &environmentTrait{ BasePlatformTrait: NewBasePlatformTrait(environmentTraitID, environmentTraitOrder), - EnvironmentTrait: traitv1.EnvironmentTrait{ - ContainerMeta: ptr.To(true), - }, } } diff --git a/pkg/trait/environment_test.go b/pkg/trait/environment_test.go index 05bf6cd8e6..d7ffdf0c84 100644 --- a/pkg/trait/environment_test.go +++ b/pkg/trait/environment_test.go @@ -43,8 +43,9 @@ func TestDefaultEnvironment(t *testing.T) { env := mockEnvironment(catalog) env.Platform.ResyncStatusFullConfig() - conditions, err := NewEnvironmentTestCatalog().apply(&env) + conditions, traits, err := NewEnvironmentTestCatalog().apply(&env) require.NoError(t, err) + assert.Empty(t, traits) assert.NotEmpty(t, conditions) ns := false @@ -92,8 +93,9 @@ func TestEnabledContainerMetaDataEnvVars(t *testing.T) { } env.Platform.ResyncStatusFullConfig() - conditions, err := NewEnvironmentTestCatalog().apply(&env) + conditions, traits, err := NewEnvironmentTestCatalog().apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) ns := false @@ -132,8 +134,9 @@ func TestDisabledContainerMetaDataEnvVars(t *testing.T) { env.Platform.ResyncStatusFullConfig() - conditions, err := NewEnvironmentTestCatalog().apply(&env) + conditions, traits, err := NewEnvironmentTestCatalog().apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) ns := false @@ -171,8 +174,9 @@ func TestCustomEnvVars(t *testing.T) { } env.Platform.ResyncStatusFullConfig() - conditions, err := NewEnvironmentTestCatalog().apply(&env) + conditions, traits, err := NewEnvironmentTestCatalog().apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) userK1 := false @@ -205,8 +209,9 @@ func TestValueSourceEnvVars(t *testing.T) { } env.Platform.ResyncStatusFullConfig() - conditions, err := NewEnvironmentTestCatalog().apply(&env) + conditions, traits, err := NewEnvironmentTestCatalog().apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) userK1 := false diff --git a/pkg/trait/health.go b/pkg/trait/health.go index 98d87b046d..fe4e4bc0dd 100644 --- a/pkg/trait/health.go +++ b/pkg/trait/health.go @@ -47,11 +47,6 @@ type healthTrait struct { func newHealthTrait() Trait { return &healthTrait{ BaseTrait: NewBaseTrait(healthTraitID, healthTraitOrder), - HealthTrait: traitv1.HealthTrait{ - LivenessScheme: string(corev1.URISchemeHTTP), - ReadinessScheme: string(corev1.URISchemeHTTP), - StartupScheme: string(corev1.URISchemeHTTP), - }, } } @@ -156,12 +151,36 @@ func (t *healthTrait) setProbes(container *corev1.Container, port *intstr.IntOrS return nil } +func (t *healthTrait) getLivenessScheme() corev1.URIScheme { + if t.LivenessScheme == "" { + return corev1.URISchemeHTTP + } + + return corev1.URIScheme(t.LivenessScheme) +} + +func (t *healthTrait) getReadinessScheme() corev1.URIScheme { + if t.ReadinessScheme == "" { + return corev1.URISchemeHTTP + } + + return corev1.URIScheme(t.ReadinessScheme) +} + +func (t *healthTrait) getStartupScheme() corev1.URIScheme { + if t.StartupScheme == "" { + return corev1.URISchemeHTTP + } + + return corev1.URIScheme(t.StartupScheme) +} + func (t *healthTrait) newLivenessProbe(port *intstr.IntOrString, path string) *corev1.Probe { p := corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Path: path, - Scheme: corev1.URIScheme(t.LivenessScheme), + Scheme: t.getLivenessScheme(), }, }, InitialDelaySeconds: t.LivenessInitialDelay, @@ -183,7 +202,7 @@ func (t *healthTrait) newReadinessProbe(port *intstr.IntOrString, path string) * ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Path: path, - Scheme: corev1.URIScheme(t.ReadinessScheme), + Scheme: t.getReadinessScheme(), }, }, InitialDelaySeconds: t.ReadinessInitialDelay, @@ -205,7 +224,7 @@ func (t *healthTrait) newStartupProbe(port *intstr.IntOrString, path string) *co ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Path: path, - Scheme: corev1.URIScheme(t.StartupScheme), + Scheme: t.getStartupScheme(), }, }, InitialDelaySeconds: t.StartupInitialDelay, diff --git a/pkg/trait/health_test.go b/pkg/trait/health_test.go index e60e8fd150..c9092ef6b4 100644 --- a/pkg/trait/health_test.go +++ b/pkg/trait/health_test.go @@ -90,7 +90,7 @@ func TestHealthTrait(t *testing.T) { Resources: kubernetes.NewCollection(), } environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) d := environment.Resources.GetDeploymentForIntegration(environment.Integration) @@ -108,7 +108,7 @@ func TestHealthTrait(t *testing.T) { environment.Integration.Spec.Traits.Health.StartupProbeEnabled = ptr.To(true) environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) d = environment.Resources.GetDeploymentForIntegration(environment.Integration) assert.NotNil(t, d) diff --git a/pkg/trait/ingress.go b/pkg/trait/ingress.go index 6ee6a4c1d8..d78a6248e6 100644 --- a/pkg/trait/ingress.go +++ b/pkg/trait/ingress.go @@ -32,6 +32,9 @@ import ( const ( ingressTraitID = "ingress" ingressTraitOrder = 2400 + + defaultPath = "/" + defaultPathTypePrefix = networkingv1.PathTypePrefix ) type ingressTrait struct { @@ -42,15 +45,6 @@ type ingressTrait struct { func newIngressTrait() Trait { return &ingressTrait{ BaseTrait: NewBaseTrait(ingressTraitID, ingressTraitOrder), - IngressTrait: traitv1.IngressTrait{ - IngressClassName: "", - Annotations: map[string]string{}, - Host: "", - Path: "/", - PathType: ptrFrom(networkingv1.PathTypePrefix), - TLSHosts: []string{}, - TLSSecretName: "", - }, } } @@ -88,7 +82,7 @@ func (t *ingressTrait) Configure(e *Environment) (bool, *TraitCondition, error) func (t *ingressTrait) Apply(e *Environment) error { service := e.Resources.GetUserServiceForIntegration(e.Integration) if service == nil { - return errors.New("cannot Apply ingress trait: no target service") + return errors.New("cannot apply ingress trait: no target service") } ingress := networkingv1.Ingress{ @@ -109,8 +103,8 @@ func (t *ingressTrait) Apply(e *Environment) error { HTTP: &networkingv1.HTTPIngressRuleValue{ Paths: []networkingv1.HTTPIngressPath{ { - Path: t.Path, - PathType: t.PathType, + Path: t.getPath(), + PathType: t.getPathType(), Backend: networkingv1.IngressBackend{ Service: &networkingv1.IngressServiceBackend{ Name: service.Name, @@ -153,3 +147,19 @@ func (t *ingressTrait) Apply(e *Environment) error { return nil } + +func (t *ingressTrait) getPath() string { + if t.Path == "" { + return defaultPath + } + + return t.Path +} + +func (t *ingressTrait) getPathType() *networkingv1.PathType { + if t.PathType == nil { + return ptr.To(defaultPathTypePrefix) + } + + return t.PathType +} diff --git a/pkg/trait/ingress_test.go b/pkg/trait/ingress_test.go index d07aa57ba6..6635bd1249 100644 --- a/pkg/trait/ingress_test.go +++ b/pkg/trait/ingress_test.go @@ -95,7 +95,7 @@ func TestApplyIngressTraitWithoutUserServiceDoesNotSucceed(t *testing.T) { err := ingressTrait.Apply(environment) require.Error(t, err) - assert.Equal(t, "cannot Apply ingress trait: no target service", err.Error()) + assert.Equal(t, "cannot apply ingress trait: no target service", err.Error()) assert.Len(t, environment.Resources.Items(), 0) } diff --git a/pkg/trait/istio.go b/pkg/trait/istio.go index 93169ddbc1..b61a4d1dd5 100644 --- a/pkg/trait/istio.go +++ b/pkg/trait/istio.go @@ -43,14 +43,13 @@ type istioTrait struct { const ( istioSidecarInjectAnnotation = "sidecar.istio.io/inject" istioOutboundIPRangesAnnotation = "traffic.sidecar.istio.io/includeOutboundIPRanges" + + defaultAllow = "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" ) func newIstioTrait() Trait { return &istioTrait{ BaseTrait: NewBaseTrait(istioTraitID, istioTraitOrder), - IstioTrait: traitv1.IstioTrait{ - Allow: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16", - }, } } @@ -63,7 +62,7 @@ func (t *istioTrait) Configure(e *Environment) (bool, *TraitCondition, error) { } func (t *istioTrait) Apply(e *Environment) error { - if t.Allow != "" { + if t.getAllow() != "" { e.Resources.VisitDeployment(func(d *appsv1.Deployment) { d.Spec.Template.Annotations = t.injectIstioAnnotation(d.Spec.Template.Annotations, true) }) @@ -78,7 +77,7 @@ func (t *istioTrait) injectIstioAnnotation(annotations map[string]string, includ if annotations == nil { annotations = make(map[string]string) } - annotations[istioOutboundIPRangesAnnotation] = t.Allow + annotations[istioOutboundIPRangesAnnotation] = t.getAllow() if includeInject { annotations[istioSidecarInjectAnnotation] = boolean.TrueString } @@ -87,3 +86,11 @@ func (t *istioTrait) injectIstioAnnotation(annotations map[string]string, includ } return annotations } + +func (t *istioTrait) getAllow() string { + if t.Allow == "" { + return defaultAllow + } + + return t.Allow +} diff --git a/pkg/trait/istio_test.go b/pkg/trait/istio_test.go index e6f63df4c5..13d2369e23 100644 --- a/pkg/trait/istio_test.go +++ b/pkg/trait/istio_test.go @@ -102,8 +102,9 @@ func TestIstioInject(t *testing.T) { } env := NewIstioTestEnv(t, &d, &s, true) - conditions, err := env.Catalog.apply(&env) + conditions, traits, err := env.Catalog.apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.Empty(t, s.Spec.ConfigurationSpec.Template.Annotations[istioSidecarInjectAnnotation]) assert.NotEmpty(t, d.Spec.Template.Annotations[istioSidecarInjectAnnotation]) @@ -127,8 +128,9 @@ func TestIstioForcedInjectTrue(t *testing.T) { env.Integration.Spec.Traits.Istio.Enabled = ptr.To(true) env.Integration.Spec.Traits.Istio.Inject = ptr.To(true) - conditions, err := env.Catalog.apply(&env) + conditions, traits, err := env.Catalog.apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.Equal(t, boolean.TrueString, s.Spec.ConfigurationSpec.Template.Annotations[istioSidecarInjectAnnotation]) assert.Equal(t, boolean.TrueString, d.Spec.Template.Annotations[istioSidecarInjectAnnotation]) @@ -152,8 +154,9 @@ func TestIstioForcedInjectFalse(t *testing.T) { env.Integration.Spec.Traits.Istio.Enabled = ptr.To(true) env.Integration.Spec.Traits.Istio.Inject = ptr.To(false) - conditions, err := env.Catalog.apply(&env) + conditions, traits, err := env.Catalog.apply(&env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.Equal(t, boolean.FalseString, s.Spec.ConfigurationSpec.Template.Annotations[istioSidecarInjectAnnotation]) assert.Equal(t, boolean.FalseString, d.Spec.Template.Annotations[istioSidecarInjectAnnotation]) @@ -175,8 +178,9 @@ func TestIstioDisabled(t *testing.T) { env := NewIstioTestEnv(t, &d, &s, false) - conditions, err := env.Catalog.apply(&env) + conditions, traits, err := env.Catalog.apply(&env) require.NoError(t, err) + assert.Empty(t, traits) assert.NotEmpty(t, conditions) assert.NotContains(t, env.ExecutedTraits, "istio") } diff --git a/pkg/trait/jolokia.go b/pkg/trait/jolokia.go index d5f602ea66..cf4dc6959e 100644 --- a/pkg/trait/jolokia.go +++ b/pkg/trait/jolokia.go @@ -45,9 +45,6 @@ type jolokiaTrait struct { func newJolokiaTrait() Trait { return &jolokiaTrait{ BaseTrait: NewBaseTrait(jolokiaTraitID, jolokiaTraitOrder), - JolokiaTrait: traitv1.JolokiaTrait{ - Port: defaultJolokiaPort, - }, } } @@ -107,7 +104,7 @@ func (t *jolokiaTrait) Apply(e *Environment) error { t.addToJolokiaOptions(options, "extendedClientCheck", t.ExtendedClientCheck) t.addToJolokiaOptions(options, "host", t.Host) t.addToJolokiaOptions(options, "password", t.Password) - t.addToJolokiaOptions(options, "port", t.Port) + t.addToJolokiaOptions(options, "port", t.getPort()) t.addToJolokiaOptions(options, "protocol", t.Protocol) t.addToJolokiaOptions(options, "user", t.User) t.addToJolokiaOptions(options, "useSslClientAuthentication", t.UseSslClientAuthentication) @@ -131,7 +128,7 @@ func (t *jolokiaTrait) Apply(e *Environment) error { containerPort := corev1.ContainerPort{ Name: "jolokia", - ContainerPort: int32(t.Port), + ContainerPort: int32(t.getPort()), Protocol: corev1.ProtocolTCP, } @@ -147,6 +144,14 @@ func (t *jolokiaTrait) Apply(e *Environment) error { return nil } +func (t *jolokiaTrait) getPort() int { + if t.Port == 0 { + return defaultJolokiaPort + } + + return t.Port +} + func (t *jolokiaTrait) setDefaultJolokiaOption(options map[string]string, option interface{}, key string, value interface{}) { // Do not override existing option if _, ok := options[key]; ok { diff --git a/pkg/trait/jolokia_test.go b/pkg/trait/jolokia_test.go index b9ffa492b2..cf9b06fdf0 100644 --- a/pkg/trait/jolokia_test.go +++ b/pkg/trait/jolokia_test.go @@ -53,10 +53,11 @@ func TestApplyJolokiaTraitNominalShouldSucceed(t *testing.T) { container := environment.Resources.GetContainerByName(defaultContainerName) assert.NotNil(t, container) - assert.Equal(t, container.Args, []string{ + assert.Equal(t, []string{ "-javaagent:dependencies/lib/main/org.jolokia.jolokia-agent-jvm-1.7.1.jar=discoveryEnabled=false,host=*,port=8778", "-cp", "dependencies/lib/main/org.jolokia.jolokia-agent-jvm-1.7.1.jar", - }) + }, + container.Args) assert.Len(t, container.Ports, 1) containerPort := container.Ports[0] diff --git a/pkg/trait/jvm.go b/pkg/trait/jvm.go index 0a28ba036d..096d315308 100644 --- a/pkg/trait/jvm.go +++ b/pkg/trait/jvm.go @@ -57,9 +57,6 @@ type jvmTrait struct { func newJvmTrait() Trait { return &jvmTrait{ BaseTrait: NewBaseTrait(jvmTraitID, jvmTraitOrder), - JVMTrait: traitv1.JVMTrait{ - DebugAddress: "*:5005", - }, } } @@ -230,6 +227,7 @@ func (t *jvmTrait) enableDebug(e *Environment) string { } meta.Labels["camel.apache.org/debug"] = "true" }) + t.DebugAddress = "*:5005" return fmt.Sprintf("-agentlib:jdwp=transport=dt_socket,server=y,suspend=%s,address=%s", suspend, t.DebugAddress) diff --git a/pkg/trait/kamelets.go b/pkg/trait/kamelets.go index fd764264b6..328b432664 100644 --- a/pkg/trait/kamelets.go +++ b/pkg/trait/kamelets.go @@ -48,8 +48,6 @@ const ( contentKey = "content" KameletLocationProperty = "camel.component.kamelet.location" - kameletLabel = "camel.apache.org/kamelet" - kameletConfigurationLabel = "camel.apache.org/kamelet.configuration" kameletMountPointAnnotation = "camel.apache.org/kamelet.mount-point" ) @@ -76,9 +74,6 @@ func (t *kameletsTrait) Configure(e *Environment) (bool, *TraitCondition, error) if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization) && !e.IntegrationInRunningPhases() { return false, nil, nil } - if t.MountPoint == "" { - t.MountPoint = filepath.Join(camel.BasePath, "kamelets") - } if ptr.Deref(t.Auto, true) { var kamelets []string _, err := e.ConsumeMeta(func(meta metadata.IntegrationMetadata) bool { @@ -223,7 +218,7 @@ func (t *kameletsTrait) addKamelets(e *Environment) error { e.ApplicationProperties = map[string]string{} } for _, cm := range bundleConfigmaps { - kameletMountPoint := fmt.Sprintf("%s/%s", t.MountPoint, cm.Name) + kameletMountPoint := fmt.Sprintf("%s/%s", t.getMountPoint(), cm.Name) cm.Annotations[kameletMountPointAnnotation] = kameletMountPoint e.Resources.Add(cm) if e.ApplicationProperties[KameletLocationProperty] == "" { @@ -300,6 +295,14 @@ func (t *kameletsTrait) getKameletKeys(withVersion bool) []string { return answer } +func (t *kameletsTrait) getMountPoint() string { + if t.MountPoint == "" { + return filepath.Join(camel.BasePath, "kamelets") + } + + return t.MountPoint +} + func getKameletKey(item string, withVersion bool) string { i := strings.Trim(item, " \t\"") if strings.Contains(i, "/") { diff --git a/pkg/trait/kamelets_test.go b/pkg/trait/kamelets_test.go index ac7ad9411c..2b43d40afa 100644 --- a/pkg/trait/kamelets_test.go +++ b/pkg/trait/kamelets_test.go @@ -22,6 +22,7 @@ import ( "testing" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" "github.com/apache/camel-k/v2/pkg/util/camel" "github.com/apache/camel-k/v2/pkg/util/kubernetes" @@ -695,3 +696,55 @@ func TestKameletSyntheticKitAutoConditionFalse(t *testing.T) { }) assert.Nil(t, kameletsBundle) } + +func TestKameletAuto(t *testing.T) { + flow := ` +- from: + uri: kamelet:timer + steps: + - to: kamelet:none +` + trait, environment := createKameletsTestEnvironment( + flow, + &v1.Kamelet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "timer", + }, + Spec: v1.KameletSpec{ + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + }, + }, + }, + &v1.Kamelet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "none", + }, + Spec: v1.KameletSpec{ + KameletSpecBase: v1.KameletSpecBase{ + Template: templateOrFail(map[string]interface{}{ + "from": map[string]interface{}{ + "uri": "timer:tick", + }, + }), + }, + }, + }) + + enabled, condition, err := trait.Configure(environment) + require.NoError(t, err) + assert.True(t, enabled) + assert.Nil(t, condition) + + err = trait.Apply(environment) + require.NoError(t, err) + assert.Equal(t, traitv1.KameletsTrait{ + List: "none,timer", + }, trait.KameletsTrait) +} diff --git a/pkg/trait/knative.go b/pkg/trait/knative.go index 9f52337284..19439453d3 100644 --- a/pkg/trait/knative.go +++ b/pkg/trait/knative.go @@ -80,25 +80,7 @@ func (t *knativeTrait) Configure(e *Environment) (bool, *TraitCondition, error) return false, nil, nil } - knativeInstalled, err := knativeutil.IsEventingInstalled(e.Client) - if err != nil { - return false, nil, err - } - - if !ptr.Deref(t.Auto, true) { - if !knativeInstalled { - return false, NewIntegrationCondition( - "Knative", - v1.IntegrationConditionKnativeAvailable, - corev1.ConditionFalse, - v1.IntegrationConditionKnativeNotInstalledReason, - "integration cannot run. Knative is not installed in the cluster", - ), fmt.Errorf("integration cannot run. Knative is not installed in the cluster") - } - return true, nil, nil - } - - _, err = e.ConsumeMeta(func(meta metadata.IntegrationMetadata) bool { + _, err := e.ConsumeMeta(func(meta metadata.IntegrationMetadata) bool { if len(t.ChannelSources) == 0 { t.ChannelSources = filterMetaItems(meta, knativeapi.CamelServiceTypeChannel, "from") } @@ -123,30 +105,37 @@ func (t *knativeTrait) Configure(e *Environment) (bool, *TraitCondition, error) return false, nil, err } - hasKnativeEndpoint := len(t.ChannelSources) > 0 || len(t.ChannelSinks) > 0 || len(t.EndpointSources) > 0 || len(t.EndpointSinks) > 0 || len(t.EventSources) > 0 || len(t.EventSinks) > 0 - - if !hasKnativeEndpoint && !ptr.Deref(t.Enabled, false) { - return false, nil, nil - } - if !knativeInstalled { - return false, NewIntegrationCondition( - "Knative", - v1.IntegrationConditionKnativeAvailable, - corev1.ConditionFalse, - v1.IntegrationConditionKnativeNotInstalledReason, - "integration cannot run. Knative is not installed in the cluster", - ), fmt.Errorf("integration cannot run. Knative is not installed in the cluster") + if t.Enabled == nil { + // If the trait is enabled, then, we can skip this optimization + hasKnativeEndpoint := len(t.ChannelSources) > 0 || len(t.ChannelSinks) > 0 || + len(t.EndpointSources) > 0 || len(t.EndpointSinks) > 0 || + len(t.EventSources) > 0 || len(t.EventSinks) > 0 + t.Enabled = ptr.To(hasKnativeEndpoint) } - if t.FilterSourceChannels == nil { - // Filtering is no longer used by default - t.FilterSourceChannels = ptr.To(false) - } - if t.SinkBinding == nil { - allowed := t.isSinkBindingAllowed(e) - t.SinkBinding = &allowed + + if ptr.Deref(t.Enabled, false) { + // Verify if Knative eventing is installed + knativeInstalled, err := knativeutil.IsEventingInstalled(e.Client) + if err != nil { + return false, nil, err + } + if !knativeInstalled { + return false, NewIntegrationCondition( + "Knative", + v1.IntegrationConditionKnativeAvailable, + corev1.ConditionFalse, + v1.IntegrationConditionKnativeNotInstalledReason, + "integration cannot run. Knative is not installed in the cluster", + ), fmt.Errorf("integration cannot run. Knative is not installed in the cluster") + } + + if t.SinkBinding == nil { + allowed := t.isSinkBindingAllowed(e) + t.SinkBinding = &allowed + } } - return true, nil, nil + return ptr.Deref(t.Enabled, false), nil, nil } func filterMetaItems(meta metadata.IntegrationMetadata, cst knativeapi.CamelServiceType, uriType string) []string { @@ -158,6 +147,9 @@ func filterMetaItems(meta metadata.IntegrationMetadata, cst knativeapi.CamelServ uris = meta.ToURIs } items = append(items, knativeutil.FilterURIs(uris, cst)...) + if len(items) == 0 { + return nil + } sort.Strings(items) return items } diff --git a/pkg/trait/knative_service.go b/pkg/trait/knative_service.go index 3d55db707a..0a53459923 100644 --- a/pkg/trait/knative_service.go +++ b/pkg/trait/knative_service.go @@ -61,9 +61,6 @@ var _ ControllerStrategySelector = &knativeServiceTrait{} func newKnativeServiceTrait() Trait { return &knativeServiceTrait{ BaseTrait: NewBaseTrait(knativeServiceTraitID, knativeServiceTraitOrder), - KnativeServiceTrait: traitv1.KnativeServiceTrait{ - Annotations: map[string]string{}, - }, } } @@ -92,13 +89,7 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, *TraitCondition, if e.Resources.GetDeploymentForIntegration(e.Integration) != nil { // A controller is already present for the integration - return false, NewIntegrationCondition( - "KnativeService", - v1.IntegrationConditionKnativeServiceAvailable, - corev1.ConditionFalse, - v1.IntegrationConditionKnativeServiceNotAvailableReason, - fmt.Sprintf("different controller strategy used (%s)", string(ControllerStrategyDeployment)), - ), nil + return false, nil, nil } strategy, err := e.DetermineControllerStrategy() @@ -111,22 +102,15 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, *TraitCondition, err.Error(), ), err } - if strategy != ControllerStrategyKnativeService { - return false, NewIntegrationCondition( - "KnativeService", - v1.IntegrationConditionKnativeServiceAvailable, - corev1.ConditionFalse, - v1.IntegrationConditionKnativeServiceNotAvailableReason, - fmt.Sprintf("different controller strategy used (%s)", string(strategy)), - ), nil - } - if e.IntegrationInPhase(v1.IntegrationPhaseRunning, v1.IntegrationPhaseError) { + if strategy == ControllerStrategyKnativeService { + t.Enabled = ptr.To(true) + } else if e.IntegrationInPhase(v1.IntegrationPhaseRunning, v1.IntegrationPhaseError) { condition := e.Integration.Status.GetCondition(v1.IntegrationConditionKnativeServiceAvailable) - return condition != nil && condition.Status == corev1.ConditionTrue, nil, nil + t.Enabled = ptr.To(condition != nil && condition.Status == corev1.ConditionTrue) } - return true, nil, nil + return ptr.Deref(t.Enabled, false), nil, nil } func (t *knativeServiceTrait) Apply(e *Environment) error { diff --git a/pkg/trait/knative_service_test.go b/pkg/trait/knative_service_test.go index 24eae32635..80cf2628cb 100644 --- a/pkg/trait/knative_service_test.go +++ b/pkg/trait/knative_service_test.go @@ -124,7 +124,7 @@ func TestKnativeService(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -252,7 +252,7 @@ func TestKnativeServiceWithCustomContainerName(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -337,7 +337,7 @@ func TestKnativeServiceWithRest(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -405,7 +405,7 @@ func TestKnativeServiceNotApplicable(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -480,7 +480,7 @@ func TestKnativeServiceNoServingAvailable(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -606,7 +606,7 @@ func createKnativeServiceTestEnvironment(t *testing.T, trait *traitv1.KnativeSer environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(environment) + _, _, err = traitCatalog.apply(environment) require.NoError(t, err) return environment @@ -619,7 +619,7 @@ func TestServiceAnnotation(t *testing.T) { }) traitsCatalog := environment.Catalog - _, err := traitsCatalog.apply(environment) + _, _, err := traitsCatalog.apply(environment) require.NoError(t, err) @@ -629,5 +629,77 @@ func TestServiceAnnotation(t *testing.T) { assert.NotNil(t, service) assert.True(t, reflect.DeepEqual(service.GetAnnotations(), annotationsTest)) +} +func TestKnativeServiceAuto(t *testing.T) { + catalog, err := camel.DefaultCatalog() + require.NoError(t, err) + + client, _ := test.NewFakeClient() + traitCatalog := NewCatalog(nil) + + compressedRoute, err := gzip.CompressBase64([]byte(`from("platform-http:test").log("hello")`)) + require.NoError(t, err) + + environment := Environment{ + CamelCatalog: catalog, + Catalog: traitCatalog, + Client: client, + Integration: &v1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: KnativeServiceTestName, + Namespace: KnativeServiceTestNamespace, + }, + Status: v1.IntegrationStatus{ + Phase: v1.IntegrationPhaseDeploying, + }, + Spec: v1.IntegrationSpec{ + Profile: v1.TraitProfileKnative, + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "routes.js", + Content: string(compressedRoute), + Compression: true, + }, + Language: v1.LanguageJavaScript, + }, + }, + }, + }, + IntegrationKit: &v1.IntegrationKit{ + Status: v1.IntegrationKitStatus{ + Phase: v1.IntegrationKitPhaseReady, + }, + }, + Platform: &v1.IntegrationPlatform{ + Spec: v1.IntegrationPlatformSpec{ + Cluster: v1.IntegrationPlatformClusterOpenShift, + Build: v1.IntegrationPlatformBuildSpec{ + PublishStrategy: v1.IntegrationPlatformBuildPublishStrategyS2I, + Registry: v1.RegistrySpec{Address: "registry"}, + RuntimeVersion: catalog.Runtime.Version, + }, + }, + Status: v1.IntegrationPlatformStatus{ + Phase: v1.IntegrationPlatformPhaseReady, + }, + }, + EnvVars: make([]corev1.EnvVar, 0), + ExecutedTraits: make([]Trait, 0), + Resources: kubernetes.NewCollection(), + } + environment.Platform.ResyncStatusFullConfig() + + // don't care about conditions in this unit test + _, traits, err := traitCatalog.apply(&environment) + + require.NoError(t, err) + assert.NotEmpty(t, traits) + assert.NotEmpty(t, environment.ExecutedTraits) + assert.Equal(t, &traitv1.KnativeServiceTrait{ + Trait: traitv1.Trait{ + Enabled: ptr.To(true), + }, + }, traits.KnativeService) } diff --git a/pkg/trait/knative_test.go b/pkg/trait/knative_test.go index 7a8a7e45f0..7a6ebe49f0 100644 --- a/pkg/trait/knative_test.go +++ b/pkg/trait/knative_test.go @@ -355,7 +355,7 @@ func TestKnativeTriggerExplicitFilterConfig(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -455,7 +455,7 @@ func TestKnativeTriggerExplicitFilterConfigNoEventTypeFilter(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -552,7 +552,7 @@ func TestKnativeTriggerDefaultEventTypeFilter(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -649,7 +649,7 @@ func TestKnativeTriggerDefaultEventTypeFilterDisabled(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -749,7 +749,7 @@ func TestKnativeMultipleTrigger(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -887,7 +887,7 @@ func TestKnativeMultipleTriggerAdditionalFilterConfig(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -1022,7 +1022,7 @@ func TestKnativeTriggerNoEventType(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -1119,7 +1119,7 @@ func TestKnativeTriggerNoServingAvailable(t *testing.T) { environment.Platform.ResyncStatusFullConfig() // don't care about conditions in this unit test - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -1180,7 +1180,7 @@ func TestKnativePlatformHttpConfig(t *testing.T) { err = tc.Configure(&environment) require.NoError(t, err) - _, err = tc.apply(&environment) + _, _, err = tc.apply(&environment) require.NoError(t, err) assert.Contains(t, environment.Integration.Status.Capabilities, v1.CapabilityPlatformHTTP) }) @@ -1226,8 +1226,9 @@ func TestKnativePlatformHttpDependencies(t *testing.T) { err = tc.Configure(&environment) require.NoError(t, err) - conditions, err := tc.apply(&environment) + conditions, traits, err := tc.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.Contains(t, environment.Integration.Status.Capabilities, v1.CapabilityPlatformHTTP) assert.Contains(t, environment.Integration.Status.Dependencies, "mvn:org.apache.camel.quarkus:camel-quarkus-platform-http") @@ -1653,7 +1654,7 @@ func TestKnativeSinkBinding(t *testing.T) { err = tc.Configure(&environment) require.NoError(t, err) - _, err = tc.apply(&environment) + _, _, err = tc.apply(&environment) require.NoError(t, err) baseProp := "camel.component.knative.environment.resources[0]" assert.Equal(t, "channel-sink-1", environment.ApplicationProperties[baseProp+".name"]) @@ -1806,3 +1807,63 @@ func createEnvironmentMissingEventingCRDs() *Environment { return environment } + +func TestKnativeAutoConfiguration(t *testing.T) { + client, _ := test.NewFakeClient() + replicas := int32(3) + catalog, _ := camel.QuarkusCatalog() + environment := &Environment{ + CamelCatalog: catalog, + Catalog: NewCatalog(nil), + Client: client, + Integration: &v1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "integration-name", + }, + Spec: v1.IntegrationSpec{ + Replicas: &replicas, + Traits: v1.Traits{}, + }, + Status: v1.IntegrationStatus{ + Phase: v1.IntegrationPhaseInitialization, + }, + }, + Platform: &v1.IntegrationPlatform{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "namespace", + }, + Spec: v1.IntegrationPlatformSpec{ + Cluster: v1.IntegrationPlatformClusterKubernetes, + Profile: v1.TraitProfileKubernetes, + }, + }, + Resources: kubernetes.NewCollection(), + ApplicationProperties: make(map[string]string), + } + environment.Platform.ResyncStatusFullConfig() + + trait, _ := newKnativeTrait().(*knativeTrait) + environment.Integration.Spec.Sources = []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "test.java", + Content: ` + from("knative:channel/test").to("log:${body}; + `, + }, + Language: v1.LanguageJavaSource, + }, + } + + configured, condition, err := trait.Configure(environment) + require.NoError(t, err) + assert.Nil(t, condition) + assert.True(t, configured) + err = trait.Apply(environment) + require.NoError(t, err) + expectedTrait, _ := newKnativeTrait().(*knativeTrait) + expectedTrait.Enabled = ptr.To(true) + expectedTrait.SinkBinding = ptr.To(false) + expectedTrait.ChannelSources = []string{"knative:channel/test"} + assert.Equal(t, expectedTrait, trait) +} diff --git a/pkg/trait/logging.go b/pkg/trait/logging.go index fdd6656a47..8d88d21fcc 100644 --- a/pkg/trait/logging.go +++ b/pkg/trait/logging.go @@ -44,13 +44,10 @@ type loggingTrait struct { func newLoggingTraitTrait() Trait { return &loggingTrait{ BaseTrait: NewBaseTrait(loggingTraitID, loggingTraitOrder), - LoggingTrait: traitv1.LoggingTrait{ - Level: defaultLogLevel, - }, } } -func (l loggingTrait) Configure(e *Environment) (bool, *TraitCondition, error) { +func (l *loggingTrait) Configure(e *Environment) (bool, *TraitCondition, error) { if e.Integration == nil { return false, nil, nil } @@ -61,7 +58,7 @@ func (l loggingTrait) Configure(e *Environment) (bool, *TraitCondition, error) { return e.IntegrationInRunningPhases(), nil, nil } -func (l loggingTrait) Apply(e *Environment) error { +func (l *loggingTrait) Apply(e *Environment) error { if e.CamelCatalog.Runtime.Capabilities["logging"].RuntimeProperties != nil { l.setCatalogConfiguration(e) } else { @@ -72,8 +69,8 @@ func (l loggingTrait) Apply(e *Environment) error { } // Deprecated: to be removed in future release in favor of func setCatalogConfiguration(). -func (l loggingTrait) setEnvConfiguration(e *Environment) { - envvar.SetVal(&e.EnvVars, envVarQuarkusLogLevel, l.Level) +func (l *loggingTrait) setEnvConfiguration(e *Environment) { + envvar.SetVal(&e.EnvVars, envVarQuarkusLogLevel, l.getLevel()) if l.Format != "" { envvar.SetVal(&e.EnvVars, envVarQuarkusLogConsoleFormat, l.Format) @@ -94,11 +91,11 @@ func (l loggingTrait) setEnvConfiguration(e *Environment) { } } -func (l loggingTrait) setCatalogConfiguration(e *Environment) { +func (l *loggingTrait) setCatalogConfiguration(e *Environment) { if e.ApplicationProperties == nil { e.ApplicationProperties = make(map[string]string) } - e.ApplicationProperties["camel.k.logging.level"] = l.Level + e.ApplicationProperties["camel.k.logging.level"] = l.getLevel() if l.Format != "" { e.ApplicationProperties["camel.k.logging.format"] = l.Format } @@ -121,3 +118,11 @@ func (l loggingTrait) setCatalogConfiguration(e *Environment) { } } } + +func (l *loggingTrait) getLevel() string { + if l.Level == "" { + return defaultLogLevel + } + + return l.Level +} diff --git a/pkg/trait/logging_test.go b/pkg/trait/logging_test.go index 7d77ff7e07..49a4ec878d 100644 --- a/pkg/trait/logging_test.go +++ b/pkg/trait/logging_test.go @@ -113,9 +113,10 @@ func NewLoggingTestCatalog() *Catalog { func TestEmptyLoggingTrait(t *testing.T) { env := createDefaultLoggingTestEnv(t) - conditions, err := NewLoggingTestCatalog().apply(env) + conditions, traits, err := NewLoggingTestCatalog().apply(env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, env.ExecutedTraits) @@ -135,9 +136,10 @@ func TestEmptyLoggingTrait(t *testing.T) { func TestJsonLoggingTrait(t *testing.T) { // When running, this log should look like "09:07:00 INFO (main) Profile prod activated." env := createLoggingTestEnv(t, true, true, true, "TRACE", "%d{HH:mm:ss} %-5p (%t) %s%e%n") - conditions, err := NewLoggingTestCatalog().apply(env) + conditions, traits, err := NewLoggingTestCatalog().apply(env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, env.ExecutedTraits) @@ -162,9 +164,10 @@ func TestDefaultQuarkusLogging(t *testing.T) { RuntimeProperties: nil, } env.EnvVars = []corev1.EnvVar{} - conditions, err := NewLoggingTestCatalog().apply(env) + conditions, traits, err := NewLoggingTestCatalog().apply(env) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, env.ExecutedTraits) diff --git a/pkg/trait/mount_test.go b/pkg/trait/mount_test.go index f23e1bbbc9..0a480e3147 100644 --- a/pkg/trait/mount_test.go +++ b/pkg/trait/mount_test.go @@ -43,9 +43,10 @@ func TestMountVolumesEmpty(t *testing.T) { environment.Integration.Spec.Traits = v1.Traits{} // empty traits environment.Platform.ResyncStatusFullConfig() - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("mount")) @@ -66,9 +67,10 @@ func TestMountVolumesIntegrationPhaseDeploying(t *testing.T) { environment := getNominalEnv(t, traitCatalog) environment.Platform.ResyncStatusFullConfig() - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("mount")) @@ -116,9 +118,10 @@ func TestEmptyDirVolumeIntegrationPhaseDeploying(t *testing.T) { EmptyDirs: []string{"my-empty-dir:/some/path"}, } environment.Platform.ResyncStatusFullConfig() - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("mount")) @@ -161,9 +164,10 @@ func TestMountVolumesIntegrationPhaseInitialization(t *testing.T) { environment.Integration.Status.Phase = v1.IntegrationPhaseInitialization environment.Platform.ResyncStatusFullConfig() - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.Empty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.Nil(t, environment.GetTrait("mount")) diff --git a/pkg/trait/platform.go b/pkg/trait/platform.go index cc9a04bbbb..d129e4bca6 100644 --- a/pkg/trait/platform.go +++ b/pkg/trait/platform.go @@ -39,6 +39,9 @@ const ( type platformTrait struct { BasePlatformTrait traitv1.PlatformTrait `property:",squash"` + // Parameters to be used internally + createDefault *bool + global *bool } func newPlatformTrait() Trait { @@ -62,17 +65,17 @@ func (t *platformTrait) Configure(e *Environment) (bool, *TraitCondition, error) if ocp, err := openshift.IsOpenShift(t.Client); err != nil { return false, nil, err } else if ocp { - t.CreateDefault = ptr.To(true) + t.createDefault = ptr.To(true) } else if addr, err := image.GetRegistryAddress(e.Ctx, t.Client); err != nil { return false, nil, err } else if addr != nil { - t.CreateDefault = ptr.To(true) + t.createDefault = ptr.To(true) } } if t.Global == nil { globalOperator := platform.IsCurrentOperatorGlobal() - t.Global = &globalOperator + t.global = &globalOperator } return true, nil, nil @@ -117,7 +120,7 @@ func (t *platformTrait) getOrCreatePlatform(e *Environment) (*v1.IntegrationPlat if err != nil && !apierrors.IsNotFound(err) { return nil, err } - if apierrors.IsNotFound(err) && ptr.Deref(t.CreateDefault, false) { + if apierrors.IsNotFound(err) && ptr.Deref(t.getCreateDefault(), false) { pl = t.createDefaultPlatform(e) e.Resources.Add(pl) @@ -146,7 +149,7 @@ func (t *platformTrait) createDefaultPlatform(e *Environment) *v1.IntegrationPla platformName = platform.DefaultPlatformName } namespace := e.Integration.Namespace - if ptr.Deref(t.Global, false) { + if ptr.Deref(t.getGlobal(), false) { operatorNamespace := platform.GetOperatorNamespace() if operatorNamespace != "" { namespace = operatorNamespace @@ -175,3 +178,19 @@ func (t *platformTrait) installViewerRole(e *Environment, itp *v1.IntegrationPla t.L.Infof("Cannot install global IntegrationPlatform viewer role in namespace '%s': skipping.", itp.Namespace) } } + +func (t *platformTrait) getCreateDefault() *bool { + if t.CreateDefault == nil { + return t.createDefault + } + + return t.CreateDefault +} + +func (t *platformTrait) getGlobal() *bool { + if t.Global == nil { + return t.global + } + + return t.Global +} diff --git a/pkg/trait/prometheus.go b/pkg/trait/prometheus.go index 4138ff8fd5..ba11aaa55a 100644 --- a/pkg/trait/prometheus.go +++ b/pkg/trait/prometheus.go @@ -44,9 +44,6 @@ type prometheusTrait struct { func newPrometheusTrait() Trait { return &prometheusTrait{ BaseTrait: NewBaseTrait(prometheusTraitID, prometheusTraitOrder), - PrometheusTrait: traitv1.PrometheusTrait{ - PodMonitor: ptr.To(true), - }, } } @@ -89,7 +86,7 @@ func (t *prometheusTrait) Apply(e *Environment) error { condition.Message = fmt.Sprintf("%s(%d)", container.Name, containerPort.ContainerPort) // Add the PodMonitor resource - if ptr.Deref(t.PodMonitor, false) { + if ptr.Deref(t.PodMonitor, true) { portName := containerPort.Name podMonitor, err := t.getPodMonitorFor(e, portName) if err != nil { diff --git a/pkg/trait/route.go b/pkg/trait/route.go index c35b83c879..710bb3a368 100644 --- a/pkg/trait/route.go +++ b/pkg/trait/route.go @@ -48,9 +48,6 @@ type routeTrait struct { func newRouteTrait() Trait { return &routeTrait{ BaseTrait: NewBaseTrait(routeTraitID, routeTraitOrder), - RouteTrait: traitv1.RouteTrait{ - Annotations: map[string]string{}, - }, } } diff --git a/pkg/trait/route_test.go b/pkg/trait/route_test.go index 376464fa68..2ab1b31065 100644 --- a/pkg/trait/route_test.go +++ b/pkg/trait/route_test.go @@ -207,8 +207,9 @@ func TestRoute_Default(t *testing.T) { environment := createTestRouteEnvironment(t, name) traitsCatalog := environment.Catalog - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("container")) @@ -243,8 +244,9 @@ func TestRoute_Disabled(t *testing.T) { "explicitly disabled", ) traitsCatalog := environment.Catalog - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.Contains(t, conditions, expectedCondition) assert.NotEmpty(t, environment.ExecutedTraits) assert.Nil(t, environment.GetTrait("route")) @@ -282,9 +284,10 @@ func TestRoute_Host(t *testing.T) { }, } - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("route")) @@ -313,9 +316,10 @@ func TestRoute_TLS_From_Secret_reencrypt(t *testing.T) { TLSDestinationCACertificateSecret: tlsMultipleSecretsName + "/" + tlsMultipleSecretsCert3Key, }, } - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("route")) @@ -349,7 +353,8 @@ func TestRoute_TLS_wrong_secret(t *testing.T) { TLSDestinationCACertificateSecret: "404", }, } - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) + assert.Empty(t, traits) assert.Empty(t, conditions) // there must be errors as the trait has wrong configuration require.Error(t, err) @@ -377,7 +382,8 @@ func TestRoute_TLS_secret_wrong_key(t *testing.T) { TLSCACertificateSecret: tlsMultipleSecretsName + "/foo", }, } - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) + assert.Empty(t, traits) assert.Empty(t, conditions) // there must be errors as the trait has wrong configuration require.Error(t, err) @@ -405,7 +411,8 @@ func TestRoute_TLS_secret_missing_key(t *testing.T) { TLSCACertificateSecret: tlsMultipleSecretsName, }, } - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) + assert.Empty(t, traits) assert.Empty(t, conditions) // there must be errors as the trait has wrong configuration require.Error(t, err) @@ -434,8 +441,9 @@ func TestRoute_TLS_reencrypt(t *testing.T) { TLSDestinationCACertificate: destinationCaCert, }, } - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("route")) @@ -468,8 +476,9 @@ func TestRoute_TLS_edge(t *testing.T) { TLSCACertificate: caCert, }, } - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("route")) @@ -500,8 +509,9 @@ func TestRoute_TLS_passthrough(t *testing.T) { TLSInsecureEdgeTerminationPolicy: string(routev1.InsecureEdgeTerminationPolicyAllow), }, } - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("route")) @@ -530,8 +540,9 @@ func TestRoute_WithCustomServicePort(t *testing.T) { } traitsCatalog := environment.Catalog - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("container")) @@ -564,8 +575,9 @@ func TestRouteAnnotation(t *testing.T) { } traitsCatalog := environment.Catalog - conditions, err := traitsCatalog.apply(environment) + conditions, traits, err := traitsCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) route := environment.Resources.GetRoute(func(r *routev1.Route) bool { diff --git a/pkg/trait/security_context.go b/pkg/trait/security_context.go index c43d90f980..d02c58a3f0 100644 --- a/pkg/trait/security_context.go +++ b/pkg/trait/security_context.go @@ -44,10 +44,6 @@ type securityContextTrait struct { func newSecurityContextTrait() Trait { return &securityContextTrait{ BasePlatformTrait: NewBasePlatformTrait(securityContextTraitID, securityContextTraitOder), - SecurityContextTrait: traitv1.SecurityContextTrait{ - RunAsNonRoot: ptr.To(defaultPodRunAsNonRoot), - SeccompProfileType: defaultPodSeccompProfileType, - }, } } @@ -66,7 +62,9 @@ func (t *securityContextTrait) Configure(e *Environment) (bool, *TraitCondition, if condition != nil && condition.Status == corev1.ConditionTrue { return false, NewIntegrationConditionPlatformDisabledWithMessage( "SecurityContext", - "pod security context is disabled for Knative Service. PodSecurityContext properties can affect non-user sidecar containers that come from Knative or your service mesh. Use container security context instead.", + "pod security context is disabled for Knative Service. "+ + "PodSecurityContext properties can affect non-user sidecar containers that come from Knative or your service mesh. "+ + "Use container security context instead.", ), nil } @@ -83,9 +81,9 @@ func (t *securityContextTrait) Apply(e *Environment) error { func (t *securityContextTrait) setSecurityContext(e *Environment, podSpec *corev1.PodSpec) error { sc := corev1.PodSecurityContext{ - RunAsNonRoot: t.RunAsNonRoot, + RunAsNonRoot: t.getRunAsNonRoot(), SeccompProfile: &corev1.SeccompProfile{ - Type: t.SeccompProfileType, + Type: t.getSeccompProfileType(), }, } @@ -123,3 +121,19 @@ func (t *securityContextTrait) getUser(e *Environment) (*int64, error) { return runAsUser, nil } + +func (t *securityContextTrait) getRunAsNonRoot() *bool { + if t.RunAsNonRoot == nil { + return ptr.To(defaultPodRunAsNonRoot) + } + + return t.RunAsNonRoot +} + +func (t *securityContextTrait) getSeccompProfileType() corev1.SeccompProfileType { + if t.SeccompProfileType == "" { + return defaultPodSeccompProfileType + } + + return t.SeccompProfileType +} diff --git a/pkg/trait/security_context_test.go b/pkg/trait/security_context_test.go index 9315657fa0..b9a49a2436 100644 --- a/pkg/trait/security_context_test.go +++ b/pkg/trait/security_context_test.go @@ -40,9 +40,10 @@ func TestDefaultPodKubernetesSecurityContextInitializationPhase(t *testing.T) { environment.Integration.Status.Phase = v1.IntegrationPhaseInitialization traitCatalog := NewCatalog(nil) - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.Empty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.Nil(t, environment.GetTrait("security-context")) @@ -52,9 +53,10 @@ func TestDefaultPodKubernetesSecurityContext(t *testing.T) { environment := createPodSettingContextEnvironment(t, v1.TraitProfileKubernetes) traitCatalog := NewCatalog(nil) - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.Empty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("deployment")) @@ -72,9 +74,10 @@ func TestDefaultPodOpenshiftSecurityContext(t *testing.T) { environment := createOpenshiftPodSettingContextEnvironment(t, v1.TraitProfileOpenShift) traitCatalog := NewCatalog(nil) - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("deployment")) @@ -97,9 +100,10 @@ func TestDefaultPodKnativeSecurityContext(t *testing.T) { } traitCatalog := NewCatalog(nil) - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.Nil(t, environment.GetTrait("deployment")) @@ -125,9 +129,10 @@ func TestUserPodSecurityContext(t *testing.T) { } traitCatalog := NewCatalog(nil) - conditions, err := traitCatalog.apply(environment) + conditions, traits, err := traitCatalog.apply(environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.NotEmpty(t, conditions) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait("deployment")) diff --git a/pkg/trait/service.go b/pkg/trait/service.go index 087f289dba..b114115f63 100644 --- a/pkg/trait/service.go +++ b/pkg/trait/service.go @@ -86,7 +86,8 @@ func (t *serviceTrait) Configure(e *Environment) (bool, *TraitCondition, error) ) return false, condition, err } - return exposeHTTPServices, nil, nil + + t.Enabled = ptr.To(exposeHTTPServices) } return ptr.Deref(t.Enabled, false), nil, nil diff --git a/pkg/trait/service_test.go b/pkg/trait/service_test.go index b61149115f..4d0f3f0859 100644 --- a/pkg/trait/service_test.go +++ b/pkg/trait/service_test.go @@ -109,7 +109,7 @@ func TestServiceWithDefaults(t *testing.T) { } environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -216,7 +216,7 @@ func TestService(t *testing.T) { } environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -303,7 +303,7 @@ func TestServiceWithCustomContainerName(t *testing.T) { } environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -394,7 +394,7 @@ func TestServiceWithNodePort(t *testing.T) { } environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(&environment) + _, _, err = traitCatalog.apply(&environment) require.NoError(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -503,9 +503,10 @@ func TestServiceWithKnativeServiceEnabled(t *testing.T) { "TraitConfiguration", "explicitly disabled by the platform: knative-service trait has priority over this trait", ) - conditions, err := traitCatalog.apply(&environment) + conditions, traits, err := traitCatalog.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.Contains(t, conditions, deploymentCondition) assert.Contains(t, conditions, serviceCondition) assert.NotEmpty(t, environment.ExecutedTraits) @@ -583,9 +584,10 @@ func TestServicesWithKnativeProfile(t *testing.T) { "TraitConfiguration", "explicitly disabled by the platform: knative-service trait has priority over this trait", ) - conditions, err := traitCatalog.apply(&environment) + conditions, traits, err := traitCatalog.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.Contains(t, conditions, deploymentCondition) assert.Contains(t, conditions, serviceCondition) assert.NotEmpty(t, environment.ExecutedTraits) @@ -664,11 +666,77 @@ func TestServiceWithKnativeServiceDisabledInIntegrationPlatform(t *testing.T) { "KnativeServiceNotAvailable", "explicitly disabled", ) - conditions, err := traitCatalog.apply(&environment) + conditions, traits, err := traitCatalog.apply(&environment) require.NoError(t, err) + assert.NotEmpty(t, traits) assert.Contains(t, conditions, expectedCondition) assert.NotEmpty(t, environment.ExecutedTraits) assert.NotNil(t, environment.GetTrait(serviceTraitID)) assert.Nil(t, environment.GetTrait(knativeServiceTraitID)) } + +func TestServiceAutoConfiguration(t *testing.T) { + catalog, err := camel.DefaultCatalog() + require.NoError(t, err) + + client, _ := test.NewFakeClient() + traitCatalog := NewCatalog(nil) + + compressedRoute, err := gzip.CompressBase64([]byte(`from("netty-http:test").log("hello")`)) + require.NoError(t, err) + + environment := Environment{ + CamelCatalog: catalog, + Catalog: traitCatalog, + Client: client, + Integration: &v1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: ServiceTestName, + Namespace: "ns", + }, + Status: v1.IntegrationStatus{ + Phase: v1.IntegrationPhaseDeploying, + }, + Spec: v1.IntegrationSpec{ + Profile: v1.TraitProfileKubernetes, + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "routes.js", + Content: string(compressedRoute), + Compression: true, + }, + Language: v1.LanguageJavaScript, + }, + }, + }, + }, + IntegrationKit: &v1.IntegrationKit{ + Status: v1.IntegrationKitStatus{ + Phase: v1.IntegrationKitPhaseReady, + }, + }, + Platform: &v1.IntegrationPlatform{ + Spec: v1.IntegrationPlatformSpec{ + Cluster: v1.IntegrationPlatformClusterOpenShift, + Build: v1.IntegrationPlatformBuildSpec{ + PublishStrategy: v1.IntegrationPlatformBuildPublishStrategyS2I, + Registry: v1.RegistrySpec{Address: "registry"}, + RuntimeVersion: catalog.Runtime.Version, + }, + }, + Status: v1.IntegrationPlatformStatus{ + Phase: v1.IntegrationPlatformPhaseReady, + }, + }, + EnvVars: make([]corev1.EnvVar, 0), + ExecutedTraits: make([]Trait, 0), + Resources: kubernetes.NewCollection(), + } + environment.Platform.ResyncStatusFullConfig() + + _, traits, err := traitCatalog.apply(&environment) + require.NoError(t, err) + assert.Equal(t, ptr.To(true), traits.Service.Enabled) +} diff --git a/pkg/trait/trait.go b/pkg/trait/trait.go index 5d9076c135..93c10a74aa 100644 --- a/pkg/trait/trait.go +++ b/pkg/trait/trait.go @@ -57,22 +57,28 @@ func Apply(ctx context.Context, c client.Client, integration *v1.Integration, ki environment.Catalog = catalog // invoke the trait framework to determine the needed resources - conditions, err := catalog.apply(environment) - // Conditions contains informative message coming from the trait execution and useful to be reported into it or ik CR + conditions, traits, err := catalog.apply(environment) + // WARNING: Conditions contains informative message coming from the trait execution and useful to be reported into it or ik CR // they must be applied before returning after an error for _, tc := range conditions { switch { case integration != nil: - // set an Integration condition integration.Status.SetCondition(tc.integrationCondition()) case kit != nil: - // set an IntegrationKit condition kit.Status.SetCondition(tc.integrationKitCondition()) } } if err != nil { return nil, fmt.Errorf("error during trait customization: %w", err) } + // Set the executed traits taking care to merge in order to avoid the distinct execution + // phase to clean up any previous executed trait + if integration != nil { + integration.Status.Traits = integration.Spec.Traits.DeepCopy() + if err := integration.Status.Traits.Merge(*traits); err != nil { + return nil, fmt.Errorf("error setting status traits: %w", err) + } + } postActionErrors := make([]error, 0) // execute post actions registered by traits @@ -172,7 +178,7 @@ func NewSyntheticEnvironment(ctx context.Context, c client.Client, integration * // set the catalog env.Catalog = catalog // we need to simulate the execution of the traits to fill certain values used later by monitoring - _, err := catalog.apply(&env) + _, _, err := catalog.apply(&env) if err != nil { return nil, fmt.Errorf("error during trait customization: %w", err) } diff --git a/pkg/trait/trait_catalog.go b/pkg/trait/trait_catalog.go index 867c758143..bdf62d009b 100644 --- a/pkg/trait/trait_catalog.go +++ b/pkg/trait/trait_catalog.go @@ -27,6 +27,7 @@ import ( "github.com/apache/camel-k/v2/pkg/client" "github.com/apache/camel-k/v2/pkg/util/log" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/json" ) // Catalog collects all information about traits in one place. @@ -86,10 +87,10 @@ func (c *Catalog) TraitsForProfile(profile v1.TraitProfile) []Trait { return res } -func (c *Catalog) apply(environment *Environment) ([]*TraitCondition, error) { +func (c *Catalog) apply(environment *Environment) ([]*TraitCondition, *v1.Traits, error) { traitsConditions := []*TraitCondition{} if err := c.Configure(environment); err != nil { - return traitsConditions, err + return traitsConditions, nil, err } traits := c.traitsFor(environment) environment.ConfiguredTraits = traits @@ -107,49 +108,89 @@ func (c *Catalog) apply(environment *Environment) ([]*TraitCondition, error) { traitsConditions = append(traitsConditions, condition) } if err != nil { - return traitsConditions, fmt.Errorf("%s trait configuration failed: %w", trait.ID(), err) + return traitsConditions, nil, fmt.Errorf("%s trait configuration failed: %w", trait.ID(), err) } - if enabled { err = trait.Apply(environment) if err != nil { - return traitsConditions, fmt.Errorf("%s trait execution failed: %w", trait.ID(), err) + return traitsConditions, nil, fmt.Errorf("%s trait execution failed: %w", trait.ID(), err) } environment.ExecutedTraits = append(environment.ExecutedTraits, trait) // execute post step processors for _, processor := range environment.PostStepProcessors { err := processor(environment) if err != nil { - return traitsConditions, fmt.Errorf("%s trait executing post step action failed: %w", trait.ID(), err) + return traitsConditions, nil, fmt.Errorf("%s trait executing post step action failed: %w", trait.ID(), err) } } } } - traitsConditions = append(traitsConditions, c.executedTraitCondition(environment.ExecutedTraits)) + cs, ts, err := c.executedTraitCondition(environment.ExecutedTraits) + if err != nil { + return traitsConditions, &ts, err + } + traitsConditions = append(traitsConditions, cs) if !applicable && environment.PlatformInPhase(v1.IntegrationPlatformPhaseReady) { - return traitsConditions, errors.New("no trait can be executed because of no ready platform found") + return traitsConditions, nil, errors.New("no trait can be executed because of no ready platform found") } for _, processor := range environment.PostProcessors { err := processor(environment) if err != nil { - return traitsConditions, fmt.Errorf("error executing post processor: %w", err) + return traitsConditions, nil, fmt.Errorf("error executing post processor: %w", err) } } - return traitsConditions, nil + return traitsConditions, &ts, nil } -func (c *Catalog) executedTraitCondition(executedTrait []Trait) *TraitCondition { +func (c *Catalog) executedTraitCondition(executedTrait []Trait) (*TraitCondition, v1.Traits, error) { + var traits v1.Traits + var traitMap = make(map[string]map[string]interface{}) traitIds := make([]string, 0) for _, trait := range executedTrait { + data, err := json.Marshal(trait) + if err != nil { + return nil, traits, err + } + var traitIDMap map[string]interface{} + if err := json.Unmarshal(data, &traitIDMap); err != nil { + return nil, traits, err + } + if len(traitIDMap) > 0 { + if isAddon(string(trait.ID())) { + traitMap["addons"] = map[string]interface{}{ + string(trait.ID()): traitIDMap, + } + } else { + traitMap[string(trait.ID())] = traitIDMap + } + } + traitIds = append(traitIds, string(trait.ID())) } + + traitData, err := json.Marshal(traitMap) + if err != nil { + return nil, traits, err + } + if err := json.Unmarshal(traitData, &traits); err != nil { + return nil, traits, err + } + message := fmt.Sprintf("Applied traits: %s", strings.Join(traitIds, ",")) c.L.Debugf(message) - return NewIntegrationCondition("", v1.IntegrationConditionTraitInfo, corev1.ConditionTrue, traitConfigurationReason, message) + return NewIntegrationCondition("", v1.IntegrationConditionTraitInfo, corev1.ConditionTrue, traitConfigurationReason, message), traits, nil +} + +// Deprecated: remove this check when we include the addons traits into regular traits +// see https://github.com/apache/camel-k/issues/5787 +// isAddon returns true if the trait is an addon. +func isAddon(id string) bool { + return id == "master" || id == "keda" || id == "3scale" || id == "tracing" || + id == "aws-secrets-manager" || id == "azure-key-vault" || id == "gcp-secret-manager" || id == "hashicorp-vault" } // GetTrait returns the trait with the given ID. diff --git a/pkg/trait/trait_catalog_test.go b/pkg/trait/trait_catalog_test.go new file mode 100644 index 0000000000..3475ae2f0e --- /dev/null +++ b/pkg/trait/trait_catalog_test.go @@ -0,0 +1,89 @@ +/* +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 trait + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + "github.com/apache/camel-k/v2/pkg/util/camel" + "github.com/apache/camel-k/v2/pkg/util/defaults" + "github.com/apache/camel-k/v2/pkg/util/kubernetes" + "github.com/apache/camel-k/v2/pkg/util/test" +) + +func TestIntegrationExecutedTrait(t *testing.T) { + ip := v1.IntegrationPlatform{} + ip.Namespace = "ns" + ip.Name = "ck" + ip.Status = v1.IntegrationPlatformStatus{ + IntegrationPlatformSpec: v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + RuntimeProvider: v1.RuntimeProviderQuarkus, + RuntimeVersion: defaults.DefaultRuntimeVersion, + }, + }, + Phase: v1.IntegrationPlatformPhaseReady, + } + c, err := test.NewFakeClient(&ip) + require.NoError(t, err) + catalog := NewCatalog(c) + env := Environment{ + CamelCatalog: &camel.RuntimeCatalog{ + CamelCatalogSpec: v1.CamelCatalogSpec{ + Runtime: v1.RuntimeSpec{ + Version: defaults.DefaultRuntimeVersion, + Provider: v1.RuntimeProviderQuarkus, + }, + }, + }, + Catalog: catalog, + Ctx: context.Background(), + Client: c, + Integration: &v1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "it", + Namespace: "ns", + }, + Spec: v1.IntegrationSpec{ + Profile: v1.TraitProfileKubernetes, + }, + Status: v1.IntegrationStatus{ + Phase: v1.IntegrationPhaseRunning, + Conditions: []v1.IntegrationCondition{ + { + Type: v1.IntegrationConditionDeploymentAvailable, + Status: corev1.ConditionTrue, + }, + }, + }, + }, + Platform: &ip, + Resources: kubernetes.NewCollection(), + } + + _, ts, err := catalog.apply(&env) + require.NoError(t, err) + assert.Equal(t, &v1.Traits{}, ts) +} diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go index 1bbf4d06dd..105e4eace7 100644 --- a/pkg/trait/trait_test.go +++ b/pkg/trait/trait_test.go @@ -18,10 +18,13 @@ limitations under the License. package trait import ( + "context" + "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" routev1 "github.com/openshift/api/route/v1" @@ -32,8 +35,10 @@ import ( v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" + "github.com/apache/camel-k/v2/pkg/resources" "github.com/apache/camel-k/v2/pkg/util/boolean" "github.com/apache/camel-k/v2/pkg/util/camel" + "github.com/apache/camel-k/v2/pkg/util/defaults" "github.com/apache/camel-k/v2/pkg/util/kubernetes" "github.com/apache/camel-k/v2/pkg/util/test" ) @@ -43,7 +48,7 @@ const ( ) func TestOpenShiftTraits(t *testing.T) { - env := createTestEnv(t, v1.IntegrationPlatformClusterOpenShift, "camel:core") + env := createTestEnv(t, v1.IntegrationPlatformClusterOpenShift, "from('timer:my-timer').to('log:info')") res := processTestEnv(t, env) assert.NotEmpty(t, env.ExecutedTraits) @@ -413,7 +418,7 @@ func processTestEnv(t *testing.T, env *Environment) *kubernetes.Collection { t.Helper() catalog := NewTraitTestCatalog() - _, err := catalog.apply(env) + _, _, err := catalog.apply(env) require.NoError(t, err) return env.Resources } @@ -478,11 +483,11 @@ func NewTraitTestCatalog() *Catalog { } func TestExecutedTraitsCondition(t *testing.T) { - env := createTestEnv(t, v1.IntegrationPlatformClusterOpenShift, "camel:core") + env := createTestEnv(t, v1.IntegrationPlatformClusterOpenShift, "from('timer:test').to('log:info')") catalog := NewTraitTestCatalog() - conditions, err := catalog.apply(env) + conditions, traits, err := catalog.apply(env) require.NoError(t, err) - + assert.Empty(t, traits) expectedCondition := NewIntegrationCondition( "", v1.IntegrationConditionTraitInfo, @@ -492,3 +497,192 @@ func TestExecutedTraitsCondition(t *testing.T) { ) assert.Contains(t, conditions, expectedCondition) } + +func testDefaultIntegrationPhaseTraitsSetting(t *testing.T, phase v1.IntegrationPhase) { + it := &v1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-it", + Namespace: "ns", + }, + Spec: v1.IntegrationSpec{ + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "file.groovy", + Content: "from('timer:test').to('log:info')", + }, + Language: v1.LanguageGroovy, + }, + }, + }, + Status: v1.IntegrationStatus{ + Phase: phase, + Conditions: []v1.IntegrationCondition{ + { + Type: v1.IntegrationConditionDeploymentAvailable, + Status: corev1.ConditionTrue, + }, + }, + }, + } + platform := &v1.IntegrationPlatform{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-platform", + Namespace: "ns", + }, + Status: v1.IntegrationPlatformStatus{ + IntegrationPlatformSpec: v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + RuntimeProvider: v1.RuntimeProviderQuarkus, + RuntimeVersion: defaults.DefaultRuntimeVersion, + }, + }, + Phase: v1.IntegrationPlatformPhaseReady, + }, + } + // Load the default catalog + camelCatalogData, err := resources.Resource(fmt.Sprintf("/resources/camel-catalog-%s.yaml", defaults.DefaultRuntimeVersion)) + require.NoError(t, err) + var cat v1.CamelCatalog + err = yaml.Unmarshal(camelCatalogData, &cat) + require.NoError(t, err) + cat.SetAnnotations(platform.Annotations) + cat.SetNamespace(platform.Namespace) + + client, err := test.NewFakeClient(platform, &cat) + require.NoError(t, err) + env, err := Apply(context.Background(), client, it, nil) + require.NoError(t, err) + assert.NotEmpty(t, env.Integration) + // We should get no values slicking from the spec + // only traits marked as "auto" should be able to scan + // the sources and set values. + // Neither default traits values should not be included here. + assert.Equal(t, &v1.Traits{}, env.Integration.Status.Traits) +} + +func TestDefaultIntegrationRunningTraitsSetting(t *testing.T) { + testDefaultIntegrationPhaseTraitsSetting(t, v1.IntegrationPhaseRunning) +} + +func TestDefaultIntegrationNoneTraitsSetting(t *testing.T) { + testDefaultIntegrationPhaseTraitsSetting(t, v1.IntegrationPhaseNone) +} + +func TestDefaultIntegrationInitTraitsSetting(t *testing.T) { + testDefaultIntegrationPhaseTraitsSetting(t, v1.IntegrationPhaseInitialization) +} + +func TestDefaultIntegrationWaitingPlatformTraitsSetting(t *testing.T) { + testDefaultIntegrationPhaseTraitsSetting(t, v1.IntegrationPhaseWaitingForPlatform) +} + +func TestDefaultIntegrationBuildingKitTraitsSetting(t *testing.T) { + testDefaultIntegrationPhaseTraitsSetting(t, v1.IntegrationPhaseBuildingKit) +} + +func TestDefaultIntegrationDeployingTraitsSetting(t *testing.T) { + testDefaultIntegrationPhaseTraitsSetting(t, v1.IntegrationPhaseDeploying) +} + +func TestDefaultIntegrationErrorTraitsSetting(t *testing.T) { + testDefaultIntegrationPhaseTraitsSetting(t, v1.IntegrationPhaseError) +} + +func TestIntegrationTraitsSetting(t *testing.T) { + it := &v1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-it", + Namespace: "ns", + }, + Spec: v1.IntegrationSpec{ + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "file.groovy", + Content: "from('timer:test').to('log:info')", + }, + Language: v1.LanguageGroovy, + }, + }, + Traits: v1.Traits{ + Builder: &traitv1.BuilderTrait{ + Properties: []string{ + "building=yes", + }, + }, + Camel: &traitv1.CamelTrait{ + Properties: []string{ + "hello=world", + }, + }, + Container: &traitv1.ContainerTrait{ + Name: "my-container-name", + }, + Addons: map[string]v1.AddonTrait{ + "master": ToAddonTrait(t, map[string]interface{}{ + "resourceName": "test-lock", + }), + }, + }, + }, + Status: v1.IntegrationStatus{ + Phase: v1.IntegrationPhaseRunning, + Conditions: []v1.IntegrationCondition{ + { + Type: v1.IntegrationConditionDeploymentAvailable, + Status: corev1.ConditionTrue, + }, + }, + }, + } + platform := &v1.IntegrationPlatform{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-platform", + Namespace: "ns", + }, + Status: v1.IntegrationPlatformStatus{ + IntegrationPlatformSpec: v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + RuntimeProvider: v1.RuntimeProviderQuarkus, + RuntimeVersion: defaults.DefaultRuntimeVersion, + }, + }, + Phase: v1.IntegrationPlatformPhaseReady, + }, + } + // Load the default catalog + camelCatalogData, err := resources.Resource(fmt.Sprintf("/resources/camel-catalog-%s.yaml", defaults.DefaultRuntimeVersion)) + require.NoError(t, err) + var cat v1.CamelCatalog + err = yaml.Unmarshal(camelCatalogData, &cat) + require.NoError(t, err) + cat.SetAnnotations(platform.Annotations) + cat.SetNamespace(platform.Namespace) + + client, err := test.NewFakeClient(platform, &cat) + require.NoError(t, err) + env, err := Apply(context.Background(), client, it, nil) + require.NoError(t, err) + assert.NotEmpty(t, env.Integration) + assert.Equal(t, &v1.Traits{ + Builder: &traitv1.BuilderTrait{ + Properties: []string{ + "building=yes", + }, + }, + Camel: &traitv1.CamelTrait{ + Properties: []string{ + "hello=world", + }, + }, + Container: &traitv1.ContainerTrait{ + Name: "my-container-name", + }, + Addons: map[string]v1.AddonTrait{ + "master": ToAddonTrait(t, map[string]interface{}{ + "resourceName": "test-lock", + }), + }, + }, env.Integration.Status.Traits) +} diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go index 7a890af40e..5b6ac50b96 100644 --- a/pkg/trait/trait_types.go +++ b/pkg/trait/trait_types.go @@ -670,7 +670,7 @@ func (e *Environment) GetIntegrationContainerName() string { if dt := e.Catalog.GetTrait(containerTraitID); dt != nil { if ct, ok := dt.(*containerTrait); ok { - containerName = ct.Name + containerName = ct.getContainerName() } } return containerName @@ -727,7 +727,7 @@ func (e *Environment) createContainerPort() *corev1.ContainerPort { if t := e.Catalog.GetTrait(containerTraitID); t != nil { if ct, ok := t.(*containerTrait); ok { name = ct.PortName - port = ct.Port + port = ct.getPort() } } @@ -757,7 +757,8 @@ func CapabilityPropertyKey(camelPropertyKey string, vars map[string]string) stri } // ConsumeMeta is used to consume metadata information coming from Integration sources. If no sources available, -// would return false. +// would return false. When consuming from meta you should make sure that the configuration is stored in the +// status traits by setting each trait configuration when in "auto" mode. func (e *Environment) ConsumeMeta(consumeMeta func(metadata.IntegrationMetadata) bool) (bool, error) { return e.consumeSourcesMeta(nil, consumeMeta) } diff --git a/pkg/trait/trait_types_test.go b/pkg/trait/trait_types_test.go index 8f945397f8..608fc3d076 100644 --- a/pkg/trait/trait_types_test.go +++ b/pkg/trait/trait_types_test.go @@ -198,7 +198,7 @@ func TestDetermineControllerStrategySyntheticKitForceKnative(t *testing.T) { Auto: ptr.To(false), } e.Platform.ResyncStatusFullConfig() - _, err := e.Catalog.apply(e) + _, _, err := e.Catalog.apply(e) require.NoError(t, err) strategy, err := e.DetermineControllerStrategy() @@ -270,7 +270,7 @@ func createTestEnvironment(t *testing.T, profile v1.TraitProfile) *Environment { environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(environment) + _, _, err = traitCatalog.apply(environment) require.NoError(t, err) return environment @@ -321,7 +321,7 @@ func createNonManagedBuildTestEnvironment(t *testing.T, profile v1.TraitProfile) environment.Platform.ResyncStatusFullConfig() - _, err = traitCatalog.apply(environment) + _, _, err = traitCatalog.apply(environment) require.NoError(t, err) return environment diff --git a/pkg/trait/util.go b/pkg/trait/util.go index 6a8083b59c..ec1f62c6fd 100644 --- a/pkg/trait/util.go +++ b/pkg/trait/util.go @@ -37,10 +37,6 @@ import ( "github.com/apache/camel-k/v2/pkg/util/sets" ) -func ptrFrom[T any](value T) *T { - return &value -} - type Options map[string]map[string]interface{} func (u Options) Get(id string) (map[string]interface{}, bool) { From 3a52c351e80e466c9fda0f65e99bb053284d55bf Mon Sep 17 00:00:00 2001 From: Pasquale Congiusti Date: Sat, 21 Sep 2024 15:10:43 +0200 Subject: [PATCH 2/2] feat: promote with traits --- docs/modules/ROOT/pages/pipes/promoting.adoc | 13 +++ .../modules/ROOT/pages/running/promoting.adoc | 12 +++ pkg/apis/camel/v1/pipe_types_support.go | 36 ++++++++ pkg/apis/camel/v1/pipe_types_support_test.go | 45 +++++++++ pkg/cmd/promote.go | 51 ++++++++--- pkg/cmd/promote_test.go | 91 +++++++++++++++++++ 6 files changed, 233 insertions(+), 15 deletions(-) diff --git a/docs/modules/ROOT/pages/pipes/promoting.adoc b/docs/modules/ROOT/pages/pipes/promoting.adoc index 3c2b7a5766..4f9a1805e8 100644 --- a/docs/modules/ROOT/pages/pipes/promoting.adoc +++ b/docs/modules/ROOT/pages/pipes/promoting.adoc @@ -1,7 +1,9 @@ +[[promoting-pipes]] = Promoting Pipes across environments As soon as you have an Pipes running in your cluster, you will be challenged to move that Pipe to an higher environment. Ie, you can test your Pipe in a **development** environment, and, as soon as you're happy with the result, you will need to move it into a **production** environment. +[[cli-promote]] == CLI `promote` command You may already be familiar with this command as seen when xref:running/promoting.adoc[promoting Integrations across environments]. The command is smart enough to detect when you want to promote a Pipe or an Integration and it works exactly in the same manner. @@ -49,3 +51,14 @@ status: {} ``` As you may already have seen with the Integration example, also here the Pipe is reusing the very same container image. From a release perspective we are guaranteeing the **immutability** of the Pipe as the container used is exactly the same of the one we have tested in development (what we change are just the configurations, if any). + +[[traits]] +== Moving traits + +NOTE: this feature is available starting from version 2.5 + +When you use the `promote` subcommand, you're also keeping the status of any configured trait along with the new promoted Pipe. The tool is in fact in charge to recover the trait configuration of the source Pipe and port it over to the new Pipe promoted. + +This is particularly nice when you have certain traits which are requiring the scan the source code (for instance, Service trait). In this way, when you promote the new Pipe, the traits will be automatically configured to copy any parameter, replicating the very exact behavior between the source and destination environment. + +With this approach, you won't need to worry any longer about any trait which was requiring the source to be attached in order to automatically scan for features. diff --git a/docs/modules/ROOT/pages/running/promoting.adoc b/docs/modules/ROOT/pages/running/promoting.adoc index b3784b1e10..29204d3569 100644 --- a/docs/modules/ROOT/pages/running/promoting.adoc +++ b/docs/modules/ROOT/pages/running/promoting.adoc @@ -3,6 +3,7 @@ As soon as you have an Integration running in your cluster, you will be challenged to move that Integration to an higher environment. Ie, you can test your Integration in a **development** environment, and, as soon as you're happy with the result, you will need to move it into a **production** environment. +[[cli-promote]] == CLI `promote` command Camel K has an opinionated way to achieve the promotion goal through the usage of `kamel promote` command. With this command you will be able to easily move an Integration from one namespace to another without worrying about any low level detail such as resources needed by the Integration. You only need to make sure that both the source operator and the destination operator are using the same container registry and that the destination namespace provides the required Configmaps, Secrets or Kamelets required by the Integration. @@ -52,3 +53,14 @@ hello, I am production! Something nice is that since the Integration is reusing the very same container image, the execution of the new application will be immediate. Also from a release perspective we are guaranteeing the **immutability** of the Integration as the container used is exactly the same of the one we have tested in development (what we change are just the configurations). Please notice that the Integration running in test is not altered in any way and will be running until any user will stop it. + +[[traits]] +== Moving traits + +NOTE: this feature is available starting from version 2.5 + +When you use the `promote` subcommand, you're also keeping the status of any configured trait along with the new promoted Integration. The tool is in fact in charge to recover the trait configuration of the source Integration and port it over to the new Integration promoted. + +This is particularly nice when you have certain traits which are requiring the scan the source code (for instance, Service trait). In this way, when you promote the new Integration, the traits will be automatically configured to copy any parameter, replicating the very exact behavior between the source and destination environment. + +With this approach, you won't need to worry any longer about any trait which was requiring the source to be attached in order to automatically scan for features. diff --git a/pkg/apis/camel/v1/pipe_types_support.go b/pkg/apis/camel/v1/pipe_types_support.go index ecdbb88cd7..f5f855b06c 100644 --- a/pkg/apis/camel/v1/pipe_types_support.go +++ b/pkg/apis/camel/v1/pipe_types_support.go @@ -22,6 +22,7 @@ import ( "encoding/json" "fmt" + scase "github.com/stoewer/go-strcase" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -70,6 +71,41 @@ func (in *Pipe) SetOperatorID(operatorID string) { SetAnnotation(&in.ObjectMeta, OperatorIDAnnotation, operatorID) } +// SetTrait converts a trait into the related annotation. +func (in *Pipe) SetTraits(traits *Traits) error { + var mappedTraits map[string]map[string]interface{} + data, err := json.Marshal(traits) + if err != nil { + return err + } + err = json.Unmarshal(data, &mappedTraits) + if err != nil { + return err + } + + addons := mappedTraits["addons"] + delete(mappedTraits, "addons") + if in.Annotations == nil && (len(mappedTraits) > 0 || len(addons) > 0) { + in.Annotations = make(map[string]string) + } + for id, trait := range mappedTraits { + for k, v := range trait { + in.Annotations[fmt.Sprintf("%s%s.%s", TraitAnnotationPrefix, id, scase.KebabCase(k))] = fmt.Sprintf("%v", v) + } + } + for id, trait := range addons { + castedMap, ok := trait.(map[string]interface{}) + if !ok { + return fmt.Errorf("could not cast trait addon %v", trait) + } + for k, v := range castedMap { + in.Annotations[fmt.Sprintf("%s%s.%s", TraitAnnotationPrefix, id, scase.KebabCase(k))] = fmt.Sprintf("%v", v) + } + } + + return nil +} + // GetCondition returns the condition with the provided type. func (in *PipeStatus) GetCondition(condType PipeConditionType) *PipeCondition { for i := range in.Conditions { diff --git a/pkg/apis/camel/v1/pipe_types_support_test.go b/pkg/apis/camel/v1/pipe_types_support_test.go index 7d1e69fb9f..2f69274ece 100644 --- a/pkg/apis/camel/v1/pipe_types_support_test.go +++ b/pkg/apis/camel/v1/pipe_types_support_test.go @@ -21,8 +21,10 @@ import ( "encoding/json" "testing" + "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "k8s.io/utils/ptr" ) func TestNumberConversion(t *testing.T) { @@ -46,3 +48,46 @@ func TestNumberConversion(t *testing.T) { assert.Equal(t, "123.123", res["float32"]) assert.Equal(t, "1111123.123", res["float64"]) } + +func TestSetTraits(t *testing.T) { + traits := Traits{ + Affinity: &trait.AffinityTrait{ + Trait: trait.Trait{ + Enabled: ptr.To(true), + }, + PodAffinity: ptr.To(true), + }, + Addons: map[string]AddonTrait{ + "master": toAddonTrait(t, map[string]interface{}{ + "enabled": true, + "resourceName": "test-lock", + "labelKey": "test-label", + "labelValue": "test-value", + }), + }, + Knative: &trait.KnativeTrait{ + Trait: trait.Trait{ + Enabled: ptr.To(true), + }, + ChannelSources: []string{ + "channel-a", "channel-b", + }, + }, + } + + expectedAnnotations := map[string]string(map[string]string{ + "trait.camel.apache.org/affinity.enabled": "true", + "trait.camel.apache.org/affinity.pod-affinity": "true", + "trait.camel.apache.org/knative.channel-sources": "[channel-a channel-b]", + "trait.camel.apache.org/knative.enabled": "true", + "trait.camel.apache.org/master.enabled": "true", + "trait.camel.apache.org/master.label-key": "test-label", + "trait.camel.apache.org/master.label-value": "test-value", + "trait.camel.apache.org/master.resource-name": "test-lock", + }) + + pipe := NewPipe("my-pipe", "my-ns") + err := pipe.SetTraits(&traits) + assert.NoError(t, err) + assert.Equal(t, expectedAnnotations, pipe.Annotations) +} diff --git a/pkg/cmd/promote.go b/pkg/cmd/promote.go index 339afac7a3..258569a726 100644 --- a/pkg/cmd/promote.go +++ b/pkg/cmd/promote.go @@ -135,7 +135,10 @@ func (o *promoteCmdOptions) run(cmd *cobra.Command, args []string) error { // Pipe promotion if promotePipe { - destPipe := o.editPipe(sourcePipe, sourceIntegration, sourceKit) + destPipe, err := o.editPipe(sourcePipe, sourceIntegration, sourceKit) + if err != nil { + return err + } if o.OutputFormat != "" { return showPipeOutput(cmd, destPipe, o.OutputFormat, c.GetScheme()) } @@ -242,6 +245,9 @@ func (o *promoteCmdOptions) editIntegration(it *v1.Integration, kit *v1.Integrat dstIt.Annotations = cloneAnnotations(it.Annotations, o.ToOperator) dstIt.Labels = cloneLabels(it.Labels) dstIt.Spec.IntegrationKit = nil + if it.Status.Traits != nil { + dstIt.Spec.Traits = *it.Status.Traits + } if dstIt.Spec.Traits.Container == nil { dstIt.Spec.Traits.Container = &traitv1.ContainerTrait{} } @@ -322,14 +328,40 @@ func cloneLabels(lbs map[string]string) map[string]string { return newMap } -func (o *promoteCmdOptions) editPipe(kb *v1.Pipe, it *v1.Integration, kit *v1.IntegrationKit) *v1.Pipe { +func (o *promoteCmdOptions) editPipe(kb *v1.Pipe, it *v1.Integration, kit *v1.IntegrationKit) (*v1.Pipe, error) { contImage := it.Status.Image // Pipe dst := v1.NewPipe(o.To, kb.Name) dst.Spec = *kb.Spec.DeepCopy() dst.Annotations = cloneAnnotations(kb.Annotations, o.ToOperator) dst.Labels = cloneLabels(kb.Labels) - dst.Annotations[fmt.Sprintf("%scontainer.image", v1.TraitAnnotationPrefix)] = contImage + traits := it.Status.Traits + if traits == nil { + traits = &v1.Traits{} + } + if traits.Container == nil { + traits.Container = &traitv1.ContainerTrait{} + } + traits.Container.Image = contImage + if kit != nil { + // We must provide the classpath expected for the IntegrationKit. This is calculated dynamically and + // would get lost when creating the non managed build Integration. For this reason + // we must report it in the promoted Integration. + mergedClasspath := getClasspath(kit, dst.Annotations[fmt.Sprintf("%sjvm.classpath", v1.TraitAnnotationPrefix)]) + if traits.JVM == nil { + traits.JVM = &traitv1.JVMTrait{} + } + traits.JVM.Classpath = mergedClasspath + // We must also set the runtime version so we pin it to the given catalog on which + // the container image was built + if traits.Camel == nil { + traits.Camel = &traitv1.CamelTrait{} + } + traits.Camel.RuntimeVersion = kit.Status.RuntimeVersion + } + if err := dst.SetTraits(traits); err != nil { + return nil, err + } if dst.Spec.Source.Ref != nil { dst.Spec.Source.Ref.Namespace = o.To } @@ -344,18 +376,7 @@ func (o *promoteCmdOptions) editPipe(kb *v1.Pipe, it *v1.Integration, kit *v1.In } } - if kit != nil { - // We must provide the classpath expected for the IntegrationKit. This is calculated dynamically and - // would get lost when creating the non managed build Integration. For this reason - // we must report it in the promoted Integration. - mergedClasspath := getClasspath(kit, dst.Annotations[fmt.Sprintf("%sjvm.classpath", v1.TraitAnnotationPrefix)]) - dst.Annotations[fmt.Sprintf("%sjvm.classpath", v1.TraitAnnotationPrefix)] = mergedClasspath - // We must also set the runtime version so we pin it to the given catalog on which - // the container image was built - dst.Annotations[fmt.Sprintf("%scamel.runtime-version", v1.TraitAnnotationPrefix)] = kit.Status.RuntimeVersion - } - - return &dst + return &dst, nil } func (o *promoteCmdOptions) replaceResource(res k8sclient.Object) (bool, error) { diff --git a/pkg/cmd/promote_test.go b/pkg/cmd/promote_test.go index 0d92f40897..f1b4be58bc 100644 --- a/pkg/cmd/promote_test.go +++ b/pkg/cmd/promote_test.go @@ -22,6 +22,7 @@ import ( "testing" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" "github.com/apache/camel-k/v2/pkg/platform" "github.com/apache/camel-k/v2/pkg/util/defaults" "github.com/apache/camel-k/v2/pkg/util/test" @@ -30,6 +31,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" ) const cmdPromote = "promote" @@ -371,3 +373,92 @@ spec: status: {} `, output) } + +func TestIntegrationWithSavedTraitsDryRun(t *testing.T) { + srcPlatform := v1.NewIntegrationPlatform("default", platform.DefaultPlatformName) + srcPlatform.Status.Version = defaults.Version + srcPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion + srcPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady + dstPlatform := v1.NewIntegrationPlatform("prod-namespace", platform.DefaultPlatformName) + dstPlatform.Status.Version = defaults.Version + dstPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion + dstPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady + defaultIntegration, defaultKit := nominalIntegration("my-it-test") + defaultIntegration.Status.Traits = &v1.Traits{ + Service: &trait.ServiceTrait{ + Trait: trait.Trait{ + Enabled: ptr.To(true), + }, + }, + } + srcCatalog := createTestCamelCatalog(srcPlatform) + dstCatalog := createTestCamelCatalog(dstPlatform) + + promoteCmdOptions, promoteCmd, _ := initializePromoteCmdOptions(t, &srcPlatform, &dstPlatform, &defaultIntegration, &defaultKit, &srcCatalog, &dstCatalog) + output, err := test.ExecuteCommand(promoteCmd, cmdPromote, "my-it-test", "--to", "prod-namespace", "-o", "yaml", "-n", "default") + assert.Equal(t, "yaml", promoteCmdOptions.OutputFormat) + require.NoError(t, err) + assert.Equal(t, `apiVersion: camel.apache.org/v1 +kind: Integration +metadata: + creationTimestamp: null + name: my-it-test + namespace: prod-namespace +spec: + traits: + camel: + runtimeVersion: 1.2.3 + container: + image: my-special-image + jvm: + classpath: /path/to/artifact-1/*:/path/to/artifact-2/* + service: + enabled: true +status: {} +`, output) +} + +func TestPipeWithSavedTraitsDryRun(t *testing.T) { + srcPlatform := v1.NewIntegrationPlatform("default", platform.DefaultPlatformName) + srcPlatform.Status.Version = defaults.Version + srcPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion + srcPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady + dstPlatform := v1.NewIntegrationPlatform("prod-namespace", platform.DefaultPlatformName) + dstPlatform.Status.Version = defaults.Version + dstPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion + dstPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady + defaultKB := nominalPipe("my-kb-test") + defaultKB.Annotations = map[string]string{ + "camel.apache.org/operator.id": "camel-k", + "my-annotation": "my-value", + } + defaultKB.Labels = map[string]string{ + "my-label": "my-value", + } + defaultIntegration, defaultKit := nominalIntegration("my-kb-test") + srcCatalog := createTestCamelCatalog(srcPlatform) + dstCatalog := createTestCamelCatalog(dstPlatform) + + promoteCmdOptions, promoteCmd, _ := initializePromoteCmdOptions(t, &srcPlatform, &dstPlatform, &defaultKB, &defaultIntegration, &defaultKit, &srcCatalog, &dstCatalog) + output, err := test.ExecuteCommand(promoteCmd, cmdPromote, "my-kb-test", "--to", "prod-namespace", "-o", "yaml", "-n", "default") + assert.Equal(t, "yaml", promoteCmdOptions.OutputFormat) + require.NoError(t, err) + assert.Equal(t, `apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + annotations: + my-annotation: my-value + trait.camel.apache.org/camel.runtime-version: 1.2.3 + trait.camel.apache.org/container.image: my-special-image + trait.camel.apache.org/jvm.classpath: /path/to/artifact-1/*:/path/to/artifact-2/* + creationTimestamp: null + labels: + my-label: my-value + name: my-kb-test + namespace: prod-namespace +spec: + sink: {} + source: {} +status: {} +`, output) +}