You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by GitBox <gi...@apache.org> on 2021/12/21 14:02:35 UTC

[GitHub] [camel-k] astefanutti commented on a change in pull request #2838: Add support for KEDA

astefanutti commented on a change in pull request #2838:
URL: https://github.com/apache/camel-k/pull/2838#discussion_r773124441



##########
File path: pkg/client/serverside.go
##########
@@ -0,0 +1,130 @@
+/*
+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 client
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"strings"
+	"sync"
+	"sync/atomic"
+
+	"github.com/apache/camel-k/pkg/util/log"
+	"github.com/apache/camel-k/pkg/util/patch"
+	"github.com/pkg/errors"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type ServerOrClientSideApplier struct {

Review comment:
       nit: I'd suggest to rename the file to `apply.go` as it's not restricted to server-side (even if it's the intent).

##########
File path: pkg/client/serverside.go
##########
@@ -0,0 +1,130 @@
+/*
+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 client
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"strings"
+	"sync"
+	"sync/atomic"
+
+	"github.com/apache/camel-k/pkg/util/log"
+	"github.com/apache/camel-k/pkg/util/patch"
+	"github.com/pkg/errors"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type ServerOrClientSideApplier struct {
+	Client             ctrl.Client
+	hasServerSideApply atomic.Value
+	tryServerSideApply sync.Once
+}
+
+func (c *defaultClient) ServerOrClientSideApplier() ServerOrClientSideApplier {
+	return ServerOrClientSideApplier{
+		Client: c,
+	}
+}
+
+func (a *ServerOrClientSideApplier) Apply(ctx context.Context, object ctrl.Object) error {
+	once := false
+	var err error
+	// nolint: ifshort
+	needsRetry := false
+	a.tryServerSideApply.Do(func() {
+		once = true
+		if err = a.serverSideApply(ctx, object); err != nil {
+			if isIncompatibleServerError(err) {
+				log.Info("Fallback to client-side apply for installing resources")
+				a.hasServerSideApply.Store(false)
+				err = nil
+			} else {
+				needsRetry = true

Review comment:
       Out of curiosity, what is the reason behind moving the reset out of the critical section?

##########
File path: addons/keda/keda.go
##########
@@ -0,0 +1,551 @@
+/*
+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 keda
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"sort"
+	"strings"
+	"text/template"
+
+	kedav1alpha1 "github.com/apache/camel-k/addons/keda/duck/v1alpha1"
+	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	camelv1alpha1 "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/apache/camel-k/pkg/kamelet/repository"
+	"github.com/apache/camel-k/pkg/metadata"
+	"github.com/apache/camel-k/pkg/platform"
+	"github.com/apache/camel-k/pkg/trait"
+	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/property"
+	"github.com/apache/camel-k/pkg/util/source"
+	"github.com/apache/camel-k/pkg/util/uri"
+	"github.com/pkg/errors"
+	scase "github.com/stoewer/go-strcase"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+const (
+	// kameletURNMetadataPrefix allows binding Kamelet properties to KEDA metadata.
+	kameletURNMetadataPrefix = "urn:keda:metadata:"
+	// kameletURNAuthenticationPrefix allows binding Kamelet properties to KEDA authentication options.
+	kameletURNAuthenticationPrefix = "urn:keda:authentication:"
+	// kameletURNRequiredTag is used to mark properties required by KEDA.
+	kameletURNRequiredTag = "urn:keda:required"
+
+	// kameletAnnotationType indicates the scaler type associated to a Kamelet.
+	kameletAnnotationType = "camel.apache.org/keda.type"
+	// kameletAnnotationMetadataPrefix is used to define virtual metadata fields computed from Kamelet properties.
+	kameletAnnotationMetadataPrefix = "camel.apache.org/keda.metadata."
+	// kameletAnnotationAuthenticationPrefix is used to define virtual authentication fields computed from Kamelet properties.
+	kameletAnnotationAuthenticationPrefix = "camel.apache.org/keda.authentication."
+)
+
+// The KEDA trait can be used for automatic integration with KEDA autoscalers.
+// The trait can be either manually configured using the `triggers` option or automatically configured
+// via markers in the Kamelets.
+//
+// For information on how to use KEDA enabled Kamelets with the KEDA trait, refer to
+// xref:ROOT:kamelets/kamelets-user.adoc#kamelet-keda-user[the KEDA section in the Kamelets user guide].
+// If you want to create Kamelets that contain KEDA metadata, refer to
+// xref:ROOT:kamelets/kamelets-dev.adoc#kamelet-keda-dev[the KEDA section in the Kamelets development guide].
+//
+// The KEDA trait is disabled by default.
+//
+// +camel-k:trait=keda.
+type kedaTrait struct {
+	trait.BaseTrait `property:",squash"`
+	// Enables automatic configuration of the trait. Allows the trait to infer KEDA triggers from the Kamelets.
+	Auto *bool `property:"auto" json:"auto,omitempty"`
+	// Convert metadata properties to camelCase (needed because Camel K trait properties use kebab-case from command line). Disabled by default.

Review comment:
       Are they really kebab-cased? It's true for top-level keys, but the hierarchical ones are unstructured and arbitrary?

##########
File path: addons/keda/keda.go
##########
@@ -0,0 +1,551 @@
+/*
+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 keda
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"sort"
+	"strings"
+	"text/template"
+
+	kedav1alpha1 "github.com/apache/camel-k/addons/keda/duck/v1alpha1"
+	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	camelv1alpha1 "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/apache/camel-k/pkg/kamelet/repository"
+	"github.com/apache/camel-k/pkg/metadata"
+	"github.com/apache/camel-k/pkg/platform"
+	"github.com/apache/camel-k/pkg/trait"
+	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/property"
+	"github.com/apache/camel-k/pkg/util/source"
+	"github.com/apache/camel-k/pkg/util/uri"
+	"github.com/pkg/errors"
+	scase "github.com/stoewer/go-strcase"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+const (
+	// kameletURNMetadataPrefix allows binding Kamelet properties to KEDA metadata.
+	kameletURNMetadataPrefix = "urn:keda:metadata:"
+	// kameletURNAuthenticationPrefix allows binding Kamelet properties to KEDA authentication options.
+	kameletURNAuthenticationPrefix = "urn:keda:authentication:"
+	// kameletURNRequiredTag is used to mark properties required by KEDA.
+	kameletURNRequiredTag = "urn:keda:required"
+
+	// kameletAnnotationType indicates the scaler type associated to a Kamelet.
+	kameletAnnotationType = "camel.apache.org/keda.type"
+	// kameletAnnotationMetadataPrefix is used to define virtual metadata fields computed from Kamelet properties.
+	kameletAnnotationMetadataPrefix = "camel.apache.org/keda.metadata."
+	// kameletAnnotationAuthenticationPrefix is used to define virtual authentication fields computed from Kamelet properties.
+	kameletAnnotationAuthenticationPrefix = "camel.apache.org/keda.authentication."
+)
+
+// The KEDA trait can be used for automatic integration with KEDA autoscalers.
+// The trait can be either manually configured using the `triggers` option or automatically configured
+// via markers in the Kamelets.
+//
+// For information on how to use KEDA enabled Kamelets with the KEDA trait, refer to
+// xref:ROOT:kamelets/kamelets-user.adoc#kamelet-keda-user[the KEDA section in the Kamelets user guide].
+// If you want to create Kamelets that contain KEDA metadata, refer to
+// xref:ROOT:kamelets/kamelets-dev.adoc#kamelet-keda-dev[the KEDA section in the Kamelets development guide].
+//
+// The KEDA trait is disabled by default.
+//
+// +camel-k:trait=keda.
+type kedaTrait struct {
+	trait.BaseTrait `property:",squash"`
+	// Enables automatic configuration of the trait. Allows the trait to infer KEDA triggers from the Kamelets.
+	Auto *bool `property:"auto" json:"auto,omitempty"`
+	// Convert metadata properties to camelCase (needed because Camel K trait properties use kebab-case from command line). Disabled by default.
+	CamelCaseConversion *bool `property:"camel-case-conversion" json:"camelCaseConversion,omitempty"`
+	// Set the spec->replicas field on the top level controller to an explicit value if missing, to allow KEDA to recognize it as a scalable resource.

Review comment:
       I'm sure you've come to the conclusion it's necessary, but that smells for an outside reader. My understand from the Keda documentation is that the only requirement for custom resources to be Keda-scalable is to declare a `scale` sub-resource. Also, the path to the replicas spec is variable, and abstracted by the `scale` sub-resource. Does that mean Keda access the replicas spec directly, rather than via the `scale` endpoint?   

##########
File path: pkg/client/serverside.go
##########
@@ -0,0 +1,130 @@
+/*
+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 client
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"strings"
+	"sync"
+	"sync/atomic"
+
+	"github.com/apache/camel-k/pkg/util/log"
+	"github.com/apache/camel-k/pkg/util/patch"
+	"github.com/pkg/errors"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type ServerOrClientSideApplier struct {
+	Client             ctrl.Client
+	hasServerSideApply atomic.Value
+	tryServerSideApply sync.Once
+}
+
+func (c *defaultClient) ServerOrClientSideApplier() ServerOrClientSideApplier {

Review comment:
       Maybe it could also be used in the _deployer_ trait while we are at it?

##########
File path: addons/keda/keda.go
##########
@@ -0,0 +1,551 @@
+/*
+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 keda
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"sort"
+	"strings"
+	"text/template"
+
+	kedav1alpha1 "github.com/apache/camel-k/addons/keda/duck/v1alpha1"
+	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	camelv1alpha1 "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/apache/camel-k/pkg/kamelet/repository"
+	"github.com/apache/camel-k/pkg/metadata"
+	"github.com/apache/camel-k/pkg/platform"
+	"github.com/apache/camel-k/pkg/trait"
+	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/property"
+	"github.com/apache/camel-k/pkg/util/source"
+	"github.com/apache/camel-k/pkg/util/uri"
+	"github.com/pkg/errors"
+	scase "github.com/stoewer/go-strcase"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+const (
+	// kameletURNMetadataPrefix allows binding Kamelet properties to KEDA metadata.
+	kameletURNMetadataPrefix = "urn:keda:metadata:"
+	// kameletURNAuthenticationPrefix allows binding Kamelet properties to KEDA authentication options.
+	kameletURNAuthenticationPrefix = "urn:keda:authentication:"
+	// kameletURNRequiredTag is used to mark properties required by KEDA.
+	kameletURNRequiredTag = "urn:keda:required"
+
+	// kameletAnnotationType indicates the scaler type associated to a Kamelet.
+	kameletAnnotationType = "camel.apache.org/keda.type"
+	// kameletAnnotationMetadataPrefix is used to define virtual metadata fields computed from Kamelet properties.
+	kameletAnnotationMetadataPrefix = "camel.apache.org/keda.metadata."
+	// kameletAnnotationAuthenticationPrefix is used to define virtual authentication fields computed from Kamelet properties.
+	kameletAnnotationAuthenticationPrefix = "camel.apache.org/keda.authentication."
+)
+
+// The KEDA trait can be used for automatic integration with KEDA autoscalers.
+// The trait can be either manually configured using the `triggers` option or automatically configured
+// via markers in the Kamelets.
+//
+// For information on how to use KEDA enabled Kamelets with the KEDA trait, refer to
+// xref:ROOT:kamelets/kamelets-user.adoc#kamelet-keda-user[the KEDA section in the Kamelets user guide].
+// If you want to create Kamelets that contain KEDA metadata, refer to
+// xref:ROOT:kamelets/kamelets-dev.adoc#kamelet-keda-dev[the KEDA section in the Kamelets development guide].
+//
+// The KEDA trait is disabled by default.
+//
+// +camel-k:trait=keda.
+type kedaTrait struct {
+	trait.BaseTrait `property:",squash"`
+	// Enables automatic configuration of the trait. Allows the trait to infer KEDA triggers from the Kamelets.
+	Auto *bool `property:"auto" json:"auto,omitempty"`
+	// Convert metadata properties to camelCase (needed because Camel K trait properties use kebab-case from command line). Disabled by default.
+	CamelCaseConversion *bool `property:"camel-case-conversion" json:"camelCaseConversion,omitempty"`
+	// Set the spec->replicas field on the top level controller to an explicit value if missing, to allow KEDA to recognize it as a scalable resource.
+	HackControllerReplicas *bool `property:"hack-controller-replicas" json:"hackControllerReplicas,omitempty"`
+	// Interval (seconds) to check each trigger on (minimum 10 seconds).
+	PollingInterval *int32 `property:"polling-interval" json:"pollingInterval,omitempty"`
+	// The wait period between the last active trigger reported and scaling the resource back to 0.
+	CooldownPeriod *int32 `property:"cooldown-period" json:"cooldownPeriod,omitempty"`
+	// Enabling this property allows KEDA to scale the resource down to the specified number of replicas.
+	IdleReplicaCount *int32 `property:"idle-replica-count" json:"idleReplicaCount,omitempty"`
+	// Minimum number of replicas.
+	MinReplicaCount *int32 `property:"min-replica-count" json:"minReplicaCount,omitempty"`
+	// Maximum number of replicas.
+	MaxReplicaCount *int32 `property:"max-replica-count" json:"maxReplicaCount,omitempty"`
+	// Definition of triggers according to the KEDA format. Each trigger must contain `type` field corresponding
+	// to the name of a KEDA autoscaler and a key/value map named `metadata` containing specific trigger options.
+	// An optional `authentication-secret` can be declared per trigger and the operator will link each entry of
+	// the secret to a KEDA authentication parameter.
+	Triggers []kedaTrigger `property:"triggers" json:"triggers,omitempty"`
+}
+
+type kedaTrigger struct {
+	Type                 string            `property:"type" json:"type,omitempty"`
+	Metadata             map[string]string `property:"metadata" json:"metadata,omitempty"`
+	AuthenticationSecret string            `property:"authentication-secret" json:"authenticationSecret,omitempty"`
+	authentication       map[string]string
+}
+
+// NewKedaTrait --.
+func NewKedaTrait() trait.Trait {
+	return &kedaTrait{
+		BaseTrait: trait.NewBaseTrait("keda", trait.TraitOrderPostProcessResources),
+	}
+}
+
+func (t *kedaTrait) Configure(e *trait.Environment) (bool, error) {
+	if t.Enabled == nil || !*t.Enabled {
+		return false, nil
+	}
+
+	if !e.IntegrationInPhase(camelv1.IntegrationPhaseInitialization) && !e.IntegrationInRunningPhases() {
+		return false, nil
+	}
+
+	if t.Auto == nil || *t.Auto {
+		if err := t.populateTriggersFromKamelets(e); err != nil {
+			return false, err
+		}
+	}
+
+	return len(t.Triggers) > 0, nil
+}
+
+func (t *kedaTrait) Apply(e *trait.Environment) error {
+	if e.IntegrationInPhase(camelv1.IntegrationPhaseInitialization) {
+		if t.HackControllerReplicas == nil || *t.HackControllerReplicas {
+			if err := t.hackControllerReplicas(e); err != nil {
+				return err
+			}
+		}
+	} else if e.IntegrationInRunningPhases() {
+		if err := t.addScalingResources(e); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (t *kedaTrait) addScalingResources(e *trait.Environment) error {
+	if len(t.Triggers) == 0 {
+		return nil
+	}
+
+	obj := kedav1alpha1.NewScaledObject(e.Integration.Namespace, e.Integration.Name)
+	obj.Spec.ScaleTargetRef = t.getTopControllerReference(e)
+	if t.PollingInterval != nil {
+		obj.Spec.PollingInterval = t.PollingInterval
+	}
+	if t.CooldownPeriod != nil {
+		obj.Spec.CooldownPeriod = t.CooldownPeriod
+	}
+	if t.IdleReplicaCount != nil {
+		obj.Spec.IdleReplicaCount = t.IdleReplicaCount
+	}
+	if t.MinReplicaCount != nil {
+		obj.Spec.MinReplicaCount = t.MinReplicaCount
+	}
+	if t.MaxReplicaCount != nil {
+		obj.Spec.MaxReplicaCount = t.MaxReplicaCount
+	}
+	for idx, trigger := range t.Triggers {
+		meta := make(map[string]string)
+		for k, v := range trigger.Metadata {
+			kk := k
+			if t.CamelCaseConversion != nil && *t.CamelCaseConversion {
+				kk = scase.LowerCamelCase(k)
+			}
+			meta[kk] = v
+		}
+		var authenticationRef *kedav1alpha1.ScaledObjectAuthRef
+		if len(trigger.authentication) > 0 && trigger.AuthenticationSecret != "" {
+			return errors.New("an authentication secret cannot be provided for auto-configured triggers")
+		}
+		extConfigName := fmt.Sprintf("%s-keda-%d", e.Integration.Name, idx)
+		if len(trigger.authentication) > 0 {
+			// Save all authentication config in a secret
+			secret := v1.Secret{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "Secret",
+					APIVersion: v1.SchemeGroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: e.Integration.Namespace,
+					Name:      extConfigName,
+				},
+				StringData: trigger.authentication,
+			}
+			e.Resources.Add(&secret)
+
+			// Link the secret using a TriggerAuthentication
+			triggerAuth := kedav1alpha1.TriggerAuthentication{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "TriggerAuthentication",
+					APIVersion: kedav1alpha1.SchemeGroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: e.Integration.Namespace,
+					Name:      extConfigName,
+				},
+			}
+			for _, k := range util.SortedStringMapKeys(trigger.authentication) {
+				triggerAuth.Spec.SecretTargetRef = append(triggerAuth.Spec.SecretTargetRef, kedav1alpha1.AuthSecretTargetRef{
+					Parameter: k,
+					Name:      extConfigName,
+					Key:       k,
+				})
+			}
+			e.Resources.Add(&triggerAuth)
+			authenticationRef = &kedav1alpha1.ScaledObjectAuthRef{
+				Name: extConfigName,
+			}
+		} else if trigger.AuthenticationSecret != "" {
+			s := v1.Secret{}
+			key := ctrl.ObjectKey{
+				Namespace: e.Integration.Namespace,
+				Name:      trigger.AuthenticationSecret,
+			}
+			if err := e.Client.Get(e.Ctx, key, &s); err != nil {
+				return errors.Wrapf(err, "could not load secret named %q in namespace %q", trigger.AuthenticationSecret, e.Integration.Namespace)
+			}
+			// Fill a TriggerAuthentication from the secret
+			triggerAuth := kedav1alpha1.TriggerAuthentication{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "TriggerAuthentication",
+					APIVersion: kedav1alpha1.SchemeGroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: e.Integration.Namespace,
+					Name:      extConfigName,
+				},
+			}
+			sortedKeys := make([]string, 0, len(s.Data))
+			for k := range s.Data {
+				sortedKeys = append(sortedKeys, k)
+			}
+			sort.Strings(sortedKeys)
+			for _, k := range sortedKeys {
+				triggerAuth.Spec.SecretTargetRef = append(triggerAuth.Spec.SecretTargetRef, kedav1alpha1.AuthSecretTargetRef{
+					Parameter: k,
+					Name:      s.Name,
+					Key:       k,
+				})
+			}
+			e.Resources.Add(&triggerAuth)
+			authenticationRef = &kedav1alpha1.ScaledObjectAuthRef{
+				Name: extConfigName,
+			}
+		}
+
+		st := kedav1alpha1.ScaleTriggers{
+			Type:              trigger.Type,
+			Metadata:          meta,
+			AuthenticationRef: authenticationRef,
+		}
+		obj.Spec.Triggers = append(obj.Spec.Triggers, st)
+	}
+	e.Resources.Add(&obj)
+	return nil
+}
+
+func (t *kedaTrait) hackControllerReplicas(e *trait.Environment) error {
+	ctrlRef := t.getTopControllerReference(e)
+	if ctrlRef.Kind == camelv1alpha1.KameletBindingKind {
+		// Update the KameletBinding directly (do not add it to env resources, it's the integration parent)
+		key := ctrl.ObjectKey{
+			Namespace: e.Integration.Namespace,
+			Name:      ctrlRef.Name,
+		}
+		klb := camelv1alpha1.KameletBinding{}
+		if err := e.Client.Get(e.Ctx, key, &klb); err != nil {
+			return err
+		}
+		if klb.Spec.Replicas == nil {
+			one := int32(1)
+			klb.Spec.Replicas = &one
+			if err := e.Client.Update(e.Ctx, &klb); err != nil {
+				return err
+			}
+		}
+	} else if e.Integration.Spec.Replicas == nil {
+		one := int32(1)
+		e.Integration.Spec.Replicas = &one
+		// Update the Integration directly as the spec section is not merged by default

Review comment:
       nit: Maybe patching the `scale` endpoint could be used instead, rather than updating the whole resource:
   
   https://github.com/apache/camel-k/blob/fef775de02b3a59b9c844ba8bf9c418bf8a708b7/e2e/common/scale_integration_test.go#L79




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org