diff --git a/CHANGELOG.md b/CHANGELOG.md index 36385c5ba4f..66972e46597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,7 +52,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio ### Improvements -- TODO ([#XXX](https://github.com/kedacore/keda/issue/XXX)) +- **General:**: Add ScaledObject/ScaledJob names to output of `kubectl get triggerauthentication/clustertriggerauthentication` ([#796](https://github.com/kedacore/keda/issues/796)) ### Fixes diff --git a/apis/keda/v1alpha1/triggerauthentication_types.go b/apis/keda/v1alpha1/triggerauthentication_types.go old mode 100644 new mode 100755 index d97b827a5a7..c2fb5d9ab5f --- a/apis/keda/v1alpha1/triggerauthentication_types.go +++ b/apis/keda/v1alpha1/triggerauthentication_types.go @@ -26,15 +26,19 @@ import ( // +genclient // +genclient:nonNamespaced // +kubebuilder:resource:path=clustertriggerauthentications,scope=Cluster,shortName=cta;clustertriggerauth +// +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="PodIdentity",type="string",JSONPath=".spec.podIdentity.provider" // +kubebuilder:printcolumn:name="Secret",type="string",JSONPath=".spec.secretTargetRef[*].name" // +kubebuilder:printcolumn:name="Env",type="string",JSONPath=".spec.env[*].name" // +kubebuilder:printcolumn:name="VaultAddress",type="string",JSONPath=".spec.hashiCorpVault.address" +// +kubebuilder:printcolumn:name="ScaledObjects",type="string",priority=1,JSONPath=".status.scaledobjects" +// +kubebuilder:printcolumn:name="ScaledJobs",type="string",priority=1,JSONPath=".status.scaledjobs" type ClusterTriggerAuthentication struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec TriggerAuthenticationSpec `json:"spec"` + Spec TriggerAuthenticationSpec `json:"spec"` + Status TriggerAuthenticationStatus `json:"status,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -51,15 +55,19 @@ type ClusterTriggerAuthenticationList struct { // TriggerAuthentication defines how a trigger can authenticate // +genclient // +kubebuilder:resource:path=triggerauthentications,scope=Namespaced,shortName=ta;triggerauth +// +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="PodIdentity",type="string",JSONPath=".spec.podIdentity.provider" // +kubebuilder:printcolumn:name="Secret",type="string",JSONPath=".spec.secretTargetRef[*].name" // +kubebuilder:printcolumn:name="Env",type="string",JSONPath=".spec.env[*].name" // +kubebuilder:printcolumn:name="VaultAddress",type="string",JSONPath=".spec.hashiCorpVault.address" +// +kubebuilder:printcolumn:name="ScaledObjects",type="string",priority=1,JSONPath=".status.scaledobjects" +// +kubebuilder:printcolumn:name="ScaledJobs",type="string",priority=1,JSONPath=".status.scaledjobs" type TriggerAuthentication struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec TriggerAuthenticationSpec `json:"spec"` + Spec TriggerAuthenticationSpec `json:"spec"` + Status TriggerAuthenticationStatus `json:"status,omitempty"` } // TriggerAuthenticationSpec defines the various ways to authenticate @@ -80,6 +88,14 @@ type TriggerAuthenticationSpec struct { AzureKeyVault *AzureKeyVault `json:"azureKeyVault,omitempty"` } +// TriggerAuthenticationStatus defines the observed state of TriggerAuthentication +type TriggerAuthenticationStatus struct { + // +optional + ScaledObjectNamesStr string `json:"scaledobjects,omitempty"` + // +optional + ScaledJobNamesStr string `json:"scaledjobs,omitempty"` +} + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // TriggerAuthenticationList contains a list of TriggerAuthentication diff --git a/apis/keda/v1alpha1/zz_generated.deepcopy.go b/apis/keda/v1alpha1/zz_generated.deepcopy.go old mode 100644 new mode 100755 index 149cdb2effc..e10bb0fb81b --- a/apis/keda/v1alpha1/zz_generated.deepcopy.go +++ b/apis/keda/v1alpha1/zz_generated.deepcopy.go @@ -214,6 +214,7 @@ func (in *ClusterTriggerAuthentication) DeepCopyInto(out *ClusterTriggerAuthenti out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterTriggerAuthentication. @@ -827,6 +828,7 @@ func (in *TriggerAuthentication) DeepCopyInto(out *TriggerAuthentication) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TriggerAuthentication. @@ -919,6 +921,21 @@ func (in *TriggerAuthenticationSpec) DeepCopy() *TriggerAuthenticationSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TriggerAuthenticationStatus) DeepCopyInto(out *TriggerAuthenticationStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TriggerAuthenticationStatus. +func (in *TriggerAuthenticationStatus) DeepCopy() *TriggerAuthenticationStatus { + if in == nil { + return nil + } + out := new(TriggerAuthenticationStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ValueFromSecret) DeepCopyInto(out *ValueFromSecret) { *out = *in diff --git a/config/crd/bases/keda.sh_clustertriggerauthentications.yaml b/config/crd/bases/keda.sh_clustertriggerauthentications.yaml index 2e0c1e36523..b6e352ce52e 100644 --- a/config/crd/bases/keda.sh_clustertriggerauthentications.yaml +++ b/config/crd/bases/keda.sh_clustertriggerauthentications.yaml @@ -30,6 +30,14 @@ spec: - jsonPath: .spec.hashiCorpVault.address name: VaultAddress type: string + - jsonPath: .status.scaledobjects + name: ScaledObjects + priority: 1 + type: string + - jsonPath: .status.scaledjobs + name: ScaledJobs + priority: 1 + type: string name: v1alpha1 schema: openAPIV3Schema: @@ -222,9 +230,19 @@ spec: type: object type: array type: object + status: + description: TriggerAuthenticationStatus defines the observed state of + TriggerAuthentication + properties: + scaledjobs: + type: string + scaledobjects: + type: string + type: object required: - spec type: object served: true storage: true - subresources: {} + subresources: + status: {} diff --git a/config/crd/bases/keda.sh_triggerauthentications.yaml b/config/crd/bases/keda.sh_triggerauthentications.yaml index e97e481f74d..04650e8d908 100644 --- a/config/crd/bases/keda.sh_triggerauthentications.yaml +++ b/config/crd/bases/keda.sh_triggerauthentications.yaml @@ -30,6 +30,14 @@ spec: - jsonPath: .spec.hashiCorpVault.address name: VaultAddress type: string + - jsonPath: .status.scaledobjects + name: ScaledObjects + priority: 1 + type: string + - jsonPath: .status.scaledjobs + name: ScaledJobs + priority: 1 + type: string name: v1alpha1 schema: openAPIV3Schema: @@ -221,9 +229,19 @@ spec: type: object type: array type: object + status: + description: TriggerAuthenticationStatus defines the observed state of + TriggerAuthentication + properties: + scaledjobs: + type: string + scaledobjects: + type: string + type: object required: - spec type: object served: true storage: true - subresources: {} + subresources: + status: {} diff --git a/controllers/keda/hpa.go b/controllers/keda/hpa.go index 0b96382e8d7..be2edeefed3 100644 --- a/controllers/keda/hpa.go +++ b/controllers/keda/hpa.go @@ -32,7 +32,7 @@ import ( kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" kedacontrollerutil "github.com/kedacore/keda/v2/controllers/keda/util" "github.com/kedacore/keda/v2/pkg/scaling/executor" - kedautil "github.com/kedacore/keda/v2/pkg/util" + kedastatus "github.com/kedacore/keda/v2/pkg/status" version "github.com/kedacore/keda/v2/version" ) @@ -61,7 +61,7 @@ func (r *ScaledObjectReconciler) createAndDeployNewHPA(ctx context.Context, logg status := scaledObject.Status.DeepCopy() status.HpaName = hpaName - err = kedautil.UpdateScaledObjectStatus(ctx, r.Client, logger, scaledObject, status) + err = kedastatus.UpdateScaledObjectStatus(ctx, r.Client, logger, scaledObject, status) if err != nil { logger.Error(err, "Error updating scaledObject status with used hpaName") return err @@ -246,7 +246,7 @@ func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context, updateHealthStatus(scaledObject, externalMetricNames, status) - err = kedautil.UpdateScaledObjectStatus(ctx, r.Client, logger, scaledObject, status) + err = kedastatus.UpdateScaledObjectStatus(ctx, r.Client, logger, scaledObject, status) if err != nil { logger.Error(err, "Error updating scaledObject status with used externalMetricNames") return nil, err diff --git a/controllers/keda/scaledjob_controller.go b/controllers/keda/scaledjob_controller.go old mode 100644 new mode 100755 index f268e6a0b22..9e9ec7dd594 --- a/controllers/keda/scaledjob_controller.go +++ b/controllers/keda/scaledjob_controller.go @@ -43,7 +43,7 @@ import ( "github.com/kedacore/keda/v2/pkg/eventreason" "github.com/kedacore/keda/v2/pkg/prommetrics" "github.com/kedacore/keda/v2/pkg/scaling" - kedautil "github.com/kedacore/keda/v2/pkg/util" + kedastatus "github.com/kedacore/keda/v2/pkg/status" ) // +kubebuilder:rbac:groups=keda.sh,resources=scaledjobs;scaledjobs/finalizers;scaledjobs/status,verbs="*" @@ -129,7 +129,7 @@ func (r *ScaledJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // ensure Status Conditions are initialized if !scaledJob.Status.Conditions.AreInitialized() { conditions := kedav1alpha1.GetInitializedConditions() - if err := kedautil.SetStatusConditions(ctx, r.Client, reqLogger, scaledJob, conditions); err != nil { + if err := kedastatus.SetStatusConditions(ctx, r.Client, reqLogger, scaledJob, conditions); err != nil { return ctrl.Result{}, err } } @@ -157,9 +157,14 @@ func (r *ScaledJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( conditions.SetReadyCondition(metav1.ConditionTrue, "ScaledJobReady", msg) } - if err := kedautil.SetStatusConditions(ctx, r.Client, reqLogger, scaledJob, &conditions); err != nil { + if err := kedastatus.SetStatusConditions(ctx, r.Client, reqLogger, scaledJob, &conditions); err != nil { return ctrl.Result{}, err } + + if _, err := r.updateTriggerAuthenticationStatus(ctx, reqLogger, scaledJob); err != nil { + reqLogger.Error(err, "Error updating TriggerAuthentication Status") + } + return ctrl.Result{}, err } @@ -351,3 +356,17 @@ func (r *ScaledJobReconciler) updatePromMetricsOnDelete(namespacedName string) { delete(scaledJobPromMetricsMap, namespacedName) } + +func (r *ScaledJobReconciler) updateTriggerAuthenticationStatus(ctx context.Context, logger logr.Logger, scaledJob *kedav1alpha1.ScaledJob) (string, error) { + return kedastatus.UpdateTriggerAuthenticationStatusFromTriggers(ctx, logger, r.Client, scaledJob.GetNamespace(), scaledJob.Spec.Triggers, func(triggerAuthenticationStatus *kedav1alpha1.TriggerAuthenticationStatus) *kedav1alpha1.TriggerAuthenticationStatus { + triggerAuthenticationStatus.ScaledJobNamesStr = kedacontrollerutil.AppendIntoString(triggerAuthenticationStatus.ScaledJobNamesStr, scaledJob.GetName(), ",") + return triggerAuthenticationStatus + }) +} + +func (r *ScaledJobReconciler) updateTriggerAuthenticationStatusOnDelete(ctx context.Context, logger logr.Logger, scaledJob *kedav1alpha1.ScaledJob) (string, error) { + return kedastatus.UpdateTriggerAuthenticationStatusFromTriggers(ctx, logger, r.Client, scaledJob.GetNamespace(), scaledJob.Spec.Triggers, func(triggerAuthenticationStatus *kedav1alpha1.TriggerAuthenticationStatus) *kedav1alpha1.TriggerAuthenticationStatus { + triggerAuthenticationStatus.ScaledJobNamesStr = kedacontrollerutil.RemoveFromString(triggerAuthenticationStatus.ScaledJobNamesStr, scaledJob.GetName(), ",") + return triggerAuthenticationStatus + }) +} diff --git a/controllers/keda/scaledjob_finalizer.go b/controllers/keda/scaledjob_finalizer.go index 4275ceb5c60..99636eca2e9 100644 --- a/controllers/keda/scaledjob_finalizer.go +++ b/controllers/keda/scaledjob_finalizer.go @@ -50,6 +50,9 @@ func (r *ScaledJobReconciler) finalizeScaledJob(ctx context.Context, logger logr return err } + if _, err := r.updateTriggerAuthenticationStatusOnDelete(ctx, logger, scaledJob); err != nil { + logger.Error(err, "Failed to update TriggerAuthentication Status after removing a finalizer") + } r.updatePromMetricsOnDelete(namespacedName) } diff --git a/controllers/keda/scaledobject_controller.go b/controllers/keda/scaledobject_controller.go old mode 100644 new mode 100755 index 25de0c431c9..cacde0a4e05 --- a/controllers/keda/scaledobject_controller.go +++ b/controllers/keda/scaledobject_controller.go @@ -46,7 +46,7 @@ import ( "github.com/kedacore/keda/v2/pkg/eventreason" "github.com/kedacore/keda/v2/pkg/prommetrics" "github.com/kedacore/keda/v2/pkg/scaling" - kedautil "github.com/kedacore/keda/v2/pkg/util" + kedastatus "github.com/kedacore/keda/v2/pkg/status" ) // +kubebuilder:rbac:groups=keda.sh,resources=scaledobjects;scaledobjects/finalizers;scaledobjects/status,verbs="*" @@ -167,7 +167,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request // ensure Status Conditions are initialized if !scaledObject.Status.Conditions.AreInitialized() { conditions := kedav1alpha1.GetInitializedConditions() - if err := kedautil.SetStatusConditions(ctx, r.Client, reqLogger, scaledObject, conditions); err != nil { + if err := kedastatus.SetStatusConditions(ctx, r.Client, reqLogger, scaledObject, conditions); err != nil { return ctrl.Result{}, err } } @@ -189,10 +189,14 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request conditions.SetReadyCondition(metav1.ConditionTrue, kedav1alpha1.ScaledObjectConditionReadySucccesReason, msg) } - if err := kedautil.SetStatusConditions(ctx, r.Client, reqLogger, scaledObject, &conditions); err != nil { + if err := kedastatus.SetStatusConditions(ctx, r.Client, reqLogger, scaledObject, &conditions); err != nil { return ctrl.Result{}, err } + if _, err := r.updateTriggerAuthenticationStatus(ctx, reqLogger, scaledObject); err != nil { + reqLogger.Error(err, "Failed to update TriggerAuthentication Status after removing a finalizer") + } + return ctrl.Result{}, err } @@ -354,7 +358,7 @@ func (r *ScaledObjectReconciler) checkTargetResourceIsScalable(ctx context.Conte status.PausedReplicaCount = nil } - if err := kedautil.UpdateScaledObjectStatus(ctx, r.Client, logger, scaledObject, status); err != nil { + if err := kedastatus.UpdateScaledObjectStatus(ctx, r.Client, logger, scaledObject, status); err != nil { return gvkr, err } logger.Info("Detected resource targeted for scaling", "resource", gvkString, "name", scaledObject.Spec.ScaleTargetRef.Name) @@ -552,3 +556,19 @@ func (r *ScaledObjectReconciler) updatePromMetricsOnDelete(namespacedName string delete(scaledObjectPromMetricsMap, namespacedName) } + +func (r *ScaledObjectReconciler) updateTriggerAuthenticationStatus(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) (string, error) { + return kedastatus.UpdateTriggerAuthenticationStatusFromTriggers(ctx, logger, r.Client, scaledObject.GetNamespace(), scaledObject.Spec.Triggers, + func(triggerAuthenticationStatus *kedav1alpha1.TriggerAuthenticationStatus) *kedav1alpha1.TriggerAuthenticationStatus { + triggerAuthenticationStatus.ScaledObjectNamesStr = kedacontrollerutil.AppendIntoString(triggerAuthenticationStatus.ScaledObjectNamesStr, scaledObject.GetName(), ",") + return triggerAuthenticationStatus + }) +} + +func (r *ScaledObjectReconciler) updateTriggerAuthenticationStatusOnDelete(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) (string, error) { + return kedastatus.UpdateTriggerAuthenticationStatusFromTriggers(ctx, logger, r.Client, scaledObject.GetNamespace(), scaledObject.Spec.Triggers, + func(triggerAuthenticationStatus *kedav1alpha1.TriggerAuthenticationStatus) *kedav1alpha1.TriggerAuthenticationStatus { + triggerAuthenticationStatus.ScaledObjectNamesStr = kedacontrollerutil.RemoveFromString(triggerAuthenticationStatus.ScaledObjectNamesStr, scaledObject.GetName(), ",") + return triggerAuthenticationStatus + }) +} diff --git a/controllers/keda/scaledobject_finalizer.go b/controllers/keda/scaledobject_finalizer.go index 4c26caaa844..b3d48adbd77 100644 --- a/controllers/keda/scaledobject_finalizer.go +++ b/controllers/keda/scaledobject_finalizer.go @@ -79,6 +79,9 @@ func (r *ScaledObjectReconciler) finalizeScaledObject(ctx context.Context, logge return err } + if _, err := r.updateTriggerAuthenticationStatusOnDelete(ctx, logger, scaledObject); err != nil { + logger.Error(err, "Failed to update TriggerAuthentication Status after removing a finalizer") + } r.updatePromMetricsOnDelete(namespacedName) } diff --git a/controllers/keda/triggerauthentication_controller.go b/controllers/keda/triggerauthentication_controller.go old mode 100644 new mode 100755 diff --git a/controllers/keda/util/string_lists.go b/controllers/keda/util/string_lists.go index 90a26408ff9..379fb57770e 100644 --- a/controllers/keda/util/string_lists.go +++ b/controllers/keda/util/string_lists.go @@ -16,6 +16,10 @@ limitations under the License. package util +import ( + "strings" +) + // Contains checks if the passed string is present in the given slice of strings. func Contains(list []string, s string) bool { for _, v := range list { @@ -35,3 +39,44 @@ func Remove(list []string, s string) []string { } return list } + +// AppendIntoString append a new string into a string that has seprator +// For example, +// +// -- input: `viewer,editor`, `owner`, `,` output: `viewer,editor,owner` +func AppendIntoString(srcStr string, appendStr string, sep string) string { + if appendStr == "" { + return srcStr + } + + splitStrings := []string{} + if srcStr != "" { + splitStrings = strings.Split(srcStr, sep) + } + + if !Contains(splitStrings, appendStr) { + splitStrings = append(splitStrings, appendStr) + srcStr = strings.Join(splitStrings, sep) + } + return srcStr +} + +// RemoveFromString remove a string from src string that has seprator +// For example, +// +// -- input: `viewer,editor,owner`, `owner`, `,` output: `viewer,editor` +// -- input: `viewer,editor,owner`, `owner`, `:` output: `viewer,editor,owner` +func RemoveFromString(srcStr string, str string, sep string) string { + if srcStr == "" { + return srcStr + } + + splitStrings := []string{} + if srcStr != "" { + splitStrings = strings.Split(srcStr, sep) + } + + splitStrings = Remove(splitStrings, str) + srcStr = strings.Join(splitStrings, sep) + return srcStr +} diff --git a/controllers/keda/util/string_lists_test.go b/controllers/keda/util/string_lists_test.go new file mode 100644 index 00000000000..70dbb51d1bc --- /dev/null +++ b/controllers/keda/util/string_lists_test.go @@ -0,0 +1,75 @@ +/* +Copyright 2021 The KEDA Authors + +Licensed 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 util + +import ( + "reflect" + "testing" +) + +func TestAppendString(t *testing.T) { + testData := []struct { + name string + from string + append string + sep string + exp string + }{ + {"success", "viewer,editor", "owner", ",", "viewer,editor,owner"}, + {"single_success", "viewer", "owner", ",", "viewer,owner"}, + {"exist", "viewer,editor,owner", "editor", ",", "viewer,editor,owner"}, + {"no_separator", "viewer,editor", "owner", "", "viewer,editorowner"}, + {"space_separator", "viewer,editor", "owner", " ", "viewer,editor owner"}, + {"diff_separator", "viewer,editor", "owner", ":", "viewer,editor:owner"}, + {"no_from_str", "", "owner", ",", "owner"}, + {"no_append_str", "viewer,editor", "", ",", "viewer,editor"}, + } + + for _, tt := range testData { + got := AppendIntoString(tt.from, tt.append, tt.sep) + + if !reflect.DeepEqual(tt.exp, got) { + t.Errorf("Expected %v but got %v\n", tt.exp, got) + } + } +} + +func TestRemoveFromString(t *testing.T) { + testData := []struct { + name string + from string + delete string + sep string + exp string + }{ + {"success", "viewer,editor,owner", "owner", ",", "viewer,editor"}, + {"no_exist_success", "viewer", "owner", ",", "viewer"}, + {"no_separator", "viewer,editor,owner", "owner", "", "viewer,editor,owner"}, + {"space_separator", "viewer editor owner", "editor", " ", "viewer owner"}, + {"diff_separator", "viewer,editor,owner", "editor", ":", "viewer,editor,owner"}, + {"no_from_str", "", "owner", ",", ""}, + {"no_delete_str", "viewer,editor", "", ",", "viewer,editor"}, + } + + for _, tt := range testData { + got := RemoveFromString(tt.from, tt.delete, tt.sep) + + if !reflect.DeepEqual(tt.exp, got) { + t.Errorf("Expected %v but got %v\n", tt.exp, got) + } + } +} diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/clustertriggerauthentication.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/clustertriggerauthentication.go index b59f2955d9a..3773d69e357 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/clustertriggerauthentication.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/clustertriggerauthentication.go @@ -40,6 +40,7 @@ type ClusterTriggerAuthenticationsGetter interface { type ClusterTriggerAuthenticationInterface interface { Create(ctx context.Context, clusterTriggerAuthentication *v1alpha1.ClusterTriggerAuthentication, opts v1.CreateOptions) (*v1alpha1.ClusterTriggerAuthentication, error) Update(ctx context.Context, clusterTriggerAuthentication *v1alpha1.ClusterTriggerAuthentication, opts v1.UpdateOptions) (*v1alpha1.ClusterTriggerAuthentication, error) + UpdateStatus(ctx context.Context, clusterTriggerAuthentication *v1alpha1.ClusterTriggerAuthentication, opts v1.UpdateOptions) (*v1alpha1.ClusterTriggerAuthentication, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.ClusterTriggerAuthentication, error) @@ -128,6 +129,21 @@ func (c *clusterTriggerAuthentications) Update(ctx context.Context, clusterTrigg return } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *clusterTriggerAuthentications) UpdateStatus(ctx context.Context, clusterTriggerAuthentication *v1alpha1.ClusterTriggerAuthentication, opts v1.UpdateOptions) (result *v1alpha1.ClusterTriggerAuthentication, err error) { + result = &v1alpha1.ClusterTriggerAuthentication{} + err = c.client.Put(). + Resource("clustertriggerauthentications"). + Name(clusterTriggerAuthentication.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(clusterTriggerAuthentication). + Do(ctx). + Into(result) + return +} + // Delete takes name of the clusterTriggerAuthentication and deletes it. Returns an error if one occurs. func (c *clusterTriggerAuthentications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { return c.client.Delete(). diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_clustertriggerauthentication.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_clustertriggerauthentication.go index 64a12709ee0..7e1d056d622 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_clustertriggerauthentication.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_clustertriggerauthentication.go @@ -95,6 +95,17 @@ func (c *FakeClusterTriggerAuthentications) Update(ctx context.Context, clusterT return obj.(*v1alpha1.ClusterTriggerAuthentication), err } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeClusterTriggerAuthentications) UpdateStatus(ctx context.Context, clusterTriggerAuthentication *v1alpha1.ClusterTriggerAuthentication, opts v1.UpdateOptions) (*v1alpha1.ClusterTriggerAuthentication, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(clustertriggerauthenticationsResource, "status", clusterTriggerAuthentication), &v1alpha1.ClusterTriggerAuthentication{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ClusterTriggerAuthentication), err +} + // Delete takes name of the clusterTriggerAuthentication and deletes it. Returns an error if one occurs. func (c *FakeClusterTriggerAuthentications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_triggerauthentication.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_triggerauthentication.go index 5cecdac7917..a272dcc5912 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_triggerauthentication.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_triggerauthentication.go @@ -101,6 +101,18 @@ func (c *FakeTriggerAuthentications) Update(ctx context.Context, triggerAuthenti return obj.(*v1alpha1.TriggerAuthentication), err } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeTriggerAuthentications) UpdateStatus(ctx context.Context, triggerAuthentication *v1alpha1.TriggerAuthentication, opts v1.UpdateOptions) (*v1alpha1.TriggerAuthentication, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(triggerauthenticationsResource, "status", c.ns, triggerAuthentication), &v1alpha1.TriggerAuthentication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TriggerAuthentication), err +} + // Delete takes name of the triggerAuthentication and deletes it. Returns an error if one occurs. func (c *FakeTriggerAuthentications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/triggerauthentication.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/triggerauthentication.go index 81b9232e0fc..5fe86261af6 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/triggerauthentication.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/triggerauthentication.go @@ -40,6 +40,7 @@ type TriggerAuthenticationsGetter interface { type TriggerAuthenticationInterface interface { Create(ctx context.Context, triggerAuthentication *v1alpha1.TriggerAuthentication, opts v1.CreateOptions) (*v1alpha1.TriggerAuthentication, error) Update(ctx context.Context, triggerAuthentication *v1alpha1.TriggerAuthentication, opts v1.UpdateOptions) (*v1alpha1.TriggerAuthentication, error) + UpdateStatus(ctx context.Context, triggerAuthentication *v1alpha1.TriggerAuthentication, opts v1.UpdateOptions) (*v1alpha1.TriggerAuthentication, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.TriggerAuthentication, error) @@ -135,6 +136,22 @@ func (c *triggerAuthentications) Update(ctx context.Context, triggerAuthenticati return } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *triggerAuthentications) UpdateStatus(ctx context.Context, triggerAuthentication *v1alpha1.TriggerAuthentication, opts v1.UpdateOptions) (result *v1alpha1.TriggerAuthentication, err error) { + result = &v1alpha1.TriggerAuthentication{} + err = c.client.Put(). + Namespace(c.ns). + Resource("triggerauthentications"). + Name(triggerAuthentication.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(triggerAuthentication). + Do(ctx). + Into(result) + return +} + // Delete takes name of the triggerAuthentication and deletes it. Returns an error if one occurs. func (c *triggerAuthentications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { return c.client.Delete(). diff --git a/pkg/scaling/executor/scale_executor.go b/pkg/scaling/executor/scale_executor.go index 2e0d6a83e31..3e013e5440b 100644 --- a/pkg/scaling/executor/scale_executor.go +++ b/pkg/scaling/executor/scale_executor.go @@ -29,7 +29,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" - kedautil "github.com/kedacore/keda/v2/pkg/util" + kedastatus "github.com/kedacore/keda/v2/pkg/status" ) const ( @@ -78,7 +78,7 @@ func (e *scaleExecutor) updateLastActiveTime(ctx context.Context, logger logr.Lo } return nil } - return kedautil.TransformObject(ctx, e.client, logger, object, now, transform) + return kedastatus.TransformObject(ctx, e.client, logger, object, now, transform) } func (e *scaleExecutor) setCondition(ctx context.Context, logger logr.Logger, object interface{}, status metav1.ConditionStatus, reason string, message string, setCondition func(kedav1alpha1.Conditions, metav1.ConditionStatus, string, string)) error { @@ -103,7 +103,7 @@ func (e *scaleExecutor) setCondition(ctx context.Context, logger logr.Logger, ob reason: reason, message: message, } - return kedautil.TransformObject(ctx, e.client, logger, object, &target, transform) + return kedastatus.TransformObject(ctx, e.client, logger, object, &target, transform) } func (e *scaleExecutor) setReadyCondition(ctx context.Context, logger logr.Logger, object interface{}, status metav1.ConditionStatus, reason string, message string) error { diff --git a/pkg/scaling/executor/scale_scaledobjects.go b/pkg/scaling/executor/scale_scaledobjects.go index 4f67e08a3f7..4f7eea15798 100644 --- a/pkg/scaling/executor/scale_scaledobjects.go +++ b/pkg/scaling/executor/scale_scaledobjects.go @@ -31,7 +31,7 @@ import ( kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" kedacontrollerutil "github.com/kedacore/keda/v2/controllers/keda/util" "github.com/kedacore/keda/v2/pkg/eventreason" - kedautil "github.com/kedacore/keda/v2/pkg/util" + kedastatus "github.com/kedacore/keda/v2/pkg/status" ) func (e *scaleExecutor) RequestScale(ctx context.Context, scaledObject *kedav1alpha1.ScaledObject, isActive bool, isError bool) { @@ -106,7 +106,7 @@ func (e *scaleExecutor) RequestScale(ctx context.Context, scaledObject *kedav1al } if *pausedCount != currentReplicas || status.PausedReplicaCount == nil { status.PausedReplicaCount = pausedCount - err = kedautil.UpdateScaledObjectStatus(ctx, e.client, logger, scaledObject, status) + err = kedastatus.UpdateScaledObjectStatus(ctx, e.client, logger, scaledObject, status) if err != nil { logger.Error(err, "error updating status paused replica count") return diff --git a/pkg/status/status.go b/pkg/status/status.go new file mode 100755 index 00000000000..73a44122141 --- /dev/null +++ b/pkg/status/status.go @@ -0,0 +1,189 @@ +/* +Copyright 2023 The KEDA Authors + +Licensed 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 status + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + "github.com/pkg/errors" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" + + kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" +) + +// SetStatusConditions patches given object with passed list of conditions based on the object's type or returns an error. +func SetStatusConditions(ctx context.Context, client runtimeclient.StatusClient, logger logr.Logger, object interface{}, conditions *kedav1alpha1.Conditions) error { + transform := func(runtimeObj runtimeclient.Object, target interface{}) error { + conditions, ok := target.(*kedav1alpha1.Conditions) + if !ok { + return fmt.Errorf("transform target is not kedav1alpha1.Conditions type %v", target) + } + switch obj := runtimeObj.(type) { + case *kedav1alpha1.ScaledObject: + obj.Status.Conditions = *conditions + case *kedav1alpha1.ScaledJob: + obj.Status.Conditions = *conditions + default: + } + return nil + } + return TransformObject(ctx, client, logger, object, conditions, transform) +} + +// UpdateScaledObjectStatus patches the given ScaledObject with the updated status passed to it or returns an error. +func UpdateScaledObjectStatus(ctx context.Context, client runtimeclient.StatusClient, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, status *kedav1alpha1.ScaledObjectStatus) error { + transform := func(runtimeObj runtimeclient.Object, target interface{}) error { + status, ok := target.(*kedav1alpha1.ScaledObjectStatus) + if !ok { + return fmt.Errorf("transform target is not kedav1alpha1.ScaledObjectStatus type %v", target) + } + switch obj := runtimeObj.(type) { + case *kedav1alpha1.ScaledObject: + obj.Status = *status + default: + } + return nil + } + return TransformObject(ctx, client, logger, scaledObject, status, transform) +} + +// getTriggerAuth returns TriggerAuthentication/ClusterTriggerAuthentication object and its status from AuthenticationRef or returns an error. +func getTriggerAuth(ctx context.Context, client runtimeclient.Client, triggerAuthRef *kedav1alpha1.AuthenticationRef, namespace string) (runtimeclient.Object, *kedav1alpha1.TriggerAuthenticationStatus, error) { + if triggerAuthRef == nil { + return nil, nil, fmt.Errorf("triggerAuthRef is nil") + } + + if triggerAuthRef.Kind == "" || triggerAuthRef.Kind == "TriggerAuthentication" { + triggerAuth := &kedav1alpha1.TriggerAuthentication{} + err := client.Get(ctx, types.NamespacedName{Name: triggerAuthRef.Name, Namespace: namespace}, triggerAuth) + if err != nil { + return nil, nil, err + } + return triggerAuth, &triggerAuth.Status, nil + } else if triggerAuthRef.Kind == "ClusterTriggerAuthentication" { + clusterTriggerAuth := &kedav1alpha1.ClusterTriggerAuthentication{} + err := client.Get(ctx, types.NamespacedName{Name: triggerAuthRef.Name, Namespace: namespace}, clusterTriggerAuth) + if err != nil { + return nil, nil, err + } + return clusterTriggerAuth, &clusterTriggerAuth.Status, nil + } + return nil, nil, fmt.Errorf("unknown trigger auth kind %s", triggerAuthRef.Kind) +} + +// updateTriggerAuthenticationStatus patches TriggerAuthentication/ClusterTriggerAuthentication from AuthenticationRef with the status that updated by statushanler function or returns an error. +func updateTriggerAuthenticationStatus(ctx context.Context, logger logr.Logger, client runtimeclient.Client, namespace string, triggerAuthRef *kedav1alpha1.AuthenticationRef, statusHandler func(*kedav1alpha1.TriggerAuthenticationStatus) *kedav1alpha1.TriggerAuthenticationStatus) error { + triggerAuth, triggerAuthStatus, err := getTriggerAuth(ctx, client, triggerAuthRef, namespace) + + if err != nil { + if k8sErrors.IsNotFound(err) { + logger.Info("TriggerAuthentication Not Found") + } + logger.Error(err, "Failed to get TriggerAuthentication") + return err + } + + triggerAuthenticationStatus := statusHandler(triggerAuthStatus.DeepCopy()) + + transform := func(runtimeObj runtimeclient.Object, target interface{}) error { + status, ok := target.(*kedav1alpha1.TriggerAuthenticationStatus) + if !ok { + return fmt.Errorf("transform target is not kedav1alpha1.TriggerAuthenticationStatus type %v", target) + } + switch obj := runtimeObj.(type) { + case *kedav1alpha1.TriggerAuthentication: + obj.Status = *status + case *kedav1alpha1.ClusterTriggerAuthentication: + obj.Status = *status + default: + } + return nil + } + + if err := TransformObject(ctx, client, logger, triggerAuth, triggerAuthenticationStatus, transform); err != nil { + logger.Error(err, "Failed to update TriggerAuthenticationStatus") + } + + return err +} + +// UpdateTriggerAuthenticationStatusFromTriggers patches triggerAuthenticationStatus From the given Triggers or returns an error. +func UpdateTriggerAuthenticationStatusFromTriggers(ctx context.Context, logger logr.Logger, client runtimeclient.Client, namespace string, scaleTriggers []kedav1alpha1.ScaleTriggers, statusHandler func(*kedav1alpha1.TriggerAuthenticationStatus) *kedav1alpha1.TriggerAuthenticationStatus) (string, error) { + var errs error + for _, trigger := range scaleTriggers { + if trigger.AuthenticationRef == nil { + continue + } + + err := updateTriggerAuthenticationStatus(ctx, logger, client, namespace, trigger.AuthenticationRef, statusHandler) + if err != nil { + errs = errors.Wrap(errs, err.Error()) + } + } + + if errs != nil { + return "Update TriggerAuthentication Status Failed", errs + } + return "Update TriggerAuthentication Status Successfully", nil +} + +// TransformObject patches the given object with the targeted passed to it through a transformer function or returns an error. +func TransformObject(ctx context.Context, client runtimeclient.StatusClient, logger logr.Logger, object interface{}, target interface{}, transform func(runtimeclient.Object, interface{}) error) error { + var patch runtimeclient.Patch + + runtimeObj := object.(runtimeclient.Object) + switch obj := runtimeObj.(type) { + case *kedav1alpha1.ScaledObject: + patch = runtimeclient.MergeFrom(obj.DeepCopy()) + if err := transform(obj, target); err != nil { + logger.Error(err, "failed to patch ScaledObject") + return err + } + case *kedav1alpha1.ScaledJob: + patch = runtimeclient.MergeFrom(obj.DeepCopy()) + if err := transform(obj, target); err != nil { + logger.Error(err, "failed to patch ScaledJob") + return err + } + case *kedav1alpha1.TriggerAuthentication: + patch = runtimeclient.MergeFrom(obj.DeepCopy()) + if err := transform(obj, target); err != nil { + logger.Error(err, "failed to patch TriggerAuthentication") + return err + } + case *kedav1alpha1.ClusterTriggerAuthentication: + patch = runtimeclient.MergeFrom(obj.DeepCopy()) + if err := transform(obj, target); err != nil { + logger.Error(err, "failed to patch ClusterTriggerAuthentication") + return err + } + default: + err := fmt.Errorf("unknown scalable object type %v", obj) + logger.Error(err, "failed to patch Objects") + return err + } + + err := client.Status().Patch(ctx, runtimeObj, patch) + if err != nil { + logger.Error(err, "failed to patch Objects") + } + return err +} diff --git a/pkg/util/status.go b/pkg/util/status.go deleted file mode 100644 index 479ead8b083..00000000000 --- a/pkg/util/status.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2023 The KEDA Authors - -Licensed 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 util - -import ( - "context" - "fmt" - - "github.com/go-logr/logr" - runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" - - kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" -) - -// SetStatusConditions patches given object with passed list of conditions based on the object's type or returns an error. -func SetStatusConditions(ctx context.Context, client runtimeclient.StatusClient, logger logr.Logger, object interface{}, conditions *kedav1alpha1.Conditions) error { - transform := func(runtimeObj runtimeclient.Object, target interface{}) error { - conditions, ok := target.(*kedav1alpha1.Conditions) - if !ok { - return fmt.Errorf("transform target is not kedav1alpha1.Conditions type %v", target) - } - switch obj := runtimeObj.(type) { - case *kedav1alpha1.ScaledObject: - obj.Status.Conditions = *conditions - case *kedav1alpha1.ScaledJob: - obj.Status.Conditions = *conditions - default: - } - return nil - } - return TransformObject(ctx, client, logger, object, conditions, transform) -} - -// UpdateScaledObjectStatus patches the given ScaledObject with the updated status passed to it or returns an error. -func UpdateScaledObjectStatus(ctx context.Context, client runtimeclient.StatusClient, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, status *kedav1alpha1.ScaledObjectStatus) error { - transform := func(runtimeObj runtimeclient.Object, target interface{}) error { - status, ok := target.(*kedav1alpha1.ScaledObjectStatus) - if !ok { - return fmt.Errorf("transform target is not kedav1alpha1.ScaledObjectStatus type %v", target) - } - switch obj := runtimeObj.(type) { - case *kedav1alpha1.ScaledObject: - obj.Status = *status - default: - } - return nil - } - return TransformObject(ctx, client, logger, scaledObject, status, transform) -} - -// TransformObject patches the given object with the targeted passed to it through a transformer function or returns an error. -func TransformObject(ctx context.Context, client runtimeclient.StatusClient, logger logr.Logger, object interface{}, target interface{}, transform func(runtimeclient.Object, interface{}) error) error { - var patch runtimeclient.Patch - - runtimeObj := object.(runtimeclient.Object) - switch obj := runtimeObj.(type) { - case *kedav1alpha1.ScaledObject: - patch = runtimeclient.MergeFrom(obj.DeepCopy()) - if err := transform(obj, target); err != nil { - logger.Error(err, "failed to patch ScaledObject") - return err - } - case *kedav1alpha1.ScaledJob: - patch = runtimeclient.MergeFrom(obj.DeepCopy()) - if err := transform(obj, target); err != nil { - logger.Error(err, "failed to patch ScaledJob") - return err - } - default: - err := fmt.Errorf("unknown scalable object type %v", obj) - logger.Error(err, "failed to patch Objects") - return err - } - - err := client.Status().Patch(ctx, runtimeObj, patch) - if err != nil { - logger.Error(err, "failed to patch Objects") - } - return err -} diff --git a/tests/helper/helper.go b/tests/helper/helper.go index 12bc9e1cf30..f300caaa624 100644 --- a/tests/helper/helper.go +++ b/tests/helper/helper.go @@ -796,3 +796,15 @@ func generateCA(t *testing.T) { require.NoErrorf(t, err, "error closing custom CA key file- %s", err) } } + +// CheckKubectlGetResult runs `kubectl get` with parameters and compares output with expected value +func CheckKubectlGetResult(t *testing.T, kind string, name string, namespace string, otherparameter string, expected string) { + time.Sleep(1 * time.Second) // wait a second for recource deployment finished + kctlGetCmd := fmt.Sprintf(`kubectl get %s/%s -n %s %s"`, kind, name, namespace, otherparameter) + t.Log("Running kubectl cmd:", kctlGetCmd) + output, err := ExecuteCommand(kctlGetCmd) + assert.NoErrorf(t, err, "cannot get rollout info - %s", err) + + unqoutedOutput := strings.ReplaceAll(string(output), "\"", "") + assert.Equal(t, expected, unqoutedOutput) +} diff --git a/tests/internals/update_ta/update_ta_test.go b/tests/internals/update_ta/update_ta_test.go new file mode 100644 index 00000000000..f63c81e9126 --- /dev/null +++ b/tests/internals/update_ta/update_ta_test.go @@ -0,0 +1,352 @@ +//go:build e2e +// +build e2e + +package update_ta_so_test + +import ( + "fmt" + "testing" + + "github.com/joho/godotenv" + "github.com/stretchr/testify/assert" + + . "github.com/kedacore/keda/v2/tests/helper" +) + +const ( + testName = "update-ta-so-test" +) + +// Load environment variables from .env file +var _ = godotenv.Load("../../.env") + +var ( + namespace = fmt.Sprintf("%s-ns", testName) + deploymentName = fmt.Sprintf("%s-deployment", testName) + deployment2Name = fmt.Sprintf("%s-deployment-2", testName) + scaledObjectName = fmt.Sprintf("%s-so", testName) + scaledObject2Name = fmt.Sprintf("%s-so-2", testName) + secretName = fmt.Sprintf("%s-secret", testName) + triggerAuthName = fmt.Sprintf("%s-ta", testName) + scaledJobName = fmt.Sprintf("%s-sj", testName) + scaledJob2Name = fmt.Sprintf("%s-sj-2", testName) + minReplicas = 1 + maxReplicas = 5 + triggerAuthKind = "TriggerAuthentication" + clusterTriggerAuthKind = "ClusterTriggerAuthentication" +) + +type templateData struct { + TestNamespace string + TriggerAuthKind string + DeploymentName string + Deployment2Name string + ScaledObject string + ScaledObject2 string + TriggerAuthName string + SecretName string + MinReplicas string + MaxReplicas string + ScaledJob string + ScaledJob2 string +} + +const ( + secretTemplate = `apiVersion: v1 +kind: Secret +metadata: + name: {{.SecretName}} + namespace: {{.TestNamespace}} +data: + AUTH_PASSWORD: U0VDUkVUCg== + AUTH_USERNAME: VVNFUgo= +` + + triggerAuthenticationTemplate = `apiVersion: keda.sh/v1alpha1 +kind: {{.TriggerAuthKind}} +metadata: + name: {{.TriggerAuthName}} + namespace: {{.TestNamespace}} +spec: + secretTargetRef: + - parameter: username + name: {{.SecretName}} + key: AUTH_USERNAME + - parameter: password + name: {{.SecretName}} + key: AUTH_PASSWORD +` + + deploymentTemplate = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + deploy: {{.DeploymentName}} + name: {{.DeploymentName}} + namespace: {{.TestNamespace}} +spec: + selector: + matchLabels: + app: {{.DeploymentName}} + replicas: {{.MinReplicas}} + template: + metadata: + labels: + app: {{.DeploymentName}} + spec: + containers: + - name: nginx + image: nginxinc/nginx-unprivileged + ports: + - containerPort: 80 + resources: + requests: + cpu: "200m" + limits: + cpu: "500m" +` + + deployment2Template = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + deploy: {{.Deployment2Name}} + name: {{.Deployment2Name}} + namespace: {{.TestNamespace}} +spec: + selector: + matchLabels: + app: {{.Deployment2Name}} + replicas: {{.MinReplicas}} + template: + metadata: + labels: + app: {{.Deployment2Name}} + spec: + containers: + - name: nginx + image: nginxinc/nginx-unprivileged + ports: + - containerPort: 80 + resources: + requests: + cpu: "200m" + limits: + cpu: "500m" +` + + scaledObjectTriggerTemplate = ` +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: {{.ScaledObject}} + namespace: {{.TestNamespace}} + labels: + app: {{.DeploymentName}} +spec: + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: 10 + pollingInterval: 10 + scaleTargetRef: + name: {{.DeploymentName}} + minReplicaCount: {{.MinReplicas}} + maxReplicaCount: {{.MaxReplicas}} + cooldownPeriod: 1 + triggers: + - type: cpu + metricType: Utilization + metadata: + value: "50" + authenticationRef: + name: {{.TriggerAuthName}} + kind: {{.TriggerAuthKind}} +` + + scaledObjectTrigger2Template = ` +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: {{.ScaledObject2}} + namespace: {{.TestNamespace}} + labels: + app: {{.DeploymentName}} +spec: + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: 10 + pollingInterval: 10 + scaleTargetRef: + name: {{.Deployment2Name}} + minReplicaCount: {{.MinReplicas}} + maxReplicaCount: {{.MaxReplicas}} + cooldownPeriod: 1 + triggers: + - type: cpu + metricType: Utilization + metadata: + value: "50" + authenticationRef: + name: {{.TriggerAuthName}} + kind: {{.TriggerAuthKind}} +` + + scaledJobTemplate = ` +apiVersion: keda.sh/v1alpha1 +kind: ScaledJob +metadata: + name: {{.ScaledJob}} + namespace: {{.TestNamespace}} +spec: + jobTargetRef: + template: + spec: + containers: + - name: external-executor + image: busybox + command: + - sleep + - "30" + imagePullPolicy: IfNotPresent + restartPolicy: Never + backoffLimit: 1 + pollingInterval: 5 + minReplicaCount: {{.MinReplicas}} + maxReplicaCount: {{.MaxReplicas}} + successfulJobsHistoryLimit: 0 + failedJobsHistoryLimit: 0 + triggers: + - type: cpu + metadata: + type: Utilization + value: "50" + authenticationRef: + name: {{.TriggerAuthName}} + kind: {{.TriggerAuthKind}} +` + + scaledJob2Template = ` +apiVersion: keda.sh/v1alpha1 +kind: ScaledJob +metadata: + name: {{.ScaledJob2}} + namespace: {{.TestNamespace}} +spec: + jobTargetRef: + template: + spec: + containers: + - name: external-executor + image: busybox + command: + - sleep + - "30" + imagePullPolicy: IfNotPresent + restartPolicy: Never + backoffLimit: 1 + pollingInterval: 5 + minReplicaCount: {{.MinReplicas}} + maxReplicaCount: {{.MaxReplicas}} + successfulJobsHistoryLimit: 0 + failedJobsHistoryLimit: 0 + triggers: + - type: cpu + metadata: + type: Utilization + value: "50" + authenticationRef: + name: {{.TriggerAuthName}} + kind: {{.TriggerAuthKind}} +` +) + +func TestTriggerAuthenticationGeneral(t *testing.T) { + // setup + t.Log("--- setting up ---") + // Create kubernetes resources + kc := GetKubernetesClient(t) + + t.Log("--- testing triggerauthentication ---") + data, templates := getTemplateData(triggerAuthKind) + CreateKubernetesResources(t, kc, namespace, data, templates) + + assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, namespace, minReplicas, 180, 3), + "replica count should be %d after 3 minutes", minReplicas) + + testTriggerAuthenticationStatusValue(t, data, triggerAuthKind) + + // Clean resources and then testing clustertriggerauthentication + DeleteKubernetesResources(t, namespace, data, templates) + + t.Log("--- testing clustertriggerauthentication ---") + data, templates = getTemplateData(clusterTriggerAuthKind) + CreateKubernetesResources(t, kc, namespace, data, templates) + + assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, namespace, minReplicas, 180, 3), + "replica count should be %d after 3 minutes", minReplicas) + + testTriggerAuthenticationStatusValue(t, data, clusterTriggerAuthKind) + DeleteKubernetesResources(t, namespace, data, templates) +} + +// tests basic scaling with one trigger based on metrics +func testTriggerAuthenticationStatusValue(t *testing.T, data templateData, kind string) { + KubectlApplyWithTemplate(t, data, "triggerAuthenticationTemplate", triggerAuthenticationTemplate) + t.Log("--- test one scaledObject ---") + KubectlApplyWithTemplate(t, data, "scaledObjectTriggerTemplate", scaledObjectTriggerTemplate) + otherparameter := `-o jsonpath="{.status.scaledobjects}"` + CheckKubectlGetResult(t, kind, triggerAuthName, namespace, otherparameter, scaledObjectName) + + t.Log("--- test two scaledObject ---") + KubectlApplyWithTemplate(t, data, "scaledObjectTrigger2Template", scaledObjectTrigger2Template) + CheckKubectlGetResult(t, kind, triggerAuthName, namespace, otherparameter, scaledObjectName+","+scaledObject2Name) + + t.Log("--- test reomve scaledObject ---") + KubectlDeleteWithTemplate(t, data, "scaledObjectTriggerTemplate", scaledObjectTriggerTemplate) + CheckKubectlGetResult(t, kind, triggerAuthName, namespace, otherparameter, scaledObject2Name) + KubectlDeleteWithTemplate(t, data, "scaledObjectTrigger2Template", scaledObjectTrigger2Template) + CheckKubectlGetResult(t, kind, triggerAuthName, namespace, otherparameter, "") + + t.Log("--- test one scaledJob ---") + otherparameter = `-o jsonpath="{.status.scaledjobs}"` + KubectlApplyWithTemplate(t, data, "scaledJobTemplate", scaledJobTemplate) + CheckKubectlGetResult(t, kind, triggerAuthName, namespace, otherparameter, scaledJobName) + + t.Log("--- test two scaledJob ---") + KubectlApplyWithTemplate(t, data, "scaledJob2Template", scaledJob2Template) + CheckKubectlGetResult(t, kind, triggerAuthName, namespace, otherparameter, scaledJobName+","+scaledJob2Name) + + t.Log("--- test reomve scaledObject ---") + KubectlDeleteWithTemplate(t, data, "scaledJobTemplate", scaledJobTemplate) + CheckKubectlGetResult(t, kind, triggerAuthName, namespace, otherparameter, scaledJob2Name) + KubectlDeleteWithTemplate(t, data, "scaledJob2Template", scaledJob2Template) + CheckKubectlGetResult(t, kind, triggerAuthName, namespace, otherparameter, "") +} + +// help function to load template data +func getTemplateData(triggerAuthKind string) (templateData, []Template) { + return templateData{ + TestNamespace: namespace, + TriggerAuthKind: triggerAuthKind, + DeploymentName: deploymentName, + Deployment2Name: deployment2Name, + TriggerAuthName: triggerAuthName, + ScaledObject: scaledObjectName, + ScaledObject2: scaledObject2Name, + ScaledJob: scaledJobName, + ScaledJob2: scaledJob2Name, + SecretName: secretName, + MinReplicas: fmt.Sprintf("%v", minReplicas), + MaxReplicas: fmt.Sprintf("%v", maxReplicas), + }, []Template{ + {Name: "secretTemplate", Config: secretTemplate}, + {Name: "deploymentTemplate", Config: deploymentTemplate}, + {Name: "deployment2Template", Config: deployment2Template}, + } +}