From 0bf2f9e78f6a65d79eed0135e49289816e9a2533 Mon Sep 17 00:00:00 2001 From: Rakshit Gondwal <98955085+rakshitgondwal@users.noreply.github.com> Date: Fri, 5 May 2023 12:38:16 +0530 Subject: [PATCH 01/62] feat(operator): trim KeptnAppVersion name that exceed max limit (#1296) Signed-off-by: Rakshit Gondwal --- .../apis/lifecycle/v1alpha3/common/common.go | 1 + .../lifecycle/keptnapp/controller.go | 3 +++ .../lifecycle/keptnapp/controller_test.go | 21 +++++++++++++++++++ .../keptnworkloadinstance/controller.go | 1 + 4 files changed, 26 insertions(+) diff --git a/operator/apis/lifecycle/v1alpha3/common/common.go b/operator/apis/lifecycle/v1alpha3/common/common.go index fe40028763..40c9a368c3 100644 --- a/operator/apis/lifecycle/v1alpha3/common/common.go +++ b/operator/apis/lifecycle/v1alpha3/common/common.go @@ -35,6 +35,7 @@ const MaxAppNameLength = 25 const MaxWorkloadNameLength = 25 const MaxTaskNameLength = 25 const MaxVersionLength = 12 +const MaxK8sObjectLength = 253 type AppType string diff --git a/operator/controllers/lifecycle/keptnapp/controller.go b/operator/controllers/lifecycle/keptnapp/controller.go index cfc61323fd..69a30624e0 100644 --- a/operator/controllers/lifecycle/keptnapp/controller.go +++ b/operator/controllers/lifecycle/keptnapp/controller.go @@ -159,6 +159,9 @@ func (r *KeptnAppReconciler) createAppVersion(ctx context.Context, app *klcv1alp } appVersion := app.GenerateAppVersion(previousVersion, traceContextCarrier) + if len(appVersion.ObjectMeta.Name) > common.MaxK8sObjectLength { + appVersion.ObjectMeta.Name = common.TruncateString(appVersion.ObjectMeta.Name, common.MaxK8sObjectLength) + } appVersion.Spec.TraceId = appTraceContextCarrier err := controllerutil.SetControllerReference(app, &appVersion, r.Scheme) if err != nil { diff --git a/operator/controllers/lifecycle/keptnapp/controller_test.go b/operator/controllers/lifecycle/keptnapp/controller_test.go index 9f37e4d1b3..b0566c3cd6 100644 --- a/operator/controllers/lifecycle/keptnapp/controller_test.go +++ b/operator/controllers/lifecycle/keptnapp/controller_test.go @@ -47,6 +47,27 @@ func TestKeptnAppReconciler_createAppVersionSuccess(t *testing.T) { assert.Equal(t, appVersion.Name, fmt.Sprintf("%s-%s-%s", app.Name, app.Spec.Version, apicommon.Hash(app.Generation))) } +func TestKeptnAppReconciler_createAppVersionWithLongName(t *testing.T) { + //nolint:gci + longName := `loremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremloremax` + //nolint:gci + trimmedName := `loremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustrylorem` + + app := &lfcv1alpha3.KeptnApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: longName, + }, + } + r, _, _ := setupReconciler() + + appVersion, err := r.createAppVersion(context.Background(), app) + if err != nil { + t.Errorf("Error creating app version: %s", err.Error()) + } + t.Log("Verifying app name length is not greater than MaxK8sObjectLen") + assert.Equal(t, appVersion.ObjectMeta.Name, trimmedName) +} + func TestKeptnAppReconciler_reconcile(t *testing.T) { r, eventChannel, tracer := setupReconciler() diff --git a/operator/controllers/lifecycle/keptnworkloadinstance/controller.go b/operator/controllers/lifecycle/keptnworkloadinstance/controller.go index aa923a67b6..b9e2141e6c 100644 --- a/operator/controllers/lifecycle/keptnworkloadinstance/controller.go +++ b/operator/controllers/lifecycle/keptnworkloadinstance/controller.go @@ -305,6 +305,7 @@ func (r *KeptnWorkloadInstanceReconciler) getAppVersionForWorkloadInstance(ctx c return false, latestVersion, err } + // If the latest version is empty or the workload is not found, return false and empty result if latestVersion.Spec.Version == "" || !workloadFound { return false, klcv1alpha3.KeptnAppVersion{}, nil } From a8bc440a4f15f624455c513373033c78a31a53b5 Mon Sep 17 00:00:00 2001 From: Florian Bacher Date: Tue, 9 May 2023 07:47:18 +0200 Subject: [PATCH 02/62] docs: added comments to document the meaning of CRD properties (#1360) Signed-off-by: Florian Bacher Co-authored-by: Giovanni Liva --- .../docs/crd-ref/lifecycle/v1alpha3/_index.md | 194 +++++++++--------- .../apis/lifecycle/v1alpha3/keptnapp_types.go | 42 +++- .../v1alpha3/keptnappcreationrequest_types.go | 4 +- .../v1alpha3/keptnappversion_types.go | 47 +++-- .../v1alpha3/keptnevaluation_types.go | 56 +++-- .../keptnevaluationdefinition_types.go | 19 +- .../lifecycle/v1alpha3/keptntask_types.go | 78 +++++-- .../v1alpha3/keptntaskdefinition_types.go | 43 +++- .../lifecycle/v1alpha3/keptnworkload_types.go | 37 +++- .../v1alpha3/keptnworkloadinstance_types.go | 56 +++-- ...cle.keptn.sh_keptnappcreationrequests.yaml | 6 +- .../bases/lifecycle.keptn.sh_keptnapps.yaml | 34 ++- .../lifecycle.keptn.sh_keptnappversions.yaml | 85 +++++++- ...e.keptn.sh_keptnevaluationdefinitions.yaml | 17 +- .../lifecycle.keptn.sh_keptnevaluations.yaml | 42 +++- ...fecycle.keptn.sh_keptntaskdefinitions.yaml | 55 ++++- .../bases/lifecycle.keptn.sh_keptntasks.yaml | 62 +++++- ...cycle.keptn.sh_keptnworkloadinstances.yaml | 76 ++++++- .../lifecycle.keptn.sh_keptnworkloads.yaml | 29 ++- 19 files changed, 763 insertions(+), 219 deletions(-) diff --git a/docs/content/en/docs/crd-ref/lifecycle/v1alpha3/_index.md b/docs/content/en/docs/crd-ref/lifecycle/v1alpha3/_index.md index 6756faec8a..3aa8e00baa 100644 --- a/docs/content/en/docs/crd-ref/lifecycle/v1alpha3/_index.md +++ b/docs/content/en/docs/crd-ref/lifecycle/v1alpha3/_index.md @@ -47,7 +47,7 @@ _Appears in:_ | Field | Description | | --- | --- | -| `name` _string_ | | +| `name` _string_ | Name is the name of the referenced ConfigMap. | @@ -63,8 +63,8 @@ _Appears in:_ | Field | Description | | --- | --- | -| `value` _string_ | | -| `message` _string_ | | +| `value` _string_ | Value represents the value of the KeptnMetric being evaluated. | +| `message` _string_ | Message contains additional information about the evaluation of an objective. This can include explanations about why an evaluation has failed (e.g. due to a missed objective), or if there was any error during the evaluation of the objective. | #### FunctionReference @@ -78,7 +78,7 @@ _Appears in:_ | Field | Description | | --- | --- | -| `name` _string_ | | +| `name` _string_ | Name is the name of the referenced KeptnTaksDefinition. | #### FunctionSpec @@ -92,12 +92,12 @@ _Appears in:_ | Field | Description | | --- | --- | -| `functionRef` _[FunctionReference](#functionreference)_ | | -| `inline` _[Inline](#inline)_ | | -| `httpRef` _[HttpReference](#httpreference)_ | | -| `configMapRef` _[ConfigMapReference](#configmapreference)_ | | -| `parameters` _[TaskParameters](#taskparameters)_ | | -| `secureParameters` _[SecureParameters](#secureparameters)_ | | +| `functionRef` _[FunctionReference](#functionreference)_ | FunctionReference allows to reference another KeptnTaskDefinition which contains the source code of the function to be executes for KeptnTasks based on this KeptnTaskDefinition. This can be useful when you have multiple KeptnTaskDefinitions that should execute the same logic, but each with different parameters. | +| `inline` _[Inline](#inline)_ | Inline allows to specify the code that should be executed directly in the KeptnTaskDefinition, as a multi-line string. | +| `httpRef` _[HttpReference](#httpreference)_ | HttpReference allows to point to an HTTP URL containing the code of the function. | +| `configMapRef` _[ConfigMapReference](#configmapreference)_ | ConfigMapReference allows to reference a ConfigMap containing the code of the function. When referencing a ConfigMap, the code of the function must be available as a value of the 'code' key of the referenced ConfigMap. | +| `parameters` _[TaskParameters](#taskparameters)_ | Parameters contains parameters that will be passed to the job that executes the task. | +| `secureParameters` _[SecureParameters](#secureparameters)_ | SecureParameters contains secure parameters that will be passed to the job that executes the task. These will be stored and accessed as secrets in the cluster. | #### FunctionStatus @@ -111,7 +111,7 @@ _Appears in:_ | Field | Description | | --- | --- | -| `configMap` _string_ | INSERT ADDITIONAL STATUS FIELD - define observed state of cluster Important: Run "make" to regenerate code after modifying this file | +| `configMap` _string_ | ConfigMap indicates the ConfigMap in which the function code is stored. | #### HttpReference @@ -125,7 +125,7 @@ _Appears in:_ | Field | Description | | --- | --- | -| `url` _string_ | | +| `url` _string_ | Url is the URL containing the code of the function. | #### Inline @@ -139,7 +139,7 @@ _Appears in:_ | Field | Description | | --- | --- | -| `code` _string_ | | +| `code` _string_ | Code contains the code of the function. | #### ItemStatus @@ -156,8 +156,8 @@ _Appears in:_ | --- | --- | | `definitionName` _string_ | DefinitionName is the name of the EvaluationDefinition/TaskDefiniton | | `name` _string_ | Name is the name of the Evaluation/Task | -| `startTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#time-v1-meta)_ | | -| `endTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#time-v1-meta)_ | | +| `startTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#time-v1-meta)_ | StartTime represents the time at which the Item (Evaluation/Task) started. | +| `endTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#time-v1-meta)_ | EndTime represents the time at which the Item (Evaluation/Task) started. | #### KeptnApp @@ -174,7 +174,7 @@ _Appears in:_ | `apiVersion` _string_ | `lifecycle.keptn.sh/v1alpha3` | `kind` _string_ | `KeptnApp` | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | -| `spec` _[KeptnAppSpec](#keptnappspec)_ | | +| `spec` _[KeptnAppSpec](#keptnappspec)_ | Spec describes the desired state of the KeptnApp. | #### KeptnAppCreationRequest @@ -191,7 +191,7 @@ _Appears in:_ | `apiVersion` _string_ | `lifecycle.keptn.sh/v1alpha3` | `kind` _string_ | `KeptnAppCreationRequest` | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | -| `spec` _[KeptnAppCreationRequestSpec](#keptnappcreationrequestspec)_ | | +| `spec` _[KeptnAppCreationRequestSpec](#keptnappcreationrequestspec)_ | Spec describes the desired state of the KeptnAppCreationRequest. | #### KeptnAppCreationRequestList @@ -254,13 +254,13 @@ _Appears in:_ | Field | Description | | --- | --- | -| `version` _string_ | | -| `revision` _integer_ | | -| `workloads` _[KeptnWorkloadRef](#keptnworkloadref) array_ | | -| `preDeploymentTasks` _string array_ | | -| `postDeploymentTasks` _string array_ | | -| `preDeploymentEvaluations` _string array_ | | -| `postDeploymentEvaluations` _string array_ | | +| `version` _string_ | Version defines the version of the application. For automatically created KeptnApps, the version is a function of all KeptnWorkloads that are part of the KeptnApp. | +| `revision` _integer_ | Revision can be modified to trigger another deployment of a KeptnApp of the same version. This can be used for restarting a KeptnApp which failed to deploy, e.g. due to a failed preDeploymentEvaluation/preDeploymentTask. | +| `workloads` _[KeptnWorkloadRef](#keptnworkloadref) array_ | Workloads is a list of all KeptnWorkloads that are part of the KeptnApp. | +| `preDeploymentTasks` _string array_ | PreDeploymentTasks is a list of all tasks to be performed during the pre-deployment phase of the KeptnApp. The items of this list refer to the names of KeptnTaskDefinitions located in the same namespace as the KeptnApp, or in the KLT namespace. | +| `postDeploymentTasks` _string array_ | PostDeploymentTasks is a list of all tasks to be performed during the post-deployment phase of the KeptnApp. The items of this list refer to the names of KeptnTaskDefinitions located in the same namespace as the KeptnApp, or in the KLT namespace. | +| `preDeploymentEvaluations` _string array_ | PreDeploymentEvaluations is a list of all evaluations to be performed during the pre-deployment phase of the KeptnApp. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnApp, or in the KLT namespace. | +| `postDeploymentEvaluations` _string array_ | PostDeploymentEvaluations is a list of all evaluations to be performed during the post-deployment phase of the KeptnApp. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnApp, or in the KLT namespace. | @@ -279,7 +279,7 @@ _Appears in:_ | `apiVersion` _string_ | `lifecycle.keptn.sh/v1alpha3` | `kind` _string_ | `KeptnAppVersion` | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | -| `spec` _[KeptnAppVersionSpec](#keptnappversionspec)_ | | +| `spec` _[KeptnAppVersionSpec](#keptnappversionspec)_ | Spec describes the desired state of the KeptnAppVersion. | #### KeptnAppVersionList @@ -309,16 +309,16 @@ _Appears in:_ | Field | Description | | --- | --- | -| `version` _string_ | | -| `revision` _integer_ | | -| `workloads` _[KeptnWorkloadRef](#keptnworkloadref) array_ | | -| `preDeploymentTasks` _string array_ | | -| `postDeploymentTasks` _string array_ | | -| `preDeploymentEvaluations` _string array_ | | -| `postDeploymentEvaluations` _string array_ | | -| `appName` _string_ | | -| `previousVersion` _string_ | | -| `traceId` _object (keys:string, values:string)_ | | +| `version` _string_ | Version defines the version of the application. For automatically created KeptnApps, the version is a function of all KeptnWorkloads that are part of the KeptnApp. | +| `revision` _integer_ | Revision can be modified to trigger another deployment of a KeptnApp of the same version. This can be used for restarting a KeptnApp which failed to deploy, e.g. due to a failed preDeploymentEvaluation/preDeploymentTask. | +| `workloads` _[KeptnWorkloadRef](#keptnworkloadref) array_ | Workloads is a list of all KeptnWorkloads that are part of the KeptnApp. | +| `preDeploymentTasks` _string array_ | PreDeploymentTasks is a list of all tasks to be performed during the pre-deployment phase of the KeptnApp. The items of this list refer to the names of KeptnTaskDefinitions located in the same namespace as the KeptnApp, or in the KLT namespace. | +| `postDeploymentTasks` _string array_ | PostDeploymentTasks is a list of all tasks to be performed during the post-deployment phase of the KeptnApp. The items of this list refer to the names of KeptnTaskDefinitions located in the same namespace as the KeptnApp, or in the KLT namespace. | +| `preDeploymentEvaluations` _string array_ | PreDeploymentEvaluations is a list of all evaluations to be performed during the pre-deployment phase of the KeptnApp. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnApp, or in the KLT namespace. | +| `postDeploymentEvaluations` _string array_ | PostDeploymentEvaluations is a list of all evaluations to be performed during the post-deployment phase of the KeptnApp. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnApp, or in the KLT namespace. | +| `appName` _string_ | AppName is the name of the KeptnApp. | +| `previousVersion` _string_ | PreviousVersion is the version of the KeptnApp that has been deployed prior to this version. | +| `traceId` _object (keys:string, values:string)_ | TraceId contains the OpenTelemetry trace ID. | @@ -337,7 +337,7 @@ _Appears in:_ | `apiVersion` _string_ | `lifecycle.keptn.sh/v1alpha3` | `kind` _string_ | `KeptnEvaluation` | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | -| `spec` _[KeptnEvaluationSpec](#keptnevaluationspec)_ | | +| `spec` _[KeptnEvaluationSpec](#keptnevaluationspec)_ | Spec describes the desired state of the KeptnEvaluation. | #### KeptnEvaluationDefinition @@ -354,7 +354,7 @@ _Appears in:_ | `apiVersion` _string_ | `lifecycle.keptn.sh/v1alpha3` | `kind` _string_ | `KeptnEvaluationDefinition` | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | -| `spec` _[KeptnEvaluationDefinitionSpec](#keptnevaluationdefinitionspec)_ | | +| `spec` _[KeptnEvaluationDefinitionSpec](#keptnevaluationdefinitionspec)_ | Spec describes the desired state of the KeptnEvaluationDefinition. | #### KeptnEvaluationDefinitionList @@ -384,7 +384,7 @@ _Appears in:_ | Field | Description | | --- | --- | -| `objectives` _[Objective](#objective) array_ | | +| `objectives` _[Objective](#objective) array_ | Objectives is a list of objectives that have to be met for a KeptnEvaluation referencing this KeptnEvaluationDefinition to be successful. | @@ -466,15 +466,15 @@ _Appears in:_ | Field | Description | | --- | --- | -| `workload` _string_ | | -| `workloadVersion` _string_ | | -| `appName` _string_ | | -| `appVersion` _string_ | | -| `evaluationDefinition` _string_ | | -| `retries` _integer_ | | -| `retryInterval` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#duration-v1-meta)_ | | +| `workload` _string_ | Workload defines the KeptnWorkload for which the KeptnEvaluation is done. | +| `workloadVersion` _string_ | WorkloadVersion defines the version of the KeptnWorkload for which the KeptnEvaluation is done. | +| `appName` _string_ | AppName defines the KeptnApp for which the KeptnEvaluation is done. | +| `appVersion` _string_ | AppVersion defines the version of the KeptnApp for which the KeptnEvaluation is done. | +| `evaluationDefinition` _string_ | EvaluationDefinition refers to the name of the KeptnEvaluationDefinition which includes the objectives for the KeptnEvaluation. The KeptnEvaluationDefinition can be located in the same namespace as the KeptnEvaluation, or in the KLT namespace. | +| `retries` _integer_ | Retries indicates how many times the KeptnEvaluation can be attempted in the case of an error or missed evaluation objective, before considering the KeptnEvaluation to be failed. | +| `retryInterval` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#duration-v1-meta)_ | RetryInterval specifies the interval at which the KeptnEvaluation is retried in the case of an error or a missed objective. | | `failAction` _string_ | | -| `checkType` _CheckType_ | | +| `checkType` _CheckType_ | Type indicates whether the KeptnEvaluation is part of the pre- or postDeployment phase. | @@ -490,8 +490,8 @@ _Appears in:_ | Field | Description | | --- | --- | -| `name` _string_ | | -| `namespace` _string_ | | +| `name` _string_ | Name is the name of the referenced KeptnMetric. | +| `namespace` _string_ | Namespace is the namespace where the referenced KeptnMetric is located. | #### KeptnTask @@ -508,7 +508,7 @@ _Appears in:_ | `apiVersion` _string_ | `lifecycle.keptn.sh/v1alpha3` | `kind` _string_ | `KeptnTask` | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | -| `spec` _[KeptnTaskSpec](#keptntaskspec)_ | | +| `spec` _[KeptnTaskSpec](#keptntaskspec)_ | Spec describes the desired state of the KeptnTask. | #### KeptnTaskDefinition @@ -525,7 +525,7 @@ _Appears in:_ | `apiVersion` _string_ | `lifecycle.keptn.sh/v1alpha3` | `kind` _string_ | `KeptnTaskDefinition` | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | -| `spec` _[KeptnTaskDefinitionSpec](#keptntaskdefinitionspec)_ | | +| `spec` _[KeptnTaskDefinitionSpec](#keptntaskdefinitionspec)_ | Spec describes the desired state of the KeptnTaskDefinition. | #### KeptnTaskDefinitionList @@ -555,9 +555,9 @@ _Appears in:_ | Field | Description | | --- | --- | -| `function` _[FunctionSpec](#functionspec)_ | | -| `retries` _integer_ | | -| `timeout` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#duration-v1-meta)_ | | +| `function` _[FunctionSpec](#functionspec)_ | Function contains the definition for the function that is to be executed in KeptnTasks based on the KeptnTaskDefinitions. | +| `retries` _integer_ | Retries specifies how many times a job executing the KeptnTaskDefinition should be restarted in the case of an unsuccessful attempt. | +| `timeout` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#duration-v1-meta)_ | Timeout specifies the maximum time to wait for the task to be completed successfully. If the task does not complete successfully within this time frame, it will be considered to be failed. | @@ -589,17 +589,17 @@ _Appears in:_ | Field | Description | | --- | --- | -| `workload` _string_ | | -| `workloadVersion` _string_ | | -| `app` _string_ | | -| `appVersion` _string_ | | -| `taskDefinition` _string_ | | -| `context` _[TaskContext](#taskcontext)_ | | -| `parameters` _[TaskParameters](#taskparameters)_ | | -| `secureParameters` _[SecureParameters](#secureparameters)_ | | -| `checkType` _CheckType_ | | -| `retries` _integer_ | | -| `timeout` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#duration-v1-meta)_ | | +| `workload` _string_ | Workload defines the KeptnWorkload for which the KeptnTask is executed. | +| `workloadVersion` _string_ | WorkloadVersion defines the version of the KeptnWorkload for which the KeptnTask is executed. | +| `app` _string_ | AppName defines the KeptnApp for which the KeptnTask is executed. | +| `appVersion` _string_ | AppVersion defines the version of the KeptnApp for which the KeptnTask is executed. | +| `taskDefinition` _string_ | TaskDefinition refers to the name of the KeptnTaskDefinition which includes the specification for the task to be performed. The KeptnTaskDefinition can be located in the same namespace as the KeptnTask, or in the KLT namespace. | +| `context` _[TaskContext](#taskcontext)_ | Context contains contextual information about the task execution. | +| `parameters` _[TaskParameters](#taskparameters)_ | Parameters contains parameters that will be passed to the job that executes the task. | +| `secureParameters` _[SecureParameters](#secureparameters)_ | SecureParameters contains secure parameters that will be passed to the job that executes the task. These will be stored and accessed as secrets in the cluster. | +| `checkType` _CheckType_ | Type indicates whether the KeptnTask is part of the pre- or postDeployment phase. | +| `retries` _integer_ | Retries indicates how many times the KeptnTask can be attempted in the case of an error before considering the KeptnTask to be failed. | +| `timeout` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#duration-v1-meta)_ | Timeout specifies the maximum time to wait for the task to be completed successfully. If the task does not complete successfully within this time frame, it will be considered to be failed. | @@ -618,7 +618,7 @@ _Appears in:_ | `apiVersion` _string_ | `lifecycle.keptn.sh/v1alpha3` | `kind` _string_ | `KeptnWorkload` | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | -| `spec` _[KeptnWorkloadSpec](#keptnworkloadspec)_ | | +| `spec` _[KeptnWorkloadSpec](#keptnworkloadspec)_ | Spec describes the desired state of the KeptnWorkload. | #### KeptnWorkloadInstance @@ -635,7 +635,7 @@ _Appears in:_ | `apiVersion` _string_ | `lifecycle.keptn.sh/v1alpha3` | `kind` _string_ | `KeptnWorkloadInstance` | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | -| `spec` _[KeptnWorkloadInstanceSpec](#keptnworkloadinstancespec)_ | | +| `spec` _[KeptnWorkloadInstanceSpec](#keptnworkloadinstancespec)_ | Spec describes the desired state of the KeptnWorkloadInstance. | #### KeptnWorkloadInstanceList @@ -665,16 +665,16 @@ _Appears in:_ | Field | Description | | --- | --- | -| `app` _string_ | | -| `version` _string_ | | -| `preDeploymentTasks` _string array_ | | -| `postDeploymentTasks` _string array_ | | -| `preDeploymentEvaluations` _string array_ | | -| `postDeploymentEvaluations` _string array_ | | -| `resourceReference` _[ResourceReference](#resourcereference)_ | | -| `workloadName` _string_ | | -| `previousVersion` _string_ | | -| `traceId` _object (keys:string, values:string)_ | | +| `app` _string_ | AppName is the name of the KeptnApp containing the KeptnWorkload. | +| `version` _string_ | Version defines the version of the KeptnWorkload. | +| `preDeploymentTasks` _string array_ | PreDeploymentTasks is a list of all tasks to be performed during the pre-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnTaskDefinitions located in the same namespace as the KeptnApp, or in the KLT namespace. | +| `postDeploymentTasks` _string array_ | PostDeploymentTasks is a list of all tasks to be performed during the post-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnTaskDefinitions located in the same namespace as the KeptnWorkload, or in the KLT namespace. | +| `preDeploymentEvaluations` _string array_ | PreDeploymentEvaluations is a list of all evaluations to be performed during the pre-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnWorkload, or in the KLT namespace. | +| `postDeploymentEvaluations` _string array_ | PostDeploymentEvaluations is a list of all evaluations to be performed during the post-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnWorkload, or in the KLT namespace. | +| `resourceReference` _[ResourceReference](#resourcereference)_ | ResourceReference is a reference to the Kubernetes resource (Deployment, DaemonSet, StatefulSet or ReplicaSet) the KeptnWorkload is representing. | +| `workloadName` _string_ | WorkloadName is the name of the KeptnWorkload. | +| `previousVersion` _string_ | PreviousVersion is the version of the KeptnWorkload that has been deployed prior to this version. | +| `traceId` _object (keys:string, values:string)_ | TraceId contains the OpenTelemetry trace ID. | @@ -699,7 +699,7 @@ KeptnWorkloadList contains a list of KeptnWorkload - +KeptnWorkloadRef refers to a KeptnWorkload that is part of a KeptnApp _Appears in:_ - [KeptnAppSpec](#keptnappspec) @@ -708,8 +708,8 @@ _Appears in:_ | Field | Description | | --- | --- | -| `name` _string_ | | -| `version` _string_ | | +| `name` _string_ | Name is the name of the KeptnWorkload. | +| `version` _string_ | Version is the version of the KeptnWorkload. | #### KeptnWorkloadSpec @@ -724,13 +724,13 @@ _Appears in:_ | Field | Description | | --- | --- | -| `app` _string_ | | -| `version` _string_ | | -| `preDeploymentTasks` _string array_ | | -| `postDeploymentTasks` _string array_ | | -| `preDeploymentEvaluations` _string array_ | | -| `postDeploymentEvaluations` _string array_ | | -| `resourceReference` _[ResourceReference](#resourcereference)_ | | +| `app` _string_ | AppName is the name of the KeptnApp containing the KeptnWorkload. | +| `version` _string_ | Version defines the version of the KeptnWorkload. | +| `preDeploymentTasks` _string array_ | PreDeploymentTasks is a list of all tasks to be performed during the pre-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnTaskDefinitions located in the same namespace as the KeptnApp, or in the KLT namespace. | +| `postDeploymentTasks` _string array_ | PostDeploymentTasks is a list of all tasks to be performed during the post-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnTaskDefinitions located in the same namespace as the KeptnWorkload, or in the KLT namespace. | +| `preDeploymentEvaluations` _string array_ | PreDeploymentEvaluations is a list of all evaluations to be performed during the pre-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnWorkload, or in the KLT namespace. | +| `postDeploymentEvaluations` _string array_ | PostDeploymentEvaluations is a list of all evaluations to be performed during the post-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnWorkload, or in the KLT namespace. | +| `resourceReference` _[ResourceReference](#resourcereference)_ | ResourceReference is a reference to the Kubernetes resource (Deployment, DaemonSet, StatefulSet or ReplicaSet) the KeptnWorkload is representing. | @@ -746,8 +746,8 @@ _Appears in:_ | Field | Description | | --- | --- | -| `keptnMetricRef` _[KeptnMetricReference](#keptnmetricreference)_ | | -| `evaluationTarget` _string_ | | +| `keptnMetricRef` _[KeptnMetricReference](#keptnmetricreference)_ | KeptnMetricRef references the KeptnMetric that should be evaluated. | +| `evaluationTarget` _string_ | EvaluationTarget specifies the target value for the references KeptnMetric. Needs to start with either '<' or '>', followed by the target value (e.g. '<10'). | #### ResourceReference @@ -779,7 +779,7 @@ _Appears in:_ | Field | Description | | --- | --- | -| `secret` _string_ | | +| `secret` _string_ | Secret contains the parameters that will be made available to the job executing the KeptnTask via the 'SECRET_DATA' environment variable. The 'SECRET_DATA' environment variable's content will the same as value of the 'SECRET_DATA' key of the referenced secret. | #### TaskContext @@ -793,12 +793,12 @@ _Appears in:_ | Field | Description | | --- | --- | -| `workloadName` _string_ | | -| `appName` _string_ | | -| `appVersion` _string_ | | -| `workloadVersion` _string_ | | -| `taskType` _string_ | | -| `objectType` _string_ | | +| `workloadName` _string_ | WorkloadName the name of the KeptnWorkload the KeptnTask is being executed for. | +| `appName` _string_ | AppName the name of the KeptnApp the KeptnTask is being executed for. | +| `appVersion` _string_ | AppVersion the version of the KeptnApp the KeptnTask is being executed for. | +| `workloadVersion` _string_ | WorkloadVersion the version of the KeptnWorkload the KeptnTask is being executed for. | +| `taskType` _string_ | TaskType indicates whether the KeptnTask is part of the pre- or postDeployment phase. | +| `objectType` _string_ | ObjectType indicates whether the KeptnTask is being executed for a KeptnApp or KeptnWorkload. | #### TaskParameters @@ -813,7 +813,7 @@ _Appears in:_ | Field | Description | | --- | --- | -| `map` _object (keys:string, values:string)_ | | +| `map` _object (keys:string, values:string)_ | Inline contains the parameters that will be made available to the job executing the KeptnTask via the 'DATA' environment variable. The 'DATA' environment variable's content will be a json encoded string containing all properties of the map provided. | #### WorkloadStatus @@ -827,6 +827,6 @@ _Appears in:_ | Field | Description | | --- | --- | -| `workload` _[KeptnWorkloadRef](#keptnworkloadref)_ | | +| `workload` _[KeptnWorkloadRef](#keptnworkloadref)_ | Workload refers to a KeptnWorkload that is part of the KeptnAppVersion. | diff --git a/operator/apis/lifecycle/v1alpha3/keptnapp_types.go b/operator/apis/lifecycle/v1alpha3/keptnapp_types.go index a5ec6b80b3..4e0ac69875 100644 --- a/operator/apis/lifecycle/v1alpha3/keptnapp_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptnapp_types.go @@ -31,23 +31,47 @@ import ( // KeptnAppSpec defines the desired state of KeptnApp type KeptnAppSpec struct { + // Version defines the version of the application. For automatically created KeptnApps, + // the version is a function of all KeptnWorkloads that are part of the KeptnApp. Version string `json:"version"` + // Revision can be modified to trigger another deployment of a KeptnApp of the same version. + // This can be used for restarting a KeptnApp which failed to deploy, + // e.g. due to a failed preDeploymentEvaluation/preDeploymentTask. // +kubebuilder:default:=1 - Revision uint `json:"revision,omitempty"` - Workloads []KeptnWorkloadRef `json:"workloads,omitempty"` - PreDeploymentTasks []string `json:"preDeploymentTasks,omitempty"` - PostDeploymentTasks []string `json:"postDeploymentTasks,omitempty"` - PreDeploymentEvaluations []string `json:"preDeploymentEvaluations,omitempty"` - PostDeploymentEvaluations []string `json:"postDeploymentEvaluations,omitempty"` + Revision uint `json:"revision,omitempty"` + // Workloads is a list of all KeptnWorkloads that are part of the KeptnApp. + Workloads []KeptnWorkloadRef `json:"workloads,omitempty"` + // PreDeploymentTasks is a list of all tasks to be performed during the pre-deployment phase of the KeptnApp. + // The items of this list refer to the names of KeptnTaskDefinitions + // located in the same namespace as the KeptnApp, or in the KLT namespace. + PreDeploymentTasks []string `json:"preDeploymentTasks,omitempty"` + // PostDeploymentTasks is a list of all tasks to be performed during the post-deployment phase of the KeptnApp. + // The items of this list refer to the names of KeptnTaskDefinitions + // located in the same namespace as the KeptnApp, or in the KLT namespace. + PostDeploymentTasks []string `json:"postDeploymentTasks,omitempty"` + // PreDeploymentEvaluations is a list of all evaluations to be performed + // during the pre-deployment phase of the KeptnApp. + // The items of this list refer to the names of KeptnEvaluationDefinitions + // located in the same namespace as the KeptnApp, or in the KLT namespace. + PreDeploymentEvaluations []string `json:"preDeploymentEvaluations,omitempty"` + // PostDeploymentEvaluations is a list of all evaluations to be performed + // during the post-deployment phase of the KeptnApp. + // The items of this list refer to the names of KeptnEvaluationDefinitions + // located in the same namespace as the KeptnApp, or in the KLT namespace. + PostDeploymentEvaluations []string `json:"postDeploymentEvaluations,omitempty"` } // KeptnAppStatus defines the observed state of KeptnApp type KeptnAppStatus struct { + // CurrentVersion indicates the version that is currently deployed or being reconciled. CurrentVersion string `json:"currentVersion,omitempty"` } +// KeptnWorkloadRef refers to a KeptnWorkload that is part of a KeptnApp type KeptnWorkloadRef struct { - Name string `json:"name"` + // Name is the name of the KeptnWorkload. + Name string `json:"name"` + // Version is the version of the KeptnWorkload. Version string `json:"version"` } @@ -60,7 +84,9 @@ type KeptnApp struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec KeptnAppSpec `json:"spec,omitempty"` + // Spec describes the desired state of the KeptnApp. + Spec KeptnAppSpec `json:"spec,omitempty"` + // Status describes the current state of the KeptnApp. Status KeptnAppStatus `json:"status,omitempty"` } diff --git a/operator/apis/lifecycle/v1alpha3/keptnappcreationrequest_types.go b/operator/apis/lifecycle/v1alpha3/keptnappcreationrequest_types.go index 2795ede381..8258aafc33 100644 --- a/operator/apis/lifecycle/v1alpha3/keptnappcreationrequest_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptnappcreationrequest_types.go @@ -46,7 +46,9 @@ type KeptnAppCreationRequest struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec KeptnAppCreationRequestSpec `json:"spec,omitempty"` + // Spec describes the desired state of the KeptnAppCreationRequest. + Spec KeptnAppCreationRequestSpec `json:"spec,omitempty"` + // Status describes the current state of the KeptnAppCreationRequest. Status KeptnAppCreationRequestStatus `json:"status,omitempty"` } diff --git a/operator/apis/lifecycle/v1alpha3/keptnappversion_types.go b/operator/apis/lifecycle/v1alpha3/keptnappversion_types.go index bdbfeec829..0d2f353689 100644 --- a/operator/apis/lifecycle/v1alpha3/keptnappversion_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptnappversion_types.go @@ -33,41 +33,60 @@ import ( // KeptnAppVersionSpec defines the desired state of KeptnAppVersion type KeptnAppVersionSpec struct { - KeptnAppSpec `json:",inline"` - AppName string `json:"appName"` + KeptnAppSpec `json:",inline"` + // AppName is the name of the KeptnApp. + AppName string `json:"appName"` + // PreviousVersion is the version of the KeptnApp that has been deployed prior to this version. PreviousVersion string `json:"previousVersion,omitempty"` - + // TraceId contains the OpenTelemetry trace ID. TraceId map[string]string `json:"traceId,omitempty"` } // KeptnAppVersionStatus defines the observed state of KeptnAppVersion type KeptnAppVersionStatus struct { + // PreDeploymentStatus indicates the current status of the KeptnAppVersion's PreDeployment phase. // +kubebuilder:default:=Pending PreDeploymentStatus common.KeptnState `json:"preDeploymentStatus,omitempty"` + // PostDeploymentStatus indicates the current status of the KeptnAppVersion's PostDeployment phase. // +kubebuilder:default:=Pending PostDeploymentStatus common.KeptnState `json:"postDeploymentStatus,omitempty"` + // PreDeploymentEvaluationStatus indicates the current status of the KeptnAppVersion's PreDeploymentEvaluation phase. // +kubebuilder:default:=Pending PreDeploymentEvaluationStatus common.KeptnState `json:"preDeploymentEvaluationStatus,omitempty"` + // PostDeploymentEvaluationStatus indicates the current status of the KeptnAppVersion's PostDeploymentEvaluation phase. // +kubebuilder:default:=Pending PostDeploymentEvaluationStatus common.KeptnState `json:"postDeploymentEvaluationStatus,omitempty"` + // WorkloadOverallStatus indicates the current status of the KeptnAppVersion's Workload deployment phase. // +kubebuilder:default:=Pending - WorkloadOverallStatus common.KeptnState `json:"workloadOverallStatus,omitempty"` - WorkloadStatus []WorkloadStatus `json:"workloadStatus,omitempty"` - CurrentPhase string `json:"currentPhase,omitempty"` - PreDeploymentTaskStatus []ItemStatus `json:"preDeploymentTaskStatus,omitempty"` - PostDeploymentTaskStatus []ItemStatus `json:"postDeploymentTaskStatus,omitempty"` - PreDeploymentEvaluationTaskStatus []ItemStatus `json:"preDeploymentEvaluationTaskStatus,omitempty"` - PostDeploymentEvaluationTaskStatus []ItemStatus `json:"postDeploymentEvaluationTaskStatus,omitempty"` - PhaseTraceIDs common.PhaseTraceID `json:"phaseTraceIDs,omitempty"` + WorkloadOverallStatus common.KeptnState `json:"workloadOverallStatus,omitempty"` + // WorkloadStatus contains the current status of each KeptnWorkload that is part of the KeptnAppVersion. + WorkloadStatus []WorkloadStatus `json:"workloadStatus,omitempty"` + // CurrentPhase indicates the current phase of the KeptnAppVersion. + CurrentPhase string `json:"currentPhase,omitempty"` + // PreDeploymentTaskStatus indicates the current state of each preDeploymentTask of the KeptnAppVersion. + PreDeploymentTaskStatus []ItemStatus `json:"preDeploymentTaskStatus,omitempty"` + // PostDeploymentTaskStatus indicates the current state of each postDeploymentTask of the KeptnAppVersion. + PostDeploymentTaskStatus []ItemStatus `json:"postDeploymentTaskStatus,omitempty"` + // PreDeploymentEvaluationTaskStatus indicates the current state of each preDeploymentEvaluation of the KeptnAppVersion. + PreDeploymentEvaluationTaskStatus []ItemStatus `json:"preDeploymentEvaluationTaskStatus,omitempty"` + // PostDeploymentEvaluationTaskStatus indicates the current state of each postDeploymentEvaluation of the KeptnAppVersion. + PostDeploymentEvaluationTaskStatus []ItemStatus `json:"postDeploymentEvaluationTaskStatus,omitempty"` + // PhaseTraceIDs contains the trace IDs of the OpenTelemetry spans of each phase of the KeptnAppVersion. + PhaseTraceIDs common.PhaseTraceID `json:"phaseTraceIDs,omitempty"` + // Status represents the overall status of the KeptnAppVersion. // +kubebuilder:default:=Pending Status common.KeptnState `json:"status,omitempty"` + // StartTime represents the time at which the deployment of the KeptnAppVersion started. StartTime metav1.Time `json:"startTime,omitempty"` - EndTime metav1.Time `json:"endTime,omitempty"` + // EndTime represents the time at which the deployment of the KeptnAppVersion finished. + EndTime metav1.Time `json:"endTime,omitempty"` } type WorkloadStatus struct { + // Workload refers to a KeptnWorkload that is part of the KeptnAppVersion. Workload KeptnWorkloadRef `json:"workload,omitempty"` + // Status indicates the current status of the KeptnWorkload. // +kubebuilder:default:=Pending Status common.KeptnState `json:"status,omitempty"` } @@ -90,7 +109,9 @@ type KeptnAppVersion struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec KeptnAppVersionSpec `json:"spec,omitempty"` + // Spec describes the desired state of the KeptnAppVersion. + Spec KeptnAppVersionSpec `json:"spec,omitempty"` + // Status describes the current state of the KeptnAppVersion. Status KeptnAppVersionStatus `json:"status,omitempty"` } diff --git a/operator/apis/lifecycle/v1alpha3/keptnevaluation_types.go b/operator/apis/lifecycle/v1alpha3/keptnevaluation_types.go index bbf814505c..4f3c55bd62 100644 --- a/operator/apis/lifecycle/v1alpha3/keptnevaluation_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptnevaluation_types.go @@ -32,38 +32,64 @@ import ( // KeptnEvaluationSpec defines the desired state of KeptnEvaluation type KeptnEvaluationSpec struct { - Workload string `json:"workload,omitempty"` - WorkloadVersion string `json:"workloadVersion"` - AppName string `json:"appName,omitempty"` - AppVersion string `json:"appVersion,omitempty"` + // Workload defines the KeptnWorkload for which the KeptnEvaluation is done. + Workload string `json:"workload,omitempty"` + // WorkloadVersion defines the version of the KeptnWorkload for which the KeptnEvaluation is done. + WorkloadVersion string `json:"workloadVersion"` + // AppName defines the KeptnApp for which the KeptnEvaluation is done. + AppName string `json:"appName,omitempty"` + // AppVersion defines the version of the KeptnApp for which the KeptnEvaluation is done. + AppVersion string `json:"appVersion,omitempty"` + // EvaluationDefinition refers to the name of the KeptnEvaluationDefinition + // which includes the objectives for the KeptnEvaluation. + // The KeptnEvaluationDefinition can be + // located in the same namespace as the KeptnEvaluation, or in the KLT namespace. EvaluationDefinition string `json:"evaluationDefinition"` + // Retries indicates how many times the KeptnEvaluation can be attempted in the case of an error or + // missed evaluation objective, before considering the KeptnEvaluation to be failed. // +kubebuilder:default:=10 Retries int `json:"retries,omitempty"` + // RetryInterval specifies the interval at which the KeptnEvaluation is retried in the case of an error + // or a missed objective. // +optional // +kubebuilder:default:="5s" // +kubebuilder:validation:Pattern="^0|([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" // +kubebuilder:validation:Type:=string // +optional - RetryInterval metav1.Duration `json:"retryInterval,omitempty"` - FailAction string `json:"failAction,omitempty"` - Type common.CheckType `json:"checkType,omitempty"` + RetryInterval metav1.Duration `json:"retryInterval,omitempty"` + FailAction string `json:"failAction,omitempty"` + // Type indicates whether the KeptnEvaluation is part of the pre- or postDeployment phase. + Type common.CheckType `json:"checkType,omitempty"` } // KeptnEvaluationStatus defines the observed state of KeptnEvaluation type KeptnEvaluationStatus struct { + // RetryCount indicates how many times the KeptnEvaluation has been attempted already. // +kubebuilder:default:=0 - RetryCount int `json:"retryCount"` + RetryCount int `json:"retryCount"` + // EvaluationStatus describes the status of each objective of the KeptnEvaluationDefinition + // referenced by the KeptnEvaluation. EvaluationStatus map[string]EvaluationStatusItem `json:"evaluationStatus"` + // OverallStatus describes the overall status of the KeptnEvaluation. The Overall status is derived + // from the status of the individual objectives of the KeptnEvaluationDefinition + // referenced by the KeptnEvaluation. // +kubebuilder:default:=Pending OverallStatus common.KeptnState `json:"overallStatus"` - StartTime metav1.Time `json:"startTime,omitempty"` - EndTime metav1.Time `json:"endTime,omitempty"` + // StartTime represents the time at which the KeptnEvaluation started. + StartTime metav1.Time `json:"startTime,omitempty"` + // EndTime represents the time at which the KeptnEvaluation finished. + EndTime metav1.Time `json:"endTime,omitempty"` } type EvaluationStatusItem struct { - Value string `json:"value"` - Status common.KeptnState `json:"status"` - Message string `json:"message,omitempty"` + // Value represents the value of the KeptnMetric being evaluated. + Value string `json:"value"` + // Status indicates the status of the objective being evaluated. + Status common.KeptnState `json:"status"` + // Message contains additional information about the evaluation of an objective. + // This can include explanations about why an evaluation has failed (e.g. due to a missed objective), + // or if there was any error during the evaluation of the objective. + Message string `json:"message,omitempty"` } //+kubebuilder:object:root=true @@ -83,7 +109,9 @@ type KeptnEvaluation struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec KeptnEvaluationSpec `json:"spec,omitempty"` + // Spec describes the desired state of the KeptnEvaluation. + Spec KeptnEvaluationSpec `json:"spec,omitempty"` + // Status describes the current state of the KeptnEvaluation. Status KeptnEvaluationStatus `json:"status,omitempty"` } diff --git a/operator/apis/lifecycle/v1alpha3/keptnevaluationdefinition_types.go b/operator/apis/lifecycle/v1alpha3/keptnevaluationdefinition_types.go index 1fe70566ef..9d3930f07d 100644 --- a/operator/apis/lifecycle/v1alpha3/keptnevaluationdefinition_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptnevaluationdefinition_types.go @@ -25,20 +25,27 @@ import ( // KeptnEvaluationDefinitionSpec defines the desired state of KeptnEvaluationDefinition type KeptnEvaluationDefinitionSpec struct { + // Objectives is a list of objectives that have to be met for a KeptnEvaluation referencing this + // KeptnEvaluationDefinition to be successful. Objectives []Objective `json:"objectives"` } type Objective struct { - KeptnMetricRef KeptnMetricReference `json:"keptnMetricRef"` - EvaluationTarget string `json:"evaluationTarget"` + // KeptnMetricRef references the KeptnMetric that should be evaluated. + KeptnMetricRef KeptnMetricReference `json:"keptnMetricRef"` + // EvaluationTarget specifies the target value for the references KeptnMetric. + // Needs to start with either '<' or '>', followed by the target value (e.g. '<10'). + EvaluationTarget string `json:"evaluationTarget"` } type KeptnMetricReference struct { - Name string `json:"name"` + // Name is the name of the referenced KeptnMetric. + Name string `json:"name"` + // Namespace is the namespace where the referenced KeptnMetric is located. Namespace string `json:"namespace,omitempty"` } -// KeptnEvaluationDefinitionStatus defines the observed state of KeptnEvaluationDefinition +// KeptnEvaluationDefinitionStatus defines the observed state of KeptnEvaluationDefinition. type KeptnEvaluationDefinitionStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file @@ -54,7 +61,9 @@ type KeptnEvaluationDefinition struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec KeptnEvaluationDefinitionSpec `json:"spec,omitempty"` + // Spec describes the desired state of the KeptnEvaluationDefinition. + Spec KeptnEvaluationDefinitionSpec `json:"spec,omitempty"` + // Status describes the current state of the KeptnEvaluationDefinition. Status KeptnEvaluationDefinitionStatus `json:"status,omitempty"` } diff --git a/operator/apis/lifecycle/v1alpha3/keptntask_types.go b/operator/apis/lifecycle/v1alpha3/keptntask_types.go index 98221eb0b5..5112b468bc 100644 --- a/operator/apis/lifecycle/v1alpha3/keptntask_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptntask_types.go @@ -32,17 +32,35 @@ import ( // KeptnTaskSpec defines the desired state of KeptnTask type KeptnTaskSpec struct { - Workload string `json:"workload"` - WorkloadVersion string `json:"workloadVersion"` - AppName string `json:"app"` - AppVersion string `json:"appVersion"` - TaskDefinition string `json:"taskDefinition"` - Context TaskContext `json:"context"` - Parameters TaskParameters `json:"parameters,omitempty"` + // Workload defines the KeptnWorkload for which the KeptnTask is executed. + Workload string `json:"workload"` + // WorkloadVersion defines the version of the KeptnWorkload for which the KeptnTask is executed. + WorkloadVersion string `json:"workloadVersion"` + // AppName defines the KeptnApp for which the KeptnTask is executed. + AppName string `json:"app"` + // AppVersion defines the version of the KeptnApp for which the KeptnTask is executed. + AppVersion string `json:"appVersion"` + // TaskDefinition refers to the name of the KeptnTaskDefinition + // which includes the specification for the task to be performed. + // The KeptnTaskDefinition can be + // located in the same namespace as the KeptnTask, or in the KLT namespace. + TaskDefinition string `json:"taskDefinition"` + // Context contains contextual information about the task execution. + Context TaskContext `json:"context"` + // Parameters contains parameters that will be passed to the job that executes the task. + Parameters TaskParameters `json:"parameters,omitempty"` + // SecureParameters contains secure parameters that will be passed to the job that executes the task. + // These will be stored and accessed as secrets in the cluster. SecureParameters SecureParameters `json:"secureParameters,omitempty"` - Type common.CheckType `json:"checkType,omitempty"` + // Type indicates whether the KeptnTask is part of the pre- or postDeployment phase. + Type common.CheckType `json:"checkType,omitempty"` + // Retries indicates how many times the KeptnTask can be attempted in the case of an error + // before considering the KeptnTask to be failed. // +kubebuilder:default:=10 Retries *int32 `json:"retries,omitempty"` + // Timeout specifies the maximum time to wait for the task to be completed successfully. + // If the task does not complete successfully within this time frame, it will be + // considered to be failed. // +optional // +kubebuilder:default:="5m" // +kubebuilder:validation:Pattern="^0|([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" @@ -52,31 +70,51 @@ type KeptnTaskSpec struct { } type TaskContext struct { - WorkloadName string `json:"workloadName"` - AppName string `json:"appName"` - AppVersion string `json:"appVersion"` + // WorkloadName the name of the KeptnWorkload the KeptnTask is being executed for. + WorkloadName string `json:"workloadName"` + // AppName the name of the KeptnApp the KeptnTask is being executed for. + AppName string `json:"appName"` + // AppVersion the version of the KeptnApp the KeptnTask is being executed for. + AppVersion string `json:"appVersion"` + // WorkloadVersion the version of the KeptnWorkload the KeptnTask is being executed for. WorkloadVersion string `json:"workloadVersion"` - TaskType string `json:"taskType"` - ObjectType string `json:"objectType"` + // TaskType indicates whether the KeptnTask is part of the pre- or postDeployment phase. + TaskType string `json:"taskType"` + // ObjectType indicates whether the KeptnTask is being executed for a KeptnApp or KeptnWorkload. + ObjectType string `json:"objectType"` } type TaskParameters struct { + // Inline contains the parameters that will be made available to the job + // executing the KeptnTask via the 'DATA' environment variable. + // The 'DATA' environment variable's content will be a json + // encoded string containing all properties of the map provided. Inline map[string]string `json:"map,omitempty"` } type SecureParameters struct { + // Secret contains the parameters that will be made available to the job + // executing the KeptnTask via the 'SECRET_DATA' environment variable. + // The 'SECRET_DATA' environment variable's content will the same as value of the 'SECRET_DATA' + // key of the referenced secret. Secret string `json:"secret,omitempty"` } // KeptnTaskStatus defines the observed state of KeptnTask type KeptnTaskStatus struct { + // JobName is the name of the Job executing the Task. JobName string `json:"jobName,omitempty"` + // Status represents the overall state of the KeptnTask. // +kubebuilder:default:=Pending - Status common.KeptnState `json:"status,omitempty"` - Message string `json:"message,omitempty"` - StartTime metav1.Time `json:"startTime,omitempty"` - EndTime metav1.Time `json:"endTime,omitempty"` - Reason string `json:"reason,omitempty"` + Status common.KeptnState `json:"status,omitempty"` + // Message contains information about unexpected errors encountered during the execution of the KeptnTask. + Message string `json:"message,omitempty"` + // StartTime represents the time at which the KeptnTask started. + StartTime metav1.Time `json:"startTime,omitempty"` + // EndTime represents the time at which the KeptnTask finished. + EndTime metav1.Time `json:"endTime,omitempty"` + // Reason contains more information about the reason for the last transition of the Job executing the KeptnTask. + Reason string `json:"reason,omitempty"` } // +kubebuilder:object:root=true @@ -94,7 +132,9 @@ type KeptnTask struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec KeptnTaskSpec `json:"spec,omitempty"` + // Spec describes the desired state of the KeptnTask. + Spec KeptnTaskSpec `json:"spec,omitempty"` + // Status describes the current state of the KeptnTask. Status KeptnTaskStatus `json:"status,omitempty"` } diff --git a/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types.go b/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types.go index 0823d58cce..95df2aef36 100644 --- a/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types.go @@ -25,9 +25,16 @@ import ( // KeptnTaskDefinitionSpec defines the desired state of KeptnTaskDefinition type KeptnTaskDefinitionSpec struct { + // Function contains the definition for the function that is to be executed in KeptnTasks based on + // the KeptnTaskDefinitions. Function FunctionSpec `json:"function,omitempty"` + // Retries specifies how many times a job executing the KeptnTaskDefinition should be restarted in the case + // of an unsuccessful attempt. // +kubebuilder:default:=10 Retries *int32 `json:"retries,omitempty"` + // Timeout specifies the maximum time to wait for the task to be completed successfully. + // If the task does not complete successfully within this time frame, it will be + // considered to be failed. // +optional // +kubebuilder:default:="5m" // +kubebuilder:validation:Pattern="^0|([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" @@ -37,27 +44,43 @@ type KeptnTaskDefinitionSpec struct { } type FunctionSpec struct { - FunctionReference FunctionReference `json:"functionRef,omitempty"` - Inline Inline `json:"inline,omitempty"` - HttpReference HttpReference `json:"httpRef,omitempty"` + // FunctionReference allows to reference another KeptnTaskDefinition which contains the source code of the + // function to be executes for KeptnTasks based on this KeptnTaskDefinition. This can be useful when you have + // multiple KeptnTaskDefinitions that should execute the same logic, but each with different parameters. + FunctionReference FunctionReference `json:"functionRef,omitempty"` + // Inline allows to specify the code that should be executed directly in the KeptnTaskDefinition, as a multi-line + // string. + Inline Inline `json:"inline,omitempty"` + // HttpReference allows to point to an HTTP URL containing the code of the function. + HttpReference HttpReference `json:"httpRef,omitempty"` + // ConfigMapReference allows to reference a ConfigMap containing the code of the function. + // When referencing a ConfigMap, the code of the function must be available as a value of the 'code' key + // of the referenced ConfigMap. ConfigMapReference ConfigMapReference `json:"configMapRef,omitempty"` - Parameters TaskParameters `json:"parameters,omitempty"` - SecureParameters SecureParameters `json:"secureParameters,omitempty"` + // Parameters contains parameters that will be passed to the job that executes the task. + Parameters TaskParameters `json:"parameters,omitempty"` + // SecureParameters contains secure parameters that will be passed to the job that executes the task. + // These will be stored and accessed as secrets in the cluster. + SecureParameters SecureParameters `json:"secureParameters,omitempty"` } type ConfigMapReference struct { + // Name is the name of the referenced ConfigMap. Name string `json:"name,omitempty"` } type FunctionReference struct { + // Name is the name of the referenced KeptnTaksDefinition. Name string `json:"name,omitempty"` } type Inline struct { + // Code contains the code of the function. Code string `json:"code,omitempty"` } type HttpReference struct { + // Url is the URL containing the code of the function. Url string `json:"url,omitempty"` } @@ -66,14 +89,12 @@ type ContainerSpec struct { // KeptnTaskDefinitionStatus defines the observed state of KeptnTaskDefinition type KeptnTaskDefinitionStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file + // Function contains status information of the function definition for the task. Function FunctionStatus `json:"function,omitempty"` } type FunctionStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file + // ConfigMap indicates the ConfigMap in which the function code is stored. ConfigMap string `json:"configMap,omitempty"` } @@ -86,7 +107,9 @@ type KeptnTaskDefinition struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec KeptnTaskDefinitionSpec `json:"spec,omitempty"` + // Spec describes the desired state of the KeptnTaskDefinition. + Spec KeptnTaskDefinitionSpec `json:"spec,omitempty"` + // Status describes the current state of the KeptnTaskDefinition. Status KeptnTaskDefinitionStatus `json:"status,omitempty"` } diff --git a/operator/apis/lifecycle/v1alpha3/keptnworkload_types.go b/operator/apis/lifecycle/v1alpha3/keptnworkload_types.go index c4c6937941..6b6b31ae6c 100644 --- a/operator/apis/lifecycle/v1alpha3/keptnworkload_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptnworkload_types.go @@ -32,17 +32,36 @@ import ( // KeptnWorkloadSpec defines the desired state of KeptnWorkload type KeptnWorkloadSpec struct { - AppName string `json:"app"` - Version string `json:"version"` - PreDeploymentTasks []string `json:"preDeploymentTasks,omitempty"` - PostDeploymentTasks []string `json:"postDeploymentTasks,omitempty"` - PreDeploymentEvaluations []string `json:"preDeploymentEvaluations,omitempty"` - PostDeploymentEvaluations []string `json:"postDeploymentEvaluations,omitempty"` - ResourceReference ResourceReference `json:"resourceReference"` + // AppName is the name of the KeptnApp containing the KeptnWorkload. + AppName string `json:"app"` + // Version defines the version of the KeptnWorkload. + Version string `json:"version"` + // PreDeploymentTasks is a list of all tasks to be performed during the pre-deployment phase of the KeptnWorkload. + // The items of this list refer to the names of KeptnTaskDefinitions + // located in the same namespace as the KeptnApp, or in the KLT namespace. + PreDeploymentTasks []string `json:"preDeploymentTasks,omitempty"` + // PostDeploymentTasks is a list of all tasks to be performed during the post-deployment phase of the KeptnWorkload. + // The items of this list refer to the names of KeptnTaskDefinitions + // located in the same namespace as the KeptnWorkload, or in the KLT namespace. + PostDeploymentTasks []string `json:"postDeploymentTasks,omitempty"` + // PreDeploymentEvaluations is a list of all evaluations to be performed + // during the pre-deployment phase of the KeptnWorkload. + // The items of this list refer to the names of KeptnEvaluationDefinitions + // located in the same namespace as the KeptnWorkload, or in the KLT namespace. + PreDeploymentEvaluations []string `json:"preDeploymentEvaluations,omitempty"` + // PostDeploymentEvaluations is a list of all evaluations to be performed + // during the post-deployment phase of the KeptnWorkload. + // The items of this list refer to the names of KeptnEvaluationDefinitions + // located in the same namespace as the KeptnWorkload, or in the KLT namespace. + PostDeploymentEvaluations []string `json:"postDeploymentEvaluations,omitempty"` + // ResourceReference is a reference to the Kubernetes resource + // (Deployment, DaemonSet, StatefulSet or ReplicaSet) the KeptnWorkload is representing. + ResourceReference ResourceReference `json:"resourceReference"` } // KeptnWorkloadStatus defines the observed state of KeptnWorkload type KeptnWorkloadStatus struct { + // CurrentVersion indicates the version that is currently deployed or being reconciled. CurrentVersion string `json:"currentVersion,omitempty"` } @@ -57,7 +76,9 @@ type KeptnWorkload struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec KeptnWorkloadSpec `json:"spec,omitempty"` + // Spec describes the desired state of the KeptnWorkload. + Spec KeptnWorkloadSpec `json:"spec,omitempty"` + // Status describes the current state of the KeptnWorkload. Status KeptnWorkloadStatus `json:"status,omitempty"` } diff --git a/operator/apis/lifecycle/v1alpha3/keptnworkloadinstance_types.go b/operator/apis/lifecycle/v1alpha3/keptnworkloadinstance_types.go index 3b03b64d9f..0440489ac3 100644 --- a/operator/apis/lifecycle/v1alpha3/keptnworkloadinstance_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptnworkloadinstance_types.go @@ -34,31 +34,53 @@ import ( // KeptnWorkloadInstanceSpec defines the desired state of KeptnWorkloadInstance type KeptnWorkloadInstanceSpec struct { KeptnWorkloadSpec `json:",inline"` - WorkloadName string `json:"workloadName"` - PreviousVersion string `json:"previousVersion,omitempty"` - TraceId map[string]string `json:"traceId,omitempty"` + // WorkloadName is the name of the KeptnWorkload. + WorkloadName string `json:"workloadName"` + // PreviousVersion is the version of the KeptnWorkload that has been deployed prior to this version. + PreviousVersion string `json:"previousVersion,omitempty"` + // TraceId contains the OpenTelemetry trace ID. + TraceId map[string]string `json:"traceId,omitempty"` } // KeptnWorkloadInstanceStatus defines the observed state of KeptnWorkloadInstance type KeptnWorkloadInstanceStatus struct { + // PreDeploymentStatus indicates the current status of the KeptnWorkloadInstance's PreDeployment phase. // +kubebuilder:default:=Pending PreDeploymentStatus common.KeptnState `json:"preDeploymentStatus,omitempty"` + // DeploymentStatus indicates the current status of the KeptnWorkloadInstance's Deployment phase. // +kubebuilder:default:=Pending DeploymentStatus common.KeptnState `json:"deploymentStatus,omitempty"` + // PreDeploymentEvaluationStatus indicates the current status of the KeptnWorkloadInstance's PreDeploymentEvaluation phase. // +kubebuilder:default:=Pending PreDeploymentEvaluationStatus common.KeptnState `json:"preDeploymentEvaluationStatus,omitempty"` + // PostDeploymentEvaluationStatus indicates the current status of the KeptnWorkloadInstance's PostDeploymentEvaluation phase. // +kubebuilder:default:=Pending PostDeploymentEvaluationStatus common.KeptnState `json:"postDeploymentEvaluationStatus,omitempty"` + // PostDeploymentStatus indicates the current status of the KeptnWorkloadInstance's PostDeployment phase. // +kubebuilder:default:=Pending - PostDeploymentStatus common.KeptnState `json:"postDeploymentStatus,omitempty"` - PreDeploymentTaskStatus []ItemStatus `json:"preDeploymentTaskStatus,omitempty"` - PostDeploymentTaskStatus []ItemStatus `json:"postDeploymentTaskStatus,omitempty"` - PreDeploymentEvaluationTaskStatus []ItemStatus `json:"preDeploymentEvaluationTaskStatus,omitempty"` - PostDeploymentEvaluationTaskStatus []ItemStatus `json:"postDeploymentEvaluationTaskStatus,omitempty"` - StartTime metav1.Time `json:"startTime,omitempty"` - EndTime metav1.Time `json:"endTime,omitempty"` - CurrentPhase string `json:"currentPhase,omitempty"` - PhaseTraceIDs common.PhaseTraceID `json:"phaseTraceIDs,omitempty"` + PostDeploymentStatus common.KeptnState `json:"postDeploymentStatus,omitempty"` + // PreDeploymentTaskStatus indicates the current state of each preDeploymentTask of the KeptnWorkloadInstance. + PreDeploymentTaskStatus []ItemStatus `json:"preDeploymentTaskStatus,omitempty"` + // PostDeploymentTaskStatus indicates the current state of each postDeploymentTask of the KeptnWorkloadInstance. + PostDeploymentTaskStatus []ItemStatus `json:"postDeploymentTaskStatus,omitempty"` + // PreDeploymentEvaluationTaskStatus indicates the current state of each preDeploymentEvaluation of the KeptnWorkloadInstance. + PreDeploymentEvaluationTaskStatus []ItemStatus `json:"preDeploymentEvaluationTaskStatus,omitempty"` + // PostDeploymentEvaluationTaskStatus indicates the current state of each postDeploymentEvaluation of the KeptnWorkloadInstance. + PostDeploymentEvaluationTaskStatus []ItemStatus `json:"postDeploymentEvaluationTaskStatus,omitempty"` + // StartTime represents the time at which the deployment of the KeptnWorkloadInstance started. + StartTime metav1.Time `json:"startTime,omitempty"` + // EndTime represents the time at which the deployment of the KeptnWorkloadInstance finished. + EndTime metav1.Time `json:"endTime,omitempty"` + // CurrentPhase indicates the current phase of the KeptnWorkloadInstance. This can be: + // - PreDeploymentTasks + // - PreDeploymentEvaluations + // - Deployment + // - PostDeploymentTasks + // - PostDeploymentEvaluations + CurrentPhase string `json:"currentPhase,omitempty"` + // PhaseTraceIDs contains the trace IDs of the OpenTelemetry spans of each phase of the KeptnWorkloadInstance + PhaseTraceIDs common.PhaseTraceID `json:"phaseTraceIDs,omitempty"` + // Status represents the overall status of the KeptnWorkloadInstance. // +kubebuilder:default:=Pending Status common.KeptnState `json:"status,omitempty"` } @@ -69,9 +91,11 @@ type ItemStatus struct { // +kubebuilder:default:=Pending Status common.KeptnState `json:"status,omitempty"` // Name is the name of the Evaluation/Task - Name string `json:"name,omitempty"` + Name string `json:"name,omitempty"` + // StartTime represents the time at which the Item (Evaluation/Task) started. StartTime metav1.Time `json:"startTime,omitempty"` - EndTime metav1.Time `json:"endTime,omitempty"` + // EndTime represents the time at which the Item (Evaluation/Task) started. + EndTime metav1.Time `json:"endTime,omitempty"` } //+kubebuilder:object:root=true @@ -93,7 +117,9 @@ type KeptnWorkloadInstance struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec KeptnWorkloadInstanceSpec `json:"spec,omitempty"` + // Spec describes the desired state of the KeptnWorkloadInstance. + Spec KeptnWorkloadInstanceSpec `json:"spec,omitempty"` + // Status describes the current state of the KeptnWorkloadInstance. Status KeptnWorkloadInstanceStatus `json:"status,omitempty"` } diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml index 3ef83179fc..bf61e26866 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml @@ -33,8 +33,7 @@ spec: metadata: type: object spec: - description: KeptnAppCreationRequestSpec defines the desired state of - KeptnAppCreationRequest + description: Spec describes the desired state of the KeptnAppCreationRequest. properties: appName: description: AppName is the name of the KeptnApp the KeptnAppCreationRequest @@ -44,8 +43,7 @@ spec: - appName type: object status: - description: KeptnAppCreationRequestStatus defines the observed state - of KeptnAppCreationRequest + description: Status describes the current state of the KeptnAppCreationRequest. type: object type: object served: true diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnapps.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnapps.yaml index 7385e9ac35..fef928d54d 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnapps.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnapps.yaml @@ -163,35 +163,63 @@ spec: metadata: type: object spec: - description: KeptnAppSpec defines the desired state of KeptnApp + description: Spec describes the desired state of the KeptnApp. properties: postDeploymentEvaluations: + description: PostDeploymentEvaluations is a list of all evaluations + to be performed during the post-deployment phase of the KeptnApp. + The items of this list refer to the names of KeptnEvaluationDefinitions + located in the same namespace as the KeptnApp, or in the KLT namespace. items: type: string type: array postDeploymentTasks: + description: PostDeploymentTasks is a list of all tasks to be performed + during the post-deployment phase of the KeptnApp. The items of this + list refer to the names of KeptnTaskDefinitions located in the same + namespace as the KeptnApp, or in the KLT namespace. items: type: string type: array preDeploymentEvaluations: + description: PreDeploymentEvaluations is a list of all evaluations + to be performed during the pre-deployment phase of the KeptnApp. + The items of this list refer to the names of KeptnEvaluationDefinitions + located in the same namespace as the KeptnApp, or in the KLT namespace. items: type: string type: array preDeploymentTasks: + description: PreDeploymentTasks is a list of all tasks to be performed + during the pre-deployment phase of the KeptnApp. The items of this + list refer to the names of KeptnTaskDefinitions located in the same + namespace as the KeptnApp, or in the KLT namespace. items: type: string type: array revision: default: 1 + description: Revision can be modified to trigger another deployment + of a KeptnApp of the same version. This can be used for restarting + a KeptnApp which failed to deploy, e.g. due to a failed preDeploymentEvaluation/preDeploymentTask. type: integer version: + description: Version defines the version of the application. For automatically + created KeptnApps, the version is a function of all KeptnWorkloads + that are part of the KeptnApp. type: string workloads: + description: Workloads is a list of all KeptnWorkloads that are part + of the KeptnApp. items: + description: KeptnWorkloadRef refers to a KeptnWorkload that is + part of a KeptnApp properties: name: + description: Name is the name of the KeptnWorkload. type: string version: + description: Version is the version of the KeptnWorkload. type: string required: - name @@ -202,9 +230,11 @@ spec: - version type: object status: - description: KeptnAppStatus defines the observed state of KeptnApp + description: Status describes the current state of the KeptnApp. properties: currentVersion: + description: CurrentVersion indicates the version that is currently + deployed or being reconciled. type: string type: object type: object diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnappversions.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnappversions.yaml index 7aade16857..8794425a74 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnappversions.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnappversions.yaml @@ -525,43 +525,75 @@ spec: metadata: type: object spec: - description: KeptnAppVersionSpec defines the desired state of KeptnAppVersion + description: Spec describes the desired state of the KeptnAppVersion. properties: appName: + description: AppName is the name of the KeptnApp. type: string postDeploymentEvaluations: + description: PostDeploymentEvaluations is a list of all evaluations + to be performed during the post-deployment phase of the KeptnApp. + The items of this list refer to the names of KeptnEvaluationDefinitions + located in the same namespace as the KeptnApp, or in the KLT namespace. items: type: string type: array postDeploymentTasks: + description: PostDeploymentTasks is a list of all tasks to be performed + during the post-deployment phase of the KeptnApp. The items of this + list refer to the names of KeptnTaskDefinitions located in the same + namespace as the KeptnApp, or in the KLT namespace. items: type: string type: array preDeploymentEvaluations: + description: PreDeploymentEvaluations is a list of all evaluations + to be performed during the pre-deployment phase of the KeptnApp. + The items of this list refer to the names of KeptnEvaluationDefinitions + located in the same namespace as the KeptnApp, or in the KLT namespace. items: type: string type: array preDeploymentTasks: + description: PreDeploymentTasks is a list of all tasks to be performed + during the pre-deployment phase of the KeptnApp. The items of this + list refer to the names of KeptnTaskDefinitions located in the same + namespace as the KeptnApp, or in the KLT namespace. items: type: string type: array previousVersion: + description: PreviousVersion is the version of the KeptnApp that has + been deployed prior to this version. type: string revision: default: 1 + description: Revision can be modified to trigger another deployment + of a KeptnApp of the same version. This can be used for restarting + a KeptnApp which failed to deploy, e.g. due to a failed preDeploymentEvaluation/preDeploymentTask. type: integer traceId: additionalProperties: type: string + description: TraceId contains the OpenTelemetry trace ID. type: object version: + description: Version defines the version of the application. For automatically + created KeptnApps, the version is a function of all KeptnWorkloads + that are part of the KeptnApp. type: string workloads: + description: Workloads is a list of all KeptnWorkloads that are part + of the KeptnApp. items: + description: KeptnWorkloadRef refers to a KeptnWorkload that is + part of a KeptnApp properties: name: + description: Name is the name of the KeptnWorkload. type: string version: + description: Version is the version of the KeptnWorkload. type: string required: - name @@ -573,11 +605,14 @@ spec: - version type: object status: - description: KeptnAppVersionStatus defines the observed state of KeptnAppVersion + description: Status describes the current state of the KeptnAppVersion. properties: currentPhase: + description: CurrentPhase indicates the current phase of the KeptnAppVersion. type: string endTime: + description: EndTime represents the time at which the deployment of + the KeptnAppVersion finished. format: date-time type: string phaseTraceIDs: @@ -587,23 +622,33 @@ spec: description: MapCarrier is a TextMapCarrier that uses a map held in memory as a storage medium for propagated key-value pairs. type: object + description: PhaseTraceIDs contains the trace IDs of the OpenTelemetry + spans of each phase of the KeptnAppVersion. type: object postDeploymentEvaluationStatus: default: Pending + description: PostDeploymentEvaluationStatus indicates the current + status of the KeptnAppVersion's PostDeploymentEvaluation phase. type: string postDeploymentEvaluationTaskStatus: + description: PostDeploymentEvaluationTaskStatus indicates the current + state of each postDeploymentEvaluation of the KeptnAppVersion. items: properties: definitionName: description: DefinitionName is the name of the EvaluationDefinition/TaskDefiniton type: string endTime: + description: EndTime represents the time at which the Item (Evaluation/Task) + started. format: date-time type: string name: description: Name is the name of the Evaluation/Task type: string startTime: + description: StartTime represents the time at which the Item + (Evaluation/Task) started. format: date-time type: string status: @@ -613,20 +658,28 @@ spec: type: array postDeploymentStatus: default: Pending + description: PostDeploymentStatus indicates the current status of + the KeptnAppVersion's PostDeployment phase. type: string postDeploymentTaskStatus: + description: PostDeploymentTaskStatus indicates the current state + of each postDeploymentTask of the KeptnAppVersion. items: properties: definitionName: description: DefinitionName is the name of the EvaluationDefinition/TaskDefiniton type: string endTime: + description: EndTime represents the time at which the Item (Evaluation/Task) + started. format: date-time type: string name: description: Name is the name of the Evaluation/Task type: string startTime: + description: StartTime represents the time at which the Item + (Evaluation/Task) started. format: date-time type: string status: @@ -636,20 +689,28 @@ spec: type: array preDeploymentEvaluationStatus: default: Pending + description: PreDeploymentEvaluationStatus indicates the current status + of the KeptnAppVersion's PreDeploymentEvaluation phase. type: string preDeploymentEvaluationTaskStatus: + description: PreDeploymentEvaluationTaskStatus indicates the current + state of each preDeploymentEvaluation of the KeptnAppVersion. items: properties: definitionName: description: DefinitionName is the name of the EvaluationDefinition/TaskDefiniton type: string endTime: + description: EndTime represents the time at which the Item (Evaluation/Task) + started. format: date-time type: string name: description: Name is the name of the Evaluation/Task type: string startTime: + description: StartTime represents the time at which the Item + (Evaluation/Task) started. format: date-time type: string status: @@ -659,20 +720,28 @@ spec: type: array preDeploymentStatus: default: Pending + description: PreDeploymentStatus indicates the current status of the + KeptnAppVersion's PreDeployment phase. type: string preDeploymentTaskStatus: + description: PreDeploymentTaskStatus indicates the current state of + each preDeploymentTask of the KeptnAppVersion. items: properties: definitionName: description: DefinitionName is the name of the EvaluationDefinition/TaskDefiniton type: string endTime: + description: EndTime represents the time at which the Item (Evaluation/Task) + started. format: date-time type: string name: description: Name is the name of the Evaluation/Task type: string startTime: + description: StartTime represents the time at which the Item + (Evaluation/Task) started. format: date-time type: string status: @@ -681,25 +750,37 @@ spec: type: object type: array startTime: + description: StartTime represents the time at which the deployment + of the KeptnAppVersion started. format: date-time type: string status: default: Pending + description: Status represents the overall status of the KeptnAppVersion. type: string workloadOverallStatus: default: Pending + description: WorkloadOverallStatus indicates the current status of + the KeptnAppVersion's Workload deployment phase. type: string workloadStatus: + description: WorkloadStatus contains the current status of each KeptnWorkload + that is part of the KeptnAppVersion. items: properties: status: default: Pending + description: Status indicates the current status of the KeptnWorkload. type: string workload: + description: Workload refers to a KeptnWorkload that is part + of the KeptnAppVersion. properties: name: + description: Name is the name of the KeptnWorkload. type: string version: + description: Version is the version of the KeptnWorkload. type: string required: - name diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationdefinitions.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationdefinitions.yaml index 42a5201fc2..f053546f39 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationdefinitions.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationdefinitions.yaml @@ -139,19 +139,29 @@ spec: metadata: type: object spec: - description: KeptnEvaluationDefinitionSpec defines the desired state of - KeptnEvaluationDefinition + description: Spec describes the desired state of the KeptnEvaluationDefinition. properties: objectives: + description: Objectives is a list of objectives that have to be met + for a KeptnEvaluation referencing this KeptnEvaluationDefinition + to be successful. items: properties: evaluationTarget: + description: EvaluationTarget specifies the target value for + the references KeptnMetric. Needs to start with either '<' + or '>', followed by the target value (e.g. '<10'). type: string keptnMetricRef: + description: KeptnMetricRef references the KeptnMetric that + should be evaluated. properties: name: + description: Name is the name of the referenced KeptnMetric. type: string namespace: + description: Namespace is the namespace where the referenced + KeptnMetric is located. type: string required: - name @@ -165,8 +175,7 @@ spec: - objectives type: object status: - description: KeptnEvaluationDefinitionStatus defines the observed state - of KeptnEvaluationDefinition + description: Status describes the current state of the KeptnEvaluationDefinition. type: object type: object served: true diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluations.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluations.yaml index 9a4c08af68..2f01a450c3 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluations.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluations.yaml @@ -268,60 +268,98 @@ spec: metadata: type: object spec: - description: KeptnEvaluationSpec defines the desired state of KeptnEvaluation + description: Spec describes the desired state of the KeptnEvaluation. properties: appName: + description: AppName defines the KeptnApp for which the KeptnEvaluation + is done. type: string appVersion: + description: AppVersion defines the version of the KeptnApp for which + the KeptnEvaluation is done. type: string checkType: + description: Type indicates whether the KeptnEvaluation is part of + the pre- or postDeployment phase. type: string evaluationDefinition: + description: EvaluationDefinition refers to the name of the KeptnEvaluationDefinition + which includes the objectives for the KeptnEvaluation. The KeptnEvaluationDefinition + can be located in the same namespace as the KeptnEvaluation, or + in the KLT namespace. type: string failAction: type: string retries: default: 10 + description: Retries indicates how many times the KeptnEvaluation + can be attempted in the case of an error or missed evaluation objective, + before considering the KeptnEvaluation to be failed. type: integer retryInterval: default: 5s + description: RetryInterval specifies the interval at which the KeptnEvaluation + is retried in the case of an error or a missed objective. pattern: ^0|([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string workload: + description: Workload defines the KeptnWorkload for which the KeptnEvaluation + is done. type: string workloadVersion: + description: WorkloadVersion defines the version of the KeptnWorkload + for which the KeptnEvaluation is done. type: string required: - evaluationDefinition - workloadVersion type: object status: - description: KeptnEvaluationStatus defines the observed state of KeptnEvaluation + description: Status describes the current state of the KeptnEvaluation. properties: endTime: + description: EndTime represents the time at which the KeptnEvaluation + finished. format: date-time type: string evaluationStatus: additionalProperties: properties: message: + description: Message contains additional information about the + evaluation of an objective. This can include explanations + about why an evaluation has failed (e.g. due to a missed objective), + or if there was any error during the evaluation of the objective. type: string status: + description: Status indicates the status of the objective being + evaluated. type: string value: + description: Value represents the value of the KeptnMetric being + evaluated. type: string required: - status - value type: object + description: EvaluationStatus describes the status of each objective + of the KeptnEvaluationDefinition referenced by the KeptnEvaluation. type: object overallStatus: default: Pending + description: OverallStatus describes the overall status of the KeptnEvaluation. + The Overall status is derived from the status of the individual + objectives of the KeptnEvaluationDefinition referenced by the KeptnEvaluation. type: string retryCount: default: 0 + description: RetryCount indicates how many times the KeptnEvaluation + has been attempted already. type: integer startTime: + description: StartTime represents the time at which the KeptnEvaluation + started. format: date-time type: string required: diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml index b9284fe6a6..5af542e0ae 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml @@ -187,64 +187,103 @@ spec: metadata: type: object spec: - description: KeptnTaskDefinitionSpec defines the desired state of KeptnTaskDefinition + description: Spec describes the desired state of the KeptnTaskDefinition. properties: function: + description: Function contains the definition for the function that + is to be executed in KeptnTasks based on the KeptnTaskDefinitions. properties: configMapRef: + description: ConfigMapReference allows to reference a ConfigMap + containing the code of the function. When referencing a ConfigMap, + the code of the function must be available as a value of the + 'code' key of the referenced ConfigMap. properties: name: + description: Name is the name of the referenced ConfigMap. type: string type: object functionRef: + description: FunctionReference allows to reference another KeptnTaskDefinition + which contains the source code of the function to be executes + for KeptnTasks based on this KeptnTaskDefinition. This can be + useful when you have multiple KeptnTaskDefinitions that should + execute the same logic, but each with different parameters. properties: name: + description: Name is the name of the referenced KeptnTaksDefinition. type: string type: object httpRef: + description: HttpReference allows to point to an HTTP URL containing + the code of the function. properties: url: + description: Url is the URL containing the code of the function. type: string type: object inline: + description: Inline allows to specify the code that should be + executed directly in the KeptnTaskDefinition, as a multi-line + string. properties: code: + description: Code contains the code of the function. type: string type: object parameters: + description: Parameters contains parameters that will be passed + to the job that executes the task. properties: map: additionalProperties: type: string + description: Inline contains the parameters that will be made + available to the job executing the KeptnTask via the 'DATA' + environment variable. The 'DATA' environment variable's + content will be a json encoded string containing all properties + of the map provided. type: object type: object secureParameters: + description: SecureParameters contains secure parameters that + will be passed to the job that executes the task. These will + be stored and accessed as secrets in the cluster. properties: secret: + description: Secret contains the parameters that will be made + available to the job executing the KeptnTask via the 'SECRET_DATA' + environment variable. The 'SECRET_DATA' environment variable's + content will the same as value of the 'SECRET_DATA' key + of the referenced secret. type: string type: object type: object retries: default: 10 + description: Retries specifies how many times a job executing the + KeptnTaskDefinition should be restarted in the case of an unsuccessful + attempt. format: int32 type: integer timeout: default: 5m + description: Timeout specifies the maximum time to wait for the task + to be completed successfully. If the task does not complete successfully + within this time frame, it will be considered to be failed. pattern: ^0|([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string type: object status: - description: KeptnTaskDefinitionStatus defines the observed state of KeptnTaskDefinition + description: Status describes the current state of the KeptnTaskDefinition. properties: function: - description: 'INSERT ADDITIONAL STATUS FIELD - define observed state - of cluster Important: Run "make" to regenerate code after modifying - this file' + description: Function contains status information of the function + definition for the task. properties: configMap: - description: 'INSERT ADDITIONAL STATUS FIELD - define observed - state of cluster Important: Run "make" to regenerate code after - modifying this file' + description: ConfigMap indicates the ConfigMap in which the function + code is stored. type: string type: object type: object diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptntasks.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptntasks.yaml index 423576ce7f..58a8cb6262 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptntasks.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptntasks.yaml @@ -281,27 +281,47 @@ spec: metadata: type: object spec: - description: KeptnTaskSpec defines the desired state of KeptnTask + description: Spec describes the desired state of the KeptnTask. properties: app: + description: AppName defines the KeptnApp for which the KeptnTask + is executed. type: string appVersion: + description: AppVersion defines the version of the KeptnApp for which + the KeptnTask is executed. type: string checkType: + description: Type indicates whether the KeptnTask is part of the pre- + or postDeployment phase. type: string context: + description: Context contains contextual information about the task + execution. properties: appName: + description: AppName the name of the KeptnApp the KeptnTask is + being executed for. type: string appVersion: + description: AppVersion the version of the KeptnApp the KeptnTask + is being executed for. type: string objectType: + description: ObjectType indicates whether the KeptnTask is being + executed for a KeptnApp or KeptnWorkload. type: string taskType: + description: TaskType indicates whether the KeptnTask is part + of the pre- or postDeployment phase. type: string workloadName: + description: WorkloadName the name of the KeptnWorkload the KeptnTask + is being executed for. type: string workloadVersion: + description: WorkloadVersion the version of the KeptnWorkload + the KeptnTask is being executed for. type: string required: - appName @@ -312,30 +332,59 @@ spec: - workloadVersion type: object parameters: + description: Parameters contains parameters that will be passed to + the job that executes the task. properties: map: additionalProperties: type: string + description: Inline contains the parameters that will be made + available to the job executing the KeptnTask via the 'DATA' + environment variable. The 'DATA' environment variable's content + will be a json encoded string containing all properties of the + map provided. type: object type: object retries: default: 10 + description: Retries indicates how many times the KeptnTask can be + attempted in the case of an error before considering the KeptnTask + to be failed. format: int32 type: integer secureParameters: + description: SecureParameters contains secure parameters that will + be passed to the job that executes the task. These will be stored + and accessed as secrets in the cluster. properties: secret: + description: Secret contains the parameters that will be made + available to the job executing the KeptnTask via the 'SECRET_DATA' + environment variable. The 'SECRET_DATA' environment variable's + content will the same as value of the 'SECRET_DATA' key of the + referenced secret. type: string type: object taskDefinition: + description: TaskDefinition refers to the name of the KeptnTaskDefinition + which includes the specification for the task to be performed. The + KeptnTaskDefinition can be located in the same namespace as the + KeptnTask, or in the KLT namespace. type: string timeout: default: 5m + description: Timeout specifies the maximum time to wait for the task + to be completed successfully. If the task does not complete successfully + within this time frame, it will be considered to be failed. pattern: ^0|([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string workload: + description: Workload defines the KeptnWorkload for which the KeptnTask + is executed. type: string workloadVersion: + description: WorkloadVersion defines the version of the KeptnWorkload + for which the KeptnTask is executed. type: string required: - app @@ -346,22 +395,31 @@ spec: - workloadVersion type: object status: - description: KeptnTaskStatus defines the observed state of KeptnTask + description: Status describes the current state of the KeptnTask. properties: endTime: + description: EndTime represents the time at which the KeptnTask finished. format: date-time type: string jobName: + description: JobName is the name of the Job executing the Task. type: string message: + description: Message contains information about unexpected errors + encountered during the execution of the KeptnTask. type: string reason: + description: Reason contains more information about the reason for + the last transition of the Job executing the KeptnTask. type: string startTime: + description: StartTime represents the time at which the KeptnTask + started. format: date-time type: string status: default: Pending + description: Status represents the overall state of the KeptnTask. type: string type: object type: object diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadinstances.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadinstances.yaml index bf59f5d604..f53bf6eec3 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadinstances.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadinstances.yaml @@ -518,29 +518,53 @@ spec: metadata: type: object spec: - description: KeptnWorkloadInstanceSpec defines the desired state of KeptnWorkloadInstance + description: Spec describes the desired state of the KeptnWorkloadInstance. properties: app: + description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string postDeploymentEvaluations: + description: PostDeploymentEvaluations is a list of all evaluations + to be performed during the post-deployment phase of the KeptnWorkload. + The items of this list refer to the names of KeptnEvaluationDefinitions + located in the same namespace as the KeptnWorkload, or in the KLT + namespace. items: type: string type: array postDeploymentTasks: + description: PostDeploymentTasks is a list of all tasks to be performed + during the post-deployment phase of the KeptnWorkload. The items + of this list refer to the names of KeptnTaskDefinitions located + in the same namespace as the KeptnWorkload, or in the KLT namespace. items: type: string type: array preDeploymentEvaluations: + description: PreDeploymentEvaluations is a list of all evaluations + to be performed during the pre-deployment phase of the KeptnWorkload. + The items of this list refer to the names of KeptnEvaluationDefinitions + located in the same namespace as the KeptnWorkload, or in the KLT + namespace. items: type: string type: array preDeploymentTasks: + description: PreDeploymentTasks is a list of all tasks to be performed + during the pre-deployment phase of the KeptnWorkload. The items + of this list refer to the names of KeptnTaskDefinitions located + in the same namespace as the KeptnApp, or in the KLT namespace. items: type: string type: array previousVersion: + description: PreviousVersion is the version of the KeptnWorkload that + has been deployed prior to this version. type: string resourceReference: + description: ResourceReference is a reference to the Kubernetes resource + (Deployment, DaemonSet, StatefulSet or ReplicaSet) the KeptnWorkload + is representing. properties: kind: type: string @@ -560,10 +584,13 @@ spec: traceId: additionalProperties: type: string + description: TraceId contains the OpenTelemetry trace ID. type: object version: + description: Version defines the version of the KeptnWorkload. type: string workloadName: + description: WorkloadName is the name of the KeptnWorkload. type: string required: - app @@ -572,15 +599,21 @@ spec: - workloadName type: object status: - description: KeptnWorkloadInstanceStatus defines the observed state of - KeptnWorkloadInstance + description: Status describes the current state of the KeptnWorkloadInstance. properties: currentPhase: + description: 'CurrentPhase indicates the current phase of the KeptnWorkloadInstance. + This can be: - PreDeploymentTasks - PreDeploymentEvaluations - Deployment + - PostDeploymentTasks - PostDeploymentEvaluations' type: string deploymentStatus: default: Pending + description: DeploymentStatus indicates the current status of the + KeptnWorkloadInstance's Deployment phase. type: string endTime: + description: EndTime represents the time at which the deployment of + the KeptnWorkloadInstance finished. format: date-time type: string phaseTraceIDs: @@ -590,23 +623,33 @@ spec: description: MapCarrier is a TextMapCarrier that uses a map held in memory as a storage medium for propagated key-value pairs. type: object + description: PhaseTraceIDs contains the trace IDs of the OpenTelemetry + spans of each phase of the KeptnWorkloadInstance type: object postDeploymentEvaluationStatus: default: Pending + description: PostDeploymentEvaluationStatus indicates the current + status of the KeptnWorkloadInstance's PostDeploymentEvaluation phase. type: string postDeploymentEvaluationTaskStatus: + description: PostDeploymentEvaluationTaskStatus indicates the current + state of each postDeploymentEvaluation of the KeptnWorkloadInstance. items: properties: definitionName: description: DefinitionName is the name of the EvaluationDefinition/TaskDefiniton type: string endTime: + description: EndTime represents the time at which the Item (Evaluation/Task) + started. format: date-time type: string name: description: Name is the name of the Evaluation/Task type: string startTime: + description: StartTime represents the time at which the Item + (Evaluation/Task) started. format: date-time type: string status: @@ -616,20 +659,28 @@ spec: type: array postDeploymentStatus: default: Pending + description: PostDeploymentStatus indicates the current status of + the KeptnWorkloadInstance's PostDeployment phase. type: string postDeploymentTaskStatus: + description: PostDeploymentTaskStatus indicates the current state + of each postDeploymentTask of the KeptnWorkloadInstance. items: properties: definitionName: description: DefinitionName is the name of the EvaluationDefinition/TaskDefiniton type: string endTime: + description: EndTime represents the time at which the Item (Evaluation/Task) + started. format: date-time type: string name: description: Name is the name of the Evaluation/Task type: string startTime: + description: StartTime represents the time at which the Item + (Evaluation/Task) started. format: date-time type: string status: @@ -639,20 +690,28 @@ spec: type: array preDeploymentEvaluationStatus: default: Pending + description: PreDeploymentEvaluationStatus indicates the current status + of the KeptnWorkloadInstance's PreDeploymentEvaluation phase. type: string preDeploymentEvaluationTaskStatus: + description: PreDeploymentEvaluationTaskStatus indicates the current + state of each preDeploymentEvaluation of the KeptnWorkloadInstance. items: properties: definitionName: description: DefinitionName is the name of the EvaluationDefinition/TaskDefiniton type: string endTime: + description: EndTime represents the time at which the Item (Evaluation/Task) + started. format: date-time type: string name: description: Name is the name of the Evaluation/Task type: string startTime: + description: StartTime represents the time at which the Item + (Evaluation/Task) started. format: date-time type: string status: @@ -662,20 +721,28 @@ spec: type: array preDeploymentStatus: default: Pending + description: PreDeploymentStatus indicates the current status of the + KeptnWorkloadInstance's PreDeployment phase. type: string preDeploymentTaskStatus: + description: PreDeploymentTaskStatus indicates the current state of + each preDeploymentTask of the KeptnWorkloadInstance. items: properties: definitionName: description: DefinitionName is the name of the EvaluationDefinition/TaskDefiniton type: string endTime: + description: EndTime represents the time at which the Item (Evaluation/Task) + started. format: date-time type: string name: description: Name is the name of the Evaluation/Task type: string startTime: + description: StartTime represents the time at which the Item + (Evaluation/Task) started. format: date-time type: string status: @@ -684,10 +751,13 @@ spec: type: object type: array startTime: + description: StartTime represents the time at which the deployment + of the KeptnWorkloadInstance started. format: date-time type: string status: default: Pending + description: Status represents the overall status of the KeptnWorkloadInstance. type: string type: object type: object diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml index 1292f9464d..861b930d60 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml @@ -199,27 +199,49 @@ spec: metadata: type: object spec: - description: KeptnWorkloadSpec defines the desired state of KeptnWorkload + description: Spec describes the desired state of the KeptnWorkload. properties: app: + description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string postDeploymentEvaluations: + description: PostDeploymentEvaluations is a list of all evaluations + to be performed during the post-deployment phase of the KeptnWorkload. + The items of this list refer to the names of KeptnEvaluationDefinitions + located in the same namespace as the KeptnWorkload, or in the KLT + namespace. items: type: string type: array postDeploymentTasks: + description: PostDeploymentTasks is a list of all tasks to be performed + during the post-deployment phase of the KeptnWorkload. The items + of this list refer to the names of KeptnTaskDefinitions located + in the same namespace as the KeptnWorkload, or in the KLT namespace. items: type: string type: array preDeploymentEvaluations: + description: PreDeploymentEvaluations is a list of all evaluations + to be performed during the pre-deployment phase of the KeptnWorkload. + The items of this list refer to the names of KeptnEvaluationDefinitions + located in the same namespace as the KeptnWorkload, or in the KLT + namespace. items: type: string type: array preDeploymentTasks: + description: PreDeploymentTasks is a list of all tasks to be performed + during the pre-deployment phase of the KeptnWorkload. The items + of this list refer to the names of KeptnTaskDefinitions located + in the same namespace as the KeptnApp, or in the KLT namespace. items: type: string type: array resourceReference: + description: ResourceReference is a reference to the Kubernetes resource + (Deployment, DaemonSet, StatefulSet or ReplicaSet) the KeptnWorkload + is representing. properties: kind: type: string @@ -237,6 +259,7 @@ spec: - uid type: object version: + description: Version defines the version of the KeptnWorkload. type: string required: - app @@ -244,9 +267,11 @@ spec: - version type: object status: - description: KeptnWorkloadStatus defines the observed state of KeptnWorkload + description: Status describes the current state of the KeptnWorkload. properties: currentVersion: + description: CurrentVersion indicates the version that is currently + deployed or being reconciled. type: string type: object type: object From d42d023d1d431782deb0c3ef8fa20fa2f2375ad3 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Wed, 10 May 2023 01:46:25 -0700 Subject: [PATCH 03/62] docs: add info about automatic application discovery (#1353) Signed-off-by: Meg McRoberts Signed-off-by: Meg McRoberts Signed-off-by: Meg McRoberts Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> --- .../content/en/docs/implementing/integrate.md | 102 ------- .../implementing/integrate/assets/trace.png | Bin 0 -> 168484 bytes .../en/docs/implementing/integrate/index.md | 276 ++++++++++++++++++ .../docs/yaml-crd-ref/evaluationdefinition.md | 2 +- 4 files changed, 277 insertions(+), 103 deletions(-) delete mode 100644 docs/content/en/docs/implementing/integrate.md create mode 100644 docs/content/en/docs/implementing/integrate/assets/trace.png create mode 100644 docs/content/en/docs/implementing/integrate/index.md diff --git a/docs/content/en/docs/implementing/integrate.md b/docs/content/en/docs/implementing/integrate.md deleted file mode 100644 index 5aa34f2f98..0000000000 --- a/docs/content/en/docs/implementing/integrate.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: Integrate KLT with your applications -description: How to integrate the Keptn Lifecycle Toolkit into your Kubernetes cluster -icon: concepts -layout: quickstart -weight: 45 -hidechildren: false # this flag hides all sub-pages in the sidebar-multicard.html ---- - -Use Kubernetes annotations and labels -to integrate the Keptn Lifecycle Toolkit into your Kubernetes cluster. - -The Keptn Lifecycle Toolkit monitors manifests -that have been applied against the Kubernetes API -and reacts if it finds a workload with special annotations/labels. -This is a four-step process: - -* Annotate your workload(s) -* Create a `KeptnApp` custom resource that references those workloads -* Create the `KeptnTaskDefinition`s you need -* Enable the target namespace by annotating it - -## Annotate workload(s) - -For this, you should annotate your -[Workload](https://kubernetes.io/docs/concepts/workloads/) -with (at least) the following annotations: - -```yaml -keptn.sh/app: myAwesomeAppName -keptn.sh/workload: myAwesomeWorkload -keptn.sh/version: myAwesomeWorkloadVersion -``` - -Alternatively, you can use Kubernetes -[Recommended Labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) -to annotate your workload: - -```yaml -app.kubernetes.io/part-of: myAwesomeAppName -app.kubernetes.io/name: myAwesomeWorkload -app.kubernetes.io/version: myAwesomeWorkloadVersion -``` - -Note the following: - -* The Keptn Annotations/Labels take precedence - over the Kubernetes recommended labels. -* If the Workload has no version annotation/labels - and the pod has only one container, - the Lifecycle Toolkit takes the image tag as version - (if it is not "latest"). - -This process is demonstrated in the -[Keptn Lifecycle Toolkit: Installation and KeptnTask Creation in Mintes](https://www.youtube.com/watch?v=Hh01bBwZ_qM) -video. - -## Pre- and post-deployment checks - -Further annotations are necessary -to run pre- and post-deployment checks: - -```yaml -keptn.sh/pre-deployment-tasks: verify-infrastructure-problems -keptn.sh/post-deployment-tasks: slack-notification,performance-test -``` - -The value of these annotations are -Keptn [CRDs](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) -called `KeptnTaskDefinition`s. -These CRDs contain re-usable "functions" -that can execute before and after the deployment. -In this example, before the deployment starts, -a check for open problems in your infrastructure is performed. -If everything is fine, the deployment continues and afterward, -a slack notification is sent with the result of the deployment -and a pipeline to run performance tests is invoked. -Otherwise, the deployment is kept in a pending state -until the infrastructure is capable of accepting deployments again. - -A more comprehensive example can be found in our -[examples folder](https://github.com/keptn/lifecycle-toolkit/tree/main/examples/sample-app), -where we use [Podtato-Head](https://github.com/podtato-head/podtato-head) -to run some simple pre-deployment checks. - -To run the example, use the following commands: - -```shell -cd ./examples/podtatohead-deployment/ -kubectl apply -f . -``` - -Afterward, you can monitor the status of the deployment using - -```shell -kubectl get keptnworkloadinstance -n podtato-kubectl -w -``` - -The deployment for a Workload stays in a `Pending` -state until the respective pre-deployment check is completed. -Afterwards, the deployment starts and when it is marked `Succeeded`, -the post-deployment checks start. diff --git a/docs/content/en/docs/implementing/integrate/assets/trace.png b/docs/content/en/docs/implementing/integrate/assets/trace.png new file mode 100644 index 0000000000000000000000000000000000000000..84254826c9f2620eba45d6db24b85d35a1048837 GIT binary patch literal 168484 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 z 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�sf94UKmzFk^!cXowjvzfRtJ;bmh9l@dT^+1z~X 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" + workloads: + - name: podtato-head-frontend + version: 0.1.0 + - name: podtato-head-hat + version: 1.1.1 +``` + +With the `KeptnApp` resource created, +you get observability of your application's deployments +by using the OpenTelemetry tracing features +that are provided by the Keptn Lifecycle Toolkit: + +![Application deployment trace](assets/trace.png) diff --git a/docs/content/en/docs/yaml-crd-ref/evaluationdefinition.md b/docs/content/en/docs/yaml-crd-ref/evaluationdefinition.md index 187ac50908..e054a6e08a 100644 --- a/docs/content/en/docs/yaml-crd-ref/evaluationdefinition.md +++ b/docs/content/en/docs/yaml-crd-ref/evaluationdefinition.md @@ -1,5 +1,5 @@ --- title: KeptnEvaluationDefinition description: Define all workloads and checks associated with an application -weight: 25 +weight: 20 --- From e06b01e4b3723b16b8479f3b22fa3021e8dead55 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Wed, 10 May 2023 06:33:59 -0700 Subject: [PATCH 04/62] docs: add cluster requirements (#1364) Signed-off-by: odubajDT Signed-off-by: Meg McRoberts Signed-off-by: Florian Bacher Signed-off-by: realanna Signed-off-by: Meg McRoberts Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> Co-authored-by: Florian Bacher Co-authored-by: Giovanni Liva Co-authored-by: keptn-bot <86361500+keptn-bot@users.noreply.github.com> Co-authored-by: RealAnna <89971034+RealAnna@users.noreply.github.com> --- docs/content/en/docs/install/k8s.md | 55 +++++++++++++++++++++++++--- docs/content/en/docs/install/reqs.md | 17 ++++++++- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/docs/content/en/docs/install/k8s.md b/docs/content/en/docs/install/k8s.md index 029a38af2a..0b7635ec48 100644 --- a/docs/content/en/docs/install/k8s.md +++ b/docs/content/en/docs/install/k8s.md @@ -13,13 +13,9 @@ that runs your deployment software. See [Requirements](reqs.md) for information about supported releases and advice about resources required. -You can also create a local cluster using packages -such as KinD, Minikube, K3s, and K3d -that can be used for testing, study, and demonstration purposes. - ## Create local Kubernetes cluster -You can use tools such as +You can also create a local cluster using packages such as [KinD](https://kind.sigs.k8s.io/), [k3d](https://k3d.io/), [k3s](https://k3s.io/), @@ -27,6 +23,9 @@ and [Minikube](https://minikube.sigs.k8s.io/docs/) to set up a local, lightweight Kubernetes cluster where you can install the Keptn Lifecycle Toolkit for personal study, demonstrations, and testing. +For more information, see the Kubernetes +[Install Tools](https://kubernetes.io/docs/tasks/tools/) +documentation. The [Keptn Lifecycle Toolkit: Installation and KeptnTask Creation in Minutes](https://www.youtube.com/watch?v=Hh01bBwZ_qM) video demonstrates how to create a KinD cluster. @@ -41,6 +40,10 @@ The basic steps are: kind create cluster ``` + See the + [KinD Quick Start Guide](https://kind.sigs.k8s.io/docs/user/quick-start/) + for more information + 1. When the cluster has been created, run the following to verify that the cluster is working and that it is running a supported version of Kubernetes @@ -49,3 +52,45 @@ The basic steps are: ```shell kubectl version --short ``` + +## Prepare your cluster for KLT + +The Keptn Lifecycle Toolkit installs into an existing Kubernetes cluster. +When setting up a local Kubernetes cluster +to study or demonstrate the Lifecycle Toolkit, +you need to provide these components. + +Your cluster should include the following: + +* A supported version of Kubernetes. + See [Supported Kubernetes versions](reqs.md/#supported-kubernetes-versions) + for details. + +* [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) + +* Metric provider such as + [Prometheus](https://prometheus.io/), + [Dynatrace](https://www.dynatrace.com/), + or [Datadog](https://www.datadoghq.com/). + This is used for the metrics used for the observability features. + +* Deployment tools of your choice, + such as + [Argo CD](https://argo-cd.readthedocs.io/en/stable/) or + [Flux](https://fluxcd.io/). + Alternatively, KLT also works with just `kubctl apply` for deployment. + +* For traces, install [Jaeger](https://jaegertracing.io) + or a similar tool. + +* If you want a dashboard for reviewing metrics and traces, + Install [Grafana](https://grafana.com/) + or the dashboard of your choice. + +Also note that the Keptn Lifecycle Toolkit includes +a light-weight cert-manager that, by default, is installed +as part of the KLT software. +If you are using another cert-manager in the cluster, +you can configure KLT to instead use your cert-manager. +See [Use your own cert-manager](cert-manager.md) +for detailed instructions. diff --git a/docs/content/en/docs/install/reqs.md b/docs/content/en/docs/install/reqs.md index e9ec16d46b..8f735090ba 100644 --- a/docs/content/en/docs/install/reqs.md +++ b/docs/content/en/docs/install/reqs.md @@ -1,8 +1,6 @@ --- title: Requirements description: Supported software versions and information about resources required -icon: concepts -layout: quickstart weight: 15 hidechildren: false # this flag hides all sub-pages in the sidebar-multicard.html --- @@ -11,6 +9,21 @@ hidechildren: false # this flag hides all sub-pages in the sidebar-multicard.htm The Keptn Lifecycle Controller requires Kubernetes v1.24.0 or later. +Run the following to ensure that both client and server versions +are running Kubernetes versions greater than or equal to v1.24. +In this example, both client and server are at v1.24.0 +so the Keptn Lifecycle Toolkit will work. + +```shell +kubectl version --short +``` + +```shell +Client Version: v1.24.0 +Kustomize Version: v4.5.4 +Server Version: v1.24.0 +``` + ## Resource requirements ## cert-manager From 4aa141a069b8b7d25c508ff92309ad460120beb4 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Wed, 10 May 2023 14:21:26 -0700 Subject: [PATCH 05/62] docs: create KeptnApp reference page (#1391) Signed-off-by: Meg McRoberts Signed-off-by: Meg McRoberts Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> Co-authored-by: Florian Bacher --- docs/content/en/docs/concepts/apps/_index.md | 127 ------------------ docs/content/en/docs/yaml-crd-ref/app.md | 134 +++++++++++++++++++ 2 files changed, 134 insertions(+), 127 deletions(-) delete mode 100644 docs/content/en/docs/concepts/apps/_index.md diff --git a/docs/content/en/docs/concepts/apps/_index.md b/docs/content/en/docs/concepts/apps/_index.md deleted file mode 100644 index 6a058b22b8..0000000000 --- a/docs/content/en/docs/concepts/apps/_index.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -title: Apps -description: Learn what Keptn Apps are and how to use them -icon: concepts -layout: quickstart -weight: 10 -hidechildren: true # this flag hides all sub-pages in the sidebar-multicard.html ---- - -An App contains information about all workloads and checks associated with an application. -It will use the following structure for the specification of the pre/post deployment and pre/post evaluations checks -that should be executed at app level: - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha2 -kind: KeptnApp -metadata: - name: podtato-head - namespace: podtato-kubectl -spec: - version: "1.3" - workloads: - - name: podtato-head-left-arm - version: 0.1.0 - - name: podtato-head-left-leg - version: 1.2.3 - postDeploymentTasks: - - post-deployment-hello - preDeploymentEvaluations: - - my-prometheus-definition -``` - -While changes in the workload version will affect only workload checks, a change in the app version will also cause a -new execution of app level checks. - -## Automatic App Discovery - -The Keptn Lifecycle Toolkit also provides the option to automatically discover `KeptnApp`s, based on the -recommended Kubernetes labels `app.kubernetes.io/part-of`, `app.kubernetes.io/name` `app.kubernetes.io/version`. -This allows users to enable the observability features provided by the Lifecycle Toolkit for -their existing applications, without the need for creating any Keptn-related custom resources. - -To enable the automatic discovery of `KeptnApp`s for your existing applications, the following steps will -be required: - -1. Make sure the namespace of your application is enabled to be managed by the Keptn Lifecycle Toolkit, -by adding the annotation `keptn.sh/lifecycle-toolkit: "enabled"` to your namespace. -2. Make sure the following labels and/or annotations are present in the pod template -specs of your Workloads (i.e. `Deployments`/`StatefulSets`/`DaemonSets`/`ReplicaSets`) within your application: - - `app.kubernetes.io/name`: Determines the name of the generated `KeptnWorkload` representing the - Workload. - - `app.kubernetes.io/version`: Determines the version of the `KeptnWorkload` representing the Workload. - - `app.kubernetes.io/part-of`: Determines the name of the generated `KeptnApp` representing your - Application. - All Workloads that share the same value for this label will be consolidated into the same `KeptnApp`. - -As an example, consider the following application, consisting of several deployments, which is going to be -deployed into a KLT-enabled namespace: - -```yaml -apiVersion: v1 -kind: Namespace -metadata: - name: podtato-kubectl - annotations: - keptn.sh/lifecycle-toolkit: "enabled" - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: podtato-head-frontend - namespace: podtato-kubectl -spec: - template: - metadata: - labels: - app.kubernetes.io/name: podtato-head-frontend - app.kubernetes.io/part-of: podtato-head - app.kubernetes.io/version: 0.1.0 - spec: - containers: - - name: podtato-head-frontend - image: podtato-head-frontend ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: podtato-head-hat - namespace: podtato-kubectl -spec: - replicas: 1 - template: - metadata: - labels: - app.kubernetes.io/name: podtato-head-hat - app.kubernetes.io/part-of: podtato-head - app.kubernetes.io/version: 0.1.1 - spec: - containers: - - name: podtato-head-hat - image: podtato-head-hat -``` - -Applying these resources will then result in the creation of the following `KeptnApp`: - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha2 -kind: KeptnApp -metadata: - name: podtato-head - namespace: podtato-kubectl - annotations: - app.kubernetes.io/managed-by: "klt" -spec: - version: "" - workloads: - - name: podtato-head-frontend - version: 0.1.0 - - name: podtato-head-hat - version: 1.1.1 -``` - -Due to the creation of this resource, you will now get observability of your application's deployments due to -the OpenTelemetry tracing features provided by the Keptn Lifecycle Toolkit: - -![Application deployment trace](assets/trace.png) diff --git a/docs/content/en/docs/yaml-crd-ref/app.md b/docs/content/en/docs/yaml-crd-ref/app.md index 38644caec7..e4d0d790d0 100644 --- a/docs/content/en/docs/yaml-crd-ref/app.md +++ b/docs/content/en/docs/yaml-crd-ref/app.md @@ -3,3 +3,137 @@ title: KeptnApp description: Define all workloads and checks associated with an application weight: 10 --- + +`KeptnApp` defines a list of workloads +that together constitute a logical application. +It contains information about all workloads and checks +that are associated with a Keptn application +and a list of tasks and evaluations to be executed +pre- and post-deployment. + +## Synopsis + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnApp +metadata: + name: + namespace: +spec: + version: "x.y" + revision: x + workloads: + - name: + version: x.y.z + - name: + version: x.y.z + preDeploymentTasks: + - + postDeploymentTasks: + - + preDeploymentEvaluations: + - + postDeploymentEvaluations: + - +``` + +## Fields + +* **apiVersion** -- API version being used. +* **kind** -- Resource type. + Must be set to `KeptnApp` + +* **metadata** + * **name** -- Unique name of this application. + Names must comply with the + [Kubernetes Object Names and IDs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names) + specification. + +* **spec** + * **version** -- version of the Keptn application. + Changing this version number causes a new execution + of all application-level checks + * **revision** -- revision of a `version`. + The value is an integer that can be modified + to trigger another deployment of a `KeptnApp` of the same version. + For example, increment this number to restart a `KeptnApp` version + that failed to deploy, perhaps because a + `preDeploymentEvaluation` or `preDeploymentTask` failed. + * **workloads** + * **name** - name of this Kubernetes + [workload](https://kubernetes.io/docs/concepts/workloads/). + Use the same naming rules listed above for the application name. + Provide one entry for each workload + associated with this Keptn application. + * **version** -- version number for this workload. + Changing this number causes a new execution + of checks for the Keptn application and the new version of the workload. + * **preDeploymentTasks** -- list each task to be run + as part of the pre-deployment stage. + Task names must match the value of the `name` field + for the associated [KeptnTaskDefinition](taskdefinition.md) resource. + * **postDeploymentTasks** -- list each task to be run + as part of the post-deployment stage. + Task names must match the value of the `name` field + for the associated [KeptnTaskDefinition](taskdefinition.md) resource. + * **preDeploymentEvaluations** -- list each evaluation to be run + as part of the pre-deployment stage. + Evaluation names must match the value of the `name` field + for the associated [KeptnEvaluationDefinition](evaluationdefinition.md) + resource. + * **postDeploymentEvaluations** -- list each evaluation to be run + as part of the post-deployment stage. + Evaluation names must match the value of the `name` field + for the associated [KeptnEvaluationDefinition](evaluationdefinition.md) + resource. + +## Usage + +Kubernetes defines +[workloads](https://kubernetes.io/docs/concepts/workloads/) +but does not define applications. +The Keptn Lifecycle Toolkit adds the concept of applications +defined as a set of workloads that can be executed. +A `KeptnApp` resource is added +into the repository of the deployment engine +(ArgoCD, Flux, etc) +and is then deployed by that deployment engine. + +You can create a `KeptnApp` resource as a standard YAML manifest +or you can use the +[automatic application discovery](../implementing/integrate/#use-keptn-automatic-app-discovery) +feature to automatically generate a `KeptnApp` resource +based on Keptn or [recommended Kubernetes labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/). +This allows you to use the KLT observability features for existing resources +without manually populating any Keptn related resources. + +## Example + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnApp +metadata: + name: podtato-head + namespace: podtato-kubectl +spec: + version: "1.3" + workloads: + - name: podtato-head-left-arm + version: 0.1.0 + - name: podtato-head-left-leg + version: 1.2.3 + postDeploymentTasks: + - post-deployment-hello + preDeploymentEvaluations: + - my-prometheus-definition +``` + +## Files + +## Differences between versions + +The `spec.Revision` field is introduced in v1alpha2. + +## See also + +[Use Keptn automatic app discovery](../implementing/integrate/#use-keptn-automatic-app-discovery) From 8fedf0f11c6f4e55e1ac47ab8e80705e189ffff8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 08:15:07 +0200 Subject: [PATCH 06/62] deps: update golang docker tag to v1.20.4 (#1346) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- klt-cert-manager/Dockerfile | 2 +- metrics-operator/Dockerfile | 2 +- operator/Dockerfile | 2 +- scheduler/Dockerfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/klt-cert-manager/Dockerfile b/klt-cert-manager/Dockerfile index 40bff7329e..734066bf2d 100644 --- a/klt-cert-manager/Dockerfile +++ b/klt-cert-manager/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM --platform=$BUILDPLATFORM golang:1.20.3-alpine3.16 AS builder +FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine3.16 AS builder ENV CGO_ENABLED=0 diff --git a/metrics-operator/Dockerfile b/metrics-operator/Dockerfile index 1ff6ef5afc..ddbd649207 100644 --- a/metrics-operator/Dockerfile +++ b/metrics-operator/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.20.3-alpine3.16 AS builder +FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine3.16 AS builder ENV CGO_ENABLED=0 diff --git a/operator/Dockerfile b/operator/Dockerfile index ec0e3c4256..e46ca0527b 100644 --- a/operator/Dockerfile +++ b/operator/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.20.3-alpine3.16 AS builder +FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine3.16 AS builder ENV CGO_ENABLED=0 diff --git a/scheduler/Dockerfile b/scheduler/Dockerfile index 7a33ae4552..2d49156692 100644 --- a/scheduler/Dockerfile +++ b/scheduler/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.20.3-alpine3.16 AS builder +FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine3.16 AS builder ENV CGO_ENABLED=0 From 6524d583dc9d99bd67b9a599f48f78b6d89a3877 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 08:45:15 +0200 Subject: [PATCH 07/62] deps: update module k8s.io/klog/v2 to v2.100.1 (#1324) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: odubajDT --- metrics-operator/go.mod | 2 +- metrics-operator/go.sum | 4 ++-- scheduler/go.mod | 2 +- scheduler/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index a73ccd7c7b..028d04d392 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -20,7 +20,7 @@ require ( k8s.io/apiserver v0.26.4 k8s.io/client-go v0.26.4 k8s.io/component-base v0.26.4 - k8s.io/klog/v2 v2.90.1 + k8s.io/klog/v2 v2.100.1 k8s.io/metrics v0.26.4 sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/custom-metrics-apiserver v1.25.1-0.20230116101851-63817c8ac8f2 diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index 4040d76223..f3a10423f7 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -775,8 +775,8 @@ k8s.io/client-go v0.26.4 h1:/7P/IbGBuT73A+G97trf44NTPSNqvuBREpOfdLbHvD4= k8s.io/client-go v0.26.4/go.mod h1:6qOItWm3EwxJdl/8p5t7FWtWUOwyMdA8N9ekbW4idpI= k8s.io/component-base v0.26.4 h1:Bg2xzyXNKL3eAuiTEu3XE198d6z22ENgFgGQv2GGOUk= k8s.io/component-base v0.26.4/go.mod h1:lTuWL1Xz/a4e80gmIC3YZG2JCO4xNwtKWHJWeJmsq20= -k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kms v0.26.4 h1:mQ+DeOvgAHC6+heZcozPkEd3rWtP4DVVjo1hLSih9w4= k8s.io/kms v0.26.4/go.mod h1:69qGnf1NsFOQP07fBYqNLZklqEHSJF024JqYCaeVxHg= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= diff --git a/scheduler/go.mod b/scheduler/go.mod index 4626f58884..3130b9ed40 100644 --- a/scheduler/go.mod +++ b/scheduler/go.mod @@ -19,7 +19,7 @@ require ( k8s.io/apiserver v0.25.9 k8s.io/client-go v0.25.9 k8s.io/component-base v0.25.9 - k8s.io/klog/v2 v2.90.1 + k8s.io/klog/v2 v2.100.1 k8s.io/kubernetes v1.25.9 sigs.k8s.io/controller-runtime v0.13.1 ) diff --git a/scheduler/go.sum b/scheduler/go.sum index 96cfb687fb..6ff2639d97 100644 --- a/scheduler/go.sum +++ b/scheduler/go.sum @@ -724,8 +724,8 @@ k8s.io/component-helpers v0.25.9/go.mod h1:o9yuVdUiyKe0ubYP78veYtYxThJx66PsGzpRa k8s.io/csi-translation-lib v0.25.9 h1:82oc8yYCapshWTMQVmecBEWSdbhrmpuEOBQUSlh1x5I= k8s.io/csi-translation-lib v0.25.9/go.mod h1:oWiY3igOmast1WmTreVezMXyOVRMoB1Eaa5j8CwtwEI= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/kube-scheduler v0.25.9 h1:1niibmG0cU/yGG8O+ORRUhbisQ6k7d+g7O6Fo1CZSNs= From 8b730461a9892f5ab06e51ee9519ec6fa7d83125 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 08:46:54 +0200 Subject: [PATCH 08/62] deps: update module github.com/prometheus/client_golang to v1.15.1 (#1386) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- metrics-operator/go.mod | 2 +- metrics-operator/go.sum | 4 ++-- operator/go.mod | 2 +- operator/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index 028d04d392..70161a0fb2 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -10,7 +10,7 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/open-feature/go-sdk v1.3.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.15.0 + github.com/prometheus/client_golang v1.15.1 github.com/prometheus/common v0.42.0 github.com/spf13/afero v1.9.5 github.com/stretchr/testify v1.8.2 diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index f3a10423f7..c501681c0e 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -311,8 +311,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= -github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/operator/go.mod b/operator/go.mod index 84da008aeb..f9e30cd0a2 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -13,7 +13,7 @@ require ( github.com/onsi/ginkgo/v2 v2.9.2 github.com/onsi/gomega v1.27.6 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.15.0 + github.com/prometheus/client_golang v1.15.1 github.com/spf13/afero v1.9.5 github.com/stretchr/testify v1.8.2 go.opentelemetry.io/otel v1.11.2 diff --git a/operator/go.sum b/operator/go.sum index 1511b2ec8b..0286c153a9 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -242,8 +242,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= -github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= From 2ed8dd7a7d62a44bab22cc1da11f80e02fd129f8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 08:47:04 +0200 Subject: [PATCH 09/62] deps: update module github.com/onsi/ginkgo/v2 to v2.9.4 (#1384) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: odubajDT --- operator/go.mod | 6 +++--- operator/go.sum | 8 ++++---- scheduler/go.mod | 8 ++++---- scheduler/go.sum | 12 ++++++------ 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/operator/go.mod b/operator/go.mod index f9e30cd0a2..834f082fa8 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -10,7 +10,7 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4 github.com/magiconair/properties v1.8.7 - github.com/onsi/ginkgo/v2 v2.9.2 + github.com/onsi/ginkgo/v2 v2.9.4 github.com/onsi/gomega v1.27.6 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 @@ -36,7 +36,7 @@ require ( require ( github.com/prometheus/common v0.42.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/net v0.8.0 // indirect + golang.org/x/net v0.9.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sys v0.7.0 // indirect golang.org/x/term v0.7.0 // indirect @@ -87,7 +87,7 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.8.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect diff --git a/operator/go.sum b/operator/go.sum index 0286c153a9..926148111b 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -232,8 +232,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= -github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= +github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -498,8 +498,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/scheduler/go.mod b/scheduler/go.mod index 3130b9ed40..83fd4b071c 100644 --- a/scheduler/go.mod +++ b/scheduler/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/kelseyhightower/envconfig v1.4.0 - github.com/onsi/ginkgo/v2 v2.9.2 + github.com/onsi/ginkgo/v2 v2.9.4 github.com/onsi/gomega v1.27.6 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.2 @@ -41,7 +41,7 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/zapr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect @@ -91,14 +91,14 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.21.0 // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect - golang.org/x/net v0.8.0 // indirect + golang.org/x/net v0.9.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.7.0 // indirect golang.org/x/term v0.7.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.8.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect google.golang.org/protobuf v1.28.1 // indirect diff --git a/scheduler/go.sum b/scheduler/go.sum index 6ff2639d97..ba061d91c4 100644 --- a/scheduler/go.sum +++ b/scheduler/go.sum @@ -117,8 +117,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -270,8 +270,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= -github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= +github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -573,8 +573,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 0a6b7e795a9e58425ab6baacf38a82a22dbbc0c8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 08:54:15 +0200 Subject: [PATCH 10/62] deps: update dependency kubernetes-sigs/controller-tools to v0.12.0 (#1383) Signed-off-by: Renovate Bot Signed-off-by: odubajDT Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: odubajDT --- .github/workflows/CI.yaml | 2 +- .github/workflows/helm-checks.yaml | 2 +- .github/workflows/release.yml | 2 +- klt-cert-manager/Dockerfile | 2 +- klt-cert-manager/Makefile | 2 +- metrics-operator/Dockerfile | 2 +- metrics-operator/Makefile | 2 +- .../config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml | 2 +- .../crd/bases/metrics.keptn.sh_keptnmetricsproviders.yaml | 2 +- operator/Dockerfile | 2 +- operator/Makefile | 2 +- .../crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml | 2 +- operator/config/crd/bases/lifecycle.keptn.sh_keptnapps.yaml | 2 +- .../config/crd/bases/lifecycle.keptn.sh_keptnappversions.yaml | 2 +- .../bases/lifecycle.keptn.sh_keptnevaluationdefinitions.yaml | 2 +- .../crd/bases/lifecycle.keptn.sh_keptnevaluationproviders.yaml | 2 +- .../config/crd/bases/lifecycle.keptn.sh_keptnevaluations.yaml | 2 +- .../crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml | 2 +- operator/config/crd/bases/lifecycle.keptn.sh_keptntasks.yaml | 2 +- .../crd/bases/lifecycle.keptn.sh_keptnworkloadinstances.yaml | 2 +- .../config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml | 2 +- operator/config/crd/bases/options.keptn.sh_keptnconfigs.yaml | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 181a5d69e3..78ebebd3f4 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -17,7 +17,7 @@ on: env: GO_VERSION: "~1.20" # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools - CONTROLLER_TOOLS_VERSION: "v0.11.4" + CONTROLLER_TOOLS_VERSION: "v0.12.0" ENVTEST_K8S_VERSION: "1.24.2" SCHEDULER_COMPATIBLE_K8S_VERSION: "v0.24.3" defaults: diff --git a/.github/workflows/helm-checks.yaml b/.github/workflows/helm-checks.yaml index ce11ee01dc..91085570e5 100644 --- a/.github/workflows/helm-checks.yaml +++ b/.github/workflows/helm-checks.yaml @@ -7,7 +7,7 @@ on: env: GO_VERSION: "~1.20" # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools - CONTROLLER_TOOLS_VERSION: "v0.11.4" + CONTROLLER_TOOLS_VERSION: "v0.12.0" ENVTEST_K8S_VERSION: "1.24.2" SCHEDULER_COMPATIBLE_K8S_VERSION: "v0.24.3" defaults: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 938892936f..aafd030298 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ defaults: env: GO_VERSION: "~1.20" # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools - CONTROLLER_TOOLS_VERSION: "v0.11.4" + CONTROLLER_TOOLS_VERSION: "v0.12.0" SCHEDULER_COMPATIBLE_K8S_VERSION: "v0.24.3" jobs: diff --git a/klt-cert-manager/Dockerfile b/klt-cert-manager/Dockerfile index 734066bf2d..efb8c7a815 100644 --- a/klt-cert-manager/Dockerfile +++ b/klt-cert-manager/Dockerfile @@ -13,7 +13,7 @@ RUN go mod download COPY ./ ./ # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools -ARG CONTROLLER_TOOLS_VERSION=v0.11.4 +ARG CONTROLLER_TOOLS_VERSION=v0.12.0 RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@$CONTROLLER_TOOLS_VERSION ARG GIT_HASH diff --git a/klt-cert-manager/Makefile b/klt-cert-manager/Makefile index 0531d87b33..8284d7fc54 100644 --- a/klt-cert-manager/Makefile +++ b/klt-cert-manager/Makefile @@ -120,7 +120,7 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest # renovate: datasource=github-tags depName=kubernetes-sigs/kustomize KUSTOMIZE_VERSION?=v5.0.1 # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools -CONTROLLER_TOOLS_VERSION?=v0.11.4 +CONTROLLER_TOOLS_VERSION?=v0.12.0 KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" .PHONY: kustomize diff --git a/metrics-operator/Dockerfile b/metrics-operator/Dockerfile index ddbd649207..c552f5d3c0 100644 --- a/metrics-operator/Dockerfile +++ b/metrics-operator/Dockerfile @@ -8,7 +8,7 @@ COPY go.mod go.sum ./ RUN go mod download # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools -ARG CONTROLLER_TOOLS_VERSION=v0.11.4 +ARG CONTROLLER_TOOLS_VERSION=v0.12.0 RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@$CONTROLLER_TOOLS_VERSION # Copy the go source diff --git a/metrics-operator/Makefile b/metrics-operator/Makefile index 5852ff5548..69ea38aef8 100644 --- a/metrics-operator/Makefile +++ b/metrics-operator/Makefile @@ -27,7 +27,7 @@ ENVTEST_K8S_VERSION=1.24.2 # renovate: datasource=github-tags depName=kubernetes-sigs/kustomize KUSTOMIZE_VERSION?=v5.0.1 # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools -CONTROLLER_TOOLS_VERSION?=v0.11.4 +CONTROLLER_TOOLS_VERSION?=v0.12.0 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) diff --git a/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml b/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml index 6ef7eb50d2..f3eb4d8471 100644 --- a/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml +++ b/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnmetrics.metrics.keptn.sh spec: group: metrics.keptn.sh diff --git a/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetricsproviders.yaml b/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetricsproviders.yaml index 7a807c4c90..96f75f4c96 100644 --- a/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetricsproviders.yaml +++ b/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetricsproviders.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnmetricsproviders.metrics.keptn.sh spec: group: metrics.keptn.sh diff --git a/operator/Dockerfile b/operator/Dockerfile index e46ca0527b..c597ac7c67 100644 --- a/operator/Dockerfile +++ b/operator/Dockerfile @@ -8,7 +8,7 @@ COPY go.mod go.sum ./ RUN go mod download # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools -ARG CONTROLLER_TOOLS_VERSION=v0.11.4 +ARG CONTROLLER_TOOLS_VERSION=v0.12.0 RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@$CONTROLLER_TOOLS_VERSION # Copy the go source diff --git a/operator/Makefile b/operator/Makefile index a80f58ffde..f13960097a 100644 --- a/operator/Makefile +++ b/operator/Makefile @@ -27,7 +27,7 @@ ENVTEST_K8S_VERSION=1.24.2 # renovate: datasource=github-tags depName=kubernetes-sigs/kustomize KUSTOMIZE_VERSION?=v5.0.1 # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools -CONTROLLER_TOOLS_VERSION?=v0.11.4 +CONTROLLER_TOOLS_VERSION?=v0.12.0 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml index bf61e26866..224bff0075 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnappcreationrequests.lifecycle.keptn.sh spec: group: lifecycle.keptn.sh diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnapps.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnapps.yaml index fef928d54d..51868e6817 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnapps.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnapps.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnapps.lifecycle.keptn.sh spec: group: lifecycle.keptn.sh diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnappversions.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnappversions.yaml index 8794425a74..f032b831c6 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnappversions.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnappversions.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnappversions.lifecycle.keptn.sh spec: group: lifecycle.keptn.sh diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationdefinitions.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationdefinitions.yaml index f053546f39..d66fcc87de 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationdefinitions.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationdefinitions.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnevaluationdefinitions.lifecycle.keptn.sh spec: group: lifecycle.keptn.sh diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationproviders.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationproviders.yaml index bde9971adc..973214e834 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationproviders.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluationproviders.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnevaluationproviders.lifecycle.keptn.sh spec: group: lifecycle.keptn.sh diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluations.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluations.yaml index 2f01a450c3..c72dcd6c2e 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluations.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnevaluations.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnevaluations.lifecycle.keptn.sh spec: group: lifecycle.keptn.sh diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml index 5af542e0ae..f6be0e8b34 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptntaskdefinitions.lifecycle.keptn.sh spec: group: lifecycle.keptn.sh diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptntasks.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptntasks.yaml index 58a8cb6262..713ce884b1 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptntasks.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptntasks.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptntasks.lifecycle.keptn.sh spec: group: lifecycle.keptn.sh diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadinstances.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadinstances.yaml index f53bf6eec3..5bdc6b25d9 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadinstances.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadinstances.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnworkloadinstances.lifecycle.keptn.sh spec: group: lifecycle.keptn.sh diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml index 861b930d60..d490b4da9b 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnworkloads.lifecycle.keptn.sh spec: group: lifecycle.keptn.sh diff --git a/operator/config/crd/bases/options.keptn.sh_keptnconfigs.yaml b/operator/config/crd/bases/options.keptn.sh_keptnconfigs.yaml index f6c4bfe925..e2f8357919 100644 --- a/operator/config/crd/bases/options.keptn.sh_keptnconfigs.yaml +++ b/operator/config/crd/bases/options.keptn.sh_keptnconfigs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.12.0 name: keptnconfigs.options.keptn.sh spec: group: options.keptn.sh From ac98fe566f2652eebdd6e578a6f3491df9e471d1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 09:09:54 +0200 Subject: [PATCH 11/62] deps: update sigstore/cosign-installer action to v3.0.3 (#1308) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aafd030298..2643eaee17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,7 +79,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Cosign - uses: sigstore/cosign-installer@v3.0.2 + uses: sigstore/cosign-installer@v3.0.3 - name: Build Docker Image id: docker_build_image From 025709e3147abef79d2ddbecb795db0c5e8bf2a8 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Thu, 11 May 2023 03:56:09 -0700 Subject: [PATCH 12/62] docs: enhance install page (#1399) Signed-off-by: Meg McRoberts Signed-off-by: Meg McRoberts Co-authored-by: Giovanni Liva Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> --- docs/content/en/docs/install/install.md | 61 ++++++++++++++++++++----- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/docs/content/en/docs/install/install.md b/docs/content/en/docs/install/install.md index 66499f7d67..5b762e75a3 100644 --- a/docs/content/en/docs/install/install.md +++ b/docs/content/en/docs/install/install.md @@ -1,13 +1,11 @@ --- -title: Install KLT +title: Install and enable KLT description: Install the Keptn Lifecycle Toolkit -icon: concepts -layout: quickstart weight: 35 hidechildren: false # this flag hides all sub-pages in the sidebar-multicard.html --- -Two methods are supported for installing the Keptn Lifecycle Toolkit: +Two methods are supported for installing the Keptn Lifecycle Toolkit (KLT): * Releases v0.7.0 and later can be installed using the [Helm Chart](#use-helm-chart). @@ -17,6 +15,13 @@ Two methods are supported for installing the Keptn Lifecycle Toolkit: the [manifests](#use-manifests). This is the less-preferred way because it does not support customization. +After KLT is installed, you must +[Enable KLT for your cluster](#enable-klt-for-your-cluster) +in order to run some KLT functionality. + +You are then ready to +[Integrate KLT with your applications](../implementing/integrate). + ## Use Helm Chart Version v0.7.0 and later of the Lifecycle Toolkit @@ -33,15 +38,28 @@ helm upgrade --install keptn klt/klt \ Note that the `helm repo update` command is used for fresh installs as well as for upgrades. -Use the `--version ` flag on the -`helm upgrade --install` command line to specify a different KLT version. +Some helpful hints: -Use the following command sequence to see a list of available versions: +* Use the `--version ` flag on the + `helm upgrade --install` command line to specify a different KLT version. -```shell -helm repo update -helm search repo klt -``` +* Use the following command sequence to see a list of available versions: + + ```shell + helm repo update + helm search repo klt + ``` + +* To verify that the KLT components are installed in your cluster, + run the following command: + + ```shell + kubectl get pods -n keptn-lifecycle-toolkit-system + ``` + + The output shows all components that are running on your system. + +### Modify Helm configuration options To modify configuration options, download a copy of the [helm/chart/values.yaml](https://github.com/keptn/lifecycle-toolkit/blob/main/helm/chart/values.yaml) @@ -107,3 +125,24 @@ kubectl wait --for=condition=Available deployment/lifecycle-operator \ ``` The Lifecycle Toolkit and its dependencies are now installed and ready to use. + +## Enable KLT for your cluster + +To enable KLT for your cluster, annotate the Kubernetes +[Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) +resource. +In this example, this is defined in the +[simplenode-dev-ns.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-ns.yaml) +file, which looks like this: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: simplenode-dev + annotations: + keptn.sh/lifecycle-toolkit: "enabled" +``` + +You see the annotation line `keptn.sh/lifecycle-toolkit: "enabled"`. +This line tells KLT to handle the namespace From 7712bfae84a21adaf6341ca02ec3589d0459854f Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Thu, 11 May 2023 07:10:07 -0700 Subject: [PATCH 13/62] docs: metrics & evaluation ref and guides (#1385) Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> Co-authored-by: Simon Schrottner Co-authored-by: Giovanni Liva --- .../en/docs/concepts/evaluations/_index.md | 41 ----- .../en/docs/implementing/evaluatemetrics.md | 144 +++++++++++++++++- .../en/docs/implementing/evaluations.md | 2 +- docs/content/en/docs/implementing/observe.md | 5 - docs/content/en/docs/implementing/otel.md | 29 +++- docs/content/en/docs/implementing/slack.md | 2 +- .../docs/yaml-crd-ref/evaluationdefinition.md | 123 ++++++++++++++- docs/content/en/docs/yaml-crd-ref/metric.md | 116 ++++++++++++++ .../en/docs/yaml-crd-ref/metricsprovider.md | 127 ++++++++++++++- 9 files changed, 535 insertions(+), 54 deletions(-) delete mode 100644 docs/content/en/docs/concepts/evaluations/_index.md delete mode 100644 docs/content/en/docs/implementing/observe.md diff --git a/docs/content/en/docs/concepts/evaluations/_index.md b/docs/content/en/docs/concepts/evaluations/_index.md deleted file mode 100644 index 7bc275fe64..0000000000 --- a/docs/content/en/docs/concepts/evaluations/_index.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Evaluations -description: Learn what Keptn Evaluations are and how to use them -icon: concepts -layout: quickstart -weight: 10 -hidechildren: true # this flag hides all sub-pages in the sidebar-multicard.html ---- - - -### Keptn Evaluation Definition - -A `KeptnEvaluationDefinition` is a CRD used to define evaluation tasks that can be run by the Keptn Lifecycle Toolkit -as part of pre- and post-analysis phases of a workload or application. -`KeptnEvaluationDefinition` resource can be created in the namespace where the application is running, or -in the default KLT namespace, which will be the fallback option for the system to search. - -A Keptn evaluation definition looks like the following: - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha3 -kind: KeptnEvaluationDefinition -metadata: - name: my-prometheus-evaluation - namespace: example -spec: - source: prometheus - objectives: - - keptnMetricRef: - name: available-cpus - namespace: example - evaluationTarget: ">1" - - keptnMetricRef: - name: cpus-throttling - namespace: example - evaluationTarget: "<0.01" -``` - -A `KeptnEvaluationDefinition` references one or more [`KeptnMetric`s](../metrics/). -If multiple `KeptnMetric`s are used, the Keptn Lifecycle Toolkit will consider the -evaluation successful if **all** metrics are respecting their `evaluationTarget`. diff --git a/docs/content/en/docs/implementing/evaluatemetrics.md b/docs/content/en/docs/implementing/evaluatemetrics.md index 0c70c43172..88dfb807d1 100644 --- a/docs/content/en/docs/implementing/evaluatemetrics.md +++ b/docs/content/en/docs/implementing/evaluatemetrics.md @@ -1,5 +1,145 @@ --- -title: Evaluate metrics -description: Define all workloads and checks associated with an application +title: Keptn Metrics +description: Implement Keptn metrics weight: 130 --- + +The Keptn Metrics Operator provides a single entry point +to all metrics in the cluster +and allows you to define metrics based on multiple data platforms +and multiple instances of any data platform. +Metrics are fetched independently +and can be used for an evaluation at workload- and application-level, or for scaling your workloads. + +This data can be displayed on Grafana +or another standard dashboard application that you configure +or can be retrieved using standard Kubernetes commands. + +For an introduction to Keptn metrics, see +[Getting started with Keptn metrics](../getting-started/metrics). + +## Keptn metric basics + +Keptn metrics are implemented with two resources: + +* [KeptnMetric](../yaml-crd-ref/metric.md) -- + define the metric to report +* [KeptnMetricsProvider](../yaml-crd-ref/metricsprovider.md) -- + define the configuration for a data provider + +## Accessing Metrics via the Kubernetes Custom Metrics API + +`KeptnMetrics` can also be retrieved via the Kubernetes Custom Metrics API. + +### Retrieve KeptnMetric values with kubectl + +Use the `kubectl get --raw` command +to retrieve the values of a `KeptnMetric`, as in the following example: + +```shell +$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta2/namespaces/podtato-kubectl/keptnmetrics.metrics.sh/keptnmetric-sample/keptnmetric-sample" | jq . + +{ + "kind": "MetricValueList", + "apiVersion": "custom.metrics.k8s.io/v1beta2", + "metadata": {}, + "items": [ + { + "describedObject": { + "kind": "KeptnMetric", + "namespace": "podtato-kubectl", + "name": "keptnmetric-sample", + "apiVersion": "metrics.keptn.sh/v1alpha1" + }, + "metric": { + "name": "keptnmetric-sample", + "selector": { + "matchLabels": { + "app": "frontend" + } + } + }, + "timestamp": "2023-01-25T09:26:15Z", + "value": "10" + } + ] +} +``` + +### Filter on matching labels + +You can filter based on matching labels. +For example, to retrieve all metrics +that are labelled with `app=frontend`, +use the following command: + +```shell +$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta2/namespaces/podtato-kubectl/keptnmetrics.metrics.sh/*/*?labelSelector=app%3Dfrontend" | jq . + +{ + "kind": "MetricValueList", + "apiVersion": "custom.metrics.k8s.io/v1beta2", + "metadata": {}, + "items": [ + { + "describedObject": { + "kind": "KeptnMetric", + "namespace": "keptn-lifecycle-toolkit-system", + "name": "keptnmetric-sample", + "apiVersion": "metrics.keptn.sh/v1alpha3" + }, + "metric": { + "name": "keptnmetric-sample", + "selector": { + "matchLabels": { + "app": "frontend" + } + } + }, + "timestamp": "2023-01-25T09:26:15Z", + "value": "10" + } + ] +} +``` + +## Using the HorizontalPodAutoscaler + +Use the Kubernetes Custom Metrics API +to refer to `KeptnMetric` via the +[Kubernetes HorizontalPodAutoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) +(HPA), +as in the following example: + +```yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: podtato-head-entry + namespace: podtato-kubectl +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: podtato-head-entry + minReplicas: 1 + maxReplicas: 10 + metrics: + - type: Object + object: + metric: + name: keptnmetric-sample + describedObject: + apiVersion: metrics.keptn.sh/v1alpha1 + kind: KeptnMetric + name: keptnmetric-sample + target: + type: Value + value: "10" +``` + +See the +[Scaling Kubernetes Workloads based on Dynatrace Metrics](https://www.linkedin.com/pulse/scaling-kubernetes-workloads-based-dynatrace-metrics-keptnproject/) +blog post +for a detailed discussion of doing this with Dynatrace metrics. +The same approach could be used to implement HPA with other data providers. diff --git a/docs/content/en/docs/implementing/evaluations.md b/docs/content/en/docs/implementing/evaluations.md index 982f80b3b2..f35f2005c3 100644 --- a/docs/content/en/docs/implementing/evaluations.md +++ b/docs/content/en/docs/implementing/evaluations.md @@ -1,5 +1,5 @@ --- title: Evaluations description: Understand Keptn evaluations and how to use them -weight: 110 +weight: 150 --- diff --git a/docs/content/en/docs/implementing/observe.md b/docs/content/en/docs/implementing/observe.md deleted file mode 100644 index b230f42349..0000000000 --- a/docs/content/en/docs/implementing/observe.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Implement Observability -description: Learn how to implement observability in your application -weight: 80 ---- diff --git a/docs/content/en/docs/implementing/otel.md b/docs/content/en/docs/implementing/otel.md index 73c883120a..65d62e2d17 100644 --- a/docs/content/en/docs/implementing/otel.md +++ b/docs/content/en/docs/implementing/otel.md @@ -1,5 +1,30 @@ --- -title: Integrating OpenTelemetry -description: How to integrate OpenTelemetry into your application +title: OpenTelemetry observability +description: How to standardize access to OpenTelemetry observability data weight: 140 --- +## Using OpenTelemetry with Keptn metrics + +Keptn metrics can be exposed as OpenTelemetry (OTel) metrics +via port `9999` of the KLT metrics-operator. + +To expose OTel metrics: + +* Be sure that the `EXPOSE_KEPTN_METRICS` environment variable + in the `metrics-operator` manifest is set to `true`, + which is the default value. +* Define a [KeptnConfig](../yaml-crd-ref/config.md) CRD + that has the `spec.OTelCollectorUrl` field populated + with the URL of the OpenTelemetry collector. + +To access the metrics, use the following command: + +```shell +kubectl port-forward deployment/metrics-operator 9999 -n keptn-lifecycle-toolkit-system +``` + +You can access the metrics from your browser at: `http://localhost:9999` + +For an introduction to using OpenTelemetry with Keptn metrics, see the +[Standardize access to observability data](../getting-started/observability) +getting started guide. diff --git a/docs/content/en/docs/implementing/slack.md b/docs/content/en/docs/implementing/slack.md index c5e913dadd..c1635b7791 100644 --- a/docs/content/en/docs/implementing/slack.md +++ b/docs/content/en/docs/implementing/slack.md @@ -1,5 +1,5 @@ --- title: Implement Slack notifications description: Learn how to implement Slack notification as a post-deployment task -weight: 80 +weight: 325 --- diff --git a/docs/content/en/docs/yaml-crd-ref/evaluationdefinition.md b/docs/content/en/docs/yaml-crd-ref/evaluationdefinition.md index e054a6e08a..b16a1e072b 100644 --- a/docs/content/en/docs/yaml-crd-ref/evaluationdefinition.md +++ b/docs/content/en/docs/yaml-crd-ref/evaluationdefinition.md @@ -1,5 +1,126 @@ --- title: KeptnEvaluationDefinition -description: Define all workloads and checks associated with an application +description: Define an evaluation query + weight: 20 --- + +A `KeptnEvaluationDefinition` assigns target values +to [KeptnMetric](metric.md) queries. +These are used as part of evaluation tasks +that can be run by the Keptn Lifecycle Toolkit +as part of pre- and post-analysis phases of a workload or application. + +## Yaml Synopsis + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnEvaluationDefinition +metadata: + name: +spec: + objectives: + - evaluationTarget: "" + keptnMetricRef: + name: available-cpus + namespace: some-namespace +``` + +## Fields + +* **apiVersion** -- API version being used. + Must be `v1alpha3` or later for this syntax. +* **kind** -- Resource type. + Must be set to `KeptnEvaluationDefinition` + +* **metadata** + * **name** -- Unique name of this evaluation + such as `pre-deploy-eval` or `post-deploy-resource-eval`. + Names must comply with the + [Kubernetes Object Names and IDs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names) + specification. + +* **spec** + + * **objectives** -- define the evaluations to be performed. + Each objective is expressed as a `keptnMetricRef` + and an `evaluationTarget` value. + + * **KeptnMericRef** -- A reference to the + [KeptnMetric](metric.md) object that contains the value, + identified by `name` and `namespace` + * **evaluationTarget** -- Desired value of the query, + expressed as an arithmatic formula, + usually less than (`<`) or greater than (`>`) + This is used to define success or failure criteria + for the referenced `KeptnMetric` in order to pass or fail + the pre- and post-evaluation stages + +## Usage + +A `KeptnEvaluationDefinition` references one or more +[KeptnMetric](metric.md) CRDs. +When multiple `KeptnMetric`s are used, +the Keptn Lifecycle Toolkit considers the evaluation successful +if **all** metrics meet their `evaluationTarget`. + +## Example + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnEvaluationDefinition +metadata: + name: my-prometheus-evaluation + namespace: example +spec: + source: prometheus + objectives: + - keptnMetricRef: + name: available-cpus + namespace: example + evaluationTarget: ">1" + - keptnMetricRef: + name: cpus-throttling + namespace: example + evaluationTarget: "<0.01" +``` + +## Files + +API Reference: + +## Differences between versions + +In the `v1alpha1` and `v1alpha2` API versions, +`KeptnEvaluationDefinition` referenced the `KeptnEvaluationProvider` CR +to identify the data source associated with this definition +and itself contained the queries +that are now taken from the specified [KeptnMetric](metric.md) CRD. +The synopsis was: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha2 +kind: KeptnEvaluationDefinition +metadata: + name: +spec: + source: prometheus | dynatrace | datadog + objectives: + - name: query-1 + query: "xxxx" + evaluationTarget: <20 + - name: query-2 + query: "yyyy" + evaluationTarget: >4 +``` + +Beginning with `v1alpha3` API version, +`KeptnEvaluationDefinition` references a `keptnMetricRef` +that points to a [KeptnMetric](metric.md) CR, +that defines the data source, the query and the namespace to use. +The `KeptnEvaluationDefinition` merely specifies the evaluation target. + +## See also + +* [KeptnMetricsProvider](metricsprovider.md) +* [KeptnMetric](metric.md) diff --git a/docs/content/en/docs/yaml-crd-ref/metric.md b/docs/content/en/docs/yaml-crd-ref/metric.md index 7d48b8222f..0dc11c75a1 100644 --- a/docs/content/en/docs/yaml-crd-ref/metric.md +++ b/docs/content/en/docs/yaml-crd-ref/metric.md @@ -3,3 +3,119 @@ title: KeptnMetric description: Define all workloads and checks associated with an application weight: 50 --- + +A `KeptnMetric` represents a metric that is collected from a provider. +Providing the metrics as a CR in a Kubernetes cluster +facilitates the reusability of this data across multiple components +and allows using multiple observability platforms +for different metrics at the same time. + +`KeptnMetric` CRs are also used as targets for +[EvaluationDefinition](evaluationdefinition.md) CRs. + +## Yaml Synopsis + +```yaml +apiVersion: metrics.keptn.sh/v1alpha3 +kind: KeptnMetric +metadata: + name: + namespace: +spec: + provider: + name: "" + query: "" + fetchIntervalSeconds: <#-seconds> +``` + +## Fields + +* **apiVersion** -- API version being used. +` +* **kind** -- Resource type. + Must be set to `KeptnMetric`. + +* **metadata** + * **name** -- Unique name of this metric. + Names must comply with the + [Kubernetes Object Names and IDs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names) + specification. + * **namespace** -- Namespace of the application using this metric. + +* **spec** + * **provider.name** -- + Name of this instance of the data source + from which the metric is collected. + This value must match the value of the `metadata.name` field + of the corresponding [KeptnMetricsProvider](metricsprovider.md) CRD. + + Assigning your own name to the provider + rather than just the type of provider + enables you to support multiple instances of a data provider. + For example, you might have `dev-prometheus` + as the name of the Prometheus server that monitors the dev deployment + and `prod-prometheus` as the name of the Prometheus server + that monitors the production deployment. + * **query** -- String in the provider-specific query language, + used to obtain a metric. + * **fetchIntervalSeconds** -- Number of seconds between updates of the metric. + +## Usage + +## Example + +This example pulls metrics from the data provider +defined as `my-provider` in the `spec.provider.name` field +of the corresponding `KeptnMetricsProvider` CR. + +```yaml +apiVersion: metrics.keptn.sh/v1alpha3 +kind: KeptnMetric +metadata: + name: keptnmetric-sample + namespace: podtato-kubectl +spec: + provider: + name: "my-provider" + query: "sum(kube_pod_container_resource_limits{resource='cpu'})" + fetchIntervalSeconds: 5 +``` + +## Files + +API Reference: + +## Differences between versions + +Beginning with the `v1alpha3` API version, +Keptn allows you to define multiple instances of the same data source. +In earlier versions, you could use multiple data sources +but only one instance of each. +Consequently the `v1alpha1` and `v1alpha2` API versions +define the `provider` field with the type of the data provider +(`prometheus`, `dynatrace`, or `dql`) +rather than the particular name assigned +to the instance of the data provider +that is assigned in the +[KeptnMetricsProvider](metricsprovider.md) CR. + +So the `v1alpha1` and `v1alpha2` synopsis +of the `spec` field is: + +```yaml +... +spec: + provider: + name: "prometheus | dynatrace | dql" + fetchIntervalSeconds: + query: >- + "" +``` + +## See also + +* [KeptnEvaluationDefinition](evaluationdefinition.md) +* [KeptnMetricsProvider](metricsprovider.md) +* Implementing [Keptn Metrics](../implementing/evaluatemetrics.md) +* [Getting started with Keptn metrics](../getting-started/metrics) +* Architecture of the [Keptn Metrics Operator](../concepts/architecture/components/metrics-operator/_index.md) diff --git a/docs/content/en/docs/yaml-crd-ref/metricsprovider.md b/docs/content/en/docs/yaml-crd-ref/metricsprovider.md index 0ca9078a11..98f70bfc49 100644 --- a/docs/content/en/docs/yaml-crd-ref/metricsprovider.md +++ b/docs/content/en/docs/yaml-crd-ref/metricsprovider.md @@ -1,5 +1,130 @@ --- title: KeptnMetricsProvider -description: Define all workloads and checks associated with an application +description: Define a data provider used for metrics and evaluations weight: 55 --- + +`KeptnMetricsProvider` defines an instance of the data provider +(such as Prometheus, Dynatrace, or Datadog) +that is used by the [KeptnMetric](metric.md) resource. +One Keptn application can perform evaluations based on metrics +from more than one data provider +and, beginning with the v1alpha3 API version, +can use more than one instance of each data provider. +To implement this, create a `KeptnMetricsProvider` resource +for each instance of each data provider being used, +then reference the appropriate provider +for each metric definition by its name. + +## Yaml Synopsis + +```yaml +apiVersion: metrics.keptn.sh/v1alpha3 +kind: KeptnMetricsProvider +metadata: + name: + namespace: +spec: + type: prometheus | dynatrace | dql | datadog + targetServer: "" + secretKeyRef: + name: + key: +``` + +## Fields + +* **apiVersion** -- API version being used. +` +* **kind** -- Resource type. + Must be set to `KeptnMetricsProvider` + +* **metadata** + * **name** -- Unique name of this provider, + used to reference the provider for the + [KeptnEvaluationDefinition](evaluationdefinition.md) + and [KeptnMetric](metric.md) resources. + Names must comply with the + [Kubernetes Object Names and IDs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names) + specification. + + For example, you might define `dev-prometheus` + for the Prometheus instance that monitors the development deployment, + and `qa-prometheus` for the Prometheus instance + that monitors the Prometheus instance that monitors the QA deployment, + and `prod-dynatrace` for the Dynatrace instance + that monitors the production deployment. + + * **namespace** -- Namespace where this provider is used. + +* **spec** + + * **type** -- The type of data provider for this instance + * **targetServer** -- URL of the data provider, enclosed in double quotes + * **secretKeyRef** + * **name:** -- Name of the token for this data provider + * **key:** -- Key for this data provider + +## Usage + +## Examples + +### Example 1: Dynatrace data provider + +```yaml +apiVersion: metrics.keptn.sh/v1alpha3 +kind: KeptnMetricsProvider +metadata: + name: dynatrace + namespace: podtato-kubectl +spec: + targetServer: "" + secretKeyRef: + name: dt-api-token + key: DT_TOKEN +``` + +## Files + +API Reference: + +* [KeptnEvaluationDefinition](../crd-ref/lifecycle/v1alpha3/_index.md#keptnevaluationdefinition) + +## Differences between versions + +For the `v1alpha2` API version, +Keptn did not support +using more than one instance of a particular data provider +in the same namespace. +In other words, one namespace could support one instance each +of Prometheus, Dynatrace, and Datadog +but could not support, for example, two instances of Prometheus. + +The synopsis in those older API versions +only specified the `metadata.name` field +that identified the data provider (`prometheus`, `dynatrace`, or `dql`): + +```yaml +apiVersion: metrics.keptn.sh/v1alpha2 +kind: KeptnMetricsProvider +metadata: + name: prometheus | dynatrace |dql + namespace: +spec: + targetServer: "" + secretKeyRef: + name: dt-api-token + key: DT_TOKEN +``` + +Also note that, for the v1alpha1 and v1alpha2 API versions, +`KeptnMetricsProvider` only specifies the provider +for the `KeptnMetrics` resource. +Beginning with `v1alpha3` API version, +`KeptnMetricsProvider` is also used to specify the provider +for the `KeptnEvaluationDefinition` resource. + +## See also + +* [KeptnEvaluationDefinition](evaluationdefinition.md) +* [KeptnMetric](metric.md) From 8de6d8f8ca34c576466d9cc8b32d1d3865123ad8 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Thu, 11 May 2023 07:59:22 -0700 Subject: [PATCH 14/62] docs: create keptn metrics getting started (#1375) Signed-off-by: Meg McRoberts Signed-off-by: Meg McRoberts Signed-off-by: Simon Schrottner Signed-off-by: Meg McRoberts meg.mcroberts@dynatrace.com Co-authored-by: Giovanni Liva Co-authored-by: Simon Schrottner Co-authored-by: Florian Bacher --- .../en/docs/getting-started/metrics/_index.md | 265 ++++++++++++++++++ .../integrate/{index.md => _index.md} | 0 2 files changed, 265 insertions(+) rename docs/content/en/docs/implementing/integrate/{index.md => _index.md} (100%) diff --git a/docs/content/en/docs/getting-started/metrics/_index.md b/docs/content/en/docs/getting-started/metrics/_index.md index df78e07f70..777240bca4 100644 --- a/docs/content/en/docs/getting-started/metrics/_index.md +++ b/docs/content/en/docs/getting-started/metrics/_index.md @@ -4,3 +4,268 @@ description: Learn how Keptn metrics enhances your deployment weight: 25 --- +The Keptn metrics component of the Keptn Lifecycle Toolkit +allow you to define any type of metric +from multiple instances of any type of data source in your Kubernetes cluster. +You may have tools like Argo, Flux, KEDA, HPA, or Keptn +that need observability data to make automated decisions. +Whether a rollout is good, whether to scale up or down. +Your observability data may come +from multiple observability solutions -- +Datadog, Dynatrace, Lightstep, Honeycomb, Splunk, +or data directly from your cloud provider such as AWS, Google, or Azure. + +The Keptn Lifecycle Toolkit hooks directly into Kubernetes primitives +so minimal configuration is required. + +The +[Kubernetes metric server](https://github.com/kubernetes-sigs/metrics-server) +requires that you maintain point-to-point integrations +from Argo Rollouts, Flux, KEDA, and HPA. +Each has plugins but it is difficult to maintain them, +especially if you are using multiple tools, +and multible observability platforms, +and multiple instance of some tools or observability platforms. +The Keptn Metrics Server unifies and standardizes access to this data. + +## Using this exercise + +This exercise is based on the +[simplenode-dev](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd) +example. +You can clone that repo to access it locally +or just look at it for examples +as you implement the functionality "from scratch" +on your local Kubernetes deployment cluster. + +The steps to implement metrics in an existing cluster are: + +1. [Install the Keptn Lifecycle Toolkit](../../install/install.md) +1. Configure metrics to use + - [Define metrics providers](#define-metrics-providers) + - [Define KeptnMetric information](#define-keptnmetric-information) + - [View available metrics](#view-available-metrics) + +If you want to create your own cluster to run this exercise, +follow the instructions in [Installation](../../install). + +See the +[Introducing Keptn Lifecycle Toolkit](https://youtu.be/449HAFYkUlY) +video for a demonstration of this exercise. + +## Define metrics to use + +You need to define the external observability platforms +from which you want to pull data +and then the specific data you want to pull. +This data is pulled and fetched continuously +at an interval you specify for each specific bit of data. +Data is available through the resource and through the data provider itself, +as well as the Kubernetes CLI. + +### Define metrics providers + +Populate a +[KeptnMetricsProvider](../../yaml-crd-ref/metricsprovider.md) +resource for each external observability platform you want to use. + +For our example, we define two observability platforms: + +- `dev-prometheus` +- `dev-dynatrace` + +You can specify a virtually unlimited number of providers, +including multiple instances of each observability platform. +Each one must be assigned a unique name, +identified by the type of platform it is +and the URL. + +> Note: The video and example application use an older syntax + of the `KeptnMetricsProvider` and `KeptnMetric` resources. + The syntax shown in this document is correct for v0.7.1 and later. + +Definition of +[dev-prometheus](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-prometheus-provider.yaml) +data source: + +```yaml +kind: KeptnMetricsProvider +metadata: + name: dev-prometheus + namespace: simplenode-dev +spec: + type: prometheus + targetserver: "http://prometheus-k8s-monitoring-svc.cluster.local:9090" +``` + +Definition of the +[dev-dynatrace](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/dynatrace-provider.yaml.tmp) +data source. +Note that the `dev-dynatrace` server is protected by a secret key +so that information is included in the provider definition: + +```yaml +kind: KeptnMetricsProvider +metadata: + name: dev-dynatrace + namespace: simplenode-dev +spec: + type: dynatrace + targetServer: "https://hci34192.live.dynatrace.com" + secretKeyRef + name: dynatrace + key: DT_TOKEN +... +``` + +### Define KeptnMetric information + +The [KeptnMetric](../../yaml-crd-ref/metric.md) resource +defines the information you want to gather, +specified as a query for the particular observability platform +you are using. +You can define any type of metric from any data source. + +In our example, we define two bits of information to retrieve: + +- Number of CPUs, derived from the `dev-prometheus` data platform +- `availability` SLO, derived from the `dev-dynatrace` data platform + +Each of these are configured to fetch data every 10 seconds +but you could configure a different `fetchIntervalSeconds` value +for each metric. + +The +[keptn-metric.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-metric.yaml) +file for our example looks like: + +```yaml +apiVersion: metrics.keptn.sh/v1alpha2 +kind: Keptnmetric +metadata: + name: available-cpus + namespace: simplenode-dev +spec: + provider: + name: dev-prometheus + query: "sum(kube_node_status_cvapacity{resources`cpu`})" + fetchIntervalSeconds: 10 +--- +apiVersion: metrics.keptn.sh/v1alpha2 +kind: Keptnmetric +metadata: + name: availability-slo + namespace: simplenode-dev +spec: + provider: + name: dev-dynatrace + query: "func:slo.availability_simplenodeservice" + fetchIntervalSeconds: 10 +``` + +Note the following: + +- Populate one YAML file per metric + then apply all of them. +- Each metric is assigned a unique `name`. +- The value of the `spec.provider.name` field + must correspond to the name assigned in a + the `metadata.name` field of a `KeptnMetricsProvider` resource. +- Information is fetched in on a continuous basis +at a rate specified by the value of the `spec.fetchIntervalSeconds` field. + +### View available metrics + +Use the following command to view +the metrics that are configured in your cluster. +This example displays the two metrics we configured above: + +```shell +kubectl get KeptnMetrics -A +``` + +```shell +NAMESPACE NAME PROVIDER QUERY +simplenode-dev availability-slo dev-dynatrace func:slo.availability_simplenodeservice +simplenode-dev available-cpus dev-prometheus sum(kube_node_status_capacity{resource=`cpu`}) +``` + +## Run the metrics + +As soon as you define your `KeptnMetricsProvider` and `KeptnMetric` resources, +the Lifecycle Toolkit begins collecting the metrics you defined. +You do not need to do anything else. + +## Observing the metrics + +The metrics can be retrieved +through CRs and through the Kubernetes Metric API. + +The syntax to retrieve metrics from the CR is: + +```shell +kubectl get keptnmetrics.metrics.keptn.sh -n +``` + +For example, the output for the `available-cpus` metric looks like: + +```shell +$ kubectl get keptnmetrics.metrics.keptn.sh -n simplenode-dev available-cpus + +NAME PROVIDER QUERY VALUE +cpu-throttling my-provider sum(kube_node_status_capacity{resource=`cpu`}) 6.000 +``` + +The syntax to retrieve metrics through the Kubernetes API is: + +```yaml +kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta2/namespaces//keptnmetrics.metrics.sh//" +``` + +For example, the output for the `available-cpus` looks like: + +```yaml +$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta2/namespaces/simplenode-dev/keptnmetrics.metrics.sh/available-cpus/available-cpus" + +{ + "kind": "MetricValueList", + "apiVersion": "custom.metrics.k8s.io/v1beta2", + "metadata": {}, + "items": [ + { + "describedObject": { + "kind": "KeptnMetric", + "namespace": "simplenode-dev", + "name": "available-cpus", + "apiVersion": "metrics.keptn.sh/v1alpha2" + }, + "metric": { + "name": "available-cpus", + "selector": {} + }, + "timestamp": "2023-05-11T08:05:36Z", + "value": "6" + } + ] +} +``` + +You can also display the graphics using a dashboard such as Grafana. + +## Implementing autoscaling with HPA + +The Kubernetes HorizontalPodAutoscaler (HPA) +uses metrics to provide autoscaling for the cluster. +HPA can retrieve KeptnMetrics and use it to implement HPA. +See +Using the [HorizontalPodAutoscaler](../../implementing/evaluatemetrics/#using-the-horizontalpodautoscaler) +for detailed information. + +## Learn more + +To learn more about the Keptn Metrics Server, see: + +- Architecture: + [Keptn Metrics Operator](../../concepts/architecture/components/metrics-operator/) +- More information about implementing Keptn Metrics: + [Keptn Metrics](../../implementing/evaluatemetrics.md/) diff --git a/docs/content/en/docs/implementing/integrate/index.md b/docs/content/en/docs/implementing/integrate/_index.md similarity index 100% rename from docs/content/en/docs/implementing/integrate/index.md rename to docs/content/en/docs/implementing/integrate/_index.md From 3d23e29b82d1296627900850b19af7ea2eb30d87 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Thu, 11 May 2023 21:20:06 -0700 Subject: [PATCH 15/62] docs: improve list on install landing page (#1400) Signed-off-by: Meg McRoberts Signed-off-by: Meg McRoberts Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> --- docs/content/en/docs/install/_index.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/content/en/docs/install/_index.md b/docs/content/en/docs/install/_index.md index ae2e110d72..5aa6ae0655 100644 --- a/docs/content/en/docs/install/_index.md +++ b/docs/content/en/docs/install/_index.md @@ -1,8 +1,6 @@ --- title: Installation description: Learn how to install, configure, and upgrade the Keptn Lifecycle Toolkit -icon: concepts -layout: quickstart weight: 15 hidechildren: false # this flag hides all sub-pages in the sidebar-multicard.html --- @@ -15,12 +13,19 @@ This section provides details about how to install and configure the components of the Keptn Lifecycle Toolkit either as a local cluster you use for study, testing, and demonstrations or as part of an existing production cluster. +The steps are: 1. Understand the [Software versions and resources](reqs.md) - that are required -1. [Bring or create your Kubernetes cluster](k8s.md) -1. [Replace the default cert-manager](cert-manager.md) (optional) - This step is only required if you want to replace the default KLT cert-manager - with another cert-manager. -1. [Install the Keptn Lifecycle Controller](install.md) -1. [Upgrade](upgrade.md) to a new version of the Keptn Lifecycle Toolkit + that are required. +1. [Bring or create your Kubernetes cluster](k8s.md). +1. [Replace the default cert-manager](cert-manager.md) (optional). + This step is only required if you want to replace + the default KLT cert-manager with another cert-manager. +1. [Install the Keptn Lifecycle Toolkit](install.md). +1. [Enable Keptn Lifecycle Toolkit](install.md/#enable-klt-for-your-cluster). + This step is not required if you only want to run Keptn Metrics. + +This section also includes: + +1. How to [Upgrade](upgrade.md) + to a new version of the Keptn Lifecycle Toolkit From 2c51231fd700009c1588259de1974e1dfa80e8b8 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Fri, 12 May 2023 04:01:56 -0700 Subject: [PATCH 16/62] docs: mention Prometheus in intro (#1405) --- .../en/docs/getting-started/metrics/_index.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/content/en/docs/getting-started/metrics/_index.md b/docs/content/en/docs/getting-started/metrics/_index.md index 777240bca4..8f9f7d48cf 100644 --- a/docs/content/en/docs/getting-started/metrics/_index.md +++ b/docs/content/en/docs/getting-started/metrics/_index.md @@ -6,17 +6,19 @@ weight: 25 The Keptn metrics component of the Keptn Lifecycle Toolkit allow you to define any type of metric -from multiple instances of any type of data source in your Kubernetes cluster. -You may have tools like Argo, Flux, KEDA, HPA, or Keptn -that need observability data to make automated decisions. -Whether a rollout is good, whether to scale up or down. +from multiple instances +of any type of data source in your Kubernetes cluster. +You may have deployment tools like Argo, Flux, KEDA, HPA, or Keptn +that need observability data to make automated decisions +such as whether a rollout is good, whether to scale up or down. Your observability data may come from multiple observability solutions -- -Datadog, Dynatrace, Lightstep, Honeycomb, Splunk, +Prometheus, Dynatrace, Datadog and others -- or data directly from your cloud provider such as AWS, Google, or Azure. -The Keptn Lifecycle Toolkit hooks directly into Kubernetes primitives -so minimal configuration is required. +The Keptn Metrics Server unifies and standardizes access to all this data. +Minimal configuration is required +because the Keptn Lifecycle Toolkit hooks directly into Kubernetes primitives. The [Kubernetes metric server](https://github.com/kubernetes-sigs/metrics-server) @@ -26,7 +28,6 @@ Each has plugins but it is difficult to maintain them, especially if you are using multiple tools, and multible observability platforms, and multiple instance of some tools or observability platforms. -The Keptn Metrics Server unifies and standardizes access to this data. ## Using this exercise From 831fc46d9e4ebb059473f137ef6c012373c6179c Mon Sep 17 00:00:00 2001 From: Florian Bacher Date: Mon, 15 May 2023 08:40:07 +0200 Subject: [PATCH 17/62] chore: use cert-manager library in lifecycle-operator and metrics-operator to reduce code duplication (#1379) Signed-off-by: Florian Bacher Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> --- .../cmd/certificates/certificatehandler.go | 22 -- .../fake/certificatehandler_mock.go | 116 ------- metrics-operator/cmd/certificates/watcher.go | 132 -------- .../cmd/certificates/watcher_test.go | 318 ------------------ metrics-operator/cmd/webhook/builder.go | 85 ----- metrics-operator/cmd/webhook/builder_test.go | 33 -- metrics-operator/cmd/webhook/manager.go | 43 --- metrics-operator/cmd/webhook/manager_test.go | 54 --- metrics-operator/config/manager/manager.yaml | 2 - metrics-operator/go.mod | 3 +- metrics-operator/go.sum | 2 + metrics-operator/main.go | 21 +- .../cmd/certificates/certificatehandler.go | 22 -- .../fake/certificatehandler_mock.go | 116 ------- operator/cmd/certificates/watcher.go | 132 -------- operator/cmd/certificates/watcher_test.go | 318 ------------------ operator/cmd/webhook/builder.go | 96 ------ operator/cmd/webhook/builder_test.go | 33 -- operator/cmd/webhook/manager.go | 43 --- operator/cmd/webhook/manager_test.go | 54 --- operator/config/manager/manager.yaml | 2 - operator/go.mod | 3 +- operator/go.sum | 2 + operator/main.go | 33 +- 24 files changed, 54 insertions(+), 1631 deletions(-) delete mode 100644 metrics-operator/cmd/certificates/certificatehandler.go delete mode 100644 metrics-operator/cmd/certificates/fake/certificatehandler_mock.go delete mode 100644 metrics-operator/cmd/certificates/watcher.go delete mode 100644 metrics-operator/cmd/certificates/watcher_test.go delete mode 100644 metrics-operator/cmd/webhook/builder.go delete mode 100644 metrics-operator/cmd/webhook/builder_test.go delete mode 100644 metrics-operator/cmd/webhook/manager.go delete mode 100644 metrics-operator/cmd/webhook/manager_test.go delete mode 100644 operator/cmd/certificates/certificatehandler.go delete mode 100644 operator/cmd/certificates/fake/certificatehandler_mock.go delete mode 100644 operator/cmd/certificates/watcher.go delete mode 100644 operator/cmd/certificates/watcher_test.go delete mode 100644 operator/cmd/webhook/builder.go delete mode 100644 operator/cmd/webhook/builder_test.go delete mode 100644 operator/cmd/webhook/manager.go delete mode 100644 operator/cmd/webhook/manager_test.go diff --git a/metrics-operator/cmd/certificates/certificatehandler.go b/metrics-operator/cmd/certificates/certificatehandler.go deleted file mode 100644 index ba9450fad9..0000000000 --- a/metrics-operator/cmd/certificates/certificatehandler.go +++ /dev/null @@ -1,22 +0,0 @@ -package certificates - -import ( - "crypto/x509" - "encoding/pem" -) - -//go:generate moq -pkg fake -skip-ensure -out ./fake/certificatehandler_mock.go . ICertificateHandler -type ICertificateHandler interface { - Decode(data []byte) (p *pem.Block, rest []byte) - Parse(der []byte) (*x509.Certificate, error) -} - -type defaultCertificateHandler struct { -} - -func (c defaultCertificateHandler) Decode(data []byte) (p *pem.Block, rest []byte) { - return pem.Decode(data) -} -func (c defaultCertificateHandler) Parse(der []byte) (*x509.Certificate, error) { - return x509.ParseCertificate(der) -} diff --git a/metrics-operator/cmd/certificates/fake/certificatehandler_mock.go b/metrics-operator/cmd/certificates/fake/certificatehandler_mock.go deleted file mode 100644 index 45a5eacbae..0000000000 --- a/metrics-operator/cmd/certificates/fake/certificatehandler_mock.go +++ /dev/null @@ -1,116 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package fake - -import ( - "crypto/x509" - "encoding/pem" - "sync" -) - -// ICertificateHandlerMock is a mock implementation of certificates.ICertificateHandler. -// -// func TestSomethingThatUsesICertificateHandler(t *testing.T) { -// -// // make and configure a mocked certificates.ICertificateHandler -// mockedICertificateHandler := &ICertificateHandlerMock{ -// DecodeFunc: func(data []byte) (*pem.Block, []byte) { -// panic("mock out the Decode method") -// }, -// ParseFunc: func(der []byte) (*x509.Certificate, error) { -// panic("mock out the Parse method") -// }, -// } -// -// // use mockedICertificateHandler in code that requires certificates.ICertificateHandler -// // and then make assertions. -// -// } -type ICertificateHandlerMock struct { - // DecodeFunc mocks the Decode method. - DecodeFunc func(data []byte) (*pem.Block, []byte) - - // ParseFunc mocks the Parse method. - ParseFunc func(der []byte) (*x509.Certificate, error) - - // calls tracks calls to the methods. - calls struct { - // Decode holds details about calls to the Decode method. - Decode []struct { - // Data is the data argument value. - Data []byte - } - // Parse holds details about calls to the Parse method. - Parse []struct { - // Der is the der argument value. - Der []byte - } - } - lockDecode sync.RWMutex - lockParse sync.RWMutex -} - -// Decode calls DecodeFunc. -func (mock *ICertificateHandlerMock) Decode(data []byte) (*pem.Block, []byte) { - if mock.DecodeFunc == nil { - panic("ICertificateHandlerMock.DecodeFunc: method is nil but ICertificateHandler.Decode was just called") - } - callInfo := struct { - Data []byte - }{ - Data: data, - } - mock.lockDecode.Lock() - mock.calls.Decode = append(mock.calls.Decode, callInfo) - mock.lockDecode.Unlock() - return mock.DecodeFunc(data) -} - -// DecodeCalls gets all the calls that were made to Decode. -// Check the length with: -// -// len(mockedICertificateHandler.DecodeCalls()) -func (mock *ICertificateHandlerMock) DecodeCalls() []struct { - Data []byte -} { - var calls []struct { - Data []byte - } - mock.lockDecode.RLock() - calls = mock.calls.Decode - mock.lockDecode.RUnlock() - return calls -} - -// Parse calls ParseFunc. -func (mock *ICertificateHandlerMock) Parse(der []byte) (*x509.Certificate, error) { - if mock.ParseFunc == nil { - panic("ICertificateHandlerMock.ParseFunc: method is nil but ICertificateHandler.Parse was just called") - } - callInfo := struct { - Der []byte - }{ - Der: der, - } - mock.lockParse.Lock() - mock.calls.Parse = append(mock.calls.Parse, callInfo) - mock.lockParse.Unlock() - return mock.ParseFunc(der) -} - -// ParseCalls gets all the calls that were made to Parse. -// Check the length with: -// -// len(mockedICertificateHandler.ParseCalls()) -func (mock *ICertificateHandlerMock) ParseCalls() []struct { - Der []byte -} { - var calls []struct { - Der []byte - } - mock.lockParse.RLock() - calls = mock.calls.Parse - mock.lockParse.RUnlock() - return calls -} diff --git a/metrics-operator/cmd/certificates/watcher.go b/metrics-operator/cmd/certificates/watcher.go deleted file mode 100644 index 3621679ccc..0000000000 --- a/metrics-operator/cmd/certificates/watcher.go +++ /dev/null @@ -1,132 +0,0 @@ -package certificates - -import ( - "bytes" - "context" - "errors" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/go-logr/logr" - "github.com/spf13/afero" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - certificateRenewalInterval = 6 * time.Hour - ServerKey = "tls.key" - ServerCert = "tls.crt" - CertThreshold = 5 * time.Minute -) - -type CertificateWatcher struct { - apiReader client.Reader - fs afero.Fs - certificateDirectory string - namespace string - certificateSecretName string - certificateTreshold time.Duration - ICertificateHandler - Log logr.Logger -} - -func NewCertificateWatcher(reader client.Reader, certDir string, namespace string, secretName string, log logr.Logger) *CertificateWatcher { - return &CertificateWatcher{ - apiReader: reader, - fs: afero.NewOsFs(), - certificateDirectory: certDir, - namespace: namespace, - certificateSecretName: secretName, - ICertificateHandler: defaultCertificateHandler{}, - certificateTreshold: CertThreshold, - Log: log, - } -} - -func (watcher *CertificateWatcher) watchForCertificatesSecret() { - for { - <-time.After(certificateRenewalInterval) - watcher.Log.Info("checking for new certificates") - if err := watcher.updateCertificatesFromSecret(); err != nil { - watcher.Log.Error(err, "failed to update certificates") - } else { - watcher.Log.Info("updated certificate successfully") - } - } -} - -func (watcher *CertificateWatcher) updateCertificatesFromSecret() error { - var secret corev1.Secret - - err := watcher.apiReader.Get(context.TODO(), - client.ObjectKey{Name: watcher.certificateSecretName, Namespace: watcher.namespace}, &secret) - if err != nil { - return err - } - - watcher.Log.Info("checking dir", "watcher.certificateDirectory ", watcher.certificateDirectory) - if _, err = watcher.fs.Stat(watcher.certificateDirectory); os.IsNotExist(err) { - err = watcher.fs.MkdirAll(watcher.certificateDirectory, 0755) - if err != nil { - return fmt.Errorf("could not create cert directory: %w", err) - } - } - - for _, filename := range []string{ServerCert, ServerKey} { - if err = watcher.ensureCertificateFile(secret, filename); err != nil { - return err - } - } - isValid, err := watcher.ValidateCertificateExpiration(secret.Data[ServerCert], certificateRenewalInterval, time.Now()) - if err != nil { - return err - } else if !isValid { - return fmt.Errorf("certificate is outdated") - } - return nil -} - -func (watcher *CertificateWatcher) ensureCertificateFile(secret corev1.Secret, filename string) error { - f := filepath.Join(watcher.certificateDirectory, filename) - data, err := afero.ReadFile(watcher.fs, f) - if os.IsNotExist(err) || !bytes.Equal(data, secret.Data[filename]) { - return afero.WriteFile(watcher.fs, f, secret.Data[filename], 0666) - } - return err - -} - -func (watcher *CertificateWatcher) WaitForCertificates() { - for threshold := time.Now().Add(watcher.certificateTreshold); time.Now().Before(threshold); { - - if err := watcher.updateCertificatesFromSecret(); err != nil { - if k8serrors.IsNotFound(err) { - watcher.Log.Info("waiting for certificate secret to be available.") - } else { - watcher.Log.Error(err, "failed to update certificates") - } - time.Sleep(10 * time.Second) - continue - } - break - } - go watcher.watchForCertificatesSecret() -} - -func (watcher *CertificateWatcher) ValidateCertificateExpiration(certData []byte, renewalThreshold time.Duration, now time.Time) (bool, error) { - if block, _ := watcher.Decode(certData); block == nil { - watcher.Log.Error(errors.New("can't decode PEM file"), "failed to parse certificate") - return false, nil - } else if cert, err := watcher.Parse(block.Bytes); err != nil { - watcher.Log.Error(err, "failed to parse certificate") - return false, err - } else if now.After(cert.NotAfter.Add(-renewalThreshold)) { - watcher.Log.Info("certificate is outdated, waiting for new ones", "Valid until", cert.NotAfter.UTC()) - return false, nil - } - return true, nil -} diff --git a/metrics-operator/cmd/certificates/watcher_test.go b/metrics-operator/cmd/certificates/watcher_test.go deleted file mode 100644 index 4f4301e313..0000000000 --- a/metrics-operator/cmd/certificates/watcher_test.go +++ /dev/null @@ -1,318 +0,0 @@ -package certificates - -import ( - "bytes" - "crypto/x509" - "encoding/pem" - "os" - "path/filepath" - "testing" - "time" - - "github.com/go-logr/logr/testr" - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/certificates/fake" - fakeclient "github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/fake" - "github.com/pkg/errors" - "github.com/spf13/afero" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const CACERT = `-----BEGIN CERTIFICATE----- -MIICPTCCAeKgAwIBAgIRAMIV/0UqFGHgKSYOWBdx/KcwCgYIKoZIzj0EAwIwczEL -MAkGA1UEBhMCQVQxCzAJBgNVBAgTAktMMRMwEQYDVQQHEwpLbGFnZW5mdXJ0MQ4w -DAYDVQQKEwVLZXB0bjEZMBcGA1UECxMQTGlmZWN5Y2xlVG9vbGtpdDEXMBUGA1UE -AwwOKi5rZXB0bi1ucy5zdmMwHhcNMjMwNDE5MTEwNDUzWhcNMjQwNDE4MTEwNDUz -WjBzMQswCQYDVQQGEwJBVDELMAkGA1UECBMCS0wxEzARBgNVBAcTCktsYWdlbmZ1 -cnQxDjAMBgNVBAoTBUtlcHRuMRkwFwYDVQQLExBMaWZlY3ljbGVUb29sa2l0MRcw -FQYDVQQDDA4qLmtlcHRuLW5zLnN2YzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA -BPxAP4JTJfwKz/P32dXuyfVi7kinQPebSYwF/gRAUcN0dCAi6GnxbI2OXlcU0guD -zHXv3VRh3EX2fiNszcfKaCajVzBVMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAK -BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQUGe/8XYV1HsZs -nWsyrOCCGr/sQDAKBggqhkjOPQQDAgNJADBGAiEAkcPaCANDXW5Uillrof0VrnPw -ow49D22Gsrh7YM+vmTQCIQDU1L5IT0Zz+bdIyFSsDnEUXZDeydNv56DoSLh+358Y -aw== ------END CERTIFICATE-----` - -const CAKEY = `-----BEGIN PRIVATE KEY----- -MHcCAQEEII5SAqBxINKatksyu2mTvLZZhfEOpNinYJDwlQjkfreboAoGCCqGSM49 -AwEHoUQDQgAE/EA/glMl/ArP8/fZ1e7J9WLuSKdA95tJjAX+BEBRw3R0ICLoafFs -jY5eVxTSC4PMde/dVGHcRfZ+I2zNx8poJg== ------END PRIVATE KEY-----` - -const uniqueIDPEM = `-----BEGIN CERTIFICATE----- -MIIFsDCCBJigAwIBAgIIrOyC1ydafZMwDQYJKoZIhvcNAQEFBQAwgY4xgYswgYgG -A1UEAx6BgABNAGkAYwByAG8AcwBvAGYAdAAgAEYAbwByAGUAZgByAG8AbgB0ACAA -VABNAEcAIABIAFQAVABQAFMAIABJAG4AcwBwAGUAYwB0AGkAbwBuACAAQwBlAHIA -dABpAGYAaQBjAGEAdABpAG8AbgAgAEEAdQB0AGgAbwByAGkAdAB5MB4XDTE0MDEx -ODAwNDEwMFoXDTE1MTExNTA5Mzc1NlowgZYxCzAJBgNVBAYTAklEMRAwDgYDVQQI -EwdqYWthcnRhMRIwEAYDVQQHEwlJbmRvbmVzaWExHDAaBgNVBAoTE3N0aG9ub3Jl -aG90ZWxyZXNvcnQxHDAaBgNVBAsTE3N0aG9ub3JlaG90ZWxyZXNvcnQxJTAjBgNV -BAMTHG1haWwuc3Rob25vcmVob3RlbHJlc29ydC5jb20wggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCvuu0qpI+Ko2X84Twkf84cRD/rgp6vpgc5Ebejx/D4 -PEVON5edZkazrMGocK/oQqIlRxx/lefponN/chlGcllcVVPWTuFjs8k+Aat6T1qp -4iXxZekAqX+U4XZMIGJD3PckPL6G2RQSlF7/LhGCsRNRdKpMWSTbou2Ma39g52Kf -gsl3SK/GwLiWpxpcSkNQD1hugguEIsQYLxbeNwpcheXZtxbBGguPzQ7rH8c5vuKU -BkMOzaiNKLzHbBdFSrua8KWwCJg76Vdq/q36O9GlW6YgG3i+A4pCJjXWerI1lWwX -Ktk5V+SvUHGey1bkDuZKJ6myMk2pGrrPWCT7jP7WskChAgMBAAGBCQBCr1dgEleo -cKOCAfswggH3MIHDBgNVHREEgbswgbiCHG1haWwuc3Rob25vcmVob3RlbHJlc29y -dC5jb22CIGFzaGNoc3ZyLnN0aG9ub3JlaG90ZWxyZXNvcnQuY29tgiRBdXRvRGlz -Y292ZXIuc3Rob25vcmVob3RlbHJlc29ydC5jb22CHEF1dG9EaXNjb3Zlci5ob3Rl -bHJlc29ydC5jb22CCEFTSENIU1ZSghdzdGhvbm9yZWhvdGVscmVzb3J0LmNvbYIP -aG90ZWxyZXNvcnQuY29tMCEGCSsGAQQBgjcUAgQUHhIAVwBlAGIAUwBlAHIAdgBl -AHIwHQYDVR0OBBYEFMAC3UR4FwAdGekbhMgnd6lMejtbMAsGA1UdDwQEAwIFoDAT -BgNVHSUEDDAKBggrBgEFBQcDATAJBgNVHRMEAjAAMIG/BgNVHQEEgbcwgbSAFGfF -6xihk+gJJ5TfwvtWe1UFnHLQoYGRMIGOMYGLMIGIBgNVBAMegYAATQBpAGMAcgBv -AHMAbwBmAHQAIABGAG8AcgBlAGYAcgBvAG4AdAAgAFQATQBHACAASABUAFQAUABT -ACAASQBuAHMAcABlAGMAdABpAG8AbgAgAEMAZQByAHQAaQBmAGkAYwBhAHQAaQBv -AG4AIABBAHUAdABoAG8AcgBpAHQAeYIIcKhXEmBXr0IwDQYJKoZIhvcNAQEFBQAD -ggEBABlSxyCMr3+ANr+WmPSjyN5YCJBgnS0IFCwJAzIYP87bcTye/U8eQ2+E6PqG -Q7Huj7nfHEw9qnGo+HNyPp1ad3KORzXDb54c6xEoi+DeuPzYHPbn4c3hlH49I0aQ -eWW2w4RslSWpLvO6Y7Lboyz2/Thk/s2kd4RHxkkWpH2ltPqJuYYg3X6oM5+gIFHJ -WGnh+ojZ5clKvS5yXh3Wkj78M6sb32KfcBk0Hx6NkCYPt60ODYmWtvqwtw6r73u5 -TnTYWRNvo2svX69TriL+CkHY9O1Hkwf2It5zHl3gNiKTJVaak8AuEz/CKWZneovt -yYLwhUhg3PX5Co1VKYE+9TxloiE= ------END CERTIFICATE-----` - -var ERR_BAD_CERT = errors.New("bad cert") - -var emptySecret = v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "my-cert", - }, -} - -var goodSecret = v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "my-cert", - }, - Data: map[string][]byte{ - ServerCert: []byte(CACERT), - ServerKey: []byte(CAKEY), - }, -} - -func TestCertificateWatcher_ValidateCertificateExpiration(t *testing.T) { - - tests := []struct { - name string - certHandler ICertificateHandler - certData []byte - renewalThreshold time.Duration - now time.Time - want bool - wantErr error - }{ - { - name: "certificate cannot be decoded", - certHandler: &fake.ICertificateHandlerMock{ - DecodeFunc: func(data []byte) (p *pem.Block, rest []byte) { - return nil, nil //fake a failure in the decoding - }, - ParseFunc: nil, - }, - want: false, - }, - { - name: "certificate cannot be parsed", - certHandler: &fake.ICertificateHandlerMock{ - DecodeFunc: func(data []byte) (p *pem.Block, rest []byte) { - return &pem.Block{Type: "test", Bytes: []byte("testdata")}, nil - }, - ParseFunc: func(der []byte) (*x509.Certificate, error) { - return nil, ERR_BAD_CERT - }, - }, - want: false, - wantErr: ERR_BAD_CERT, - }, - { - name: "good certificate - unexpired", - certData: []byte(uniqueIDPEM), - certHandler: defaultCertificateHandler{}, - want: true, - }, - { - name: "good certificate - expired", - certData: []byte(uniqueIDPEM), - now: time.Now(), //setting up now makes sure that the threshold is passed - certHandler: defaultCertificateHandler{}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - ICertificateHandler: tt.certHandler, - Log: testr.New(t), - } - got, err := watcher.ValidateCertificateExpiration(tt.certData, tt.renewalThreshold, tt.now) - if tt.wantErr != nil { - require.Error(t, err) - t.Log("want:", tt.wantErr, "got:", err) - require.True(t, errors.Is(tt.wantErr, err)) - } - require.Equal(t, got, tt.want) - }) - } -} - -func TestCertificateWatcher_ensureCertificateFile(t *testing.T) { - - certdir := t.TempDir() - f := filepath.Join(certdir, ServerCert) - err := os.WriteFile(f, goodSecret.Data[ServerCert], 0666) - require.Nil(t, err) - baddir := t.TempDir() - f = filepath.Join(baddir, ServerCert) - err = os.WriteFile(f, goodSecret.Data[ServerKey], 0666) - require.Nil(t, err) - tests := []struct { - name string - fs afero.Fs - secret v1.Secret - filename string - certDir string - wantErr bool - err string - }{ - { - name: "if good cert exist in fs no error", - secret: goodSecret, - certDir: certdir, - filename: ServerCert, - wantErr: false, - }, - - { - name: "if unexisting file name, we expect a file system error", - secret: emptySecret, - filename: "$%&/())=$§%/=", - certDir: baddir, - wantErr: true, - err: "no such file or directory", - }, - - { - name: "wrong file content is replaced with updated cert", - certDir: baddir, - secret: goodSecret, - filename: ServerCert, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - fs: afero.NewOsFs(), - certificateDirectory: tt.certDir, - } - err := watcher.ensureCertificateFile(tt.secret, tt.filename) - if !tt.wantErr { - require.Nil(t, err) - f = filepath.Join(tt.certDir, ServerCert) - data, err := os.ReadFile(f) - if err != nil { - panic(err) - } - if !bytes.Equal(data, tt.secret.Data[tt.filename]) { - t.Errorf("ensureCertificateFile()data %v was not replaced with %v", data, tt.secret.Data[tt.filename]) - } - } else { - require.Contains(t, err.Error(), tt.err) - } - }) - } -} - -func TestCertificateWatcher_updateCertificatesFromSecret(t *testing.T) { - - oldDir := t.TempDir() - os.Remove(oldDir) - - tests := []struct { - name string - apiReader client.Reader - certificateDirectory string - namespace string - certificateSecretName string - wantErr error - }{ - { - name: "certificate not found", - apiReader: fakeclient.NewClient(), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("secrets \"my-cert\" not found"), - }, - { - name: "outdated certificate found, nothing in dir", - apiReader: fakeclient.NewClient(&emptySecret), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("certificate is outdated"), - }, - - { - name: "outdated certificate found, not existing in dir", - apiReader: fakeclient.NewClient(&emptySecret), - certificateDirectory: oldDir, - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("certificate is outdated"), - }, - { - name: "good certificate - not stored", - apiReader: fakeclient.NewClient(&goodSecret), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - apiReader: tt.apiReader, - fs: afero.NewOsFs(), - certificateDirectory: tt.certificateDirectory, - namespace: tt.namespace, - certificateSecretName: tt.certificateSecretName, - ICertificateHandler: defaultCertificateHandler{}, - Log: testr.New(t), - } - err := watcher.updateCertificatesFromSecret() - if tt.wantErr == nil { - require.Nil(t, err) - } else { - require.NotNil(t, err) - require.Contains(t, err.Error(), tt.wantErr.Error()) - } - }) - } -} - -func TestNewCertificateWatcher(t *testing.T) { - logger := testr.New(t) - client := fakeclient.NewClient() - want := &CertificateWatcher{ - apiReader: client, - fs: afero.NewOsFs(), - namespace: "default", - certificateSecretName: "my-secret", - certificateDirectory: "test", - certificateTreshold: CertThreshold, - ICertificateHandler: defaultCertificateHandler{}, - Log: testr.New(t), - } - got := NewCertificateWatcher(client, "test", "default", "my-secret", logger) - require.EqualValues(t, got, want) - -} diff --git a/metrics-operator/cmd/webhook/builder.go b/metrics-operator/cmd/webhook/builder.go deleted file mode 100644 index 311acff4ac..0000000000 --- a/metrics-operator/cmd/webhook/builder.go +++ /dev/null @@ -1,85 +0,0 @@ -package webhook - -import ( - "flag" - - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/certificates" - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/config" - cmdManager "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/manager" - "github.com/pkg/errors" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -const ( - flagCertificateDirectory = "certs-dir" - flagCertificateFileName = "cert" - flagCertificateKeyFileName = "cert-key" - secretCertsName = "klt-certs" -) - -var ( - certificateDirectory string - certificateFileName string - certificateKeyFileName string -) - -type Builder struct { - configProvider config.Provider - managerProvider cmdManager.Provider - namespace string - podName string -} - -func NewWebhookBuilder() Builder { - return Builder{} -} - -func (builder Builder) SetConfigProvider(provider config.Provider) Builder { - builder.configProvider = provider - return builder -} - -func (builder Builder) SetManagerProvider(provider cmdManager.Provider) Builder { - builder.managerProvider = provider - return builder -} - -func (builder Builder) SetNamespace(namespace string) Builder { - builder.namespace = namespace - return builder -} - -func (builder Builder) SetPodName(podName string) Builder { - builder.podName = podName - return builder -} - -func (builder Builder) GetManagerProvider() cmdManager.Provider { - if builder.managerProvider == nil { - builder.managerProvider = NewWebhookManagerProvider(certificateDirectory, certificateKeyFileName, certificateFileName) - } - - return builder.managerProvider -} - -func (builder Builder) Run(webhookManager manager.Manager) error { - - addFlags() - builder.GetManagerProvider().SetupWebhookServer(webhookManager) - - certificates. - NewCertificateWatcher(webhookManager.GetAPIReader(), webhookManager.GetWebhookServer().CertDir, builder.namespace, secretCertsName, ctrl.Log.WithName("Webhook Cert Manager")). - WaitForCertificates() - - signalHandler := ctrl.SetupSignalHandler() - err := webhookManager.Start(signalHandler) - return errors.WithStack(err) -} - -func addFlags() { - flag.StringVar(&certificateDirectory, flagCertificateDirectory, "/tmp/webhook/certs", "Directory to look certificates for.") - flag.StringVar(&certificateFileName, flagCertificateFileName, "tls.crt", "File name for the public certificate.") - flag.StringVar(&certificateKeyFileName, flagCertificateKeyFileName, "tls.key", "File name for the private key.") - flag.Parse() -} diff --git a/metrics-operator/cmd/webhook/builder_test.go b/metrics-operator/cmd/webhook/builder_test.go deleted file mode 100644 index fad6091c7d..0000000000 --- a/metrics-operator/cmd/webhook/builder_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package webhook - -import ( - "testing" - - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/fake" - "github.com/stretchr/testify/assert" -) - -func TestWebhookCommandBuilder(t *testing.T) { - - t.Run("set config provider", func(t *testing.T) { - builder := NewWebhookBuilder() - - assert.NotNil(t, builder) - - expectedProvider := &fake.MockProvider{} - builder = builder.SetConfigProvider(expectedProvider) - - assert.Equal(t, expectedProvider, builder.configProvider) - }) - t.Run("set manager provider", func(t *testing.T) { - expectedProvider := &fake.MockWebhookManager{} - builder := NewWebhookBuilder().SetManagerProvider(expectedProvider) - - assert.Equal(t, expectedProvider, builder.managerProvider) - }) - t.Run("set namespace", func(t *testing.T) { - builder := NewWebhookBuilder().SetNamespace("namespace") - - assert.Equal(t, "namespace", builder.namespace) - }) -} diff --git a/metrics-operator/cmd/webhook/manager.go b/metrics-operator/cmd/webhook/manager.go deleted file mode 100644 index 0e0fedccc6..0000000000 --- a/metrics-operator/cmd/webhook/manager.go +++ /dev/null @@ -1,43 +0,0 @@ -package webhook - -import ( - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -const ( - metricsBindAddress = ":8383" - port = 8443 -) - -type WebhookProvider struct { - certificateDirectory string - certificateFileName string - keyFileName string -} - -func NewWebhookManagerProvider(certificateDirectory string, keyFileName string, certificateFileName string) WebhookProvider { - return WebhookProvider{ - certificateDirectory: certificateDirectory, - certificateFileName: certificateFileName, - keyFileName: keyFileName, - } -} - -func (provider WebhookProvider) createOptions(scheme *runtime.Scheme, namespace string) ctrl.Options { - return ctrl.Options{ - Scheme: scheme, - MetricsBindAddress: metricsBindAddress, - Port: port, - Namespace: namespace, - } -} - -func (provider WebhookProvider) SetupWebhookServer(mgr manager.Manager) { - webhookServer := mgr.GetWebhookServer() - webhookServer.CertDir = provider.certificateDirectory - webhookServer.KeyName = provider.keyFileName - webhookServer.CertName = provider.certificateFileName - -} diff --git a/metrics-operator/cmd/webhook/manager_test.go b/metrics-operator/cmd/webhook/manager_test.go deleted file mode 100644 index 413fbf357f..0000000000 --- a/metrics-operator/cmd/webhook/manager_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package webhook - -import ( - "testing" - - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/fake" - cmdManager "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/manager" - "github.com/stretchr/testify/assert" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -func TestCreateOptions(t *testing.T) { - - t.Run("implements interface", func(t *testing.T) { - var provider cmdManager.Provider = NewWebhookManagerProvider("certs-dir", "key-file", "cert-file") - - providerImpl := provider.(WebhookProvider) - assert.Equal(t, "certs-dir", providerImpl.certificateDirectory) - assert.Equal(t, "key-file", providerImpl.keyFileName) - assert.Equal(t, "cert-file", providerImpl.certificateFileName) - }) - t.Run("creates options", func(t *testing.T) { - provider := WebhookProvider{} - options := provider.createOptions(scheme.Scheme, "test-namespace") - - assert.NotNil(t, options) - assert.Equal(t, "test-namespace", options.Namespace) - assert.Equal(t, scheme.Scheme, options.Scheme) - assert.Equal(t, metricsBindAddress, options.MetricsBindAddress) - assert.Equal(t, port, options.Port) - }) - t.Run("configures webhooks server", func(t *testing.T) { - provider := NewWebhookManagerProvider("certs-dir", "key-file", "cert-file") - expectedWebhookServer := &webhook.Server{} - - mgr := &fake.MockManager{ - GetWebhookServerFunc: func() *webhook.Server { - return expectedWebhookServer - }, - } - - provider.SetupWebhookServer(mgr) - - assert.Equal(t, "certs-dir", expectedWebhookServer.CertDir) - assert.Equal(t, "key-file", expectedWebhookServer.KeyName) - assert.Equal(t, "cert-file", expectedWebhookServer.CertName) - - mgrWebhookServer := mgr.GetWebhookServer() - assert.Equal(t, "certs-dir", mgrWebhookServer.CertDir) - assert.Equal(t, "key-file", mgrWebhookServer.KeyName) - assert.Equal(t, "cert-file", mgrWebhookServer.CertName) - }) -} diff --git a/metrics-operator/config/manager/manager.yaml b/metrics-operator/config/manager/manager.yaml index 433317c78d..cb44f8a113 100644 --- a/metrics-operator/config/manager/manager.yaml +++ b/metrics-operator/config/manager/manager.yaml @@ -45,8 +45,6 @@ spec: - /manager args: - webhook-server - # OLM mounts the certificates here, so we reuse it for simplicity - - --certs-dir=/tmp/k8s-webhook-server/serving-certs/ - --leader-elect # Secure port for the metrics adapter - --adapter-port=6443 diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index 70161a0fb2..9cc919cedf 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -8,11 +8,11 @@ require ( github.com/go-logr/logr v1.2.4 github.com/gorilla/mux v1.8.0 github.com/kelseyhightower/envconfig v1.4.0 + github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f github.com/open-feature/go-sdk v1.3.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 github.com/prometheus/common v0.42.0 - github.com/spf13/afero v1.9.5 github.com/stretchr/testify v1.8.2 k8s.io/api v0.26.4 k8s.io/apiextensions-apiserver v0.26.4 @@ -73,6 +73,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cobra v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index c501681c0e..799bc6ae6f 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -263,6 +263,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f h1:ildQjDQmBjX3fdAShsQJN7dkKfhivlnlDbEh/Etjli4= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/metrics-operator/main.go b/metrics-operator/main.go index 930e47103f..bc99c602b7 100644 --- a/metrics-operator/main.go +++ b/metrics-operator/main.go @@ -26,12 +26,13 @@ import ( "time" "github.com/kelseyhightower/envconfig" + "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/certificates" + certCommon "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/common" + "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/webhook" metricsv1alpha1 "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha1" metricsv1alpha2 "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" metricsv1alpha3 "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3" - cmdConfig "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/config" "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/metrics/adapter" - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/webhook" metricscontroller "github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/metrics" keptnserver "github.com/keptn/lifecycle-toolkit/metrics-operator/pkg/metrics" "github.com/open-feature/go-sdk/pkg/openfeature" @@ -154,10 +155,22 @@ func main() { webhookBuilder := webhook.NewWebhookBuilder(). SetNamespace(env.PodNamespace). SetPodName(env.PodName). - SetConfigProvider(cmdConfig.NewKubeConfigProvider()) + SetManagerProvider( + webhook.NewWebhookManagerProvider( + mgr.GetWebhookServer().CertDir, "tls.key", "tls.crt"), + ). + SetCertificateWatcher( + certificates.NewCertificateWatcher( + mgr.GetAPIReader(), + mgr.GetWebhookServer().CertDir, + env.PodNamespace, + certCommon.SecretName, + setupLog, + ), + ) setupLog.Info("starting webhook and manager") - if err1 := webhookBuilder.Run(mgr); err1 != nil { + if err := webhookBuilder.Run(mgr, nil); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } diff --git a/operator/cmd/certificates/certificatehandler.go b/operator/cmd/certificates/certificatehandler.go deleted file mode 100644 index ba9450fad9..0000000000 --- a/operator/cmd/certificates/certificatehandler.go +++ /dev/null @@ -1,22 +0,0 @@ -package certificates - -import ( - "crypto/x509" - "encoding/pem" -) - -//go:generate moq -pkg fake -skip-ensure -out ./fake/certificatehandler_mock.go . ICertificateHandler -type ICertificateHandler interface { - Decode(data []byte) (p *pem.Block, rest []byte) - Parse(der []byte) (*x509.Certificate, error) -} - -type defaultCertificateHandler struct { -} - -func (c defaultCertificateHandler) Decode(data []byte) (p *pem.Block, rest []byte) { - return pem.Decode(data) -} -func (c defaultCertificateHandler) Parse(der []byte) (*x509.Certificate, error) { - return x509.ParseCertificate(der) -} diff --git a/operator/cmd/certificates/fake/certificatehandler_mock.go b/operator/cmd/certificates/fake/certificatehandler_mock.go deleted file mode 100644 index 45a5eacbae..0000000000 --- a/operator/cmd/certificates/fake/certificatehandler_mock.go +++ /dev/null @@ -1,116 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package fake - -import ( - "crypto/x509" - "encoding/pem" - "sync" -) - -// ICertificateHandlerMock is a mock implementation of certificates.ICertificateHandler. -// -// func TestSomethingThatUsesICertificateHandler(t *testing.T) { -// -// // make and configure a mocked certificates.ICertificateHandler -// mockedICertificateHandler := &ICertificateHandlerMock{ -// DecodeFunc: func(data []byte) (*pem.Block, []byte) { -// panic("mock out the Decode method") -// }, -// ParseFunc: func(der []byte) (*x509.Certificate, error) { -// panic("mock out the Parse method") -// }, -// } -// -// // use mockedICertificateHandler in code that requires certificates.ICertificateHandler -// // and then make assertions. -// -// } -type ICertificateHandlerMock struct { - // DecodeFunc mocks the Decode method. - DecodeFunc func(data []byte) (*pem.Block, []byte) - - // ParseFunc mocks the Parse method. - ParseFunc func(der []byte) (*x509.Certificate, error) - - // calls tracks calls to the methods. - calls struct { - // Decode holds details about calls to the Decode method. - Decode []struct { - // Data is the data argument value. - Data []byte - } - // Parse holds details about calls to the Parse method. - Parse []struct { - // Der is the der argument value. - Der []byte - } - } - lockDecode sync.RWMutex - lockParse sync.RWMutex -} - -// Decode calls DecodeFunc. -func (mock *ICertificateHandlerMock) Decode(data []byte) (*pem.Block, []byte) { - if mock.DecodeFunc == nil { - panic("ICertificateHandlerMock.DecodeFunc: method is nil but ICertificateHandler.Decode was just called") - } - callInfo := struct { - Data []byte - }{ - Data: data, - } - mock.lockDecode.Lock() - mock.calls.Decode = append(mock.calls.Decode, callInfo) - mock.lockDecode.Unlock() - return mock.DecodeFunc(data) -} - -// DecodeCalls gets all the calls that were made to Decode. -// Check the length with: -// -// len(mockedICertificateHandler.DecodeCalls()) -func (mock *ICertificateHandlerMock) DecodeCalls() []struct { - Data []byte -} { - var calls []struct { - Data []byte - } - mock.lockDecode.RLock() - calls = mock.calls.Decode - mock.lockDecode.RUnlock() - return calls -} - -// Parse calls ParseFunc. -func (mock *ICertificateHandlerMock) Parse(der []byte) (*x509.Certificate, error) { - if mock.ParseFunc == nil { - panic("ICertificateHandlerMock.ParseFunc: method is nil but ICertificateHandler.Parse was just called") - } - callInfo := struct { - Der []byte - }{ - Der: der, - } - mock.lockParse.Lock() - mock.calls.Parse = append(mock.calls.Parse, callInfo) - mock.lockParse.Unlock() - return mock.ParseFunc(der) -} - -// ParseCalls gets all the calls that were made to Parse. -// Check the length with: -// -// len(mockedICertificateHandler.ParseCalls()) -func (mock *ICertificateHandlerMock) ParseCalls() []struct { - Der []byte -} { - var calls []struct { - Der []byte - } - mock.lockParse.RLock() - calls = mock.calls.Parse - mock.lockParse.RUnlock() - return calls -} diff --git a/operator/cmd/certificates/watcher.go b/operator/cmd/certificates/watcher.go deleted file mode 100644 index 3621679ccc..0000000000 --- a/operator/cmd/certificates/watcher.go +++ /dev/null @@ -1,132 +0,0 @@ -package certificates - -import ( - "bytes" - "context" - "errors" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/go-logr/logr" - "github.com/spf13/afero" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - certificateRenewalInterval = 6 * time.Hour - ServerKey = "tls.key" - ServerCert = "tls.crt" - CertThreshold = 5 * time.Minute -) - -type CertificateWatcher struct { - apiReader client.Reader - fs afero.Fs - certificateDirectory string - namespace string - certificateSecretName string - certificateTreshold time.Duration - ICertificateHandler - Log logr.Logger -} - -func NewCertificateWatcher(reader client.Reader, certDir string, namespace string, secretName string, log logr.Logger) *CertificateWatcher { - return &CertificateWatcher{ - apiReader: reader, - fs: afero.NewOsFs(), - certificateDirectory: certDir, - namespace: namespace, - certificateSecretName: secretName, - ICertificateHandler: defaultCertificateHandler{}, - certificateTreshold: CertThreshold, - Log: log, - } -} - -func (watcher *CertificateWatcher) watchForCertificatesSecret() { - for { - <-time.After(certificateRenewalInterval) - watcher.Log.Info("checking for new certificates") - if err := watcher.updateCertificatesFromSecret(); err != nil { - watcher.Log.Error(err, "failed to update certificates") - } else { - watcher.Log.Info("updated certificate successfully") - } - } -} - -func (watcher *CertificateWatcher) updateCertificatesFromSecret() error { - var secret corev1.Secret - - err := watcher.apiReader.Get(context.TODO(), - client.ObjectKey{Name: watcher.certificateSecretName, Namespace: watcher.namespace}, &secret) - if err != nil { - return err - } - - watcher.Log.Info("checking dir", "watcher.certificateDirectory ", watcher.certificateDirectory) - if _, err = watcher.fs.Stat(watcher.certificateDirectory); os.IsNotExist(err) { - err = watcher.fs.MkdirAll(watcher.certificateDirectory, 0755) - if err != nil { - return fmt.Errorf("could not create cert directory: %w", err) - } - } - - for _, filename := range []string{ServerCert, ServerKey} { - if err = watcher.ensureCertificateFile(secret, filename); err != nil { - return err - } - } - isValid, err := watcher.ValidateCertificateExpiration(secret.Data[ServerCert], certificateRenewalInterval, time.Now()) - if err != nil { - return err - } else if !isValid { - return fmt.Errorf("certificate is outdated") - } - return nil -} - -func (watcher *CertificateWatcher) ensureCertificateFile(secret corev1.Secret, filename string) error { - f := filepath.Join(watcher.certificateDirectory, filename) - data, err := afero.ReadFile(watcher.fs, f) - if os.IsNotExist(err) || !bytes.Equal(data, secret.Data[filename]) { - return afero.WriteFile(watcher.fs, f, secret.Data[filename], 0666) - } - return err - -} - -func (watcher *CertificateWatcher) WaitForCertificates() { - for threshold := time.Now().Add(watcher.certificateTreshold); time.Now().Before(threshold); { - - if err := watcher.updateCertificatesFromSecret(); err != nil { - if k8serrors.IsNotFound(err) { - watcher.Log.Info("waiting for certificate secret to be available.") - } else { - watcher.Log.Error(err, "failed to update certificates") - } - time.Sleep(10 * time.Second) - continue - } - break - } - go watcher.watchForCertificatesSecret() -} - -func (watcher *CertificateWatcher) ValidateCertificateExpiration(certData []byte, renewalThreshold time.Duration, now time.Time) (bool, error) { - if block, _ := watcher.Decode(certData); block == nil { - watcher.Log.Error(errors.New("can't decode PEM file"), "failed to parse certificate") - return false, nil - } else if cert, err := watcher.Parse(block.Bytes); err != nil { - watcher.Log.Error(err, "failed to parse certificate") - return false, err - } else if now.After(cert.NotAfter.Add(-renewalThreshold)) { - watcher.Log.Info("certificate is outdated, waiting for new ones", "Valid until", cert.NotAfter.UTC()) - return false, nil - } - return true, nil -} diff --git a/operator/cmd/certificates/watcher_test.go b/operator/cmd/certificates/watcher_test.go deleted file mode 100644 index 0b393332d8..0000000000 --- a/operator/cmd/certificates/watcher_test.go +++ /dev/null @@ -1,318 +0,0 @@ -package certificates - -import ( - "bytes" - "crypto/x509" - "encoding/pem" - "os" - "path/filepath" - "testing" - "time" - - "github.com/go-logr/logr/testr" - "github.com/keptn/lifecycle-toolkit/operator/cmd/certificates/fake" - fakeclient "github.com/keptn/lifecycle-toolkit/operator/controllers/common/fake" - "github.com/pkg/errors" - "github.com/spf13/afero" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const CACERT = `-----BEGIN CERTIFICATE----- -MIICPTCCAeKgAwIBAgIRAMIV/0UqFGHgKSYOWBdx/KcwCgYIKoZIzj0EAwIwczEL -MAkGA1UEBhMCQVQxCzAJBgNVBAgTAktMMRMwEQYDVQQHEwpLbGFnZW5mdXJ0MQ4w -DAYDVQQKEwVLZXB0bjEZMBcGA1UECxMQTGlmZWN5Y2xlVG9vbGtpdDEXMBUGA1UE -AwwOKi5rZXB0bi1ucy5zdmMwHhcNMjMwNDE5MTEwNDUzWhcNMjQwNDE4MTEwNDUz -WjBzMQswCQYDVQQGEwJBVDELMAkGA1UECBMCS0wxEzARBgNVBAcTCktsYWdlbmZ1 -cnQxDjAMBgNVBAoTBUtlcHRuMRkwFwYDVQQLExBMaWZlY3ljbGVUb29sa2l0MRcw -FQYDVQQDDA4qLmtlcHRuLW5zLnN2YzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA -BPxAP4JTJfwKz/P32dXuyfVi7kinQPebSYwF/gRAUcN0dCAi6GnxbI2OXlcU0guD -zHXv3VRh3EX2fiNszcfKaCajVzBVMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAK -BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQUGe/8XYV1HsZs -nWsyrOCCGr/sQDAKBggqhkjOPQQDAgNJADBGAiEAkcPaCANDXW5Uillrof0VrnPw -ow49D22Gsrh7YM+vmTQCIQDU1L5IT0Zz+bdIyFSsDnEUXZDeydNv56DoSLh+358Y -aw== ------END CERTIFICATE-----` - -const CAKEY = `-----BEGIN PRIVATE KEY----- -MHcCAQEEII5SAqBxINKatksyu2mTvLZZhfEOpNinYJDwlQjkfreboAoGCCqGSM49 -AwEHoUQDQgAE/EA/glMl/ArP8/fZ1e7J9WLuSKdA95tJjAX+BEBRw3R0ICLoafFs -jY5eVxTSC4PMde/dVGHcRfZ+I2zNx8poJg== ------END PRIVATE KEY-----` - -const uniqueIDPEM = `-----BEGIN CERTIFICATE----- -MIIFsDCCBJigAwIBAgIIrOyC1ydafZMwDQYJKoZIhvcNAQEFBQAwgY4xgYswgYgG -A1UEAx6BgABNAGkAYwByAG8AcwBvAGYAdAAgAEYAbwByAGUAZgByAG8AbgB0ACAA -VABNAEcAIABIAFQAVABQAFMAIABJAG4AcwBwAGUAYwB0AGkAbwBuACAAQwBlAHIA -dABpAGYAaQBjAGEAdABpAG8AbgAgAEEAdQB0AGgAbwByAGkAdAB5MB4XDTE0MDEx -ODAwNDEwMFoXDTE1MTExNTA5Mzc1NlowgZYxCzAJBgNVBAYTAklEMRAwDgYDVQQI -EwdqYWthcnRhMRIwEAYDVQQHEwlJbmRvbmVzaWExHDAaBgNVBAoTE3N0aG9ub3Jl -aG90ZWxyZXNvcnQxHDAaBgNVBAsTE3N0aG9ub3JlaG90ZWxyZXNvcnQxJTAjBgNV -BAMTHG1haWwuc3Rob25vcmVob3RlbHJlc29ydC5jb20wggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCvuu0qpI+Ko2X84Twkf84cRD/rgp6vpgc5Ebejx/D4 -PEVON5edZkazrMGocK/oQqIlRxx/lefponN/chlGcllcVVPWTuFjs8k+Aat6T1qp -4iXxZekAqX+U4XZMIGJD3PckPL6G2RQSlF7/LhGCsRNRdKpMWSTbou2Ma39g52Kf -gsl3SK/GwLiWpxpcSkNQD1hugguEIsQYLxbeNwpcheXZtxbBGguPzQ7rH8c5vuKU -BkMOzaiNKLzHbBdFSrua8KWwCJg76Vdq/q36O9GlW6YgG3i+A4pCJjXWerI1lWwX -Ktk5V+SvUHGey1bkDuZKJ6myMk2pGrrPWCT7jP7WskChAgMBAAGBCQBCr1dgEleo -cKOCAfswggH3MIHDBgNVHREEgbswgbiCHG1haWwuc3Rob25vcmVob3RlbHJlc29y -dC5jb22CIGFzaGNoc3ZyLnN0aG9ub3JlaG90ZWxyZXNvcnQuY29tgiRBdXRvRGlz -Y292ZXIuc3Rob25vcmVob3RlbHJlc29ydC5jb22CHEF1dG9EaXNjb3Zlci5ob3Rl -bHJlc29ydC5jb22CCEFTSENIU1ZSghdzdGhvbm9yZWhvdGVscmVzb3J0LmNvbYIP -aG90ZWxyZXNvcnQuY29tMCEGCSsGAQQBgjcUAgQUHhIAVwBlAGIAUwBlAHIAdgBl -AHIwHQYDVR0OBBYEFMAC3UR4FwAdGekbhMgnd6lMejtbMAsGA1UdDwQEAwIFoDAT -BgNVHSUEDDAKBggrBgEFBQcDATAJBgNVHRMEAjAAMIG/BgNVHQEEgbcwgbSAFGfF -6xihk+gJJ5TfwvtWe1UFnHLQoYGRMIGOMYGLMIGIBgNVBAMegYAATQBpAGMAcgBv -AHMAbwBmAHQAIABGAG8AcgBlAGYAcgBvAG4AdAAgAFQATQBHACAASABUAFQAUABT -ACAASQBuAHMAcABlAGMAdABpAG8AbgAgAEMAZQByAHQAaQBmAGkAYwBhAHQAaQBv -AG4AIABBAHUAdABoAG8AcgBpAHQAeYIIcKhXEmBXr0IwDQYJKoZIhvcNAQEFBQAD -ggEBABlSxyCMr3+ANr+WmPSjyN5YCJBgnS0IFCwJAzIYP87bcTye/U8eQ2+E6PqG -Q7Huj7nfHEw9qnGo+HNyPp1ad3KORzXDb54c6xEoi+DeuPzYHPbn4c3hlH49I0aQ -eWW2w4RslSWpLvO6Y7Lboyz2/Thk/s2kd4RHxkkWpH2ltPqJuYYg3X6oM5+gIFHJ -WGnh+ojZ5clKvS5yXh3Wkj78M6sb32KfcBk0Hx6NkCYPt60ODYmWtvqwtw6r73u5 -TnTYWRNvo2svX69TriL+CkHY9O1Hkwf2It5zHl3gNiKTJVaak8AuEz/CKWZneovt -yYLwhUhg3PX5Co1VKYE+9TxloiE= ------END CERTIFICATE-----` - -var ERR_BAD_CERT = errors.New("bad cert") - -var emptySecret = v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "my-cert", - }, -} - -var goodSecret = v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "my-cert", - }, - Data: map[string][]byte{ - ServerCert: []byte(CACERT), - ServerKey: []byte(CAKEY), - }, -} - -func TestCertificateWatcher_ValidateCertificateExpiration(t *testing.T) { - - tests := []struct { - name string - certHandler ICertificateHandler - certData []byte - renewalThreshold time.Duration - now time.Time - want bool - wantErr error - }{ - { - name: "certificate cannot be decoded", - certHandler: &fake.ICertificateHandlerMock{ - DecodeFunc: func(data []byte) (p *pem.Block, rest []byte) { - return nil, nil //fake a failure in the decoding - }, - ParseFunc: nil, - }, - want: false, - }, - { - name: "certificate cannot be parsed", - certHandler: &fake.ICertificateHandlerMock{ - DecodeFunc: func(data []byte) (p *pem.Block, rest []byte) { - return &pem.Block{Type: "test", Bytes: []byte("testdata")}, nil - }, - ParseFunc: func(der []byte) (*x509.Certificate, error) { - return nil, ERR_BAD_CERT - }, - }, - want: false, - wantErr: ERR_BAD_CERT, - }, - { - name: "good certificate - unexpired", - certData: []byte(uniqueIDPEM), - certHandler: defaultCertificateHandler{}, - want: true, - }, - { - name: "good certificate - expired", - certData: []byte(uniqueIDPEM), - now: time.Now(), //setting up now makes sure that the threshold is passed - certHandler: defaultCertificateHandler{}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - ICertificateHandler: tt.certHandler, - Log: testr.New(t), - } - got, err := watcher.ValidateCertificateExpiration(tt.certData, tt.renewalThreshold, tt.now) - if tt.wantErr != nil { - require.Error(t, err) - t.Log("want:", tt.wantErr, "got:", err) - require.True(t, errors.Is(tt.wantErr, err)) - } - require.Equal(t, got, tt.want) - }) - } -} - -func TestCertificateWatcher_ensureCertificateFile(t *testing.T) { - - certdir := t.TempDir() - f := filepath.Join(certdir, ServerCert) - err := os.WriteFile(f, goodSecret.Data[ServerCert], 0666) - require.Nil(t, err) - baddir := t.TempDir() - f = filepath.Join(baddir, ServerCert) - err = os.WriteFile(f, goodSecret.Data[ServerKey], 0666) - require.Nil(t, err) - tests := []struct { - name string - fs afero.Fs - secret v1.Secret - filename string - certDir string - wantErr bool - err string - }{ - { - name: "if good cert exist in fs no error", - secret: goodSecret, - certDir: certdir, - filename: ServerCert, - wantErr: false, - }, - - { - name: "if unexisting file name, we expect a file system error", - secret: emptySecret, - filename: "$%&/())=$§%/=", - certDir: baddir, - wantErr: true, - err: "no such file or directory", - }, - - { - name: "wrong file content is replaced with updated cert", - certDir: baddir, - secret: goodSecret, - filename: ServerCert, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - fs: afero.NewOsFs(), - certificateDirectory: tt.certDir, - } - err := watcher.ensureCertificateFile(tt.secret, tt.filename) - if !tt.wantErr { - require.Nil(t, err) - f = filepath.Join(tt.certDir, ServerCert) - data, err := os.ReadFile(f) - if err != nil { - panic(err) - } - if !bytes.Equal(data, tt.secret.Data[tt.filename]) { - t.Errorf("ensureCertificateFile()data %v was not replaced with %v", data, tt.secret.Data[tt.filename]) - } - } else { - require.Contains(t, err.Error(), tt.err) - } - }) - } -} - -func TestCertificateWatcher_updateCertificatesFromSecret(t *testing.T) { - - oldDir := t.TempDir() - os.Remove(oldDir) - - tests := []struct { - name string - apiReader client.Reader - certificateDirectory string - namespace string - certificateSecretName string - wantErr error - }{ - { - name: "certificate not found", - apiReader: fakeclient.NewClient(), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("secrets \"my-cert\" not found"), - }, - { - name: "outdated certificate found, nothing in dir", - apiReader: fakeclient.NewClient(&emptySecret), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("certificate is outdated"), - }, - - { - name: "outdated certificate found, not existing in dir", - apiReader: fakeclient.NewClient(&emptySecret), - certificateDirectory: oldDir, - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("certificate is outdated"), - }, - { - name: "good certificate - not stored", - apiReader: fakeclient.NewClient(&goodSecret), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - apiReader: tt.apiReader, - fs: afero.NewOsFs(), - certificateDirectory: tt.certificateDirectory, - namespace: tt.namespace, - certificateSecretName: tt.certificateSecretName, - ICertificateHandler: defaultCertificateHandler{}, - Log: testr.New(t), - } - err := watcher.updateCertificatesFromSecret() - if tt.wantErr == nil { - require.Nil(t, err) - } else { - require.NotNil(t, err) - require.Contains(t, err.Error(), tt.wantErr.Error()) - } - }) - } -} - -func TestNewCertificateWatcher(t *testing.T) { - logger := testr.New(t) - client := fakeclient.NewClient() - want := &CertificateWatcher{ - apiReader: client, - fs: afero.NewOsFs(), - namespace: "default", - certificateSecretName: "my-secret", - certificateDirectory: "test", - certificateTreshold: CertThreshold, - ICertificateHandler: defaultCertificateHandler{}, - Log: testr.New(t), - } - got := NewCertificateWatcher(client, "test", "default", "my-secret", logger) - require.EqualValues(t, got, want) - -} diff --git a/operator/cmd/webhook/builder.go b/operator/cmd/webhook/builder.go deleted file mode 100644 index d87b0ef591..0000000000 --- a/operator/cmd/webhook/builder.go +++ /dev/null @@ -1,96 +0,0 @@ -package webhook - -import ( - "flag" - - "github.com/keptn/lifecycle-toolkit/operator/cmd/certificates" - "github.com/keptn/lifecycle-toolkit/operator/cmd/config" - cmdManager "github.com/keptn/lifecycle-toolkit/operator/cmd/manager" - "github.com/keptn/lifecycle-toolkit/operator/webhooks" - "github.com/keptn/lifecycle-toolkit/operator/webhooks/pod_mutator" - "github.com/pkg/errors" - "go.opentelemetry.io/otel" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -const ( - FlagCertificateDirectory = "certs-dir" - FlagCertificateFileName = "cert" - FlagCertificateKeyFileName = "cert-key" -) - -var ( - certificateDirectory string - certificateFileName string - certificateKeyFileName string -) - -type Builder struct { - configProvider config.Provider - managerProvider cmdManager.Provider - namespace string - podName string -} - -func NewWebhookBuilder() Builder { - return Builder{} -} - -func (builder Builder) SetConfigProvider(provider config.Provider) Builder { - builder.configProvider = provider - return builder -} - -func (builder Builder) SetManagerProvider(provider cmdManager.Provider) Builder { - builder.managerProvider = provider - return builder -} - -func (builder Builder) SetNamespace(namespace string) Builder { - builder.namespace = namespace - return builder -} - -func (builder Builder) SetPodName(podName string) Builder { - builder.podName = podName - return builder -} - -func (builder Builder) GetManagerProvider() cmdManager.Provider { - if builder.managerProvider == nil { - builder.managerProvider = NewWebhookManagerProvider(certificateDirectory, certificateKeyFileName, certificateFileName) - } - - return builder.managerProvider -} - -func (builder Builder) Run(webhookManager manager.Manager) error { - - addFlags() - builder.GetManagerProvider().SetupWebhookServer(webhookManager) - - certificates. - NewCertificateWatcher(webhookManager.GetAPIReader(), webhookManager.GetWebhookServer().CertDir, builder.namespace, webhooks.SecretCertsName, ctrl.Log.WithName("Webhook Cert Manager")). - WaitForCertificates() - - webhookManager.GetWebhookServer().Register("/mutate-v1-pod", &webhook.Admission{ - Handler: &pod_mutator.PodMutatingWebhook{ - Client: webhookManager.GetClient(), - Tracer: otel.Tracer("keptn/webhook"), - Recorder: webhookManager.GetEventRecorderFor("keptn/webhook"), - Log: ctrl.Log.WithName("Mutating Webhook"), - }}) - - signalHandler := ctrl.SetupSignalHandler() - err := webhookManager.Start(signalHandler) - return errors.WithStack(err) -} - -func addFlags() { - flag.StringVar(&certificateDirectory, FlagCertificateDirectory, "/tmp/webhook/certs", "Directory to look certificates for.") - flag.StringVar(&certificateFileName, FlagCertificateFileName, "tls.crt", "File name for the public certificate.") - flag.StringVar(&certificateKeyFileName, FlagCertificateKeyFileName, "tls.key", "File name for the private key.") - flag.Parse() -} diff --git a/operator/cmd/webhook/builder_test.go b/operator/cmd/webhook/builder_test.go deleted file mode 100644 index 1e5118573a..0000000000 --- a/operator/cmd/webhook/builder_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package webhook - -import ( - "testing" - - "github.com/keptn/lifecycle-toolkit/operator/cmd/fake" - "github.com/stretchr/testify/assert" -) - -func TestWebhookCommandBuilder(t *testing.T) { - - t.Run("set config provider", func(t *testing.T) { - builder := NewWebhookBuilder() - - assert.NotNil(t, builder) - - expectedProvider := &fake.MockProvider{} - builder = builder.SetConfigProvider(expectedProvider) - - assert.Equal(t, expectedProvider, builder.configProvider) - }) - t.Run("set manager provider", func(t *testing.T) { - expectedProvider := &fake.MockWebhookManager{} - builder := NewWebhookBuilder().SetManagerProvider(expectedProvider) - - assert.Equal(t, expectedProvider, builder.managerProvider) - }) - t.Run("set namespace", func(t *testing.T) { - builder := NewWebhookBuilder().SetNamespace("namespace") - - assert.Equal(t, "namespace", builder.namespace) - }) -} diff --git a/operator/cmd/webhook/manager.go b/operator/cmd/webhook/manager.go deleted file mode 100644 index 0e0fedccc6..0000000000 --- a/operator/cmd/webhook/manager.go +++ /dev/null @@ -1,43 +0,0 @@ -package webhook - -import ( - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -const ( - metricsBindAddress = ":8383" - port = 8443 -) - -type WebhookProvider struct { - certificateDirectory string - certificateFileName string - keyFileName string -} - -func NewWebhookManagerProvider(certificateDirectory string, keyFileName string, certificateFileName string) WebhookProvider { - return WebhookProvider{ - certificateDirectory: certificateDirectory, - certificateFileName: certificateFileName, - keyFileName: keyFileName, - } -} - -func (provider WebhookProvider) createOptions(scheme *runtime.Scheme, namespace string) ctrl.Options { - return ctrl.Options{ - Scheme: scheme, - MetricsBindAddress: metricsBindAddress, - Port: port, - Namespace: namespace, - } -} - -func (provider WebhookProvider) SetupWebhookServer(mgr manager.Manager) { - webhookServer := mgr.GetWebhookServer() - webhookServer.CertDir = provider.certificateDirectory - webhookServer.KeyName = provider.keyFileName - webhookServer.CertName = provider.certificateFileName - -} diff --git a/operator/cmd/webhook/manager_test.go b/operator/cmd/webhook/manager_test.go deleted file mode 100644 index 3e0913648e..0000000000 --- a/operator/cmd/webhook/manager_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package webhook - -import ( - "testing" - - "github.com/keptn/lifecycle-toolkit/operator/cmd/fake" - cmdManager "github.com/keptn/lifecycle-toolkit/operator/cmd/manager" - "github.com/stretchr/testify/assert" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -func TestCreateOptions(t *testing.T) { - - t.Run("implements interface", func(t *testing.T) { - var provider cmdManager.Provider = NewWebhookManagerProvider("certs-dir", "key-file", "cert-file") - - providerImpl := provider.(WebhookProvider) - assert.Equal(t, "certs-dir", providerImpl.certificateDirectory) - assert.Equal(t, "key-file", providerImpl.keyFileName) - assert.Equal(t, "cert-file", providerImpl.certificateFileName) - }) - t.Run("creates options", func(t *testing.T) { - provider := WebhookProvider{} - options := provider.createOptions(scheme.Scheme, "test-namespace") - - assert.NotNil(t, options) - assert.Equal(t, "test-namespace", options.Namespace) - assert.Equal(t, scheme.Scheme, options.Scheme) - assert.Equal(t, metricsBindAddress, options.MetricsBindAddress) - assert.Equal(t, port, options.Port) - }) - t.Run("configures webhooks server", func(t *testing.T) { - provider := NewWebhookManagerProvider("certs-dir", "key-file", "cert-file") - expectedWebhookServer := &webhook.Server{} - - mgr := &fake.MockManager{ - GetWebhookServerFunc: func() *webhook.Server { - return expectedWebhookServer - }, - } - - provider.SetupWebhookServer(mgr) - - assert.Equal(t, "certs-dir", expectedWebhookServer.CertDir) - assert.Equal(t, "key-file", expectedWebhookServer.KeyName) - assert.Equal(t, "cert-file", expectedWebhookServer.CertName) - - mgrWebhookServer := mgr.GetWebhookServer() - assert.Equal(t, "certs-dir", mgrWebhookServer.CertDir) - assert.Equal(t, "key-file", mgrWebhookServer.KeyName) - assert.Equal(t, "cert-file", mgrWebhookServer.CertName) - }) -} diff --git a/operator/config/manager/manager.yaml b/operator/config/manager/manager.yaml index 1558e8c7a0..1c6b9ecd41 100644 --- a/operator/config/manager/manager.yaml +++ b/operator/config/manager/manager.yaml @@ -40,8 +40,6 @@ spec: - /manager args: - webhook-server - # OLM mounts the certificates here, so we reuse it for simplicity - - --certs-dir=/tmp/k8s-webhook-server/serving-certs/ - --leader-elect # Secure port for the metrics adapter - --adapter-port=6443 diff --git a/operator/go.mod b/operator/go.mod index 834f082fa8..5feeb0250b 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -8,13 +8,13 @@ require ( github.com/go-logr/logr v1.2.4 github.com/imdario/mergo v0.3.15 github.com/kelseyhightower/envconfig v1.4.0 + github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4 github.com/magiconair/properties v1.8.7 github.com/onsi/ginkgo/v2 v2.9.4 github.com/onsi/gomega v1.27.6 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 - github.com/spf13/afero v1.9.5 github.com/stretchr/testify v1.8.2 go.opentelemetry.io/otel v1.11.2 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2 @@ -80,6 +80,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/spf13/afero v1.9.5 // indirect github.com/spf13/pflag v1.0.5 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect diff --git a/operator/go.sum b/operator/go.sum index 926148111b..f2c77b678a 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -206,6 +206,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f h1:ildQjDQmBjX3fdAShsQJN7dkKfhivlnlDbEh/Etjli4= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4 h1:LI+iOb7v1zIAtHQum79CbV+4HB1PCAim+TuCCRRsW7o= github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4/go.mod h1:8rQ1flqblBWy43k4xJnoaMUA7e50zP95QIab3z6NCw4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= diff --git a/operator/main.go b/operator/main.go index f022405e00..d7b70e9b87 100644 --- a/operator/main.go +++ b/operator/main.go @@ -25,13 +25,14 @@ import ( argov1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/kelseyhightower/envconfig" + "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/certificates" + certCommon "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/common" + "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/webhook" metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" lifecyclev1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha1" lifecyclev1alpha2 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2" lifecyclev1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" optionsv1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/options/v1alpha1" - cmdConfig "github.com/keptn/lifecycle-toolkit/operator/cmd/config" - "github.com/keptn/lifecycle-toolkit/operator/cmd/webhook" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/keptnapp" "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/keptnappcreationrequest" @@ -42,7 +43,9 @@ import ( "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/keptnworkload" "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/keptnworkloadinstance" controlleroptions "github.com/keptn/lifecycle-toolkit/operator/controllers/options" + "github.com/keptn/lifecycle-toolkit/operator/webhooks/pod_mutator" "github.com/prometheus/client_golang/prometheus/promhttp" + "go.opentelemetry.io/otel" otelprom "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/sdk/metric" corev1 "k8s.io/api/core/v1" @@ -53,6 +56,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + ctrlWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" ) var ( @@ -318,10 +322,31 @@ func main() { webhookBuilder := webhook.NewWebhookBuilder(). SetNamespace(env.PodNamespace). SetPodName(env.PodName). - SetConfigProvider(cmdConfig.NewKubeConfigProvider()) + SetManagerProvider( + webhook.NewWebhookManagerProvider( + mgr.GetWebhookServer().CertDir, "tls.key", "tls.crt"), + ). + SetCertificateWatcher( + certificates.NewCertificateWatcher( + mgr.GetAPIReader(), + mgr.GetWebhookServer().CertDir, + env.PodNamespace, + certCommon.SecretName, + setupLog, + ), + ) setupLog.Info("starting webhook and manager") - if err1 := webhookBuilder.Run(mgr); err1 != nil { + if err := webhookBuilder.Run(mgr, map[string]*ctrlWebhook.Admission{ + "/mutate-v1-pod": { + Handler: &pod_mutator.PodMutatingWebhook{ + Client: mgr.GetClient(), + Tracer: otel.Tracer("keptn/webhook"), + Recorder: mgr.GetEventRecorderFor("keptn/webhook"), + Log: ctrl.Log.WithName("Mutating Webhook"), + }, + }, + }); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } From 12fcca82b904ba2f2ca72347cb176771d84d63df Mon Sep 17 00:00:00 2001 From: Florian Bacher Date: Mon, 15 May 2023 09:00:30 +0200 Subject: [PATCH 18/62] chore(cert-manager): updated readme of cert-manager (#1393) Signed-off-by: Florian Bacher --- klt-cert-manager/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/klt-cert-manager/README.md b/klt-cert-manager/README.md index 8842d2bc27..3d279c1dda 100644 --- a/klt-cert-manager/README.md +++ b/klt-cert-manager/README.md @@ -52,6 +52,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "github.com/keptn/lifecycle-toolkit/klt-cert-manager/controllers/keptnwebhookcontroller" + "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/certificates" + certCommon "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/common" "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/webhook" // +kubebuilder:scaffold:imports ) @@ -76,7 +78,20 @@ func main() { webhookBuilder := webhook.NewWebhookBuilder(). SetNamespace(env.PodNamespace). SetPodName(env.PodName). - SetConfigProvider(cmdConfig.NewKubeConfigProvider()) + SetConfigProvider(cmdConfig.NewKubeConfigProvider()). + SetManagerProvider( + webhook.NewWebhookManagerProvider( + mgr.GetWebhookServer().CertDir, "tls.key", "tls.crt"), + ). + SetCertificateWatcher( + certificates.NewCertificateWatcher( + mgr.GetAPIReader(), + mgr.GetWebhookServer().CertDir, + env.PodNamespace, + certCommon.SecretName, + setupLog, + ), + ) setupLog.Info("starting webhook and manager") if err := webhookBuilder.Run(mgr, map[string]*admission.Webhook{ From b2392c1d6a81df92adf6228167a52233eb1757ae Mon Sep 17 00:00:00 2001 From: Giovanni Liva Date: Mon, 15 May 2023 09:43:51 +0200 Subject: [PATCH 19/62] docs: fix markdown links (#1414) --- docs/.htmltest.yml | 1 + docs/content/en/docs/getting-started/metrics/_index.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/.htmltest.yml b/docs/.htmltest.yml index 3764f4c826..df24819fb8 100644 --- a/docs/.htmltest.yml +++ b/docs/.htmltest.yml @@ -8,4 +8,5 @@ IgnoreDirs: IgnoreURLs: - "linkedin.com" - "localhost" + - "twitter.com" StripQueryString: false diff --git a/docs/content/en/docs/getting-started/metrics/_index.md b/docs/content/en/docs/getting-started/metrics/_index.md index 8f9f7d48cf..22b7388d6b 100644 --- a/docs/content/en/docs/getting-started/metrics/_index.md +++ b/docs/content/en/docs/getting-started/metrics/_index.md @@ -259,7 +259,7 @@ The Kubernetes HorizontalPodAutoscaler (HPA) uses metrics to provide autoscaling for the cluster. HPA can retrieve KeptnMetrics and use it to implement HPA. See -Using the [HorizontalPodAutoscaler](../../implementing/evaluatemetrics/#using-the-horizontalpodautoscaler) +Using the [HorizontalPodAutoscaler](../../implementing/evaluatemetrics.md/#using-the-horizontalpodautoscaler) for detailed information. ## Learn more From 9b9a973a95ca59e91927695f92c0d56389be3f4f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 10:06:21 +0200 Subject: [PATCH 20/62] deps: update dependency argoproj/argo-cd to v2.7.1 (#1374) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- examples/support/argo/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/support/argo/Makefile b/examples/support/argo/Makefile index 866a64239d..1a55bc6cf8 100644 --- a/examples/support/argo/Makefile +++ b/examples/support/argo/Makefile @@ -2,7 +2,7 @@ LFC_NAMESPACE ?= keptn-lifecycle-toolkit-system PODTATO_NAMESPACE ?= podtato-kubectl ARGO_NAMESPACE ?= argocd # renovate: datasource=github-tags depName=argoproj/argo-cd -ARGO_VERSION ?= v2.6.7 +ARGO_VERSION ?= v2.7.1 ARGO_SECRET = $(shell kubectl -n ${ARGO_NAMESPACE} get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo) .PHONY: install From dab62dea8b5c0a45baba9d9b3e33c1e6f6f640e4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 10:06:35 +0200 Subject: [PATCH 21/62] deps: update dependency jaegertracing/jaeger to v1.45.0 (#1407) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- examples/sample-app/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sample-app/Makefile b/examples/sample-app/Makefile index 4427f774cf..5ddcbf2e2c 100644 --- a/examples/sample-app/Makefile +++ b/examples/sample-app/Makefile @@ -1,5 +1,5 @@ # renovate: datasource=github-tags depName=jaegertracing/jaeger -JAEGER_VERSION ?= v1.44.0 +JAEGER_VERSION ?= v1.45.0 LFC_NAMESPACE ?= keptn-lifecycle-controller-system PODTATO_NAMESPACE ?= podtato-kubectl GRAFANA_PORT_FORWARD ?= 3000 From 2f75e739ca8fa218b3d15ccf657a5d85530eecf5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 10:06:47 +0200 Subject: [PATCH 22/62] deps: update module github.com/argoproj/argo-rollouts to v1.5.0 (#1408) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- operator/go.mod | 4 ++-- operator/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/operator/go.mod b/operator/go.mod index 5feeb0250b..686dd21ca8 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -3,7 +3,7 @@ module github.com/keptn/lifecycle-toolkit/operator go 1.20 require ( - github.com/argoproj/argo-rollouts v1.4.1 + github.com/argoproj/argo-rollouts v1.5.0 github.com/benbjohnson/clock v1.3.3 github.com/go-logr/logr v1.2.4 github.com/imdario/mergo v0.3.15 @@ -97,7 +97,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/component-base v0.26.4 // indirect k8s.io/klog/v2 v2.90.1 // indirect - k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/operator/go.sum b/operator/go.sum index f2c77b678a..d474cb3809 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -40,8 +40,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/argoproj/argo-rollouts v1.4.1 h1:P+aTqdjMmWJDJfAbyVkCbONIzoGXSRVRBvim6VWxMJo= -github.com/argoproj/argo-rollouts v1.4.1/go.mod h1:KR9pcBicOYmPOu50bBLRQfp/UQVkRGoUkidHVsyjV1Q= +github.com/argoproj/argo-rollouts v1.5.0 h1:asKpzMuFSDGsXK5cGsbH/TGXUJD/v1mvM1D3o5Xwz34= +github.com/argoproj/argo-rollouts v1.5.0/go.mod h1:OaOf+oZawsss6fy+9WEDy4IaSbwuRteBj1X2QiVfqdA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.3 h1:g+rSsSaAzhHJYcIQE78hJ3AhyjjtQvleKDjlhdBnIhc= github.com/benbjohnson/clock v1.3.3/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -653,8 +653,8 @@ k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From fad37afd14cd781c9561d43dd2f1af6824973693 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 10:07:07 +0200 Subject: [PATCH 23/62] deps: update dependency kubernetes-sigs/kustomize to v5.0.3 (#1402) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Makefile | 2 +- klt-cert-manager/Makefile | 2 +- metrics-operator/Makefile | 2 +- operator/Makefile | 2 +- scheduler/Makefile | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 9c8906dea1..8c3dc8cc31 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Image URL to use all building/pushing image targets # renovate: datasource=github-tags depName=kubernetes-sigs/kustomize -KUSTOMIZE_VERSION?=v5.0.1 +KUSTOMIZE_VERSION?=v5.0.3 # renovate: datasource=github-tags depName=helm/helm HELM_VERSION ?= v3.11.3 CHART_APPVERSION ?= v0.7.1 # x-release-please-version diff --git a/klt-cert-manager/Makefile b/klt-cert-manager/Makefile index 8284d7fc54..22c24843a6 100644 --- a/klt-cert-manager/Makefile +++ b/klt-cert-manager/Makefile @@ -118,7 +118,7 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest ## Tool Versions # renovate: datasource=github-tags depName=kubernetes-sigs/kustomize -KUSTOMIZE_VERSION?=v5.0.1 +KUSTOMIZE_VERSION?=v5.0.3 # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools CONTROLLER_TOOLS_VERSION?=v0.12.0 diff --git a/metrics-operator/Makefile b/metrics-operator/Makefile index 69ea38aef8..0a101faca3 100644 --- a/metrics-operator/Makefile +++ b/metrics-operator/Makefile @@ -25,7 +25,7 @@ ENVTEST_K8S_VERSION=1.24.2 ## Tool Versions # renovate: datasource=github-tags depName=kubernetes-sigs/kustomize -KUSTOMIZE_VERSION?=v5.0.1 +KUSTOMIZE_VERSION?=v5.0.3 # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools CONTROLLER_TOOLS_VERSION?=v0.12.0 diff --git a/operator/Makefile b/operator/Makefile index f13960097a..19df8a9110 100644 --- a/operator/Makefile +++ b/operator/Makefile @@ -25,7 +25,7 @@ ENVTEST_K8S_VERSION=1.24.2 ## Tool Versions # renovate: datasource=github-tags depName=kubernetes-sigs/kustomize -KUSTOMIZE_VERSION?=v5.0.1 +KUSTOMIZE_VERSION?=v5.0.3 # renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools CONTROLLER_TOOLS_VERSION?=v0.12.0 diff --git a/scheduler/Makefile b/scheduler/Makefile index a7de6efa6c..55917be1a5 100644 --- a/scheduler/Makefile +++ b/scheduler/Makefile @@ -46,7 +46,7 @@ $(LOCALBIN): ## Tool Versions # renovate: datasource=github-tags depName=kubernetes-sigs/kustomize -KUSTOMIZE_VERSION ?= v5.0.1 +KUSTOMIZE_VERSION ?= v5.0.3 ## Tool Binaries KUSTOMIZE ?= $(LOCALBIN)/kustomize From a5d9b19901f673768cef63dcc1606aafbc5a1b51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 10:22:41 +0200 Subject: [PATCH 24/62] deps: update module google.golang.org/grpc to v1.54.1 (#1404) Signed-off-by: Renovate Bot Signed-off-by: Florian Bacher Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Florian Bacher --- operator/go.mod | 2 +- operator/go.sum | 4 ++-- scheduler/go.mod | 2 +- scheduler/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/operator/go.mod b/operator/go.mod index 686dd21ca8..a25f8cb8aa 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -24,7 +24,7 @@ require ( go.opentelemetry.io/otel/sdk v1.11.2 go.opentelemetry.io/otel/sdk/metric v0.34.0 go.opentelemetry.io/otel/trace v1.11.2 - google.golang.org/grpc v1.54.0 + google.golang.org/grpc v1.54.1 k8s.io/api v0.26.4 k8s.io/apiextensions-apiserver v0.26.4 k8s.io/apimachinery v0.26.4 diff --git a/operator/go.sum b/operator/go.sum index d474cb3809..c32d809ade 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -596,8 +596,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.54.1 h1:zQZQNqQZU9cHv2vLdDhB2mFeDZ2hGpgYM1A0PKjFsSM= +google.golang.org/grpc v1.54.1/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/scheduler/go.mod b/scheduler/go.mod index 83fd4b071c..f46c7fc65d 100644 --- a/scheduler/go.mod +++ b/scheduler/go.mod @@ -13,7 +13,7 @@ require ( go.opentelemetry.io/otel/exporters/stdout v0.20.0 go.opentelemetry.io/otel/sdk v0.20.0 go.opentelemetry.io/otel/trace v0.20.0 - google.golang.org/grpc v1.54.0 + google.golang.org/grpc v1.54.1 k8s.io/api v0.25.9 k8s.io/apimachinery v0.25.9 k8s.io/apiserver v0.25.9 diff --git a/scheduler/go.sum b/scheduler/go.sum index ba061d91c4..ece29ffc50 100644 --- a/scheduler/go.sum +++ b/scheduler/go.sum @@ -654,8 +654,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.54.1 h1:zQZQNqQZU9cHv2vLdDhB2mFeDZ2hGpgYM1A0PKjFsSM= +google.golang.org/grpc v1.54.1/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 9085785b669bbc5bdd418afa6e9bd2f81c788653 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 10:24:59 +0200 Subject: [PATCH 25/62] deps: update anchore/sbom-action action to v0.14.2 (#1401) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2643eaee17..c5062880a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,7 +112,7 @@ jobs: ${{ env.IMAGE_NAME }}@${{ env.IMAGE_DIGEST }} - name: Generate SBOM - uses: anchore/sbom-action@v0.14.1 + uses: anchore/sbom-action@v0.14.2 with: image: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} artifact-name: sbom-${{ matrix.config.name }} From dab73fb94f85022d84436453a84bf19f7f95cc5c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 10:26:10 +0200 Subject: [PATCH 26/62] deps: update dependency jaegertracing/jaeger-operator to v1.44.0 (#1258) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- examples/Makefile | 2 +- examples/support/observability/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index 80186a9b6d..12bb35d025 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,5 +1,5 @@ # renovate: datasource=github-tags depName=jaegertracing/jaeger-operator -JAEGER_VERSION ?= v1.43.0 +JAEGER_VERSION ?= v1.44.0 TOOLKIT_NAMESPACE ?= keptn-lifecycle-toolkit-system PODTATO_NAMESPACE ?= podtato-kubectl GRAFANA_PORT_FORWARD ?= 3000 diff --git a/examples/support/observability/Makefile b/examples/support/observability/Makefile index fe94c7df7d..3d5a365a33 100644 --- a/examples/support/observability/Makefile +++ b/examples/support/observability/Makefile @@ -1,5 +1,5 @@ # renovate: datasource=github-tags depName=jaegertracing/jaeger-operator -JAEGER_VERSION ?= v1.42.0 +JAEGER_VERSION ?= v1.44.0 TOOLKIT_NAMESPACE ?= keptn-lifecycle-toolkit-system PODTATO_NAMESPACE ?= podtato-kubectl GRAFANA_PORT_FORWARD ?= 3000 From f88dfd5c0d7a544d94054cce6693ebd3d88f0a9f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 10:28:14 +0200 Subject: [PATCH 27/62] deps: update module github.com/benbjohnson/clock to v1.3.4 (#1403) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- metrics-operator/go.mod | 2 +- metrics-operator/go.sum | 4 ++-- operator/go.mod | 2 +- operator/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index 9cc919cedf..2e1844c50a 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/DataDog/datadog-api-client-go/v2 v2.12.0 - github.com/benbjohnson/clock v1.3.3 + github.com/benbjohnson/clock v1.3.4 github.com/go-logr/logr v1.2.4 github.com/gorilla/mux v1.8.0 github.com/kelseyhightower/envconfig v1.4.0 diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index 799bc6ae6f..bd84793da3 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -59,8 +59,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.3 h1:g+rSsSaAzhHJYcIQE78hJ3AhyjjtQvleKDjlhdBnIhc= -github.com/benbjohnson/clock v1.3.3/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.4 h1:wj3BFPrTw8yYgA1OlMqvUk95nc8OMv3cvBSF5erT2W4= +github.com/benbjohnson/clock v1.3.4/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/operator/go.mod b/operator/go.mod index a25f8cb8aa..9345ac695b 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/argoproj/argo-rollouts v1.5.0 - github.com/benbjohnson/clock v1.3.3 + github.com/benbjohnson/clock v1.3.4 github.com/go-logr/logr v1.2.4 github.com/imdario/mergo v0.3.15 github.com/kelseyhightower/envconfig v1.4.0 diff --git a/operator/go.sum b/operator/go.sum index c32d809ade..9007300185 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -43,8 +43,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/argoproj/argo-rollouts v1.5.0 h1:asKpzMuFSDGsXK5cGsbH/TGXUJD/v1mvM1D3o5Xwz34= github.com/argoproj/argo-rollouts v1.5.0/go.mod h1:OaOf+oZawsss6fy+9WEDy4IaSbwuRteBj1X2QiVfqdA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.3 h1:g+rSsSaAzhHJYcIQE78hJ3AhyjjtQvleKDjlhdBnIhc= -github.com/benbjohnson/clock v1.3.3/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.4 h1:wj3BFPrTw8yYgA1OlMqvUk95nc8OMv3cvBSF5erT2W4= +github.com/benbjohnson/clock v1.3.4/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= From e40292ce995070a492187f5dcc7db363e03eb260 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Tue, 16 May 2023 06:01:48 -0700 Subject: [PATCH 28/62] docs: create KeptnConfig yaml ref page (#1369) --- docs/content/en/docs/yaml-crd-ref/config.md | 71 +++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/docs/content/en/docs/yaml-crd-ref/config.md b/docs/content/en/docs/yaml-crd-ref/config.md index 75a054c5af..0e308a3968 100644 --- a/docs/content/en/docs/yaml-crd-ref/config.md +++ b/docs/content/en/docs/yaml-crd-ref/config.md @@ -3,3 +3,74 @@ title: KeptnConfig description: Define configuration values weight: 20 --- + +`KeptnConfig` defines configuration values for the Keptn Lifecycle Toolkit. + +## Yaml Synopsis + +```yaml +apiVersion: options.keptn.sh/v?alpha? +kind: KeptnConfig +metadata: + name: +spec: + OTelCollectorUrl: '' + keptnAppCreationRequestTimeoutSeconds: <#-seconds> +``` + +## Fields + +* **apiVersion** -- API version being used. +* **kind** -- Resource type. + Must be set to `KeptnConfig`. + +* **metadata** + * **name** -- Unique name of this set of configurations. + Names must comply with the + [Kubernetes Object Names and IDs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names) + specification. + +* **spec** + * **OTelCollectorUrl** -- The URL and port of the OpenTelemetry collector + This field must be populated + in order to export traces to the OpenTelemetry Collector. + * **keptnAppCreationRequestTimeoutSeconds** -- + interval in which automatic app discovery searches for workloads + to put into the same auto-generated [KeptnApp](app.md). + The default value is 30 (seconds). + +## Usage + +Each cluster should have a single `KeptnConfig` CRD +that describes all configurations for that cluster. + +## Example + +### OTel example + +This example specifies the URL of the OpenTelemetry collector +and that the automatic app discovery should be run every 40 seconds: + +```yaml +apiVersion: options.keptn.sh/v1alpha2 +kind: KeptnConfig +metadata: + name: klt-config +spec: + OTelCollectorUrl: 'otel-collector:4317' + keptnAppCreationRequestTimeoutSeconds: 40 +``` + +## Files + +API Reference: + +* [KeptnTaskDefinition](../crd-ref/lifecycle/v1alpha3/_index.md#keptntaskdefinition) + +## Differences between versions + +## See also + +* [KeptnApp](../yaml-crd-ref/app.md) +* [OpenTelemetry observability](../implementing/otel.md) +* [Keptn automatic app discovery](../implementing/integrate/_index.md/#use-keptn-automatic-app-discovery) From a7475c2ae13479fed55fa4a322af3c2a47649fa1 Mon Sep 17 00:00:00 2001 From: odubajDT <93584209+odubajDT@users.noreply.github.com> Date: Wed, 17 May 2023 13:03:25 +0200 Subject: [PATCH 29/62] chore(operator): bump OTel dependencies to the latest version (#1419) Signed-off-by: odubajDT --- .../apis/lifecycle/v1alpha1/common/common.go | 19 +- .../apis/lifecycle/v1alpha2/common/common.go | 19 +- .../apis/lifecycle/v1alpha3/common/common.go | 19 +- operator/controllers/common/metrics.go | 15 +- operator/controllers/common/metrics_test.go | 21 +- operator/controllers/common/otel_utils.go | 93 ++-- .../controllers/common/otel_utils_test.go | 87 +--- operator/controllers/common/test_utils.go | 15 +- .../fake/activemetricsobject_mock.go | 9 +- .../fake/async/tracer_provider_float_mock.go | 179 -------- .../fake/async/tracer_provider_int_mock.go | 179 -------- .../lifecycle/interfaces/fake/meter_mock.go | 402 ++++++++++++------ .../interfaces/fake/spanitem_mock.go | 9 +- .../fake/sync/tracer_provider_float_mock.go | 179 -------- .../fake/sync/tracer_provider_int_mock.go | 179 -------- .../controllers/lifecycle/interfaces/meter.go | 26 +- .../lifecycle/keptnappversion/controller.go | 5 +- .../lifecycle/keptnevaluation/controller.go | 5 +- .../lifecycle/keptntask/controller.go | 5 +- .../keptnworkloadinstance/controller.go | 5 +- operator/go.mod | 23 +- operator/go.sum | 47 +- operator/test/component/common/common.go | 23 +- 23 files changed, 466 insertions(+), 1097 deletions(-) delete mode 100644 operator/controllers/lifecycle/interfaces/fake/async/tracer_provider_float_mock.go delete mode 100644 operator/controllers/lifecycle/interfaces/fake/async/tracer_provider_int_mock.go delete mode 100644 operator/controllers/lifecycle/interfaces/fake/sync/tracer_provider_float_mock.go delete mode 100644 operator/controllers/lifecycle/interfaces/fake/sync/tracer_provider_int_mock.go diff --git a/operator/apis/lifecycle/v1alpha1/common/common.go b/operator/apis/lifecycle/v1alpha1/common/common.go index 73a93f495c..b714e7b71a 100644 --- a/operator/apis/lifecycle/v1alpha1/common/common.go +++ b/operator/apis/lifecycle/v1alpha1/common/common.go @@ -6,8 +6,7 @@ import ( "math/rand" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" - "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/metric" ) const WorkloadAnnotation = "keptn.sh/workload" @@ -132,14 +131,14 @@ const PreDeploymentEvaluationCheckType CheckType = "pre-eval" const PostDeploymentEvaluationCheckType CheckType = "post-eval" type KeptnMeters struct { - TaskCount syncint64.Counter - TaskDuration syncfloat64.Histogram - DeploymentCount syncint64.Counter - DeploymentDuration syncfloat64.Histogram - AppCount syncint64.Counter - AppDuration syncfloat64.Histogram - EvaluationCount syncint64.Counter - EvaluationDuration syncfloat64.Histogram + TaskCount metric.Int64Counter + TaskDuration metric.Float64Histogram + DeploymentCount metric.Int64Counter + DeploymentDuration metric.Float64Histogram + AppCount metric.Int64Counter + AppDuration metric.Float64Histogram + EvaluationCount metric.Int64Counter + EvaluationDuration metric.Float64Histogram } const ( diff --git a/operator/apis/lifecycle/v1alpha2/common/common.go b/operator/apis/lifecycle/v1alpha2/common/common.go index 92a8605524..4f758bbb6a 100644 --- a/operator/apis/lifecycle/v1alpha2/common/common.go +++ b/operator/apis/lifecycle/v1alpha2/common/common.go @@ -6,8 +6,7 @@ import ( "math/rand" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" - "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/metric" ) const WorkloadAnnotation = "keptn.sh/workload" @@ -134,14 +133,14 @@ const PreDeploymentEvaluationCheckType CheckType = "pre-eval" const PostDeploymentEvaluationCheckType CheckType = "post-eval" type KeptnMeters struct { - TaskCount syncint64.Counter - TaskDuration syncfloat64.Histogram - DeploymentCount syncint64.Counter - DeploymentDuration syncfloat64.Histogram - AppCount syncint64.Counter - AppDuration syncfloat64.Histogram - EvaluationCount syncint64.Counter - EvaluationDuration syncfloat64.Histogram + TaskCount metric.Int64Counter + TaskDuration metric.Float64Histogram + DeploymentCount metric.Int64Counter + DeploymentDuration metric.Float64Histogram + AppCount metric.Int64Counter + AppDuration metric.Float64Histogram + EvaluationCount metric.Int64Counter + EvaluationDuration metric.Float64Histogram } const ( diff --git a/operator/apis/lifecycle/v1alpha3/common/common.go b/operator/apis/lifecycle/v1alpha3/common/common.go index 40c9a368c3..51e33a2318 100644 --- a/operator/apis/lifecycle/v1alpha3/common/common.go +++ b/operator/apis/lifecycle/v1alpha3/common/common.go @@ -8,8 +8,7 @@ import ( "strconv" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" - "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/metric" ) const WorkloadAnnotation = "keptn.sh/workload" @@ -148,14 +147,14 @@ const PreDeploymentEvaluationCheckType CheckType = "pre-eval" const PostDeploymentEvaluationCheckType CheckType = "post-eval" type KeptnMeters struct { - TaskCount syncint64.Counter - TaskDuration syncfloat64.Histogram - DeploymentCount syncint64.Counter - DeploymentDuration syncfloat64.Histogram - AppCount syncint64.Counter - AppDuration syncfloat64.Histogram - EvaluationCount syncint64.Counter - EvaluationDuration syncfloat64.Histogram + TaskCount metric.Int64Counter + TaskDuration metric.Float64Histogram + DeploymentCount metric.Int64Counter + DeploymentDuration metric.Float64Histogram + AppCount metric.Int64Counter + AppDuration metric.Float64Histogram + EvaluationCount metric.Int64Counter + EvaluationDuration metric.Float64Histogram } const ( diff --git a/operator/controllers/common/metrics.go b/operator/controllers/common/metrics.go index 4d002a1229..298a4b8f78 100644 --- a/operator/controllers/common/metrics.go +++ b/operator/controllers/common/metrics.go @@ -7,12 +7,11 @@ import ( controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/interfaces" - "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" - "go.opentelemetry.io/otel/metric/instrument/asyncint64" + "go.opentelemetry.io/otel/metric" "sigs.k8s.io/controller-runtime/pkg/client" ) -func ObserveDeploymentDuration(ctx context.Context, client client.Client, reconcileObjectList client.ObjectList, gauge asyncfloat64.Gauge) error { +func ObserveDeploymentDuration(ctx context.Context, client client.Client, reconcileObjectList client.ObjectList, gauge metric.Float64ObservableGauge, o metric.Observer) error { err := client.List(ctx, reconcileObjectList) if err != nil { return fmt.Errorf(controllererrors.ErrCannotRetrieveInstancesMsg, err) @@ -27,14 +26,14 @@ func ObserveDeploymentDuration(ctx context.Context, client client.Client, reconc reconcileObject, _ := interfaces.NewMetricsObjectWrapperFromClientObject(ro) if reconcileObject.IsEndTimeSet() { duration := reconcileObject.GetEndTime().Sub(reconcileObject.GetStartTime()) - gauge.Observe(ctx, duration.Seconds(), reconcileObject.GetDurationMetricsAttributes()...) + o.ObserveFloat64(gauge, duration.Seconds(), metric.WithAttributes(reconcileObject.GetDurationMetricsAttributes()...)) } } return nil } -func ObserveDeploymentInterval(ctx context.Context, client client.Client, reconcileObjectList client.ObjectList, gauge asyncfloat64.Gauge) error { +func ObserveDeploymentInterval(ctx context.Context, client client.Client, reconcileObjectList client.ObjectList, gauge metric.Float64ObservableGauge, o metric.Observer) error { err := client.List(ctx, reconcileObjectList) if err != nil { return fmt.Errorf(controllererrors.ErrCannotRetrieveInstancesMsg, err) @@ -59,7 +58,7 @@ func ObserveDeploymentInterval(ctx context.Context, client client.Client, reconc } previousInterval := reconcileObject.GetEndTime().Sub(predecessor.GetEndTime()) - gauge.Observe(ctx, previousInterval.Seconds(), reconcileObject.GetDurationMetricsAttributes()...) + o.ObserveFloat64(gauge, previousInterval.Seconds(), metric.WithAttributes(reconcileObject.GetDurationMetricsAttributes()...)) } } @@ -84,7 +83,7 @@ func getPredecessor(successor *interfaces.MetricsObjectWrapper, items []client.O return predecessor } -func ObserveActiveInstances(ctx context.Context, client client.Client, reconcileObjectList client.ObjectList, gauge asyncint64.Gauge) error { +func ObserveActiveInstances(ctx context.Context, client client.Client, reconcileObjectList client.ObjectList, gauge metric.Int64ObservableGauge, o metric.Observer) error { err := client.List(ctx, reconcileObjectList) if err != nil { return fmt.Errorf(controllererrors.ErrCannotRetrieveInstancesMsg, err) @@ -102,7 +101,7 @@ func ObserveActiveInstances(ctx context.Context, client client.Client, reconcile gaugeValue = int64(1) } - gauge.Observe(ctx, gaugeValue, activeMetricsObject.GetActiveMetricsAttributes()...) + o.ObserveInt64(gauge, gaugeValue, metric.WithAttributes(activeMetricsObject.GetActiveMetricsAttributes()...)) } return nil diff --git a/operator/controllers/common/metrics_test.go b/operator/controllers/common/metrics_test.go index 780fd1174a..4bfcfb46f1 100644 --- a/operator/controllers/common/metrics_test.go +++ b/operator/controllers/common/metrics_test.go @@ -9,8 +9,8 @@ import ( controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/interfaces" "github.com/stretchr/testify/require" - noop "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/noop" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" @@ -18,16 +18,14 @@ import ( ) func TestMetrics_ObserveDeploymentDuration(t *testing.T) { - - gauge, err := noop.NewNoopMeter().AsyncFloat64().Gauge("mine") - require.Nil(t, err) + gauge := noop.Float64ObservableGauge{} tests := []struct { name string clientObjects client.ObjectList list client.ObjectList err error - gauge asyncfloat64.Gauge + gauge metric.Float64ObservableGauge }{ { name: "failed to create wrapper", @@ -79,7 +77,7 @@ func TestMetrics_ObserveDeploymentDuration(t *testing.T) { err := lifecyclev1alpha3.AddToScheme(scheme.Scheme) require.Nil(t, err) client := fake.NewClientBuilder().WithLists(tt.clientObjects).Build() - err = ObserveDeploymentDuration(context.TODO(), client, tt.list, gauge) + err = ObserveDeploymentDuration(context.TODO(), client, tt.list, gauge, noop.Observer{}) require.ErrorIs(t, err, tt.err) }) @@ -153,9 +151,9 @@ func TestMetrics_ObserveActiveInstances(t *testing.T) { err := lifecyclev1alpha3.AddToScheme(scheme.Scheme) require.Nil(t, err) client := fake.NewClientBuilder().WithLists(tt.clientObjects).Build() - gauge, err := noop.NewNoopMeter().AsyncInt64().Gauge("mine") + gauge := noop.Int64ObservableGauge{} require.Nil(t, err) - err = ObserveActiveInstances(context.TODO(), client, tt.list, gauge) + err = ObserveActiveInstances(context.TODO(), client, tt.list, gauge, noop.Observer{}) require.ErrorIs(t, err, tt.err) }) @@ -365,15 +363,14 @@ func TestMetrics_ObserveDeploymentInterval(t *testing.T) { }, } - gauge, err := noop.NewNoopMeter().AsyncFloat64().Gauge("mine") - require.Nil(t, err) + gauge := noop.Float64ObservableGauge{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := lifecyclev1alpha3.AddToScheme(scheme.Scheme) require.Nil(t, err) fakeClient := fake.NewClientBuilder().WithLists(tt.clientObjects).Build() - err = ObserveDeploymentInterval(context.TODO(), fakeClient, tt.list, gauge) + err = ObserveDeploymentInterval(context.TODO(), fakeClient, tt.list, gauge, noop.Observer{}) require.ErrorIs(t, err, tt.err) }) diff --git a/operator/controllers/common/otel_utils.go b/operator/controllers/common/otel_utils.go index fcd5b483dc..0d33e31c6d 100644 --- a/operator/controllers/common/otel_utils.go +++ b/operator/controllers/common/otel_utils.go @@ -9,14 +9,11 @@ import ( lifecyclev1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" + "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/interfaces" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" - "go.opentelemetry.io/otel/metric/instrument/asyncint64" - "go.opentelemetry.io/otel/metric/unit" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" @@ -158,141 +155,141 @@ func newResource() *resource.Resource { return r } -func SetUpKeptnMeters(meter metric.Meter, mgr client.Client) { - deploymentActiveGauge, err := meter.AsyncInt64().Gauge("keptn.deployment.active", instrument.WithDescription("a gauge keeping track of the currently active Keptn Deployments")) +func SetUpKeptnMeters(meter interfaces.IMeter, mgr client.Client) { + deploymentActiveGauge, err := meter.Int64ObservableGauge("keptn.deployment.active", metric.WithDescription("a gauge keeping track of the currently active Keptn Deployments")) if err != nil { logger.Error(err, "unable to initialize active deployments OTel gauge") } - taskActiveGauge, err := meter.AsyncInt64().Gauge("keptn.task.active", instrument.WithDescription("a simple counter of active Keptn Tasks")) + taskActiveGauge, err := meter.Int64ObservableGauge("keptn.task.active", metric.WithDescription("a simple counter of active Keptn Tasks")) if err != nil { logger.Error(err, "unable to initialize active tasks OTel gauge") } - appActiveGauge, err := meter.AsyncInt64().Gauge("keptn.app.active", instrument.WithDescription("a simple counter of active Keptn Apps")) + appActiveGauge, err := meter.Int64ObservableGauge("keptn.app.active", metric.WithDescription("a simple counter of active Keptn Apps")) if err != nil { logger.Error(err, "unable to initialize active apps OTel gauge") } - evaluationActiveGauge, err := meter.AsyncInt64().Gauge("keptn.evaluation.active", instrument.WithDescription("a simple counter of active Keptn Evaluations")) + evaluationActiveGauge, err := meter.Int64ObservableGauge("keptn.evaluation.active", metric.WithDescription("a simple counter of active Keptn Evaluations")) if err != nil { logger.Error(err, "unable to initialize active evaluations OTel gauge") } - appDeploymentIntervalGauge, err := meter.AsyncFloat64().Gauge("keptn.app.deploymentinterval", instrument.WithDescription("a gauge of the interval between app deployments")) + appDeploymentIntervalGauge, err := meter.Float64ObservableGauge("keptn.app.deploymentinterval", metric.WithDescription("a gauge of the interval between app deployments")) if err != nil { logger.Error(err, "unable to initialize app deployment interval OTel gauge") } - appDeploymentDurationGauge, err := meter.AsyncFloat64().Gauge("keptn.app.deploymentduration", instrument.WithDescription("a gauge of the duration of app deployments")) + appDeploymentDurationGauge, err := meter.Float64ObservableGauge("keptn.app.deploymentduration", metric.WithDescription("a gauge of the duration of app deployments")) if err != nil { logger.Error(err, "unable to initialize app deployment duration OTel gauge") } - workloadDeploymentIntervalGauge, err := meter.AsyncFloat64().Gauge("keptn.deployment.deploymentinterval", instrument.WithDescription("a gauge of the interval between workload deployments")) + workloadDeploymentIntervalGauge, err := meter.Float64ObservableGauge("keptn.deployment.deploymentinterval", metric.WithDescription("a gauge of the interval between workload deployments")) if err != nil { logger.Error(err, "unable to initialize workload deployment interval OTel gauge") } - workloadDeploymentDurationGauge, err := meter.AsyncFloat64().Gauge("keptn.deployment.deploymentduration", instrument.WithDescription("a gauge of the duration of workload deployments")) + workloadDeploymentDurationGauge, err := meter.Float64ObservableGauge("keptn.deployment.deploymentduration", metric.WithDescription("a gauge of the duration of workload deployments")) if err != nil { logger.Error(err, "unable to initialize workload deployment duration OTel gauge") } - err = meter.RegisterCallback( - []instrument.Asynchronous{ - deploymentActiveGauge, - taskActiveGauge, - appActiveGauge, - evaluationActiveGauge, - appDeploymentIntervalGauge, - appDeploymentDurationGauge, - workloadDeploymentIntervalGauge, - workloadDeploymentDurationGauge, + _, err = meter.RegisterCallback( + func(ctx context.Context, o metric.Observer) error { + observeActiveInstances(ctx, mgr, deploymentActiveGauge, appActiveGauge, taskActiveGauge, evaluationActiveGauge, o) + observeDeploymentInterval(ctx, mgr, appDeploymentIntervalGauge, workloadDeploymentIntervalGauge, o) + observeDuration(ctx, mgr, appDeploymentDurationGauge, workloadDeploymentDurationGauge, o) + return nil }, - func(ctx context.Context) { - observeActiveInstances(ctx, mgr, deploymentActiveGauge, appActiveGauge, taskActiveGauge, evaluationActiveGauge) - observeDeploymentInterval(ctx, mgr, appDeploymentIntervalGauge, workloadDeploymentIntervalGauge) - observeDuration(ctx, mgr, appDeploymentDurationGauge, workloadDeploymentDurationGauge) - }) + deploymentActiveGauge, + taskActiveGauge, + appActiveGauge, + evaluationActiveGauge, + appDeploymentIntervalGauge, + appDeploymentDurationGauge, + workloadDeploymentIntervalGauge, + workloadDeploymentDurationGauge, + ) if err != nil { fmt.Println("Failed to register callback") panic(err) } } -func observeDuration(ctx context.Context, mgr client.Client, appDeploymentDurationGauge asyncfloat64.Gauge, workloadDeploymentDurationGauge asyncfloat64.Gauge) { +func observeDuration(ctx context.Context, mgr client.Client, appDeploymentDurationGauge metric.Float64ObservableGauge, workloadDeploymentDurationGauge metric.Float64ObservableGauge, observer metric.Observer) { - err := ObserveDeploymentDuration(ctx, mgr, &lifecyclev1alpha3.KeptnAppVersionList{}, appDeploymentDurationGauge) + err := ObserveDeploymentDuration(ctx, mgr, &lifecyclev1alpha3.KeptnAppVersionList{}, appDeploymentDurationGauge, observer) if err != nil { logger.Error(err, "unable to gather app deployment durations") } - err = ObserveDeploymentDuration(ctx, mgr, &lifecyclev1alpha3.KeptnWorkloadInstanceList{}, workloadDeploymentDurationGauge) + err = ObserveDeploymentDuration(ctx, mgr, &lifecyclev1alpha3.KeptnWorkloadInstanceList{}, workloadDeploymentDurationGauge, observer) if err != nil { logger.Error(err, "unable to gather workload deployment durations") } } -func observeDeploymentInterval(ctx context.Context, mgr client.Client, appDeploymentIntervalGauge asyncfloat64.Gauge, workloadDeploymentIntervalGauge asyncfloat64.Gauge) { - err := ObserveDeploymentInterval(ctx, mgr, &lifecyclev1alpha3.KeptnAppVersionList{}, appDeploymentIntervalGauge) +func observeDeploymentInterval(ctx context.Context, mgr client.Client, appDeploymentIntervalGauge metric.Float64ObservableGauge, workloadDeploymentIntervalGauge metric.Float64ObservableGauge, observer metric.Observer) { + err := ObserveDeploymentInterval(ctx, mgr, &lifecyclev1alpha3.KeptnAppVersionList{}, appDeploymentIntervalGauge, observer) if err != nil { logger.Error(err, "unable to gather app deployment intervals") } - err = ObserveDeploymentInterval(ctx, mgr, &lifecyclev1alpha3.KeptnWorkloadInstanceList{}, workloadDeploymentIntervalGauge) + err = ObserveDeploymentInterval(ctx, mgr, &lifecyclev1alpha3.KeptnWorkloadInstanceList{}, workloadDeploymentIntervalGauge, observer) if err != nil { logger.Error(err, "unable to gather workload deployment intervals") } } -func observeActiveInstances(ctx context.Context, mgr client.Client, deploymentActiveGauge asyncint64.Gauge, appActiveGauge asyncint64.Gauge, taskActiveGauge asyncint64.Gauge, evaluationActiveGauge asyncint64.Gauge) { +func observeActiveInstances(ctx context.Context, mgr client.Client, deploymentActiveGauge metric.Int64ObservableGauge, appActiveGauge metric.Int64ObservableGauge, taskActiveGauge metric.Int64ObservableGauge, evaluationActiveGauge metric.Int64ObservableGauge, observer metric.Observer) { - err := ObserveActiveInstances(ctx, mgr, &lifecyclev1alpha3.KeptnWorkloadInstanceList{}, deploymentActiveGauge) + err := ObserveActiveInstances(ctx, mgr, &lifecyclev1alpha3.KeptnWorkloadInstanceList{}, deploymentActiveGauge, observer) if err != nil { logger.Error(err, "unable to gather active deployments") } - err = ObserveActiveInstances(ctx, mgr, &lifecyclev1alpha3.KeptnAppVersionList{}, appActiveGauge) + err = ObserveActiveInstances(ctx, mgr, &lifecyclev1alpha3.KeptnAppVersionList{}, appActiveGauge, observer) if err != nil { logger.Error(err, "unable to gather active apps") } - err = ObserveActiveInstances(ctx, mgr, &lifecyclev1alpha3.KeptnTaskList{}, taskActiveGauge) + err = ObserveActiveInstances(ctx, mgr, &lifecyclev1alpha3.KeptnTaskList{}, taskActiveGauge, observer) if err != nil { logger.Error(err, "unable to gather active tasks") } - err = ObserveActiveInstances(ctx, mgr, &lifecyclev1alpha3.KeptnEvaluationList{}, evaluationActiveGauge) + err = ObserveActiveInstances(ctx, mgr, &lifecyclev1alpha3.KeptnEvaluationList{}, evaluationActiveGauge, observer) if err != nil { logger.Error(err, "unable to gather active evaluations") } } -func SetUpKeptnTaskMeters(meter metric.Meter) common.KeptnMeters { - deploymentCount, err := meter.SyncInt64().Counter("keptn.deployment.count", instrument.WithDescription("a simple counter for Keptn Deployments")) +func SetUpKeptnTaskMeters(meter interfaces.IMeter) common.KeptnMeters { + deploymentCount, err := meter.Int64Counter("keptn.deployment.count", metric.WithDescription("a simple counter for Keptn Deployments")) if err != nil { logger.Error(err, "unable to initialize deployment count OTel counter") } - deploymentDuration, err := meter.SyncFloat64().Histogram("keptn.deployment.duration", instrument.WithDescription("a histogram of duration for Keptn Deployments"), instrument.WithUnit(unit.Unit("s"))) + deploymentDuration, err := meter.Float64Histogram("keptn.deployment.duration", metric.WithDescription("a histogram of duration for Keptn Deployments"), metric.WithUnit("s")) if err != nil { logger.Error(err, "unable to initialize deployment duration OTel histogram") } - taskCount, err := meter.SyncInt64().Counter("keptn.task.count", instrument.WithDescription("a simple counter for Keptn Tasks")) + taskCount, err := meter.Int64Counter("keptn.task.count", metric.WithDescription("a simple counter for Keptn Tasks")) if err != nil { logger.Error(err, "unable to initialize task OTel counter") } - taskDuration, err := meter.SyncFloat64().Histogram("keptn.task.duration", instrument.WithDescription("a histogram of duration for Keptn Tasks"), instrument.WithUnit(unit.Unit("s"))) + taskDuration, err := meter.Float64Histogram("keptn.task.duration", metric.WithDescription("a histogram of duration for Keptn Tasks"), metric.WithUnit("s")) if err != nil { logger.Error(err, "unable to initialize task duration OTel histogram") } - appCount, err := meter.SyncInt64().Counter("keptn.app.count", instrument.WithDescription("a simple counter for Keptn Apps")) + appCount, err := meter.Int64Counter("keptn.app.count", metric.WithDescription("a simple counter for Keptn Apps")) if err != nil { logger.Error(err, "unable to initialize app OTel counter") } - appDuration, err := meter.SyncFloat64().Histogram("keptn.app.duration", instrument.WithDescription("a histogram of duration for Keptn Apps"), instrument.WithUnit(unit.Unit("s"))) + appDuration, err := meter.Float64Histogram("keptn.app.duration", metric.WithDescription("a histogram of duration for Keptn Apps"), metric.WithUnit("s")) if err != nil { logger.Error(err, "unable to initialize app duration OTel histogram") } - evaluationCount, err := meter.SyncInt64().Counter("keptn.evaluation.count", instrument.WithDescription("a simple counter for Keptn Evaluations")) + evaluationCount, err := meter.Int64Counter("keptn.evaluation.count", metric.WithDescription("a simple counter for Keptn Evaluations")) if err != nil { logger.Error(err, "unable to initialize evaluation OTel counter") } - evaluationDuration, err := meter.SyncFloat64().Histogram("keptn.evaluation.duration", instrument.WithDescription("a histogram of duration for Keptn Evaluations"), instrument.WithUnit(unit.Unit("s"))) + evaluationDuration, err := meter.Float64Histogram("keptn.evaluation.duration", metric.WithDescription("a histogram of duration for Keptn Evaluations"), metric.WithUnit("s")) if err != nil { logger.Error(err, "unable to initialize evaluation duration OTel histogram") } diff --git a/operator/controllers/common/otel_utils_test.go b/operator/controllers/common/otel_utils_test.go index 49a68296bb..aee0b318ad 100644 --- a/operator/controllers/common/otel_utils_test.go +++ b/operator/controllers/common/otel_utils_test.go @@ -1,21 +1,15 @@ package common import ( - "context" "net" "testing" + "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/interfaces" "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/interfaces/fake" - fakeasync "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/interfaces/fake/async" - fakesync "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/interfaces/fake/sync" "github.com/pkg/errors" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" - "go.opentelemetry.io/otel/metric/instrument/asyncint64" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" - "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/metric/noop" "google.golang.org/grpc" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -85,31 +79,20 @@ func TestGetOTelTracerProviderOptions(t *testing.T) { } func TestSetUpKeptnMeters(t *testing.T) { - fakeAsyncIntTracerProvider := &fakeasync.ITracerProviderAsyncInt64Mock{ - GaugeFunc: func(name string, opts ...instrument.Option) (asyncint64.Gauge, error) { + fakeMeter := &fake.IMeterMock{ + Int64ObservableGaugeFunc: func(name string, options ...metric.Int64ObservableGaugeOption) (metric.Int64ObservableGauge, error) { return nil, errors.New("some error") }, - } - fakeAsyncFloatTracerProvider := &fakeasync.ITracerProviderAsyncFloat64Mock{ - GaugeFunc: func(name string, opts ...instrument.Option) (asyncfloat64.Gauge, error) { + Float64ObservableGaugeFunc: func(name string, options ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) { return nil, errors.New("some error") }, - } - - fakeMeter := &fake.IMeterMock{ - AsyncInt64Func: func() asyncint64.InstrumentProvider { - return fakeAsyncIntTracerProvider - }, - AsyncFloat64Func: func() asyncfloat64.InstrumentProvider { - return fakeAsyncFloatTracerProvider - }, - RegisterCallbackFunc: func(insts []instrument.Asynchronous, function func(context.Context)) error { - return nil + RegisterCallbackFunc: func(f metric.Callback, instruments ...metric.Observable) (metric.Registration, error) { + return nil, nil }, } type args struct { - meter metric.Meter + meter interfaces.IMeter mgr client.Client } tests := []struct { @@ -120,7 +103,7 @@ func TestSetUpKeptnMeters(t *testing.T) { { name: "Basic case", args: args{ - meter: metric.NewNoopMeter(), + meter: noop.NewMeterProvider().Meter(("test")), mgr: nil, }, wantRegisterCalls: 0, @@ -143,17 +126,6 @@ func TestSetUpKeptnMeters(t *testing.T) { } func TestSetUpKeptnMetersError(t *testing.T) { - fakeAsyncIntTracerProvider := &fakeasync.ITracerProviderAsyncInt64Mock{ - GaugeFunc: func(name string, opts ...instrument.Option) (asyncint64.Gauge, error) { - return nil, errors.New("some error") - }, - } - fakeAsyncFloatTracerProvider := &fakeasync.ITracerProviderAsyncFloat64Mock{ - GaugeFunc: func(name string, opts ...instrument.Option) (asyncfloat64.Gauge, error) { - return nil, errors.New("some error") - }, - } - defer func() { if r := recover(); r == nil { t.Errorf("The code did not panic") @@ -161,14 +133,14 @@ func TestSetUpKeptnMetersError(t *testing.T) { }() errorFakeMeter := &fake.IMeterMock{ - AsyncInt64Func: func() asyncint64.InstrumentProvider { - return fakeAsyncIntTracerProvider + Int64ObservableGaugeFunc: func(name string, options ...metric.Int64ObservableGaugeOption) (metric.Int64ObservableGauge, error) { + return nil, errors.New("some error") }, - AsyncFloat64Func: func() asyncfloat64.InstrumentProvider { - return fakeAsyncFloatTracerProvider + Float64ObservableGaugeFunc: func(name string, options ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) { + return nil, errors.New("some error") }, - RegisterCallbackFunc: func(insts []instrument.Asynchronous, function func(context.Context)) error { - return errors.New("some error") + RegisterCallbackFunc: func(f metric.Callback, instruments ...metric.Observable) (metric.Registration, error) { + return nil, errors.New("some error") }, } @@ -176,9 +148,7 @@ func TestSetUpKeptnMetersError(t *testing.T) { } func TestSetUpKeptnTaskMeters(t *testing.T) { - noopMeter := metric.NewNoopMeter() - - got := SetUpKeptnTaskMeters(noopMeter) + got := SetUpKeptnTaskMeters(noop.NewMeterProvider().Meter(("test"))) require.NotNil(t, got.TaskCount) require.NotNil(t, got.TaskDuration) @@ -191,32 +161,21 @@ func TestSetUpKeptnTaskMeters(t *testing.T) { } func TestSetUpKeptnTaskMeters_ErrorCase(t *testing.T) { - fakeSyncIntTracerProvider := &fakesync.ITracerProviderSyncInt64Mock{ - CounterFunc: func(name string, opts ...instrument.Option) (syncint64.Counter, error) { + errorFakeMeter := &fake.IMeterMock{ + Int64CounterFunc: func(name string, options ...metric.Int64CounterOption) (metric.Int64Counter, error) { return nil, errors.New("some error") }, - HistogramFunc: func(name string, opts ...instrument.Option) (syncint64.Histogram, error) { + Int64HistogramFunc: func(name string, options ...metric.Int64HistogramOption) (metric.Int64Histogram, error) { return nil, errors.New("some error") }, - } - fakeSyncFloatTracerProvider := &fakesync.ITracerProviderSyncFloat64Mock{ - CounterFunc: func(name string, opts ...instrument.Option) (syncfloat64.Counter, error) { + Float64CounterFunc: func(name string, options ...metric.Float64CounterOption) (metric.Float64Counter, error) { return nil, errors.New("some error") }, - HistogramFunc: func(name string, opts ...instrument.Option) (syncfloat64.Histogram, error) { + Float64HistogramFunc: func(name string, options ...metric.Float64HistogramOption) (metric.Float64Histogram, error) { return nil, errors.New("some error") }, - } - - errorFakeMeter := &fake.IMeterMock{ - SyncInt64Func: func() syncint64.InstrumentProvider { - return fakeSyncIntTracerProvider - }, - SyncFloat64Func: func() syncfloat64.InstrumentProvider { - return fakeSyncFloatTracerProvider - }, - RegisterCallbackFunc: func(insts []instrument.Asynchronous, function func(context.Context)) error { - return errors.New("some error") + RegisterCallbackFunc: func(f metric.Callback, instruments ...metric.Observable) (metric.Registration, error) { + return nil, errors.New("some error") }, } diff --git a/operator/controllers/common/test_utils.go b/operator/controllers/common/test_utils.go index 1ef07717be..6ec5dfd62c 100644 --- a/operator/controllers/common/test_utils.go +++ b/operator/controllers/common/test_utils.go @@ -6,9 +6,8 @@ import ( lfcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/unit" - "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/metric" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -66,12 +65,12 @@ func AddAppVersion(c client.Client, namespace string, appName string, version st } func InitAppMeters() apicommon.KeptnMeters { - provider := metric.NewMeterProvider() + provider := sdkmetric.NewMeterProvider() meter := provider.Meter("keptn/task") - appCount, _ := meter.SyncInt64().Counter("keptn.app.count", instrument.WithDescription("a simple counter for Keptn Apps")) - appDuration, _ := meter.SyncFloat64().Histogram("keptn.app.duration", instrument.WithDescription("a histogram of duration for Keptn Apps"), instrument.WithUnit("s")) - deploymentCount, _ := meter.SyncInt64().Counter("keptn.deployment.count", instrument.WithDescription("a simple counter for Keptn Deployments")) - deploymentDuration, _ := meter.SyncFloat64().Histogram("keptn.deployment.duration", instrument.WithDescription("a histogram of duration for Keptn Deployments"), instrument.WithUnit(unit.Unit("s"))) + appCount, _ := meter.Int64Counter("keptn.app.count", metric.WithDescription("a simple counter for Keptn Apps")) + appDuration, _ := meter.Float64Histogram("keptn.app.duration", metric.WithDescription("a histogram of duration for Keptn Apps"), metric.WithUnit("s")) + deploymentCount, _ := meter.Int64Counter("keptn.deployment.count", metric.WithDescription("a simple counter for Keptn Deployments")) + deploymentDuration, _ := meter.Float64Histogram("keptn.deployment.duration", metric.WithDescription("a histogram of duration for Keptn Deployments"), metric.WithUnit("s")) meters := apicommon.KeptnMeters{ AppCount: appCount, diff --git a/operator/controllers/lifecycle/interfaces/fake/activemetricsobject_mock.go b/operator/controllers/lifecycle/interfaces/fake/activemetricsobject_mock.go index 13e60982a7..789e44d8c7 100644 --- a/operator/controllers/lifecycle/interfaces/fake/activemetricsobject_mock.go +++ b/operator/controllers/lifecycle/interfaces/fake/activemetricsobject_mock.go @@ -4,16 +4,15 @@ package fake import ( - "sync" - "go.opentelemetry.io/otel/attribute" + "sync" ) -// ActiveMetricsObjectMock is a mock implementation of common.ActiveMetricsObject. +// ActiveMetricsObjectMock is a mock implementation of interfaces.ActiveMetricsObject. // // func TestSomethingThatUsesActiveMetricsObject(t *testing.T) { // -// // make and configure a mocked common.ActiveMetricsObject +// // make and configure a mocked interfaces.ActiveMetricsObject // mockedActiveMetricsObject := &ActiveMetricsObjectMock{ // GetActiveMetricsAttributesFunc: func() []attribute.KeyValue { // panic("mock out the GetActiveMetricsAttributes method") @@ -23,7 +22,7 @@ import ( // }, // } // -// // use mockedActiveMetricsObject in code that requires common.ActiveMetricsObject +// // use mockedActiveMetricsObject in code that requires interfaces.ActiveMetricsObject // // and then make assertions. // // } diff --git a/operator/controllers/lifecycle/interfaces/fake/async/tracer_provider_float_mock.go b/operator/controllers/lifecycle/interfaces/fake/async/tracer_provider_float_mock.go deleted file mode 100644 index c9b589c760..0000000000 --- a/operator/controllers/lifecycle/interfaces/fake/async/tracer_provider_float_mock.go +++ /dev/null @@ -1,179 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package fake - -import ( - "sync" - - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" -) - -// ITracerProviderAsyncFloat64Mock is a mock implementation of interfaces.ITracerProviderAsyncFloat64. -// -// func TestSomethingThatUsesITracerProviderAsyncFloat64(t *testing.T) { -// -// // make and configure a mocked interfaces.ITracerProviderAsyncFloat64 -// mockedITracerProviderAsyncFloat64 := &ITracerProviderAsyncFloat64Mock{ -// CounterFunc: func(name string, opts ...instrument.Option) (asyncfloat64.Counter, error) { -// panic("mock out the Counter method") -// }, -// GaugeFunc: func(name string, opts ...instrument.Option) (asyncfloat64.Gauge, error) { -// panic("mock out the Gauge method") -// }, -// UpDownCounterFunc: func(name string, opts ...instrument.Option) (asyncfloat64.UpDownCounter, error) { -// panic("mock out the UpDownCounter method") -// }, -// } -// -// // use mockedITracerProviderAsyncFloat64 in code that requires interfaces.ITracerProviderAsyncFloat64 -// // and then make assertions. -// -// } -type ITracerProviderAsyncFloat64Mock struct { - // CounterFunc mocks the Counter method. - CounterFunc func(name string, opts ...instrument.Option) (asyncfloat64.Counter, error) - - // GaugeFunc mocks the Gauge method. - GaugeFunc func(name string, opts ...instrument.Option) (asyncfloat64.Gauge, error) - - // UpDownCounterFunc mocks the UpDownCounter method. - UpDownCounterFunc func(name string, opts ...instrument.Option) (asyncfloat64.UpDownCounter, error) - - // calls tracks calls to the methods. - calls struct { - // Counter holds details about calls to the Counter method. - Counter []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - // Gauge holds details about calls to the Gauge method. - Gauge []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - // UpDownCounter holds details about calls to the UpDownCounter method. - UpDownCounter []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - } - lockCounter sync.RWMutex - lockGauge sync.RWMutex - lockUpDownCounter sync.RWMutex -} - -// Counter calls CounterFunc. -func (mock *ITracerProviderAsyncFloat64Mock) Counter(name string, opts ...instrument.Option) (asyncfloat64.Counter, error) { - if mock.CounterFunc == nil { - panic("ITracerProviderAsyncFloat64Mock.CounterFunc: method is nil but ITracerProviderAsyncFloat64.Counter was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockCounter.Lock() - mock.calls.Counter = append(mock.calls.Counter, callInfo) - mock.lockCounter.Unlock() - return mock.CounterFunc(name, opts...) -} - -// CounterCalls gets all the calls that were made to Counter. -// Check the length with: -// -// len(mockedITracerProviderAsyncFloat64.CounterCalls()) -func (mock *ITracerProviderAsyncFloat64Mock) CounterCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockCounter.RLock() - calls = mock.calls.Counter - mock.lockCounter.RUnlock() - return calls -} - -// Gauge calls GaugeFunc. -func (mock *ITracerProviderAsyncFloat64Mock) Gauge(name string, opts ...instrument.Option) (asyncfloat64.Gauge, error) { - if mock.GaugeFunc == nil { - panic("ITracerProviderAsyncFloat64Mock.GaugeFunc: method is nil but ITracerProviderAsyncFloat64.Gauge was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockGauge.Lock() - mock.calls.Gauge = append(mock.calls.Gauge, callInfo) - mock.lockGauge.Unlock() - return mock.GaugeFunc(name, opts...) -} - -// GaugeCalls gets all the calls that were made to Gauge. -// Check the length with: -// -// len(mockedITracerProviderAsyncFloat64.GaugeCalls()) -func (mock *ITracerProviderAsyncFloat64Mock) GaugeCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockGauge.RLock() - calls = mock.calls.Gauge - mock.lockGauge.RUnlock() - return calls -} - -// UpDownCounter calls UpDownCounterFunc. -func (mock *ITracerProviderAsyncFloat64Mock) UpDownCounter(name string, opts ...instrument.Option) (asyncfloat64.UpDownCounter, error) { - if mock.UpDownCounterFunc == nil { - panic("ITracerProviderAsyncFloat64Mock.UpDownCounterFunc: method is nil but ITracerProviderAsyncFloat64.UpDownCounter was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockUpDownCounter.Lock() - mock.calls.UpDownCounter = append(mock.calls.UpDownCounter, callInfo) - mock.lockUpDownCounter.Unlock() - return mock.UpDownCounterFunc(name, opts...) -} - -// UpDownCounterCalls gets all the calls that were made to UpDownCounter. -// Check the length with: -// -// len(mockedITracerProviderAsyncFloat64.UpDownCounterCalls()) -func (mock *ITracerProviderAsyncFloat64Mock) UpDownCounterCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockUpDownCounter.RLock() - calls = mock.calls.UpDownCounter - mock.lockUpDownCounter.RUnlock() - return calls -} diff --git a/operator/controllers/lifecycle/interfaces/fake/async/tracer_provider_int_mock.go b/operator/controllers/lifecycle/interfaces/fake/async/tracer_provider_int_mock.go deleted file mode 100644 index 18c03e7259..0000000000 --- a/operator/controllers/lifecycle/interfaces/fake/async/tracer_provider_int_mock.go +++ /dev/null @@ -1,179 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package fake - -import ( - "sync" - - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/asyncint64" -) - -// ITracerProviderAsyncInt64Mock is a mock implementation of interfaces.ITracerProviderAsyncInt64. -// -// func TestSomethingThatUsesITracerProviderAsyncInt64(t *testing.T) { -// -// // make and configure a mocked interfaces.ITracerProviderAsyncInt64 -// mockedITracerProviderAsyncInt64 := &ITracerProviderAsyncInt64Mock{ -// CounterFunc: func(name string, opts ...instrument.Option) (asyncint64.Counter, error) { -// panic("mock out the Counter method") -// }, -// GaugeFunc: func(name string, opts ...instrument.Option) (asyncint64.Gauge, error) { -// panic("mock out the Gauge method") -// }, -// UpDownCounterFunc: func(name string, opts ...instrument.Option) (asyncint64.UpDownCounter, error) { -// panic("mock out the UpDownCounter method") -// }, -// } -// -// // use mockedITracerProviderAsyncInt64 in code that requires interfaces.ITracerProviderAsyncInt64 -// // and then make assertions. -// -// } -type ITracerProviderAsyncInt64Mock struct { - // CounterFunc mocks the Counter method. - CounterFunc func(name string, opts ...instrument.Option) (asyncint64.Counter, error) - - // GaugeFunc mocks the Gauge method. - GaugeFunc func(name string, opts ...instrument.Option) (asyncint64.Gauge, error) - - // UpDownCounterFunc mocks the UpDownCounter method. - UpDownCounterFunc func(name string, opts ...instrument.Option) (asyncint64.UpDownCounter, error) - - // calls tracks calls to the methods. - calls struct { - // Counter holds details about calls to the Counter method. - Counter []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - // Gauge holds details about calls to the Gauge method. - Gauge []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - // UpDownCounter holds details about calls to the UpDownCounter method. - UpDownCounter []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - } - lockCounter sync.RWMutex - lockGauge sync.RWMutex - lockUpDownCounter sync.RWMutex -} - -// Counter calls CounterFunc. -func (mock *ITracerProviderAsyncInt64Mock) Counter(name string, opts ...instrument.Option) (asyncint64.Counter, error) { - if mock.CounterFunc == nil { - panic("ITracerProviderAsyncInt64Mock.CounterFunc: method is nil but ITracerProviderAsyncInt64.Counter was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockCounter.Lock() - mock.calls.Counter = append(mock.calls.Counter, callInfo) - mock.lockCounter.Unlock() - return mock.CounterFunc(name, opts...) -} - -// CounterCalls gets all the calls that were made to Counter. -// Check the length with: -// -// len(mockedITracerProviderAsyncInt64.CounterCalls()) -func (mock *ITracerProviderAsyncInt64Mock) CounterCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockCounter.RLock() - calls = mock.calls.Counter - mock.lockCounter.RUnlock() - return calls -} - -// Gauge calls GaugeFunc. -func (mock *ITracerProviderAsyncInt64Mock) Gauge(name string, opts ...instrument.Option) (asyncint64.Gauge, error) { - if mock.GaugeFunc == nil { - panic("ITracerProviderAsyncInt64Mock.GaugeFunc: method is nil but ITracerProviderAsyncInt64.Gauge was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockGauge.Lock() - mock.calls.Gauge = append(mock.calls.Gauge, callInfo) - mock.lockGauge.Unlock() - return mock.GaugeFunc(name, opts...) -} - -// GaugeCalls gets all the calls that were made to Gauge. -// Check the length with: -// -// len(mockedITracerProviderAsyncInt64.GaugeCalls()) -func (mock *ITracerProviderAsyncInt64Mock) GaugeCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockGauge.RLock() - calls = mock.calls.Gauge - mock.lockGauge.RUnlock() - return calls -} - -// UpDownCounter calls UpDownCounterFunc. -func (mock *ITracerProviderAsyncInt64Mock) UpDownCounter(name string, opts ...instrument.Option) (asyncint64.UpDownCounter, error) { - if mock.UpDownCounterFunc == nil { - panic("ITracerProviderAsyncInt64Mock.UpDownCounterFunc: method is nil but ITracerProviderAsyncInt64.UpDownCounter was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockUpDownCounter.Lock() - mock.calls.UpDownCounter = append(mock.calls.UpDownCounter, callInfo) - mock.lockUpDownCounter.Unlock() - return mock.UpDownCounterFunc(name, opts...) -} - -// UpDownCounterCalls gets all the calls that were made to UpDownCounter. -// Check the length with: -// -// len(mockedITracerProviderAsyncInt64.UpDownCounterCalls()) -func (mock *ITracerProviderAsyncInt64Mock) UpDownCounterCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockUpDownCounter.RLock() - calls = mock.calls.UpDownCounter - mock.lockUpDownCounter.RUnlock() - return calls -} diff --git a/operator/controllers/lifecycle/interfaces/fake/meter_mock.go b/operator/controllers/lifecycle/interfaces/fake/meter_mock.go index 9569b04e66..939eb33a1d 100644 --- a/operator/controllers/lifecycle/interfaces/fake/meter_mock.go +++ b/operator/controllers/lifecycle/interfaces/fake/meter_mock.go @@ -4,14 +4,8 @@ package fake import ( - "context" + "go.opentelemetry.io/otel/metric" "sync" - - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" - "go.opentelemetry.io/otel/metric/instrument/asyncint64" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" - "go.opentelemetry.io/otel/metric/instrument/syncint64" ) // IMeterMock is a mock implementation of interfaces.IMeter. @@ -20,20 +14,26 @@ import ( // // // make and configure a mocked interfaces.IMeter // mockedIMeter := &IMeterMock{ -// AsyncFloat64Func: func() asyncfloat64.InstrumentProvider { -// panic("mock out the AsyncFloat64 method") +// Float64CounterFunc: func(name string, options ...metric.Float64CounterOption) (metric.Float64Counter, error) { +// panic("mock out the Float64Counter method") // }, -// AsyncInt64Func: func() asyncint64.InstrumentProvider { -// panic("mock out the AsyncInt64 method") +// Float64HistogramFunc: func(name string, options ...metric.Float64HistogramOption) (metric.Float64Histogram, error) { +// panic("mock out the Float64Histogram method") // }, -// RegisterCallbackFunc: func(insts []instrument.Asynchronous, function func(context.Context)) error { -// panic("mock out the RegisterCallback method") +// Float64ObservableGaugeFunc: func(name string, options ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) { +// panic("mock out the Float64ObservableGauge method") +// }, +// Int64CounterFunc: func(name string, options ...metric.Int64CounterOption) (metric.Int64Counter, error) { +// panic("mock out the Int64Counter method") // }, -// SyncFloat64Func: func() syncfloat64.InstrumentProvider { -// panic("mock out the SyncFloat64 method") +// Int64HistogramFunc: func(name string, options ...metric.Int64HistogramOption) (metric.Int64Histogram, error) { +// panic("mock out the Int64Histogram method") // }, -// SyncInt64Func: func() syncint64.InstrumentProvider { -// panic("mock out the SyncInt64 method") +// Int64ObservableGaugeFunc: func(name string, options ...metric.Int64ObservableGaugeOption) (metric.Int64ObservableGauge, error) { +// panic("mock out the Int64ObservableGauge method") +// }, +// RegisterCallbackFunc: func(f metric.Callback, instruments ...metric.Observable) (metric.Registration, error) { +// panic("mock out the RegisterCallback method") // }, // } // @@ -42,190 +42,336 @@ import ( // // } type IMeterMock struct { - // AsyncFloat64Func mocks the AsyncFloat64 method. - AsyncFloat64Func func() asyncfloat64.InstrumentProvider + // Float64CounterFunc mocks the Float64Counter method. + Float64CounterFunc func(name string, options ...metric.Float64CounterOption) (metric.Float64Counter, error) - // AsyncInt64Func mocks the AsyncInt64 method. - AsyncInt64Func func() asyncint64.InstrumentProvider + // Float64HistogramFunc mocks the Float64Histogram method. + Float64HistogramFunc func(name string, options ...metric.Float64HistogramOption) (metric.Float64Histogram, error) - // RegisterCallbackFunc mocks the RegisterCallback method. - RegisterCallbackFunc func(insts []instrument.Asynchronous, function func(context.Context)) error + // Float64ObservableGaugeFunc mocks the Float64ObservableGauge method. + Float64ObservableGaugeFunc func(name string, options ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) + + // Int64CounterFunc mocks the Int64Counter method. + Int64CounterFunc func(name string, options ...metric.Int64CounterOption) (metric.Int64Counter, error) - // SyncFloat64Func mocks the SyncFloat64 method. - SyncFloat64Func func() syncfloat64.InstrumentProvider + // Int64HistogramFunc mocks the Int64Histogram method. + Int64HistogramFunc func(name string, options ...metric.Int64HistogramOption) (metric.Int64Histogram, error) - // SyncInt64Func mocks the SyncInt64 method. - SyncInt64Func func() syncint64.InstrumentProvider + // Int64ObservableGaugeFunc mocks the Int64ObservableGauge method. + Int64ObservableGaugeFunc func(name string, options ...metric.Int64ObservableGaugeOption) (metric.Int64ObservableGauge, error) + + // RegisterCallbackFunc mocks the RegisterCallback method. + RegisterCallbackFunc func(f metric.Callback, instruments ...metric.Observable) (metric.Registration, error) // calls tracks calls to the methods. calls struct { - // AsyncFloat64 holds details about calls to the AsyncFloat64 method. - AsyncFloat64 []struct { + // Float64Counter holds details about calls to the Float64Counter method. + Float64Counter []struct { + // Name is the name argument value. + Name string + // Options is the options argument value. + Options []metric.Float64CounterOption } - // AsyncInt64 holds details about calls to the AsyncInt64 method. - AsyncInt64 []struct { + // Float64Histogram holds details about calls to the Float64Histogram method. + Float64Histogram []struct { + // Name is the name argument value. + Name string + // Options is the options argument value. + Options []metric.Float64HistogramOption } - // RegisterCallback holds details about calls to the RegisterCallback method. - RegisterCallback []struct { - // Insts is the insts argument value. - Insts []instrument.Asynchronous - // Function is the function argument value. - Function func(context.Context) + // Float64ObservableGauge holds details about calls to the Float64ObservableGauge method. + Float64ObservableGauge []struct { + // Name is the name argument value. + Name string + // Options is the options argument value. + Options []metric.Float64ObservableGaugeOption + } + // Int64Counter holds details about calls to the Int64Counter method. + Int64Counter []struct { + // Name is the name argument value. + Name string + // Options is the options argument value. + Options []metric.Int64CounterOption + } + // Int64Histogram holds details about calls to the Int64Histogram method. + Int64Histogram []struct { + // Name is the name argument value. + Name string + // Options is the options argument value. + Options []metric.Int64HistogramOption } - // SyncFloat64 holds details about calls to the SyncFloat64 method. - SyncFloat64 []struct { + // Int64ObservableGauge holds details about calls to the Int64ObservableGauge method. + Int64ObservableGauge []struct { + // Name is the name argument value. + Name string + // Options is the options argument value. + Options []metric.Int64ObservableGaugeOption } - // SyncInt64 holds details about calls to the SyncInt64 method. - SyncInt64 []struct { + // RegisterCallback holds details about calls to the RegisterCallback method. + RegisterCallback []struct { + // F is the f argument value. + F metric.Callback + // Instruments is the instruments argument value. + Instruments []metric.Observable } } - lockAsyncFloat64 sync.RWMutex - lockAsyncInt64 sync.RWMutex - lockRegisterCallback sync.RWMutex - lockSyncFloat64 sync.RWMutex - lockSyncInt64 sync.RWMutex + lockFloat64Counter sync.RWMutex + lockFloat64Histogram sync.RWMutex + lockFloat64ObservableGauge sync.RWMutex + lockInt64Counter sync.RWMutex + lockInt64Histogram sync.RWMutex + lockInt64ObservableGauge sync.RWMutex + lockRegisterCallback sync.RWMutex } -// AsyncFloat64 calls AsyncFloat64Func. -func (mock *IMeterMock) AsyncFloat64() asyncfloat64.InstrumentProvider { - if mock.AsyncFloat64Func == nil { - panic("IMeterMock.AsyncFloat64Func: method is nil but IMeter.AsyncFloat64 was just called") +// Float64Counter calls Float64CounterFunc. +func (mock *IMeterMock) Float64Counter(name string, options ...metric.Float64CounterOption) (metric.Float64Counter, error) { + if mock.Float64CounterFunc == nil { + panic("IMeterMock.Float64CounterFunc: method is nil but IMeter.Float64Counter was just called") } callInfo := struct { - }{} - mock.lockAsyncFloat64.Lock() - mock.calls.AsyncFloat64 = append(mock.calls.AsyncFloat64, callInfo) - mock.lockAsyncFloat64.Unlock() - return mock.AsyncFloat64Func() + Name string + Options []metric.Float64CounterOption + }{ + Name: name, + Options: options, + } + mock.lockFloat64Counter.Lock() + mock.calls.Float64Counter = append(mock.calls.Float64Counter, callInfo) + mock.lockFloat64Counter.Unlock() + return mock.Float64CounterFunc(name, options...) } -// AsyncFloat64Calls gets all the calls that were made to AsyncFloat64. +// Float64CounterCalls gets all the calls that were made to Float64Counter. // Check the length with: // -// len(mockedIMeter.AsyncFloat64Calls()) -func (mock *IMeterMock) AsyncFloat64Calls() []struct { +// len(mockedIMeter.Float64CounterCalls()) +func (mock *IMeterMock) Float64CounterCalls() []struct { + Name string + Options []metric.Float64CounterOption } { var calls []struct { + Name string + Options []metric.Float64CounterOption } - mock.lockAsyncFloat64.RLock() - calls = mock.calls.AsyncFloat64 - mock.lockAsyncFloat64.RUnlock() + mock.lockFloat64Counter.RLock() + calls = mock.calls.Float64Counter + mock.lockFloat64Counter.RUnlock() return calls } -// AsyncInt64 calls AsyncInt64Func. -func (mock *IMeterMock) AsyncInt64() asyncint64.InstrumentProvider { - if mock.AsyncInt64Func == nil { - panic("IMeterMock.AsyncInt64Func: method is nil but IMeter.AsyncInt64 was just called") +// Float64Histogram calls Float64HistogramFunc. +func (mock *IMeterMock) Float64Histogram(name string, options ...metric.Float64HistogramOption) (metric.Float64Histogram, error) { + if mock.Float64HistogramFunc == nil { + panic("IMeterMock.Float64HistogramFunc: method is nil but IMeter.Float64Histogram was just called") } callInfo := struct { - }{} - mock.lockAsyncInt64.Lock() - mock.calls.AsyncInt64 = append(mock.calls.AsyncInt64, callInfo) - mock.lockAsyncInt64.Unlock() - return mock.AsyncInt64Func() + Name string + Options []metric.Float64HistogramOption + }{ + Name: name, + Options: options, + } + mock.lockFloat64Histogram.Lock() + mock.calls.Float64Histogram = append(mock.calls.Float64Histogram, callInfo) + mock.lockFloat64Histogram.Unlock() + return mock.Float64HistogramFunc(name, options...) } -// AsyncInt64Calls gets all the calls that were made to AsyncInt64. +// Float64HistogramCalls gets all the calls that were made to Float64Histogram. // Check the length with: // -// len(mockedIMeter.AsyncInt64Calls()) -func (mock *IMeterMock) AsyncInt64Calls() []struct { +// len(mockedIMeter.Float64HistogramCalls()) +func (mock *IMeterMock) Float64HistogramCalls() []struct { + Name string + Options []metric.Float64HistogramOption } { var calls []struct { + Name string + Options []metric.Float64HistogramOption } - mock.lockAsyncInt64.RLock() - calls = mock.calls.AsyncInt64 - mock.lockAsyncInt64.RUnlock() + mock.lockFloat64Histogram.RLock() + calls = mock.calls.Float64Histogram + mock.lockFloat64Histogram.RUnlock() return calls } -// RegisterCallback calls RegisterCallbackFunc. -func (mock *IMeterMock) RegisterCallback(insts []instrument.Asynchronous, function func(context.Context)) error { - if mock.RegisterCallbackFunc == nil { - panic("IMeterMock.RegisterCallbackFunc: method is nil but IMeter.RegisterCallback was just called") +// Float64ObservableGauge calls Float64ObservableGaugeFunc. +func (mock *IMeterMock) Float64ObservableGauge(name string, options ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) { + if mock.Float64ObservableGaugeFunc == nil { + panic("IMeterMock.Float64ObservableGaugeFunc: method is nil but IMeter.Float64ObservableGauge was just called") } callInfo := struct { - Insts []instrument.Asynchronous - Function func(context.Context) + Name string + Options []metric.Float64ObservableGaugeOption }{ - Insts: insts, - Function: function, + Name: name, + Options: options, } - mock.lockRegisterCallback.Lock() - mock.calls.RegisterCallback = append(mock.calls.RegisterCallback, callInfo) - mock.lockRegisterCallback.Unlock() - return mock.RegisterCallbackFunc(insts, function) + mock.lockFloat64ObservableGauge.Lock() + mock.calls.Float64ObservableGauge = append(mock.calls.Float64ObservableGauge, callInfo) + mock.lockFloat64ObservableGauge.Unlock() + return mock.Float64ObservableGaugeFunc(name, options...) } -// RegisterCallbackCalls gets all the calls that were made to RegisterCallback. +// Float64ObservableGaugeCalls gets all the calls that were made to Float64ObservableGauge. // Check the length with: // -// len(mockedIMeter.RegisterCallbackCalls()) -func (mock *IMeterMock) RegisterCallbackCalls() []struct { - Insts []instrument.Asynchronous - Function func(context.Context) +// len(mockedIMeter.Float64ObservableGaugeCalls()) +func (mock *IMeterMock) Float64ObservableGaugeCalls() []struct { + Name string + Options []metric.Float64ObservableGaugeOption } { var calls []struct { - Insts []instrument.Asynchronous - Function func(context.Context) + Name string + Options []metric.Float64ObservableGaugeOption } - mock.lockRegisterCallback.RLock() - calls = mock.calls.RegisterCallback - mock.lockRegisterCallback.RUnlock() + mock.lockFloat64ObservableGauge.RLock() + calls = mock.calls.Float64ObservableGauge + mock.lockFloat64ObservableGauge.RUnlock() return calls } -// SyncFloat64 calls SyncFloat64Func. -func (mock *IMeterMock) SyncFloat64() syncfloat64.InstrumentProvider { - if mock.SyncFloat64Func == nil { - panic("IMeterMock.SyncFloat64Func: method is nil but IMeter.SyncFloat64 was just called") +// Int64Counter calls Int64CounterFunc. +func (mock *IMeterMock) Int64Counter(name string, options ...metric.Int64CounterOption) (metric.Int64Counter, error) { + if mock.Int64CounterFunc == nil { + panic("IMeterMock.Int64CounterFunc: method is nil but IMeter.Int64Counter was just called") } callInfo := struct { - }{} - mock.lockSyncFloat64.Lock() - mock.calls.SyncFloat64 = append(mock.calls.SyncFloat64, callInfo) - mock.lockSyncFloat64.Unlock() - return mock.SyncFloat64Func() + Name string + Options []metric.Int64CounterOption + }{ + Name: name, + Options: options, + } + mock.lockInt64Counter.Lock() + mock.calls.Int64Counter = append(mock.calls.Int64Counter, callInfo) + mock.lockInt64Counter.Unlock() + return mock.Int64CounterFunc(name, options...) } -// SyncFloat64Calls gets all the calls that were made to SyncFloat64. +// Int64CounterCalls gets all the calls that were made to Int64Counter. // Check the length with: // -// len(mockedIMeter.SyncFloat64Calls()) -func (mock *IMeterMock) SyncFloat64Calls() []struct { +// len(mockedIMeter.Int64CounterCalls()) +func (mock *IMeterMock) Int64CounterCalls() []struct { + Name string + Options []metric.Int64CounterOption } { var calls []struct { + Name string + Options []metric.Int64CounterOption } - mock.lockSyncFloat64.RLock() - calls = mock.calls.SyncFloat64 - mock.lockSyncFloat64.RUnlock() + mock.lockInt64Counter.RLock() + calls = mock.calls.Int64Counter + mock.lockInt64Counter.RUnlock() return calls } -// SyncInt64 calls SyncInt64Func. -func (mock *IMeterMock) SyncInt64() syncint64.InstrumentProvider { - if mock.SyncInt64Func == nil { - panic("IMeterMock.SyncInt64Func: method is nil but IMeter.SyncInt64 was just called") +// Int64Histogram calls Int64HistogramFunc. +func (mock *IMeterMock) Int64Histogram(name string, options ...metric.Int64HistogramOption) (metric.Int64Histogram, error) { + if mock.Int64HistogramFunc == nil { + panic("IMeterMock.Int64HistogramFunc: method is nil but IMeter.Int64Histogram was just called") } callInfo := struct { - }{} - mock.lockSyncInt64.Lock() - mock.calls.SyncInt64 = append(mock.calls.SyncInt64, callInfo) - mock.lockSyncInt64.Unlock() - return mock.SyncInt64Func() + Name string + Options []metric.Int64HistogramOption + }{ + Name: name, + Options: options, + } + mock.lockInt64Histogram.Lock() + mock.calls.Int64Histogram = append(mock.calls.Int64Histogram, callInfo) + mock.lockInt64Histogram.Unlock() + return mock.Int64HistogramFunc(name, options...) } -// SyncInt64Calls gets all the calls that were made to SyncInt64. +// Int64HistogramCalls gets all the calls that were made to Int64Histogram. // Check the length with: // -// len(mockedIMeter.SyncInt64Calls()) -func (mock *IMeterMock) SyncInt64Calls() []struct { +// len(mockedIMeter.Int64HistogramCalls()) +func (mock *IMeterMock) Int64HistogramCalls() []struct { + Name string + Options []metric.Int64HistogramOption } { var calls []struct { + Name string + Options []metric.Int64HistogramOption } - mock.lockSyncInt64.RLock() - calls = mock.calls.SyncInt64 - mock.lockSyncInt64.RUnlock() + mock.lockInt64Histogram.RLock() + calls = mock.calls.Int64Histogram + mock.lockInt64Histogram.RUnlock() + return calls +} + +// Int64ObservableGauge calls Int64ObservableGaugeFunc. +func (mock *IMeterMock) Int64ObservableGauge(name string, options ...metric.Int64ObservableGaugeOption) (metric.Int64ObservableGauge, error) { + if mock.Int64ObservableGaugeFunc == nil { + panic("IMeterMock.Int64ObservableGaugeFunc: method is nil but IMeter.Int64ObservableGauge was just called") + } + callInfo := struct { + Name string + Options []metric.Int64ObservableGaugeOption + }{ + Name: name, + Options: options, + } + mock.lockInt64ObservableGauge.Lock() + mock.calls.Int64ObservableGauge = append(mock.calls.Int64ObservableGauge, callInfo) + mock.lockInt64ObservableGauge.Unlock() + return mock.Int64ObservableGaugeFunc(name, options...) +} + +// Int64ObservableGaugeCalls gets all the calls that were made to Int64ObservableGauge. +// Check the length with: +// +// len(mockedIMeter.Int64ObservableGaugeCalls()) +func (mock *IMeterMock) Int64ObservableGaugeCalls() []struct { + Name string + Options []metric.Int64ObservableGaugeOption +} { + var calls []struct { + Name string + Options []metric.Int64ObservableGaugeOption + } + mock.lockInt64ObservableGauge.RLock() + calls = mock.calls.Int64ObservableGauge + mock.lockInt64ObservableGauge.RUnlock() + return calls +} + +// RegisterCallback calls RegisterCallbackFunc. +func (mock *IMeterMock) RegisterCallback(f metric.Callback, instruments ...metric.Observable) (metric.Registration, error) { + if mock.RegisterCallbackFunc == nil { + panic("IMeterMock.RegisterCallbackFunc: method is nil but IMeter.RegisterCallback was just called") + } + callInfo := struct { + F metric.Callback + Instruments []metric.Observable + }{ + F: f, + Instruments: instruments, + } + mock.lockRegisterCallback.Lock() + mock.calls.RegisterCallback = append(mock.calls.RegisterCallback, callInfo) + mock.lockRegisterCallback.Unlock() + return mock.RegisterCallbackFunc(f, instruments...) +} + +// RegisterCallbackCalls gets all the calls that were made to RegisterCallback. +// Check the length with: +// +// len(mockedIMeter.RegisterCallbackCalls()) +func (mock *IMeterMock) RegisterCallbackCalls() []struct { + F metric.Callback + Instruments []metric.Observable +} { + var calls []struct { + F metric.Callback + Instruments []metric.Observable + } + mock.lockRegisterCallback.RLock() + calls = mock.calls.RegisterCallback + mock.lockRegisterCallback.RUnlock() return calls } diff --git a/operator/controllers/lifecycle/interfaces/fake/spanitem_mock.go b/operator/controllers/lifecycle/interfaces/fake/spanitem_mock.go index 0b61718c96..bbff62ef33 100644 --- a/operator/controllers/lifecycle/interfaces/fake/spanitem_mock.go +++ b/operator/controllers/lifecycle/interfaces/fake/spanitem_mock.go @@ -4,17 +4,16 @@ package fake import ( - "sync" - "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" + "sync" ) -// SpanItemMock is a mock implementation of common.SpanItem. +// SpanItemMock is a mock implementation of interfaces.SpanItem. // // func TestSomethingThatUsesSpanItem(t *testing.T) { // -// // make and configure a mocked common.SpanItem +// // make and configure a mocked interfaces.SpanItem // mockedSpanItem := &SpanItemMock{ // GetSpanKeyFunc: func(phase string) string { // panic("mock out the GetSpanKey method") @@ -30,7 +29,7 @@ import ( // }, // } // -// // use mockedSpanItem in code that requires common.SpanItem +// // use mockedSpanItem in code that requires interfaces.SpanItem // // and then make assertions. // // } diff --git a/operator/controllers/lifecycle/interfaces/fake/sync/tracer_provider_float_mock.go b/operator/controllers/lifecycle/interfaces/fake/sync/tracer_provider_float_mock.go deleted file mode 100644 index c3ecb2c0ea..0000000000 --- a/operator/controllers/lifecycle/interfaces/fake/sync/tracer_provider_float_mock.go +++ /dev/null @@ -1,179 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package fake - -import ( - "sync" - - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" -) - -// ITracerProviderSyncFloat64Mock is a mock implementation of interfaces.ITracerProviderSyncFloat64. -// -// func TestSomethingThatUsesITracerProviderSyncFloat64(t *testing.T) { -// -// // make and configure a mocked interfaces.ITracerProviderSyncFloat64 -// mockedITracerProviderSyncFloat64 := &ITracerProviderSyncFloat64Mock{ -// CounterFunc: func(name string, opts ...instrument.Option) (syncfloat64.Counter, error) { -// panic("mock out the Counter method") -// }, -// HistogramFunc: func(name string, opts ...instrument.Option) (syncfloat64.Histogram, error) { -// panic("mock out the Histogram method") -// }, -// UpDownCounterFunc: func(name string, opts ...instrument.Option) (syncfloat64.UpDownCounter, error) { -// panic("mock out the UpDownCounter method") -// }, -// } -// -// // use mockedITracerProviderSyncFloat64 in code that requires interfaces.ITracerProviderSyncFloat64 -// // and then make assertions. -// -// } -type ITracerProviderSyncFloat64Mock struct { - // CounterFunc mocks the Counter method. - CounterFunc func(name string, opts ...instrument.Option) (syncfloat64.Counter, error) - - // HistogramFunc mocks the Histogram method. - HistogramFunc func(name string, opts ...instrument.Option) (syncfloat64.Histogram, error) - - // UpDownCounterFunc mocks the UpDownCounter method. - UpDownCounterFunc func(name string, opts ...instrument.Option) (syncfloat64.UpDownCounter, error) - - // calls tracks calls to the methods. - calls struct { - // Counter holds details about calls to the Counter method. - Counter []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - // Histogram holds details about calls to the Histogram method. - Histogram []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - // UpDownCounter holds details about calls to the UpDownCounter method. - UpDownCounter []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - } - lockCounter sync.RWMutex - lockHistogram sync.RWMutex - lockUpDownCounter sync.RWMutex -} - -// Counter calls CounterFunc. -func (mock *ITracerProviderSyncFloat64Mock) Counter(name string, opts ...instrument.Option) (syncfloat64.Counter, error) { - if mock.CounterFunc == nil { - panic("ITracerProviderSyncFloat64Mock.CounterFunc: method is nil but ITracerProviderSyncFloat64.Counter was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockCounter.Lock() - mock.calls.Counter = append(mock.calls.Counter, callInfo) - mock.lockCounter.Unlock() - return mock.CounterFunc(name, opts...) -} - -// CounterCalls gets all the calls that were made to Counter. -// Check the length with: -// -// len(mockedITracerProviderSyncFloat64.CounterCalls()) -func (mock *ITracerProviderSyncFloat64Mock) CounterCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockCounter.RLock() - calls = mock.calls.Counter - mock.lockCounter.RUnlock() - return calls -} - -// Histogram calls HistogramFunc. -func (mock *ITracerProviderSyncFloat64Mock) Histogram(name string, opts ...instrument.Option) (syncfloat64.Histogram, error) { - if mock.HistogramFunc == nil { - panic("ITracerProviderSyncFloat64Mock.HistogramFunc: method is nil but ITracerProviderSyncFloat64.Histogram was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockHistogram.Lock() - mock.calls.Histogram = append(mock.calls.Histogram, callInfo) - mock.lockHistogram.Unlock() - return mock.HistogramFunc(name, opts...) -} - -// HistogramCalls gets all the calls that were made to Histogram. -// Check the length with: -// -// len(mockedITracerProviderSyncFloat64.HistogramCalls()) -func (mock *ITracerProviderSyncFloat64Mock) HistogramCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockHistogram.RLock() - calls = mock.calls.Histogram - mock.lockHistogram.RUnlock() - return calls -} - -// UpDownCounter calls UpDownCounterFunc. -func (mock *ITracerProviderSyncFloat64Mock) UpDownCounter(name string, opts ...instrument.Option) (syncfloat64.UpDownCounter, error) { - if mock.UpDownCounterFunc == nil { - panic("ITracerProviderSyncFloat64Mock.UpDownCounterFunc: method is nil but ITracerProviderSyncFloat64.UpDownCounter was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockUpDownCounter.Lock() - mock.calls.UpDownCounter = append(mock.calls.UpDownCounter, callInfo) - mock.lockUpDownCounter.Unlock() - return mock.UpDownCounterFunc(name, opts...) -} - -// UpDownCounterCalls gets all the calls that were made to UpDownCounter. -// Check the length with: -// -// len(mockedITracerProviderSyncFloat64.UpDownCounterCalls()) -func (mock *ITracerProviderSyncFloat64Mock) UpDownCounterCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockUpDownCounter.RLock() - calls = mock.calls.UpDownCounter - mock.lockUpDownCounter.RUnlock() - return calls -} diff --git a/operator/controllers/lifecycle/interfaces/fake/sync/tracer_provider_int_mock.go b/operator/controllers/lifecycle/interfaces/fake/sync/tracer_provider_int_mock.go deleted file mode 100644 index c24380afa8..0000000000 --- a/operator/controllers/lifecycle/interfaces/fake/sync/tracer_provider_int_mock.go +++ /dev/null @@ -1,179 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package fake - -import ( - "sync" - - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/syncint64" -) - -// ITracerProviderSyncInt64Mock is a mock implementation of interfaces.ITracerProviderSyncInt64. -// -// func TestSomethingThatUsesITracerProviderSyncInt64(t *testing.T) { -// -// // make and configure a mocked interfaces.ITracerProviderSyncInt64 -// mockedITracerProviderSyncInt64 := &ITracerProviderSyncInt64Mock{ -// CounterFunc: func(name string, opts ...instrument.Option) (syncint64.Counter, error) { -// panic("mock out the Counter method") -// }, -// HistogramFunc: func(name string, opts ...instrument.Option) (syncint64.Histogram, error) { -// panic("mock out the Histogram method") -// }, -// UpDownCounterFunc: func(name string, opts ...instrument.Option) (syncint64.UpDownCounter, error) { -// panic("mock out the UpDownCounter method") -// }, -// } -// -// // use mockedITracerProviderSyncInt64 in code that requires interfaces.ITracerProviderSyncInt64 -// // and then make assertions. -// -// } -type ITracerProviderSyncInt64Mock struct { - // CounterFunc mocks the Counter method. - CounterFunc func(name string, opts ...instrument.Option) (syncint64.Counter, error) - - // HistogramFunc mocks the Histogram method. - HistogramFunc func(name string, opts ...instrument.Option) (syncint64.Histogram, error) - - // UpDownCounterFunc mocks the UpDownCounter method. - UpDownCounterFunc func(name string, opts ...instrument.Option) (syncint64.UpDownCounter, error) - - // calls tracks calls to the methods. - calls struct { - // Counter holds details about calls to the Counter method. - Counter []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - // Histogram holds details about calls to the Histogram method. - Histogram []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - // UpDownCounter holds details about calls to the UpDownCounter method. - UpDownCounter []struct { - // Name is the name argument value. - Name string - // Opts is the opts argument value. - Opts []instrument.Option - } - } - lockCounter sync.RWMutex - lockHistogram sync.RWMutex - lockUpDownCounter sync.RWMutex -} - -// Counter calls CounterFunc. -func (mock *ITracerProviderSyncInt64Mock) Counter(name string, opts ...instrument.Option) (syncint64.Counter, error) { - if mock.CounterFunc == nil { - panic("ITracerProviderSyncInt64Mock.CounterFunc: method is nil but ITracerProviderSyncInt64.Counter was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockCounter.Lock() - mock.calls.Counter = append(mock.calls.Counter, callInfo) - mock.lockCounter.Unlock() - return mock.CounterFunc(name, opts...) -} - -// CounterCalls gets all the calls that were made to Counter. -// Check the length with: -// -// len(mockedITracerProviderSyncInt64.CounterCalls()) -func (mock *ITracerProviderSyncInt64Mock) CounterCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockCounter.RLock() - calls = mock.calls.Counter - mock.lockCounter.RUnlock() - return calls -} - -// Histogram calls HistogramFunc. -func (mock *ITracerProviderSyncInt64Mock) Histogram(name string, opts ...instrument.Option) (syncint64.Histogram, error) { - if mock.HistogramFunc == nil { - panic("ITracerProviderSyncInt64Mock.HistogramFunc: method is nil but ITracerProviderSyncInt64.Histogram was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockHistogram.Lock() - mock.calls.Histogram = append(mock.calls.Histogram, callInfo) - mock.lockHistogram.Unlock() - return mock.HistogramFunc(name, opts...) -} - -// HistogramCalls gets all the calls that were made to Histogram. -// Check the length with: -// -// len(mockedITracerProviderSyncInt64.HistogramCalls()) -func (mock *ITracerProviderSyncInt64Mock) HistogramCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockHistogram.RLock() - calls = mock.calls.Histogram - mock.lockHistogram.RUnlock() - return calls -} - -// UpDownCounter calls UpDownCounterFunc. -func (mock *ITracerProviderSyncInt64Mock) UpDownCounter(name string, opts ...instrument.Option) (syncint64.UpDownCounter, error) { - if mock.UpDownCounterFunc == nil { - panic("ITracerProviderSyncInt64Mock.UpDownCounterFunc: method is nil but ITracerProviderSyncInt64.UpDownCounter was just called") - } - callInfo := struct { - Name string - Opts []instrument.Option - }{ - Name: name, - Opts: opts, - } - mock.lockUpDownCounter.Lock() - mock.calls.UpDownCounter = append(mock.calls.UpDownCounter, callInfo) - mock.lockUpDownCounter.Unlock() - return mock.UpDownCounterFunc(name, opts...) -} - -// UpDownCounterCalls gets all the calls that were made to UpDownCounter. -// Check the length with: -// -// len(mockedITracerProviderSyncInt64.UpDownCounterCalls()) -func (mock *ITracerProviderSyncInt64Mock) UpDownCounterCalls() []struct { - Name string - Opts []instrument.Option -} { - var calls []struct { - Name string - Opts []instrument.Option - } - mock.lockUpDownCounter.RLock() - calls = mock.calls.UpDownCounter - mock.lockUpDownCounter.RUnlock() - return calls -} diff --git a/operator/controllers/lifecycle/interfaces/meter.go b/operator/controllers/lifecycle/interfaces/meter.go index bca8a83d40..9d136d144d 100644 --- a/operator/controllers/lifecycle/interfaces/meter.go +++ b/operator/controllers/lifecycle/interfaces/meter.go @@ -2,23 +2,15 @@ package interfaces import ( "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" - "go.opentelemetry.io/otel/metric/instrument/asyncint64" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" - "go.opentelemetry.io/otel/metric/instrument/syncint64" ) //go:generate moq -pkg fake -skip-ensure -out ./fake/meter_mock.go . IMeter -type IMeter = metric.Meter - -//go:generate moq -pkg fake -skip-ensure -out ./fake/async/tracer_provider_int_mock.go . ITracerProviderAsyncInt64 -type ITracerProviderAsyncInt64 = asyncint64.InstrumentProvider - -//go:generate moq -pkg fake -skip-ensure -out ./fake/async/tracer_provider_float_mock.go . ITracerProviderAsyncFloat64 -type ITracerProviderAsyncFloat64 = asyncfloat64.InstrumentProvider - -//go:generate moq -pkg fake -skip-ensure -out ./fake/sync/tracer_provider_int_mock.go . ITracerProviderSyncInt64 -type ITracerProviderSyncInt64 = syncint64.InstrumentProvider - -//go:generate moq -pkg fake -skip-ensure -out ./fake/sync/tracer_provider_float_mock.go . ITracerProviderSyncFloat64 -type ITracerProviderSyncFloat64 = syncfloat64.InstrumentProvider +type IMeter interface { + Int64Counter(name string, options ...metric.Int64CounterOption) (metric.Int64Counter, error) + Int64Histogram(name string, options ...metric.Int64HistogramOption) (metric.Int64Histogram, error) + Float64Counter(name string, options ...metric.Float64CounterOption) (metric.Float64Counter, error) + Float64Histogram(name string, options ...metric.Float64HistogramOption) (metric.Float64Histogram, error) + RegisterCallback(f metric.Callback, instruments ...metric.Observable) (metric.Registration, error) + Int64ObservableGauge(name string, options ...metric.Int64ObservableGaugeOption) (metric.Int64ObservableGauge, error) + Float64ObservableGauge(name string, options ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) +} diff --git a/operator/controllers/lifecycle/keptnappversion/controller.go b/operator/controllers/lifecycle/keptnappversion/controller.go index 99c5b4476f..6d37e9d752 100644 --- a/operator/controllers/lifecycle/keptnappversion/controller.go +++ b/operator/controllers/lifecycle/keptnappversion/controller.go @@ -28,6 +28,7 @@ import ( controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "k8s.io/apimachinery/pkg/api/errors" @@ -188,7 +189,7 @@ func (r *KeptnAppVersionReconciler) finishKeptnAppVersionReconcile(ctx context.C // metrics: add app duration duration := appVersion.Status.EndTime.Time.Sub(appVersion.Status.StartTime.Time) - r.Meters.AppDuration.Record(ctx, duration.Seconds(), attrs...) + r.Meters.AppDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...)) spanAppTrace.AddEvent(appVersion.Name + " has finished") spanAppTrace.SetStatus(codes.Ok, "Finished") @@ -215,7 +216,7 @@ func (r *KeptnAppVersionReconciler) setupSpansContexts(ctx context.Context, appV if appVersion.IsEndTimeSet() { r.Log.Info("Increasing app count") attrs := appVersion.GetMetricsAttributes() - r.Meters.AppCount.Add(ctx, 1, attrs...) + r.Meters.AppCount.Add(ctx, 1, metric.WithAttributes(attrs...)) } span.End() } diff --git a/operator/controllers/lifecycle/keptnevaluation/controller.go b/operator/controllers/lifecycle/keptnevaluation/controller.go index 1202211007..7f126e182d 100644 --- a/operator/controllers/lifecycle/keptnevaluation/controller.go +++ b/operator/controllers/lifecycle/keptnevaluation/controller.go @@ -29,6 +29,7 @@ import ( controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "k8s.io/apimachinery/pkg/api/errors" @@ -248,11 +249,11 @@ func (r *KeptnEvaluationReconciler) updateFinishedEvaluationMetrics(ctx context. r.Log.Info("Increasing evaluation count") // metrics: increment evaluation counter - r.Meters.EvaluationCount.Add(ctx, 1, attrs...) + r.Meters.EvaluationCount.Add(ctx, 1, metric.WithAttributes(attrs...)) // metrics: add evaluation duration duration := evaluation.Status.EndTime.Time.Sub(evaluation.Status.StartTime.Time) - r.Meters.EvaluationDuration.Record(ctx, duration.Seconds(), attrs...) + r.Meters.EvaluationDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...)) return nil } diff --git a/operator/controllers/lifecycle/keptntask/controller.go b/operator/controllers/lifecycle/keptntask/controller.go index cd6ab1d428..9a0482197d 100644 --- a/operator/controllers/lifecycle/keptntask/controller.go +++ b/operator/controllers/lifecycle/keptntask/controller.go @@ -28,6 +28,7 @@ import ( controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" batchv1 "k8s.io/api/batch/v1" @@ -127,11 +128,11 @@ func (r *KeptnTaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( r.Log.Info("Increasing task count") // metrics: increment task counter - r.Meters.TaskCount.Add(ctx, 1, attrs...) + r.Meters.TaskCount.Add(ctx, 1, metric.WithAttributes(attrs...)) // metrics: add task duration duration := task.Status.EndTime.Time.Sub(task.Status.StartTime.Time) - r.Meters.TaskDuration.Record(ctx, duration.Seconds(), attrs...) + r.Meters.TaskDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...)) return ctrl.Result{}, nil } diff --git a/operator/controllers/lifecycle/keptnworkloadinstance/controller.go b/operator/controllers/lifecycle/keptnworkloadinstance/controller.go index b9e2141e6c..72dfc0513d 100644 --- a/operator/controllers/lifecycle/keptnworkloadinstance/controller.go +++ b/operator/controllers/lifecycle/keptnworkloadinstance/controller.go @@ -28,6 +28,7 @@ import ( controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "k8s.io/apimachinery/pkg/api/errors" @@ -195,7 +196,7 @@ func (r *KeptnWorkloadInstanceReconciler) finishKeptnWorkloadInstanceReconcile(c // metrics: add deployment duration duration := workloadInstance.Status.EndTime.Time.Sub(workloadInstance.Status.StartTime.Time) - r.Meters.DeploymentDuration.Record(ctx, duration.Seconds(), attrs...) + r.Meters.DeploymentDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...)) spanWorkloadTrace.AddEvent(workloadInstance.Name + " has finished") spanWorkloadTrace.SetStatus(codes.Ok, "Finished") @@ -236,7 +237,7 @@ func (r *KeptnWorkloadInstanceReconciler) setupSpansContexts(ctx context.Context if workloadInstance.IsEndTimeSet() { r.Log.Info("Increasing deployment count") attrs := workloadInstance.GetMetricsAttributes() - r.Meters.DeploymentCount.Add(ctx, 1, attrs...) + r.Meters.DeploymentCount.Add(ctx, 1, metric.WithAttributes(attrs...)) } span.End() } diff --git a/operator/go.mod b/operator/go.mod index 9345ac695b..f299a51c6d 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -16,14 +16,14 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 github.com/stretchr/testify v1.8.2 - go.opentelemetry.io/otel v1.11.2 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2 - go.opentelemetry.io/otel/exporters/prometheus v0.34.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2 - go.opentelemetry.io/otel/metric v0.34.0 - go.opentelemetry.io/otel/sdk v1.11.2 - go.opentelemetry.io/otel/sdk/metric v0.34.0 - go.opentelemetry.io/otel/trace v1.11.2 + go.opentelemetry.io/otel v1.15.1 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1 + go.opentelemetry.io/otel/exporters/prometheus v0.38.1 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 + go.opentelemetry.io/otel/metric v0.38.1 + go.opentelemetry.io/otel/sdk v1.15.1 + go.opentelemetry.io/otel/sdk/metric v0.38.1 + go.opentelemetry.io/otel/trace v1.15.1 google.golang.org/grpc v1.54.1 k8s.io/api v0.26.4 k8s.io/apiextensions-apiserver v0.26.4 @@ -47,7 +47,7 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect @@ -79,11 +79,10 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/pflag v1.0.5 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect diff --git a/operator/go.sum b/operator/go.sum index 9007300185..ab10917469 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -48,8 +48,8 @@ github.com/benbjohnson/clock v1.3.4/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= -github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -256,7 +256,6 @@ github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= @@ -289,26 +288,26 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0= -go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 h1:htgM8vZIF8oPSCxa341e3IZ4yr/sKxgu8KZYllByiVY= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2/go.mod h1:rqbht/LlhVBgn5+k3M5QK96K5Xb0DvXpMJ5SFQpY6uw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 h1:fqR1kli93643au1RKo0Uma3d2aPQKT+WBKfTSBaKbOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2/go.mod h1:5Qn6qvgkMsLDX+sYK64rHb1FPhpn0UtxF+ouX1uhyJE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2 h1:ERwKPn9Aer7Gxsc0+ZlutlH1bEEAUXAUhqm3Y45ABbk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2/go.mod h1:jWZUM2MWhWCJ9J9xVbRx7tzK1mXKpAlze4CeulycwVY= -go.opentelemetry.io/otel/exporters/prometheus v0.34.0 h1:L5D+HxdaC/ORB47ribbTBbkXRZs9JzPjq0EoIOMWncM= -go.opentelemetry.io/otel/exporters/prometheus v0.34.0/go.mod h1:6gUoJyfhoWqF0tOLaY0ZmKgkQRcvEQx6p5rVlKHp3s4= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2 h1:BhEVgvuE1NWLLuMLvC6sif791F45KFHi5GhOs1KunZU= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2/go.mod h1:bx//lU66dPzNT+Y0hHA12ciKoMOH9iixEwCqC1OeQWQ= -go.opentelemetry.io/otel/metric v0.34.0 h1:MCPoQxcg/26EuuJwpYN1mZTeCYAUGx8ABxfW07YkjP8= -go.opentelemetry.io/otel/metric v0.34.0/go.mod h1:ZFuI4yQGNCupurTXCwkeD/zHBt+C2bR7bw5JqUm/AP8= -go.opentelemetry.io/otel/sdk v1.11.2 h1:GF4JoaEx7iihdMFu30sOyRx52HDHOkl9xQ8SMqNXUiU= -go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU= -go.opentelemetry.io/otel/sdk/metric v0.34.0 h1:7ElxfQpXCFZlRTvVRTkcUvK8Gt5DC8QzmzsLsO2gdzo= -go.opentelemetry.io/otel/sdk/metric v0.34.0/go.mod h1:l4r16BIqiqPy5rd14kkxllPy/fOI4tWo1jkpD9Z3ffQ= -go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0= -go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= +go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8= +go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1 h1:XYDQtNzdb2T4uM1pku2m76eSMDJgqhJ+6KzkqgQBALc= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1/go.mod h1:uOTV75+LOzV+ODmL8ahRLWkFA3eQcSC2aAsbxIu4duk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 h1:tyoeaUh8REKay72DVYsSEBYV18+fGONe+YYPaOxgLoE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1/go.mod h1:HUSnrjQQ19KX9ECjpQxufsF+3ioD3zISPMlauTPZu2g= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1 h1:pIfoG5IAZFzp9EUlJzdSkpUwpaUAAnD+Ru1nBLTACIQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1/go.mod h1:poNKBqF5+nR/6ke2oGTDjHfksrsHDOHXAl2g4+9ONsY= +go.opentelemetry.io/otel/exporters/prometheus v0.38.1 h1:GwalIvFIx91qIA8qyAyqYj9lql5Ba2Oxj/jDG6+3UoU= +go.opentelemetry.io/otel/exporters/prometheus v0.38.1/go.mod h1:6K7aBvWHXRUcNYFSj6Hi5hHwzA1jYflG/T8snrX4dYM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= +go.opentelemetry.io/otel/metric v0.38.1 h1:2MM7m6wPw9B8Qv8iHygoAgkbejed59uUR6ezR5T3X2s= +go.opentelemetry.io/otel/metric v0.38.1/go.mod h1:FwqNHD3I/5iX9pfrRGZIlYICrJv0rHEUl2Ln5vdIVnQ= +go.opentelemetry.io/otel/sdk v1.15.1 h1:5FKR+skgpzvhPQHIEfcwMYjCBr14LWzs3uSqKiQzETI= +go.opentelemetry.io/otel/sdk v1.15.1/go.mod h1:8rVtxQfrbmbHKfqzpQkT5EzZMcbMBwTzNAggbEAM0KA= +go.opentelemetry.io/otel/sdk/metric v0.38.1 h1:EkO5wI4NT/fUaoPMGc0fKV28JaWe7q4vfVpEVasGb+8= +go.opentelemetry.io/otel/sdk/metric v0.38.1/go.mod h1:Rn4kSXFF9ZQZ5lL1pxQjCbK4seiO+U7s0ncmIFJaj34= +go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY= +go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -316,7 +315,7 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= diff --git a/operator/test/component/common/common.go b/operator/test/component/common/common.go index 14e1737e49..6b010ad1f4 100644 --- a/operator/test/component/common/common.go +++ b/operator/test/component/common/common.go @@ -16,9 +16,8 @@ import ( ginkgotypes "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/unit" - "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/metric" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" otelsdk "go.opentelemetry.io/otel/sdk/trace" sdktest "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" @@ -36,16 +35,16 @@ import ( ) func InitKeptnMeters() apicommon.KeptnMeters { - provider := metric.NewMeterProvider() + provider := sdkmetric.NewMeterProvider() meter := provider.Meter("keptn/task") - deploymentCount, _ := meter.SyncInt64().Counter("keptn.deployment.count", instrument.WithDescription("a simple counter for Keptn Deployments")) - deploymentDuration, _ := meter.SyncFloat64().Histogram("keptn.deployment.duration", instrument.WithDescription("a histogram of duration for Keptn Deployments"), instrument.WithUnit(unit.Unit("s"))) - taskCount, _ := meter.SyncInt64().Counter("keptn.task.count", instrument.WithDescription("a simple counter for Keptn Tasks")) - taskDuration, _ := meter.SyncFloat64().Histogram("keptn.task.duration", instrument.WithDescription("a histogram of duration for Keptn Tasks"), instrument.WithUnit(unit.Unit("s"))) - appCount, _ := meter.SyncInt64().Counter("keptn.app.count", instrument.WithDescription("a simple counter for Keptn Apps")) - appDuration, _ := meter.SyncFloat64().Histogram("keptn.app.duration", instrument.WithDescription("a histogram of duration for Keptn Apps"), instrument.WithUnit(unit.Unit("s"))) - evaluationCount, _ := meter.SyncInt64().Counter("keptn.evaluation.count", instrument.WithDescription("a simple counter for Keptn Evaluations")) - evaluationDuration, _ := meter.SyncFloat64().Histogram("keptn.evaluation.duration", instrument.WithDescription("a histogram of duration for Keptn Evaluations"), instrument.WithUnit(unit.Unit("s"))) + deploymentCount, _ := meter.Int64Counter("keptn.deployment.count", metric.WithDescription("a simple counter for Keptn Deployments")) + deploymentDuration, _ := meter.Float64Histogram("keptn.deployment.duration", metric.WithDescription("a histogram of duration for Keptn Deployments"), metric.WithUnit("s")) + taskCount, _ := meter.Int64Counter("keptn.task.count", metric.WithDescription("a simple counter for Keptn Tasks")) + taskDuration, _ := meter.Float64Histogram("keptn.task.duration", metric.WithDescription("a histogram of duration for Keptn Tasks"), metric.WithUnit("s")) + appCount, _ := meter.Int64Counter("keptn.app.count", metric.WithDescription("a simple counter for Keptn Apps")) + appDuration, _ := meter.Float64Histogram("keptn.app.duration", metric.WithDescription("a histogram of duration for Keptn Apps"), metric.WithUnit("s")) + evaluationCount, _ := meter.Int64Counter("keptn.evaluation.count", metric.WithDescription("a simple counter for Keptn Evaluations")) + evaluationDuration, _ := meter.Float64Histogram("keptn.evaluation.duration", metric.WithDescription("a histogram of duration for Keptn Evaluations"), metric.WithUnit("s")) meters := apicommon.KeptnMeters{ TaskCount: taskCount, From e381f7fc6d79703b9f32dbf49331247107597b20 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 May 2023 14:45:21 +0200 Subject: [PATCH 30/62] deps: update dependency argoproj/argo-cd to v2.7.2 (#1423) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- examples/support/argo/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/support/argo/Makefile b/examples/support/argo/Makefile index 1a55bc6cf8..61cd8312c2 100644 --- a/examples/support/argo/Makefile +++ b/examples/support/argo/Makefile @@ -2,7 +2,7 @@ LFC_NAMESPACE ?= keptn-lifecycle-toolkit-system PODTATO_NAMESPACE ?= podtato-kubectl ARGO_NAMESPACE ?= argocd # renovate: datasource=github-tags depName=argoproj/argo-cd -ARGO_VERSION ?= v2.7.1 +ARGO_VERSION ?= v2.7.2 ARGO_SECRET = $(shell kubectl -n ${ARGO_NAMESPACE} get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo) .PHONY: install From daedf878eeb8d00d717e5746b46dd651c6fba8de Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 May 2023 15:03:22 +0200 Subject: [PATCH 31/62] deps: update github.com/keptn/lifecycle-toolkit/klt-cert-manager digest to e381f7f (#1422) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- metrics-operator/go.mod | 2 +- metrics-operator/go.sum | 4 ++-- operator/go.mod | 2 +- operator/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index 2e1844c50a..5ddbfba8ef 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-logr/logr v1.2.4 github.com/gorilla/mux v1.8.0 github.com/kelseyhightower/envconfig v1.4.0 - github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f + github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 github.com/open-feature/go-sdk v1.3.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index bd84793da3..9d49047e16 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -263,8 +263,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f h1:ildQjDQmBjX3fdAShsQJN7dkKfhivlnlDbEh/Etjli4= -github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 h1:V44aeshIPrEu2L4ElKJ9Y0VPDp5j9BhT8eMzhjICyGU= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/operator/go.mod b/operator/go.mod index f299a51c6d..a34af9878e 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-logr/logr v1.2.4 github.com/imdario/mergo v0.3.15 github.com/kelseyhightower/envconfig v1.4.0 - github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f + github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4 github.com/magiconair/properties v1.8.7 github.com/onsi/ginkgo/v2 v2.9.4 diff --git a/operator/go.sum b/operator/go.sum index ab10917469..8272d267ca 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -206,8 +206,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f h1:ildQjDQmBjX3fdAShsQJN7dkKfhivlnlDbEh/Etjli4= -github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 h1:V44aeshIPrEu2L4ElKJ9Y0VPDp5j9BhT8eMzhjICyGU= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4 h1:LI+iOb7v1zIAtHQum79CbV+4HB1PCAim+TuCCRRsW7o= github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4/go.mod h1:8rQ1flqblBWy43k4xJnoaMUA7e50zP95QIab3z6NCw4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= From 24a60f5e6f8f3a383dfce554d644bfd974c4b5fd Mon Sep 17 00:00:00 2001 From: odubajDT <93584209+odubajDT@users.noreply.github.com> Date: Wed, 17 May 2023 15:49:32 +0200 Subject: [PATCH 32/62] fix(metrics-operator): introduce IsStatusSet method to KeptnMetric (#1427) Signed-off-by: odubajDT --- .../api/v1alpha3/keptnmetric_types.go | 4 ++ .../api/v1alpha3/keptnmetric_types_test.go | 53 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 metrics-operator/api/v1alpha3/keptnmetric_types_test.go diff --git a/metrics-operator/api/v1alpha3/keptnmetric_types.go b/metrics-operator/api/v1alpha3/keptnmetric_types.go index d4781566ec..5b4e507609 100644 --- a/metrics-operator/api/v1alpha3/keptnmetric_types.go +++ b/metrics-operator/api/v1alpha3/keptnmetric_types.go @@ -79,3 +79,7 @@ type KeptnMetricList struct { func init() { SchemeBuilder.Register(&KeptnMetric{}, &KeptnMetricList{}) } + +func (s *KeptnMetric) IsStatusSet() bool { + return s.Status.Value != "" +} diff --git a/metrics-operator/api/v1alpha3/keptnmetric_types_test.go b/metrics-operator/api/v1alpha3/keptnmetric_types_test.go new file mode 100644 index 0000000000..a7700f4017 --- /dev/null +++ b/metrics-operator/api/v1alpha3/keptnmetric_types_test.go @@ -0,0 +1,53 @@ +package v1alpha3 + +import ( + "testing" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestKeptnMetric_IsStatusSet(t *testing.T) { + type fields struct { + TypeMeta v1.TypeMeta + ObjectMeta v1.ObjectMeta + Spec KeptnMetricSpec + Status KeptnMetricStatus + } + tests := []struct { + name string + fields fields + want bool + }{ + { + name: "No value set", + fields: fields{ + Status: KeptnMetricStatus{ + Value: "", + }, + }, + want: false, + }, + { + name: "we have a value", + fields: fields{ + Status: KeptnMetricStatus{ + Value: "1.0", + }, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &KeptnMetric{ + TypeMeta: tt.fields.TypeMeta, + ObjectMeta: tt.fields.ObjectMeta, + Spec: tt.fields.Spec, + Status: tt.fields.Status, + } + if got := s.IsStatusSet(); got != tt.want { + t.Errorf("IsStatusSet() = %v, want %v", got, tt.want) + } + }) + } +} From f0f7edf7041d438b8d8804ad9341ef878ed625de Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 May 2023 16:10:59 +0200 Subject: [PATCH 33/62] deps: update github.com/keptn/lifecycle-toolkit/metrics-operator digest to e381f7f (#1268) Signed-off-by: Renovate Bot Signed-off-by: odubajDT Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: odubajDT --- operator/controllers/common/fake/fakeclient.go | 2 +- .../common/providers/keptnmetric/keptnmetric.go | 2 +- .../common/providers/keptnmetric/keptnmetric_test.go | 2 +- .../lifecycle/keptnevaluation/controller_test.go | 2 +- operator/go.mod | 4 ++-- operator/go.sum | 8 ++++---- operator/main.go | 2 +- operator/test/component/common/common.go | 2 +- operator/test/component/evaluation/evaluation_test.go | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/operator/controllers/common/fake/fakeclient.go b/operator/controllers/common/fake/fakeclient.go index c627e2ced3..dd4dc356a1 100644 --- a/operator/controllers/common/fake/fakeclient.go +++ b/operator/controllers/common/fake/fakeclient.go @@ -1,7 +1,7 @@ package fake import ( - metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" + metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3" lfcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" optionsv1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/options/v1alpha1" corev1 "k8s.io/api/core/v1" diff --git a/operator/controllers/common/providers/keptnmetric/keptnmetric.go b/operator/controllers/common/providers/keptnmetric/keptnmetric.go index fd08879a7b..7cc6ba799d 100644 --- a/operator/controllers/common/providers/keptnmetric/keptnmetric.go +++ b/operator/controllers/common/providers/keptnmetric/keptnmetric.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/go-logr/logr" - metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" + metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" "github.com/keptn/lifecycle-toolkit/operator/controllers/common" "k8s.io/apimachinery/pkg/types" diff --git a/operator/controllers/common/providers/keptnmetric/keptnmetric_test.go b/operator/controllers/common/providers/keptnmetric/keptnmetric_test.go index e05cb5919a..beed84da0e 100644 --- a/operator/controllers/common/providers/keptnmetric/keptnmetric_test.go +++ b/operator/controllers/common/providers/keptnmetric/keptnmetric_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" + metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" "github.com/keptn/lifecycle-toolkit/operator/controllers/common" "github.com/stretchr/testify/require" diff --git a/operator/controllers/lifecycle/keptnevaluation/controller_test.go b/operator/controllers/lifecycle/keptnevaluation/controller_test.go index 0628838095..f63900019f 100644 --- a/operator/controllers/lifecycle/keptnevaluation/controller_test.go +++ b/operator/controllers/lifecycle/keptnevaluation/controller_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/go-logr/logr" - metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" + metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" diff --git a/operator/go.mod b/operator/go.mod index a34af9878e..6f3f8fc81c 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -9,7 +9,7 @@ require ( github.com/imdario/mergo v0.3.15 github.com/kelseyhightower/envconfig v1.4.0 github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 - github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4 + github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230517134932-24a60f5e6f8f github.com/magiconair/properties v1.8.7 github.com/onsi/ginkgo/v2 v2.9.4 github.com/onsi/gomega v1.27.6 @@ -95,7 +95,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/component-base v0.26.4 // indirect - k8s.io/klog/v2 v2.90.1 // indirect + k8s.io/klog/v2 v2.100.1 // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect diff --git a/operator/go.sum b/operator/go.sum index 8272d267ca..ae45c3cd86 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -208,8 +208,8 @@ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dv github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 h1:V44aeshIPrEu2L4ElKJ9Y0VPDp5j9BhT8eMzhjICyGU= github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= -github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4 h1:LI+iOb7v1zIAtHQum79CbV+4HB1PCAim+TuCCRRsW7o= -github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4/go.mod h1:8rQ1flqblBWy43k4xJnoaMUA7e50zP95QIab3z6NCw4= +github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230517134932-24a60f5e6f8f h1:FDUUg4O36+nQFpr6L4M4Xz6ZcxG/YUr1/crE90DfjaE= +github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230517134932-24a60f5e6f8f/go.mod h1:aWxBCTaDbsZWnA3gzV4X4e3CgX4lps5fmIyi5pwPKxc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -648,8 +648,8 @@ k8s.io/client-go v0.26.4 h1:/7P/IbGBuT73A+G97trf44NTPSNqvuBREpOfdLbHvD4= k8s.io/client-go v0.26.4/go.mod h1:6qOItWm3EwxJdl/8p5t7FWtWUOwyMdA8N9ekbW4idpI= k8s.io/component-base v0.26.4 h1:Bg2xzyXNKL3eAuiTEu3XE198d6z22ENgFgGQv2GGOUk= k8s.io/component-base v0.26.4/go.mod h1:lTuWL1Xz/a4e80gmIC3YZG2JCO4xNwtKWHJWeJmsq20= -k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= diff --git a/operator/main.go b/operator/main.go index d7b70e9b87..3c1b5f2378 100644 --- a/operator/main.go +++ b/operator/main.go @@ -28,7 +28,7 @@ import ( "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/certificates" certCommon "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/common" "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/webhook" - metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" + metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3" lifecyclev1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha1" lifecyclev1alpha2 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2" lifecyclev1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" diff --git a/operator/test/component/common/common.go b/operator/test/component/common/common.go index 6b010ad1f4..3f78d1408a 100644 --- a/operator/test/component/common/common.go +++ b/operator/test/component/common/common.go @@ -8,7 +8,7 @@ import ( "strings" "time" - metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" + metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" diff --git a/operator/test/component/evaluation/evaluation_test.go b/operator/test/component/evaluation/evaluation_test.go index ba8e41e4f8..9cef296874 100644 --- a/operator/test/component/evaluation/evaluation_test.go +++ b/operator/test/component/evaluation/evaluation_test.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" + metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" From 13b04956a02a0384bfc1ad6b043e901613d1d5b2 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Mon, 22 May 2023 02:08:53 -0700 Subject: [PATCH 34/62] docs: content for KeptnTaskDefinition ref and tasks guide (#1392) Signed-off-by: Meg McRoberts Signed-off-by: Meg McRoberts Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> Co-authored-by: Giovanni Liva Co-authored-by: RealAnna <89971034+RealAnna@users.noreply.github.com> --- docs/content/en/docs/concepts/tasks/_index.md | 177 ---------- .../en/docs/concepts/workloads/_index.md | 44 ++- .../en/docs/implementing/tasks/_index.md | 189 +++++++++++ .../en/docs/tasks/write-tasks/_index.md | 115 ------- .../en/docs/yaml-crd-ref/taskdefinition.md | 307 ++++++++++++++++++ 5 files changed, 524 insertions(+), 308 deletions(-) delete mode 100644 docs/content/en/docs/concepts/tasks/_index.md create mode 100644 docs/content/en/docs/implementing/tasks/_index.md delete mode 100644 docs/content/en/docs/tasks/write-tasks/_index.md diff --git a/docs/content/en/docs/concepts/tasks/_index.md b/docs/content/en/docs/concepts/tasks/_index.md deleted file mode 100644 index b49f8ee3c3..0000000000 --- a/docs/content/en/docs/concepts/tasks/_index.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -title: Tasks -description: Learn what Keptn Tasks are and how to use them -icon: concepts -layout: quickstart -weight: 10 -hidechildren: true # this flag hides all sub-pages in the sidebar-multicard.html ---- - -### Keptn Task Definition - -A `KeptnTaskDefinition` is a CRD used to define tasks that can be run by the Keptn Lifecycle Toolkit -as part of pre- and post-deployment phases of a deployment. -`KeptnTaskDefinition` resource can be created in the namespace where the application is running, or -in the default KLT namespace, which will be the fallback option for the system to search. -The task definition is a [Deno](https://deno.land/) script -Please, refer to the [function runtime](https://github.com/keptn/lifecycle-toolkit/tree/main/functions-runtime) for more -information about the runtime. -In the future, we also intend to support other runtimes, especially running a container image directly. - -A task definition can be configured in three different ways: - -- inline -- referring to an HTTP script -- referring to another `KeptnTaskDefinition` - -An inline task definition looks like the following: - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha2 -kind: KeptnTaskDefinition -metadata: - name: deployment-hello -spec: - function: - inline: - code: | - console.log("Deployment Task has been executed"); -``` - -In the code section, it is possible to define a full-fletched Deno script. - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha2 -kind: KeptnTaskDefinition -metadata: - name: hello-keptn-inline -spec: - function: - inline: - code: | - let text = Deno.env.get("DATA"); - let data; - let name; - data = JSON.parse(text); - - name = data.name - console.log("Hello, " + name + " new"); -``` - -The runtime can also fetch the script on the fly from a remote webserver. -For this, the CRD should look like the -following: - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha2 -kind: KeptnTaskDefinition -metadata: - name: hello-keptn-http -spec: - function: - httpRef: - url: -``` - -An example is -available [here](https://github.com/keptn-sandbox/lifecycle-toolkit-examples/blob/main/sample-app/version-1/app-pre-deploy.yaml) -. - -Finally, `KeptnTaskDefinition` can build on top of other `KeptnTaskDefinition`s. -This is a common use case where a general function can be re-used in multiple places with different parameters. - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha2 -kind: KeptnTaskDefinition -metadata: - name: slack-notification-dev -spec: - function: - functionRef: - name: slack-notification - parameters: - map: - textMessage: "This is my configuration" - secureParameters: - secret: slack-token -``` - -## Context - -A context environment variable is available via `Deno.env.get("CONTEXT")`. -It can be used like this: - -```javascript -let context = Deno.env.get("CONTEXT"); - -if (contextdata.objectType == "Application") { - let application_name = contextdata.appName; - let application_version = contextdata.appVersion; -} - -if (contextdata.objectType == "Workload") { - let application_name = contextdata.appName; - let workload_name = contextdata.workloadName; - let workload_version = contextdata.workloadVersion; -} -``` - -## Input Parameters and Secret Handling - -As you might have noticed, Task Definitions also have the possibility to use input parameters. -The Lifecycle Toolkit passes the values defined inside the `map` field as a JSON object. -At the moment, multi-level maps are not supported. -The JSON object can be read through the environment variable `DATA` using `Deno.env.get("DATA");`. -K8s secrets can also be passed to the function using the `secureParameters` field. -Currently only one secret can be passed. -The secret must have a `key` called `SECURE_DATA`. -It can be accessed via the environment variable `Deno.env.get("SECURE_DATA")`. - -For example: - -```yaml -# kubectl create secret generic my-secret --from-literal=SECURE_DATA=foo - -apiVersion: lifecycle.keptn.sh/v1alpha1 -kind: KeptnTaskDefinition -metadata: - name: dummy-task - namespace: "default" -spec: - function: - secureParameters: - secret: my-secret - inline: - code: | - let secret_text = Deno.env.get("SECURE_DATA"); - // secret_text = "foo" -``` - -This methodology supports multiple variables by creating a K8s secret with a JSON string: - -```yaml -# kubectl create secret generic my-secret \ -# --from-literal=SECURE_DATA="{\"foo\": \"bar\", \"foo2\": \"bar2\"}" - -apiVersion: lifecycle.keptn.sh/v1alpha1 -kind: KeptnTaskDefinition -metadata: - name: dummy-task - namespace: "default" -spec: - function: - secureParameters: - secret: my-secret - inline: - code: | - let secret_text = Deno.env.get("SECURE_DATA"); - let secret_text_obj = JSON.parse(secret_text); - // secret_text_obj["foo"] = "bar" - // secret_text_obj["foo2"] = "bar2" -``` - -### Keptn Task - -A Task is responsible for executing the TaskDefinition of a workload. -The execution is done spawning a K8s Job to handle a single Task. -In its state, it keeps track of the current status of the K8s Job created. diff --git a/docs/content/en/docs/concepts/workloads/_index.md b/docs/content/en/docs/concepts/workloads/_index.md index 541be40c1a..f410b9e9bc 100644 --- a/docs/content/en/docs/concepts/workloads/_index.md +++ b/docs/content/en/docs/concepts/workloads/_index.md @@ -7,21 +7,33 @@ weight: 10 hidechildren: true # this flag hides all sub-pages in the sidebar-multicard.html --- -A Workload contains information about which tasks should be performed during the `preDeployment` as well as -the `postDeployment` -phase of a deployment. -In its state it keeps track of the currently active `Workload Instances`, which are responsible -for doing those checks for -a particular instance of a Deployment/StatefulSet/ReplicaSet (e.g. a Deployment of a certain version). +A `KeptnWorkload`resource contains information about +which tasks should be performed during the `preDeployment` +or `postDeployment` phase of a deployment. +In its state, +it keeps track of the currently active `Workload Instances`, +which are responsible for doing those checks +for a particular instance of a Deployment/StatefulSet/ReplicaSet +(e.g. a Deployment of a certain version). -### Keptn Workload Instance +## KeptnWorkload -A Workload Instance is responsible for executing the pre- and post deployment checks of a workload. -In its state, it -keeps track of the current status of all checks, as well as the overall state of -the Pre Deployment phase, which can be used by the scheduler to tell that a pod can be allowed to be placed on a node. -Workload Instances have a reference to the respective Deployment/StatefulSet/ReplicaSet, to check if it has reached the -desired state. -If it detects that the referenced object has reached -its desired state (e.g. all pods of a deployment are up and running), it will be able to tell that -a `PostDeploymentCheck` can be triggered. +A `KeptnWorkload` resource augments a Kubernetes +[Workload](https://kubernetes.io/docs/concepts/workloads/) +with the ability to handle extra phases. +KLT generates the `KeptnWorkload` resource +from metadata information; +it is not necessary to manually create a YAML file that defines it. + +A `KeptnWorkload` instance is responsible for executing +the pre- and post deployment checks of a workload. +In its state, it keeps track of the current status of all checks, +as well as the overall state of the Pre Deployment phase, +which the scheduler can use to determine +whether the deployment should proceed. +`KeptnWorkload` instances refer +to the respective Pod/DeamonSet/StatefulSet/ReplicaSet, +to check whether it has reached the desired state. +If it detects that the referenced object has reached its desired state +(e.g. all pods of a deployment are up and running), +it knows that a `PostDeploymentCheck` can be triggered. diff --git a/docs/content/en/docs/implementing/tasks/_index.md b/docs/content/en/docs/implementing/tasks/_index.md new file mode 100644 index 0000000000..d896143eee --- /dev/null +++ b/docs/content/en/docs/implementing/tasks/_index.md @@ -0,0 +1,189 @@ +--- +title: Working with Keptn tasks +description: Learn how to work with Keptn tasks +weight: 90 +hidechildren: false # this flag hides all sub-pages in the sidebar-multicard.html +--- + +Keptn tasks are defined in a +[KeptnTaskDefinition](../../yaml-crd-ref/taskdefinition.md/) +resource. +A task definition includes a function +that defines the action taken by that task. +It can be configured in one of three different ways: + +- inline +- referring to an HTTP script +- referring to another `KeptnTaskDefinition` +- referring to a + [ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/) + resource that is populated with the function to execute + +### Context + +A Kubernetes context is a set of access parameters +that contains a Kubernetes cluster, a user, a namespace, +the application name, workload name, and version. +For more information, see +[Configure Access to Multiple Clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/). + +You may need to include context information in the `function` code +included in the YAML file that defines a +[KeptnTaskDefinition](../../yaml-crd-ref/taskdefinition.md) +resource. +For an example of how to do this, see the +[keptn-tasks.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-tasks.yaml) +file. + +A context environment variable is available via `Deno.env.get("CONTEXT")`. +It can be used like this: + +```javascript +let context = Deno.env.get("CONTEXT"); + +if (context.objectType == "Application") { + let application_name = contextdata.appName; + let application_version = contextdata.appVersion; +} + +if (context.objectType == "Workload") { + let application_name = contextdata.appName; + let workload_name = contextdata.workloadName; + let workload_version = contextdata.workloadVersion; +} +``` + +## Parameterized functions + +`KeptnTaskDefinition`s can use input parameters. +Simple parameters are passed as a single map of key values, +while the `secret` parameters refer to a single Kubernetes `secret`. + +Consider the following example: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha2 +kind: KeptnTaskDefinition +metadata: + name: slack-notification-dev +spec: + function: + functionRef: + name: slack-notification + parameters: + map: + textMessage: "This is my configuration" + secureParameters: + secret: slack-token +``` + +Note the following about using parameters with functions: + +- The Lifecycle Toolkit passes the values + defined inside the `map` field as a JSON object. +- Multi-level maps are not currently supported. +- The JSON object can be read through the environment variable `DATA` + using `Deno.env.get("DATA");`. +- Currently only one secret can be passed. + The secret must have a `key` called `SECURE_DATA`. + It can be accessed via the environment variable `Deno.env.get("SECURE_DATA")`. + +## Create secret text + +To create a secret to use in a `KeptnTaskDefinition`, +execute this command: + +```shell +kubectl create secret generic my-secret --from-literal=SECURE_DATA=foo +``` + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: dummy-task + namespace: "default" +spec: + function: + secureParameters: + secret: my-secret + inline: + code: | + let secret_text = Deno.env.get("SECURE_DATA"); + // secret_text = "foo" +``` + +To pass multiple variables +you can create a Kubernetes secret using a JSON string: + +```shell +kubectl create secret generic my-secret \ +--from-literal=SECURE_DATA="{\"foo\": \"bar\", \"foo2\": \"bar2\"}" +``` + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: dummy-task + namespace: "default" +spec: + function: + secureParameters: + secret: my-secret + inline: + code: | + let secret_text = Deno.env.get("SECURE_DATA"); + let secret_text_obj = JSON.parse(secret_text); + // secret_text_obj["foo"] = "bar" + // secret_text_obj["foo2"] = "bar2" +``` + +## Pass secrets to a function + +In the previous example, you see that +Kubernetes +[secrets](https://kubernetes.io/docs/concepts/configuration/secret/) +can be passed to the function +using the `secureParameters` field. + +Here, the `secret` value is the name of the Kubernetes secret, +which contains a field with the key `SECURE_DATA`. +The value of that field is then available to the function's runtime +via an environment variable called `SECURE_DATA`. + +For example, if you have a task function that should make use of secret data, +you must first ensure that the secret containing the `SECURE_DATA` key exists +For example: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: deno-demo-secret + namespace: default +type: Opaque +data: + SECURE_DATA: YmFyCg== # base64 encoded string, e.g. 'bar' +``` + +Then, you can make use of that secret as follows: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: deployment-hello + namespace: "default" +spec: + function: + secureParameters: + secret: deno-demo-secret + inline: + code: | + console.log("Deployment Hello Task has been executed"); + + let foo = Deno.env.get('SECURE_DATA'); + console.log(foo); + Deno.exit(0); +``` diff --git a/docs/content/en/docs/tasks/write-tasks/_index.md b/docs/content/en/docs/tasks/write-tasks/_index.md deleted file mode 100644 index 88bed96f31..0000000000 --- a/docs/content/en/docs/tasks/write-tasks/_index.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: Write Keptn Tasks -description: Learn how to use the Keptn Lifecycle Toolkit and explore basic features. -icon: concepts -layout: quickstart -weight: 20 -hidechildren: true # this flag hides all sub-pages in the sidebar-multicard.html ---- - -## Keptn Task Definition - -A `KeptnTaskDefinition` is a CRD used to define tasks that can be run by the Keptn Lifecycle Toolkit -as part of pre- and post-deployment phases of a deployment. -The task definition is a [Deno](https://deno.land/) script. -In the future, we also intend to support other runtimes, especially running a container image directly. - -A task definition can be configured in three different ways: - -- inline -- referring to an HTTP script -- referring to another `KeptnTaskDefinition` - -An inline task definition looks like the following: - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha2 -kind: KeptnTaskDefinition -metadata: - name: deployment-hello -spec: - function: - inline: - code: | - console.log("Deployment Task has been executed"); -``` - -In the code section, it is possible to define a full-fletched Deno script. - -The runtime can also fetch the script on the fly from a remote webserver. -For this, the CRD should look like the -following: - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha2 -kind: KeptnTaskDefinition -metadata: - name: hello-keptn-http -spec: - function: - httpRef: - url: -``` - -Finally, `KeptnTaskDefinition` can build on top of other `KeptnTaskDefinition`s. -This is a common use case where a general function can be re-used in multiple places with different parameters. - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha2 -kind: KeptnTaskDefinition -metadata: - name: slack-notification-dev -spec: - function: - functionRef: - name: slack-notification - parameters: - map: - textMessage: "This is my configuration" - secureParameters: - secret: slack-token -``` - -As you might have noticed, Task Definitions also have the possibility to use input parameters. -The Lifecycle Toolkit passes the values defined inside the `map` field as a JSON object. -At the moment, multi-level maps are not supported. -The JSON object can be read through the environment variable `DATA` using `Deno.env.get("DATA");`. -Kubernetes secrets can also be passed to the function using the `secureParameters` field. - -Here, the `secret` value is the name of the K8s secret containing a field with the key `SECURE_DATA`. -The value of that field will then be available to the functions runtime via an environment variable called `SECURE_DATA`. - -For example, if you have a task function that should make use of secret data, you must first ensure that the secret -containing the `SECURE_DATA` key exists, as e.g.: - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: deno-demo-secret - namespace: default -type: Opaque -data: - SECURE_DATA: YmFyCg== # base64 encoded string, e.g. 'bar' -``` - -Then, you can make use of that secret as follows: - -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha3 -kind: KeptnTaskDefinition -metadata: - name: deployment-hello - namespace: "default" -spec: - function: - secureParameters: - secret: deno-demo-secret - inline: - code: | - console.log("Deployment Hello Task has been executed"); - - let foo = Deno.env.get('SECURE_DATA'); - console.log(foo); - Deno.exit(0); -``` diff --git a/docs/content/en/docs/yaml-crd-ref/taskdefinition.md b/docs/content/en/docs/yaml-crd-ref/taskdefinition.md index 877444e902..b4e50388b9 100644 --- a/docs/content/en/docs/yaml-crd-ref/taskdefinition.md +++ b/docs/content/en/docs/yaml-crd-ref/taskdefinition.md @@ -3,3 +3,310 @@ title: KeptnTaskDefinition description: Define tasks that can be run pre- or post-deployment weight: 89 --- + + +A `KeptnTaskDefinition` defines tasks +that are run by the Keptn Lifecycle Toolkit +as part of the pre- and post-deployment phases of a +[KeptnApp](./app.md) or +[KeptnWorkload](../concepts/workloads/). + +## Yaml Synopsis + +```yaml +apiVersion: lifecycle.keptn.sh/v?alpha? +kind: KeptnTaskDefinition +metadata: + name: +spec: + function: + inline | httpRef | functionRef | ConfigMapRef + parameters: + map: + textMessage: "This is my configuration" + secureParameters: + secret: slack-token +``` + +## Fields + +* **apiVersion** -- API version being used. +` +* **kind** -- Resource type. + Must be set to `KeptnTaskDefinition` + +* **metadata** + * **name** -- Unique name of this task. + Names must comply with the + [Kubernetes Object Names and IDs](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names) + specification. + +* **spec** + * **function** -- Code to be executed, + expressed as a [Deno](https://deno.land/) script. + Refer to [function runtime](https://github.com/keptn/lifecycle-toolkit/tree/main/functions-runtime) + for more information about the runtime. + + The `function` can be defined as one of the following: + + * **inline** - Include the actual executable code to execute. + This can be written as a full-fledged Deno script + that is included in this file. + For example: + + ```yaml + function: + inline: + code: | + console.log("Deployment Task has been executed"); + ``` + + * **httpRef** - Specify a Deno script to be executed at runtime + from the remote webserver that is specified. + For example: + + ```yaml + name: hello-keptn-http + spec: + function: + httpRef: + url: "https://www.example.com/yourscript.js" + ``` + + * **functionRef** -- Execute one or more `KeptnTaskDefinition` resources + that have been defined. + Populate this field with the value(s) of the `name` field + for the `KeptnTaskDefinition`(s) to be called. + This is commonly used to call a general function + that is used in multiple places, possibly with different parameters. + An example is: + + ```yaml + spec: + function: + functionRef: + name: slack-notification + ``` + + This can also be used to group a set of tasks + into a single `KeptnTaskDefinition`, + such as defining a `KeptnTaskDefinition` for testing. + In this case, it calls other, existing `KeptnTaskDefinition`s + for each type of test to be run, + specifying each by the value of the `name` field. + * **ConfigMapRef** - Specify the name of a + [ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/) + resource that contains the function to be executed. + + * **parameters** - An optional field to supply input parameters to a function. + The Lifecycle Toolkit passes the values defined inside the `map` field + as a JSON object. + For example: + + ```yaml + spec: + parameters: + map: + textMessage: "This is my configuration" + ``` + + See + [Parameterized functions](../implementing/tasks/#parameterized-functions) + for more information. + + * **secureParameters** -- An optional field used to pass a Kubernetes secret. + The `secret` value is the Kubernetes secret name + that is mounted into the runtime and made available to functions + using the `SECURE_DATA` environment variable. + For example: + + ```yaml + secureParameters: + secret: slack-token + ``` + + Note that, currently, only one secret can be passed. + + See [Create secret text](../implementing/tasks/#create-secret-text) + for details. + +## Usage + +A Task executes the TaskDefinition of a +[KeptnApp](app.md) or [KeptnWorkload]. +The execution is done by spawning a Kubernetes +[Job](https://kubernetes.io/docs/concepts/workloads/controllers/job/) +to handle a single Task. +In its state, it tracks the current status of this Kubernetes Job. + +The `function` is coded in JavaScript +and executed in +[Deno](https://deno.com/runtime), +which is a lightweight runtime environment +that executes in your namespace. +Note that Deno has tighter restrictions +for permissions and importing data +so a script that works properly elsewhere +may not function out of the box when run in Deno. + +A task can be executed either pre-deployment or post-deployment +as specified in the `Deployment` resource; +see +[Pre- and post-deployment tasks](../implementing/integrate/#pre--and-post-deployment-checks) +for details. +Note that the annotation identifies the task by `name`. +This means that you can modify the `function` code in the resource definition +and the revised code is picked up without additional changes. + +## Examples + +### Example 1: inline script + +This example defines a full-fledged Deno script +within the `KeptnTaskDefinition` YAML file: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: hello-keptn-inline +spec: + function: + inline: + code: | + let text = Deno.env.get("DATA"); + let data; + let name; + data = JSON.parse(text); + + name = data.name + console.log("Hello, " + name + " new"); +``` + +### Example 2: httpRef script + +This example fetches the Deno script from a remote webserver at runtime: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: hello-keptn-http +spec: + function: + httpRef: + url: "https://www.example.com/yourscript.js" +``` + +For another example, see the +[sample-app](https://github.com/keptn-sandbox/lifecycle-toolkit-examples/blob/main/sample-app/version-1/app-pre-deploy.yaml). + +See the +[sample-app/version-1](https://github.com/keptn-sandbox/lifecycle-toolkit-examples/blob/main/sample-app/version-1/app-pre-deploy.yaml) +PodtatoHead example for a more complete example. + +### Example 3: functionRef + +This example calls another defined task, +illustrating how one `KeptnTaskDefinition` can build +on top of other `KeptnTaskDefinition`s. +In this case, it calls `slack-notification-dev`, +passing `parameters` and `secureParameters` to that other task: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: slack-notification-dev +spec: + function: + functionRef: + name: slack-notification + parameters: + map: + textMessage: "This is my configuration" + secureParameters: + secret: slack-token +``` + +### Example 4: ConfigMapRef + +This example references a `ConfigMap` by the name of `dev-configmap` +that contains the code for the function to be executed. + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: keptntaskdefinition-sample +spec: + function: + configMapRef: + name: dev-configmap +``` + +### Example 5: ConfigMap + +This example illustrates the use of both a `ConfigMapRef` and a `ConfigMap`: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha2 +kind: KeptnTaskDefinition +metadata: + name: scheduled-deployment +spec: + function: + configMapRef: + name: scheduled-deployment-cm-1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: scheduled-deployment-1 +data: + code: | + let text = Deno.env.get("DATA"); + let data; + if (text != "") { + data = JSON.parse(text); + } + let targetDate = new Date(data.targetDate) + let dateTime = new Date(); + if(targetDate < dateTime) { + console.log("Date has passed - ok"); + Deno.exit(0); + } else { + console.log("It's too early - failing"); + Deno.exit(1); + } + console.log(targetDate); +``` + +### More examples + +See the [operator/config/samples](https://github.com/keptn/lifecycle-toolkit/tree/main/operator/config/samples/function_execution) +directory for more example `KeptnTaskDefinition` YAML files. + +## Files + +API Reference: + +* [KeptnTaskDefinition](../crd-ref/lifecycle/v1alpha3/_index.md#keptntaskdefinition) +* [KeptnTaskDefinitionList](../crd-ref/lifecycle/v1alpha3/_index.md#keptntaskdefinitionlist) +* [KeptnTaskDefinitionSpec](../crd-ref/lifecycle/v1alpha3/_index.md#keptntaskdefinitionspec) +* [FunctionReference](../crd-ref/lifecycle/v1alpha3/_index.md#functionreference) +* [FunctionSpec](../crd-ref/lifecycle/v1alpha3/_index.md#functionspec) +* [FunctionStatus](../crd-ref/lifecycle/v1alpha3/_index.md#functionstatus) +* [HttpReference](../crd-ref/lifecycle/v1alpha3/_index.md#httpreference) +* [Inline](../crd-ref/lifecycle/v1alpha3/_index.md#inline) + +## Differences between versions + +The `KeptnTaskDefinition` is the same for +all `v1alpha?` library versions. + +## See also + +* [Working with tasks](../implementing/tasks) +* [Pre- and post-deployment tasks](../implementing/integrate/#pre--and-post-deployment-checks) +* [Orchestrate deployment checks](../getting-started/orchestrate) From 48159862bf89b8cc1692500af7b05487a6cc03cb Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Mon, 22 May 2023 02:15:12 -0700 Subject: [PATCH 35/62] docs: create "observability" getting started guide (#1376) Signed-off-by: Meg McRoberts Signed-off-by: Simon Schrottner Signed-off-by: Meg McRoberts Co-authored-by: Simon Schrottner Co-authored-by: Giovanni Liva Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> --- .../getting-started/observability/_index.md | 276 +++++++++++++++++- .../assets/dynatrace_dora_dashboard.png | Bin 0 -> 33750 bytes docs/content/en/docs/install/k8s.md | 10 +- 3 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 docs/content/en/docs/getting-started/observability/assets/dynatrace_dora_dashboard.png diff --git a/docs/content/en/docs/getting-started/observability/_index.md b/docs/content/en/docs/getting-started/observability/_index.md index 0072031e09..cccb7c8fbe 100644 --- a/docs/content/en/docs/getting-started/observability/_index.md +++ b/docs/content/en/docs/getting-started/observability/_index.md @@ -1,6 +1,280 @@ --- title: Standardize access to observability data -description: Learn how the Keptn Lifecycle Toolkit standardizes access to observability data. +description: Learn how the Keptn Lifecycle Toolkit provides observability for Kubernetes deployments weight: 45 --- +The Keptn Lifecycle Toolkit (KLT) makes any Kubernetes deployment observable. +You can readily see why a deployment takes so long or why it fails, +even when using multiple deployment tools. +Keptn introduces a concept of an application +which is an abstraction that connects multiple +Workloads belonging together. +In other words, KLT, creates a distributed end-to-end trace +of everything Kubernetes does in the context of a Deployment. + +The observability data is an amalgamation of the following: + +- DORA metrics are collected out of the box + when the Lifecycle Toolkit is enabled +- OpenTelemetry runs traces that show everything that happens in the Kubernetes cluster + and can display this information with dashboard tools + such as Grafana. +- Specific metrics that you can define to monitor + information from all the data providers configured in your cluster. + +The Keptn Lifecycle Toolkit can provide this information +for all applications running in your cluster, +even if they are using different deployment tools. +And it can capture metrics from multiple data sources +using multiple data platforms. +With KLT deployed on your cluster, +you can easily monitor what is happening during a deployment into your Kuberenetes cluster, +and quickly get data to help you understand issues such as +why a deployment took so long or why it failed. + +## Using this exercise + +This exercise shows how to standardize access +to the observability data for your cluster. +It is based on the +[simplenode-dev](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd) +example. +You can clone that repo to access it locally +or just look at it for examples +as you implement the functionality "from scratch" +on your local Kubernetes deployment cluster. +The +[README](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/setup/observability/README.md) +file for that repo contains useful information. + +Two videos are available +to walk you through this exercise if you prefer: + +- [Introducing Keptn Lifecycle Toolkit](https://youtu.be/449HAFYkUlY) +- [Use SLOs and get DORA the Native K8s way!](https://www.youtube.com/watch?v=zeEC0475SOU) + +In the +[Getting started with Keptn metrics](../metrics) +exercise, you learn how to define and use Keptn metrics. +You may want to complete that exercise before doing this exercise +although that is not required. + +This exercise shows how to standardize access +to the observability data for your cluster. +The steps are: + +1. [Install and enable]( #install-and-enable-klt) + the Lifecycle Toolkit on your cluster +1. [Integrate the Lifecycle Toolkit with your applications](#integrate-the-lifecycle-toolkit-with-your-applications) +1. [DORA metrics](#dora-metrics) +1. [Using OpenTelemetry](#using-opentelemetry) +1. [Keptn metrics](#keptn-metrics) +1. [View the results](#view-the-results) + +## Install and enable KLT + +To install and enable the Keptn Lifecycle Toolkit on your cluster: + +1. Be sure that your cluster includes the components discussed in + [Prepare your cluster for KLT](../../install/k8s.md/#prepare-your-cluster-for-klt) +1. Follow the instructions in + [Install the Keptn Lifecycle Toolkit](../../install/install.md/#use-helm-chart) + to install KLT on your cluster using the Helm chart + + If you installed KLT on your cluster for the + [Getting started with Keptn metrics](../metrics) + exercise, you do not need to re-install it for this exercise. + However, if you only installed the `metrics-operator` for that exercise, + you now need to install the full KLT. + +1. Follow the instructions in + [Enable KLT for your cluster](../../install/install.md/#enable-klt-for-your-cluster) + to enable KLT on your cluster + by annotating the `Namespace` resource.. + See the + [simplenode-dev-ns.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-ns.yaml) + file for an example + +1. Run the following command to ensure that your Kuberetes cluster + is ready to complete this exercise: + + ```shell + kubectl get pods -n keptn-lifecycle-toolkit-system + ``` + + You should see pods for the following components: + - certificate-operator (or another cert manager) + - lifecycle-operator + - scheduler + - metrics-operator + +## Integrate the Lifecycle Toolkit with your applications + +The Keptn Lifecycle Toolkit sits in the scheduler +so it can trace all activities of all deployment workloads on the cluster, +no matter what tool is used for the deployment. +This same mechanism allows KLT to inject pre- and post-deployment checks +into all deployment workloads; +we discuss this in another exercise. + +KLT uses metadata to identify the workloads of interest. +To integrate KLT with your applications, +you need to populate the metadata it needs. +This requires the following steps: + +- Define a Keptn application +- Annotate the `Deployment` resource to recognize your Keptn application + +### Define the Keptn application + +A Keptn application defines the workloads +to be included in your Keptn Application. +We will use the application discovery feature +to automatically generate a Keptn Application +that includes all workloads on the cluster, +regardless of the tools being used. + +A Keptn application aggregates multiple workloads +that belong to a logical app into a single +[KeptnApp](../../yaml-crd-ref/app.md) +resource. + +You can view a sample of this file in the +[keptn-app.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-app.yaml.tmp) +file. +You see the metadata that names this `KeptnApp` +and identifies the namespace where it lives: + +```yaml +metadata: + name: simpleapp + namespace: simplenode-dev +``` + +You can also see the `spec.workloads` list. +In this simple example, +we only have one workload defined +but most production apps will have multiple workloads defined. + +You can create the YAML file to define the resource manually +but the easier approach is to let KLT create this definition for you. +This requires that you annotate all your workloads +(`Deployments`, `Pods`, `StatefulSets`, `DaemonSets`, and `ReplicaSets` +as described in +[Use Keptn automatic app discovery](../../implementing/integrate/#use-keptn-automatic-app-discovery). + +### Annotate your Deployment resource + +Follow the instructions in +[Annotate workload](../../implementing/integrate/#basic-annotations) +to apply basic annotations to your `Deployment` resource. + +The +[simplenode-dev-deployment.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-deployment.yaml/) +file defines the `Deployment` resource for our example. +You see that the `metadata` specifies the same +`name` and `namespace` values defined in the `KeptnApp` resource. + +The example file also includes annotations for +pre- and post-deployment activities. +We will discuss those in a separate exercise. + +## DORA metrics + +DORA metrics are an industry-standard set of measurements; +see the following for a description: + +- [What are DORA Metrics and Why Do They Matter?](https://codeclimate.com/blog/dora-metrics) +- [Are you an Elite DevOps Performer? + Find out with the Four Keys Project](https://cloud.google.com/blog/products/devops-sre/using-the-four-keys-to-measure-your-devops-performance) + +DORA metrics provide information such as: + +- How many deployments happened in the last six hours? +- Time between deployments +- Deployment time between versions +- Average time between versions. + +The Keptn Lifecycle Toolkit starts collecting these metrics +as soon as you annotate the `Deployment` resource. +Metrics are collected only for the `Deployment` resources +that are annotated. + +To view DORA metrics, run the following command: + +```shell +kubectl port-forward -n keptn-lifecycle-toolkit-system \ + svc/lifecycle-operator-metrics-service 2222 +``` + +Then view the metrics at: + +```shell +http://localhost:2222/metrics +``` + +DORA metrics are also displayed on Grafana +or whatever dashboard application you choose. +For example: + +![DORA metrics](assets/dynatrace_dora_dashboard.png) + +## Using OpenTelemetry + +The Keptn Lifecycle Toolkit extends the Kubernetes +primitives to create OpenTelemetry data +that connects all your deployment and observability tools +without worrying about where it is stored and where it is managed. +OpenTelemetry traces collect data as Kubernetes is deploying the changes, +which allows you to trace everything done in the context of that deployment. + +- You must have an OpenTelemetry collector installed on your cluster. + See + [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) + for more information. +- Follow the instructions in + [OpenTelemetry observability](../../implementing/otel.md) + to configure where your OpenTelemetry data is sent. + - Define a [KeptnConfig](../../yaml-crd-ref/config.md) resource + that defines the URL and port of the OpenTelemetry collector. + For our example, this is in the + [keptnconfig.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/setup/keptn/keptnconfig.yaml) + file. +- Set the `EXPOSE_KEPTN_METRICS` environment variable + in the `metrics-operator` + +TODO: How to set this env variable in `metrics-operator` + or where is it set in the example? + +## Keptn metrics + +You can supplement the DORA Metrics and OpenTelemetry information +with information you explicitly define using Keptn metrics. +The +[Getting started with Keptn metrics](../metrics) +exercise discusses how to define Keptn metrics. + +## View the results + +To start feeding observability data for your deployments +onto a dashboard of your choice, +modify either your `Deployment` or `KeptnApp` resource yaml file +to increment the version number +and commit that change to your repository. +Note that, from the `KeptnApp` YAML file, +you can either increment the version number of the application +(which causes all workloads to be rerun and produce observability data) +or you can increment the version number of a single workload, +(which causes just that workload to be rerun and produce data). + +The videos that go with this exercise show how the +DORA, OpenTelemetry, and Keptn metrics information +appears on a Grafana dashboard with +[Jaeger](https://grafana.com/docs/grafana-cloud/data-configuration/metrics/prometheus-config-examples/the-jaeger-authors-jaeger/). + +If you also have Jaeger extension for Grafana installed on your cluster, +you can view full end-to-end trace for everything +that happens in your deployment. +For more information, see +[Monitoring Jaeger](https://www.jaegertracing.io/docs/1.45/monitoring/). diff --git a/docs/content/en/docs/getting-started/observability/assets/dynatrace_dora_dashboard.png b/docs/content/en/docs/getting-started/observability/assets/dynatrace_dora_dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..3318a4303f42d17c927685682e83788c8ee15936 GIT binary patch literal 33750 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

@Za7i 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 G^#1@$%K}UQ literal 0 HcmV?d00001 diff --git a/docs/content/en/docs/install/k8s.md b/docs/content/en/docs/install/k8s.md index 0b7635ec48..074d34028e 100644 --- a/docs/content/en/docs/install/k8s.md +++ b/docs/content/en/docs/install/k8s.md @@ -80,13 +80,19 @@ Your cluster should include the following: [Flux](https://fluxcd.io/). Alternatively, KLT also works with just `kubctl apply` for deployment. -* For traces, install [Jaeger](https://jaegertracing.io) - or a similar tool. +* If you want to use the standardized observability feature, + install an OpenTelemetry collector on your cluster. + See + [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) + for more information. * If you want a dashboard for reviewing metrics and traces, Install [Grafana](https://grafana.com/) or the dashboard of your choice. +* For traces, install [Jaeger](https://jaegertracing.io) + or a similar tool. + Also note that the Keptn Lifecycle Toolkit includes a light-weight cert-manager that, by default, is installed as part of the KLT software. From d602115ef4b158b04be3c630ca45c6e4f39fc0f3 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Mon, 22 May 2023 04:02:10 -0700 Subject: [PATCH 36/62] docs: create pre/post-deploy getting started (#1362) Signed-off-by: Meg McRoberts Signed-off-by: Simon Schrottner Signed-off-by: Simon Schrottner Signed-off-by: Meg McRoberts Signed-off-by: Adam Gardner <26523841+agardnerIT@users.noreply.github.com> Signed-off-by: Meg McRoberts Co-authored-by: Simon Schrottner Co-authored-by: Giovanni Liva Co-authored-by: Adam Gardner <26523841+agardnerIT@users.noreply.github.com> --- .../getting-started/observability/_index.md | 430 ++++++++---------- 1 file changed, 183 insertions(+), 247 deletions(-) diff --git a/docs/content/en/docs/getting-started/observability/_index.md b/docs/content/en/docs/getting-started/observability/_index.md index cccb7c8fbe..4549f1f085 100644 --- a/docs/content/en/docs/getting-started/observability/_index.md +++ b/docs/content/en/docs/getting-started/observability/_index.md @@ -1,280 +1,216 @@ --- -title: Standardize access to observability data -description: Learn how the Keptn Lifecycle Toolkit provides observability for Kubernetes deployments -weight: 45 +title: Orchestrating pre- and post-deployment tasks and evaluations +description: Learn how the Keptn Lifecycle Toolkit can orchestrate deployment checks. +weight: 55 --- -The Keptn Lifecycle Toolkit (KLT) makes any Kubernetes deployment observable. -You can readily see why a deployment takes so long or why it fails, -even when using multiple deployment tools. -Keptn introduces a concept of an application -which is an abstraction that connects multiple -Workloads belonging together. -In other words, KLT, creates a distributed end-to-end trace -of everything Kubernetes does in the context of a Deployment. - -The observability data is an amalgamation of the following: - -- DORA metrics are collected out of the box - when the Lifecycle Toolkit is enabled -- OpenTelemetry runs traces that show everything that happens in the Kubernetes cluster - and can display this information with dashboard tools - such as Grafana. -- Specific metrics that you can define to monitor - information from all the data providers configured in your cluster. - -The Keptn Lifecycle Toolkit can provide this information -for all applications running in your cluster, -even if they are using different deployment tools. -And it can capture metrics from multiple data sources -using multiple data platforms. -With KLT deployed on your cluster, -you can easily monitor what is happening during a deployment into your Kuberenetes cluster, -and quickly get data to help you understand issues such as -why a deployment took so long or why it failed. +In this exercise, we will configure the Keptn Lifecyle Toolkit +to run deployment checks as part of your deployment. +Whether you are deploying your software with +Argo, Flux, another deployment engine, or even `kubectl apply`, +the Lifecycle Toolkit can do the following: + +* Pre-deploy: Validate external dependencies, + confirm that images are scanned, and so forth + +* Post-deply: Execute tests, notify stakeholders, + promote to the next stage + +* Automatically validate against your SLO (Service Level Objectives) + +KLT sits in the Kubernetes scheduler and can trace the deployment +from start to end. +KLT is also application aware, +so we can extend the deployment +with tasks and evaluations that +are run either before or after your whole application starts the deployment +or even at the individual workload level. +You can also validate any metric, +either pre- or post-deployment, +using the metrics from the Keptn Metrics Server introduced in +[Getting started with Keptn metrics](../metrics). +This means that you can be sure that the environment is healthy +and has adequate resources before you begin the deployment. +After the deployment succeeds, +use Keptn metrics to confirm that your deployed software is really healthy -- +not just that the pods are running but validate against SLOs +such as performance and user experience. +You can also check for new logs that came in from a log monitoring solution. ## Using this exercise -This exercise shows how to standardize access -to the observability data for your cluster. -It is based on the +This exercise is based on the [simplenode-dev](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd) example. You can clone that repo to access it locally or just look at it for examples as you implement the functionality "from scratch" on your local Kubernetes deployment cluster. -The -[README](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/setup/observability/README.md) -file for that repo contains useful information. - -Two videos are available -to walk you through this exercise if you prefer: - -- [Introducing Keptn Lifecycle Toolkit](https://youtu.be/449HAFYkUlY) -- [Use SLOs and get DORA the Native K8s way!](https://www.youtube.com/watch?v=zeEC0475SOU) - -In the -[Getting started with Keptn metrics](../metrics) -exercise, you learn how to define and use Keptn metrics. -You may want to complete that exercise before doing this exercise -although that is not required. - -This exercise shows how to standardize access -to the observability data for your cluster. -The steps are: - -1. [Install and enable]( #install-and-enable-klt) - the Lifecycle Toolkit on your cluster -1. [Integrate the Lifecycle Toolkit with your applications](#integrate-the-lifecycle-toolkit-with-your-applications) -1. [DORA metrics](#dora-metrics) -1. [Using OpenTelemetry](#using-opentelemetry) -1. [Keptn metrics](#keptn-metrics) -1. [View the results](#view-the-results) - -## Install and enable KLT - -To install and enable the Keptn Lifecycle Toolkit on your cluster: - -1. Be sure that your cluster includes the components discussed in - [Prepare your cluster for KLT](../../install/k8s.md/#prepare-your-cluster-for-klt) -1. Follow the instructions in - [Install the Keptn Lifecycle Toolkit](../../install/install.md/#use-helm-chart) - to install KLT on your cluster using the Helm chart - - If you installed KLT on your cluster for the - [Getting started with Keptn metrics](../metrics) - exercise, you do not need to re-install it for this exercise. - However, if you only installed the `metrics-operator` for that exercise, - you now need to install the full KLT. - -1. Follow the instructions in - [Enable KLT for your cluster](../../install/install.md/#enable-klt-for-your-cluster) - to enable KLT on your cluster - by annotating the `Namespace` resource.. - See the - [simplenode-dev-ns.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-ns.yaml) - file for an example - -1. Run the following command to ensure that your Kuberetes cluster - is ready to complete this exercise: - - ```shell - kubectl get pods -n keptn-lifecycle-toolkit-system - ``` - - You should see pods for the following components: - - certificate-operator (or another cert manager) - - lifecycle-operator - - scheduler - - metrics-operator - -## Integrate the Lifecycle Toolkit with your applications - -The Keptn Lifecycle Toolkit sits in the scheduler -so it can trace all activities of all deployment workloads on the cluster, -no matter what tool is used for the deployment. -This same mechanism allows KLT to inject pre- and post-deployment checks -into all deployment workloads; -we discuss this in another exercise. - -KLT uses metadata to identify the workloads of interest. -To integrate KLT with your applications, -you need to populate the metadata it needs. -This requires the following steps: - -- Define a Keptn application -- Annotate the `Deployment` resource to recognize your Keptn application - -### Define the Keptn application - -A Keptn application defines the workloads -to be included in your Keptn Application. -We will use the application discovery feature -to automatically generate a Keptn Application -that includes all workloads on the cluster, -regardless of the tools being used. - -A Keptn application aggregates multiple workloads -that belong to a logical app into a single -[KeptnApp](../../yaml-crd-ref/app.md) -resource. -You can view a sample of this file in the -[keptn-app.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-app.yaml.tmp) -file. -You see the metadata that names this `KeptnApp` -and identifies the namespace where it lives: +The steps to implement pre- and post-deployment orchestration are: -```yaml -metadata: - name: simpleapp - namespace: simplenode-dev -``` +1. [Bring or create a Kubernetes cluster](../../install/k8s.md) +1. [Install the Keptn Lifecycle Toolkit and enable it](../../install/install.md) +1. [Integrate KLT with your cluster](../../implementing/integrate/) +1. [Define evaluations to be performed pre- and post-deployment](#define-evaluations-to-be-performed-pre--and-post-deployment) +1. [Define tasks to be performed pre- and post-deployment](#define-tasks-to-be-performed-pre--and-post-deployment) -You can also see the `spec.workloads` list. -In this simple example, -we only have one workload defined -but most production apps will have multiple workloads defined. +## Bring or create a Kubernetes deployment cluster -You can create the YAML file to define the resource manually -but the easier approach is to let KLT create this definition for you. -This requires that you annotate all your workloads -(`Deployments`, `Pods`, `StatefulSets`, `DaemonSets`, and `ReplicaSets` -as described in -[Use Keptn automatic app discovery](../../implementing/integrate/#use-keptn-automatic-app-discovery). +You can run this exercise on an existing Kubernetes cluster +or you can create a new cluster. +For personal study and demonstrations, +this exercise runs well on a local Kubernetes cluster. +See [Bring or Install a Kubernetes Cluster](../../install/k8s.md). -### Annotate your Deployment resource +## Install KLT on your cluster -Follow the instructions in -[Annotate workload](../../implementing/integrate/#basic-annotations) -to apply basic annotations to your `Deployment` resource. +Install the Keptn Lifecycle Toolkit on your cluster +by executing the following command sequence: -The -[simplenode-dev-deployment.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-deployment.yaml/) -file defines the `Deployment` resource for our example. -You see that the `metadata` specifies the same -`name` and `namespace` values defined in the `KeptnApp` resource. +```shell +helm repo add klt https://charts.lifecycle.keptn.sh +helm repo update +helm upgrade --install keptn klt/klt \ + -n keptn-lifecycle-toolkit-system --create-namespace --wait +``` -The example file also includes annotations for -pre- and post-deployment activities. -We will discuss those in a separate exercise. +See +[Install KLT](../../install/install.md) +for more information about installing the Lifecycle Toolkit. -## DORA metrics +## Enable KLT for your cluster -DORA metrics are an industry-standard set of measurements; -see the following for a description: +To enable KLT for your cluster, annotate the +[Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) +resource +In this example, this is defined in the +[simplenode-dev-ns.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-ns.yaml) +file, which looks like this: -- [What are DORA Metrics and Why Do They Matter?](https://codeclimate.com/blog/dora-metrics) -- [Are you an Elite DevOps Performer? - Find out with the Four Keys Project](https://cloud.google.com/blog/products/devops-sre/using-the-four-keys-to-measure-your-devops-performance) +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: simplenode-dev + annotations: + keptn.sh/lifecycle-toolkit: "enabled" +``` -DORA metrics provide information such as: +You see the annotation line that enables `lifecycle-toolkit`. +This line tells the webhook to handle the namespace -- How many deployments happened in the last six hours? -- Time between deployments -- Deployment time between versions -- Average time between versions. +## Integrate KLT with your cluster -The Keptn Lifecycle Toolkit starts collecting these metrics -as soon as you annotate the `Deployment` resource. -Metrics are collected only for the `Deployment` resources -that are annotated. +To integrate KLT with your cluster, annotate the Kubernetes +[Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) +resource. +In this example, this is defined in the +[simplenode-dev-deployment.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-deployment.yaml) +file, which includes the following lines: -To view DORA metrics, run the following command: +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: simplenode + namespace: simplenode-dev +... +template: + metadata: + labels: + app: simplenode + app.kubernetes.io/name: simplenodeservice + annotations: + # keptn.sh/app: simpleapp + keptn.sh/workload: simplenode + keptn.sh/version: 1.0.2 + keptn.sh/pre-deployment-evaluations: evaluate-dependencies + keptn.sh/pre-deployment-tasks: notify + keptn.sh/post-deployment-evaluations: evaluate-deployment + keptn.sh/post-deployment-tasks: notify +... +``` -```shell -kubectl port-forward -n keptn-lifecycle-toolkit-system \ - svc/lifecycle-operator-metrics-service 2222 +For more information about using annotations and labels +to integrate KLT into your deployment cluster, see +[Integrate KLT with your applications](../../implementing/integrate/_index.md). + +## Define evaluations to be performed pre- and post-deployment + +An `evaluation` is a KeptnMetric that has a defined target value. +Evaluations are resources that are defined in a +[KeptinEvaluationDefinition](../../yaml-crd-ref/evaluationdefinition.md) +yaml file. +In our example, evaluations are defined in the +[keptn-evaluations.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-evaluations.yaml) +file. +For example, the definition of the `evaluate-dependencies` evaluation +looks like this: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnEvaluationDefinition +metadata: + name: evaluate-dependencies + namespace: simplenode-dev +spec: + objectives: + - keptnMetricRef: + name: available-cpus + namespace: simplenode-dev + evaluationTarget: ">4" ``` -Then view the metrics at: +You see that the `available-cpus` metric is defined in the +[keptn-metric.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-metric.yaml) +file. +The `evaluationTarget` is set to be `>4`, +so this evaluation makes sure that more than 4 CPUs are available. +You could include objectives and additional metrics in this evaluation. -```shell -http://localhost:2222/metrics +## Define tasks to be performed pre- and post-deployment + +Tasks are resources that are defined in a +[KeptnTaskDefinition](../../yaml-crd-ref/taskdefinition.md) +file. +In our example, the tasks are defined in the +[keptn-tasks.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-tasks.yaml) +file +As an example, +we have a `notify` task that composes some markdown text +to be sent as Slack notifications +The `KeptnTaskDefinition` looks like this: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: notify +spec: + function: + inline: + code: | + + secureParameters: + secret: slack-notification ``` -DORA metrics are also displayed on Grafana -or whatever dashboard application you choose. -For example: - -![DORA metrics](assets/dynatrace_dora_dashboard.png) - -## Using OpenTelemetry - -The Keptn Lifecycle Toolkit extends the Kubernetes -primitives to create OpenTelemetry data -that connects all your deployment and observability tools -without worrying about where it is stored and where it is managed. -OpenTelemetry traces collect data as Kubernetes is deploying the changes, -which allows you to trace everything done in the context of that deployment. - -- You must have an OpenTelemetry collector installed on your cluster. - See - [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) - for more information. -- Follow the instructions in - [OpenTelemetry observability](../../implementing/otel.md) - to configure where your OpenTelemetry data is sent. - - Define a [KeptnConfig](../../yaml-crd-ref/config.md) resource - that defines the URL and port of the OpenTelemetry collector. - For our example, this is in the - [keptnconfig.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/setup/keptn/keptnconfig.yaml) - file. -- Set the `EXPOSE_KEPTN_METRICS` environment variable - in the `metrics-operator` - -TODO: How to set this env variable in `metrics-operator` - or where is it set in the example? - -## Keptn metrics - -You can supplement the DORA Metrics and OpenTelemetry information -with information you explicitly define using Keptn metrics. -The -[Getting started with Keptn metrics](../metrics) -exercise discusses how to define Keptn metrics. - -## View the results - -To start feeding observability data for your deployments -onto a dashboard of your choice, -modify either your `Deployment` or `KeptnApp` resource yaml file -to increment the version number -and commit that change to your repository. -Note that, from the `KeptnApp` YAML file, -you can either increment the version number of the application -(which causes all workloads to be rerun and produce observability data) -or you can increment the version number of a single workload, -(which causes just that workload to be rerun and produce data). - -The videos that go with this exercise show how the -DORA, OpenTelemetry, and Keptn metrics information -appears on a Grafana dashboard with -[Jaeger](https://grafana.com/docs/grafana-cloud/data-configuration/metrics/prometheus-config-examples/the-jaeger-authors-jaeger/). - -If you also have Jaeger extension for Grafana installed on your cluster, -you can view full end-to-end trace for everything -that happens in your deployment. -For more information, see -[Monitoring Jaeger](https://www.jaegertracing.io/docs/1.45/monitoring/). +For more information about sending Slack notifications with KLT, see +[Implement Slack notifications](../../implementing/slack.md). +The code to be executed is expressed as a +[Deno](https://deno.land/) +script, which uses JavaScript syntax. +It can be embedded in the definition file +or pulled in from a remote webserver that is specified. +For this example, the code to be executed is embedded in this file +although, in practice, +this script would probably be located on a remote webserver. + +You can view the actual JavaScript code for the task in the repository. +You see that "context" is important in this code. +This refers to the context in which this code executes -- +for which application, for which version, for which Workload. + +Because the slack server that is required to execute this task +is protected by a secret, the task definition also specifies that secret. From 6035e552d3f46e2553603f711db008784ff99d0e Mon Sep 17 00:00:00 2001 From: Giovanni Liva Date: Mon, 22 May 2023 14:31:43 +0200 Subject: [PATCH 37/62] docs: fix getting started guides (#1447) --- .../getting-started/observability/_index.md | 430 ++++++++++-------- .../getting-started/orchestrate/_index.md | 212 ++++++++- 2 files changed, 458 insertions(+), 184 deletions(-) diff --git a/docs/content/en/docs/getting-started/observability/_index.md b/docs/content/en/docs/getting-started/observability/_index.md index 4549f1f085..cccb7c8fbe 100644 --- a/docs/content/en/docs/getting-started/observability/_index.md +++ b/docs/content/en/docs/getting-started/observability/_index.md @@ -1,216 +1,280 @@ --- -title: Orchestrating pre- and post-deployment tasks and evaluations -description: Learn how the Keptn Lifecycle Toolkit can orchestrate deployment checks. -weight: 55 +title: Standardize access to observability data +description: Learn how the Keptn Lifecycle Toolkit provides observability for Kubernetes deployments +weight: 45 --- -In this exercise, we will configure the Keptn Lifecyle Toolkit -to run deployment checks as part of your deployment. -Whether you are deploying your software with -Argo, Flux, another deployment engine, or even `kubectl apply`, -the Lifecycle Toolkit can do the following: - -* Pre-deploy: Validate external dependencies, - confirm that images are scanned, and so forth - -* Post-deply: Execute tests, notify stakeholders, - promote to the next stage - -* Automatically validate against your SLO (Service Level Objectives) - -KLT sits in the Kubernetes scheduler and can trace the deployment -from start to end. -KLT is also application aware, -so we can extend the deployment -with tasks and evaluations that -are run either before or after your whole application starts the deployment -or even at the individual workload level. -You can also validate any metric, -either pre- or post-deployment, -using the metrics from the Keptn Metrics Server introduced in -[Getting started with Keptn metrics](../metrics). -This means that you can be sure that the environment is healthy -and has adequate resources before you begin the deployment. -After the deployment succeeds, -use Keptn metrics to confirm that your deployed software is really healthy -- -not just that the pods are running but validate against SLOs -such as performance and user experience. -You can also check for new logs that came in from a log monitoring solution. +The Keptn Lifecycle Toolkit (KLT) makes any Kubernetes deployment observable. +You can readily see why a deployment takes so long or why it fails, +even when using multiple deployment tools. +Keptn introduces a concept of an application +which is an abstraction that connects multiple +Workloads belonging together. +In other words, KLT, creates a distributed end-to-end trace +of everything Kubernetes does in the context of a Deployment. + +The observability data is an amalgamation of the following: + +- DORA metrics are collected out of the box + when the Lifecycle Toolkit is enabled +- OpenTelemetry runs traces that show everything that happens in the Kubernetes cluster + and can display this information with dashboard tools + such as Grafana. +- Specific metrics that you can define to monitor + information from all the data providers configured in your cluster. + +The Keptn Lifecycle Toolkit can provide this information +for all applications running in your cluster, +even if they are using different deployment tools. +And it can capture metrics from multiple data sources +using multiple data platforms. +With KLT deployed on your cluster, +you can easily monitor what is happening during a deployment into your Kuberenetes cluster, +and quickly get data to help you understand issues such as +why a deployment took so long or why it failed. ## Using this exercise -This exercise is based on the +This exercise shows how to standardize access +to the observability data for your cluster. +It is based on the [simplenode-dev](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd) example. You can clone that repo to access it locally or just look at it for examples as you implement the functionality "from scratch" on your local Kubernetes deployment cluster. +The +[README](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/setup/observability/README.md) +file for that repo contains useful information. + +Two videos are available +to walk you through this exercise if you prefer: + +- [Introducing Keptn Lifecycle Toolkit](https://youtu.be/449HAFYkUlY) +- [Use SLOs and get DORA the Native K8s way!](https://www.youtube.com/watch?v=zeEC0475SOU) + +In the +[Getting started with Keptn metrics](../metrics) +exercise, you learn how to define and use Keptn metrics. +You may want to complete that exercise before doing this exercise +although that is not required. + +This exercise shows how to standardize access +to the observability data for your cluster. +The steps are: + +1. [Install and enable]( #install-and-enable-klt) + the Lifecycle Toolkit on your cluster +1. [Integrate the Lifecycle Toolkit with your applications](#integrate-the-lifecycle-toolkit-with-your-applications) +1. [DORA metrics](#dora-metrics) +1. [Using OpenTelemetry](#using-opentelemetry) +1. [Keptn metrics](#keptn-metrics) +1. [View the results](#view-the-results) + +## Install and enable KLT + +To install and enable the Keptn Lifecycle Toolkit on your cluster: + +1. Be sure that your cluster includes the components discussed in + [Prepare your cluster for KLT](../../install/k8s.md/#prepare-your-cluster-for-klt) +1. Follow the instructions in + [Install the Keptn Lifecycle Toolkit](../../install/install.md/#use-helm-chart) + to install KLT on your cluster using the Helm chart + + If you installed KLT on your cluster for the + [Getting started with Keptn metrics](../metrics) + exercise, you do not need to re-install it for this exercise. + However, if you only installed the `metrics-operator` for that exercise, + you now need to install the full KLT. + +1. Follow the instructions in + [Enable KLT for your cluster](../../install/install.md/#enable-klt-for-your-cluster) + to enable KLT on your cluster + by annotating the `Namespace` resource.. + See the + [simplenode-dev-ns.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-ns.yaml) + file for an example + +1. Run the following command to ensure that your Kuberetes cluster + is ready to complete this exercise: + + ```shell + kubectl get pods -n keptn-lifecycle-toolkit-system + ``` + + You should see pods for the following components: + - certificate-operator (or another cert manager) + - lifecycle-operator + - scheduler + - metrics-operator + +## Integrate the Lifecycle Toolkit with your applications + +The Keptn Lifecycle Toolkit sits in the scheduler +so it can trace all activities of all deployment workloads on the cluster, +no matter what tool is used for the deployment. +This same mechanism allows KLT to inject pre- and post-deployment checks +into all deployment workloads; +we discuss this in another exercise. + +KLT uses metadata to identify the workloads of interest. +To integrate KLT with your applications, +you need to populate the metadata it needs. +This requires the following steps: + +- Define a Keptn application +- Annotate the `Deployment` resource to recognize your Keptn application + +### Define the Keptn application + +A Keptn application defines the workloads +to be included in your Keptn Application. +We will use the application discovery feature +to automatically generate a Keptn Application +that includes all workloads on the cluster, +regardless of the tools being used. + +A Keptn application aggregates multiple workloads +that belong to a logical app into a single +[KeptnApp](../../yaml-crd-ref/app.md) +resource. -The steps to implement pre- and post-deployment orchestration are: - -1. [Bring or create a Kubernetes cluster](../../install/k8s.md) -1. [Install the Keptn Lifecycle Toolkit and enable it](../../install/install.md) -1. [Integrate KLT with your cluster](../../implementing/integrate/) -1. [Define evaluations to be performed pre- and post-deployment](#define-evaluations-to-be-performed-pre--and-post-deployment) -1. [Define tasks to be performed pre- and post-deployment](#define-tasks-to-be-performed-pre--and-post-deployment) - -## Bring or create a Kubernetes deployment cluster - -You can run this exercise on an existing Kubernetes cluster -or you can create a new cluster. -For personal study and demonstrations, -this exercise runs well on a local Kubernetes cluster. -See [Bring or Install a Kubernetes Cluster](../../install/k8s.md). - -## Install KLT on your cluster - -Install the Keptn Lifecycle Toolkit on your cluster -by executing the following command sequence: +You can view a sample of this file in the +[keptn-app.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-app.yaml.tmp) +file. +You see the metadata that names this `KeptnApp` +and identifies the namespace where it lives: -```shell -helm repo add klt https://charts.lifecycle.keptn.sh -helm repo update -helm upgrade --install keptn klt/klt \ - -n keptn-lifecycle-toolkit-system --create-namespace --wait +```yaml +metadata: + name: simpleapp + namespace: simplenode-dev ``` -See -[Install KLT](../../install/install.md) -for more information about installing the Lifecycle Toolkit. +You can also see the `spec.workloads` list. +In this simple example, +we only have one workload defined +but most production apps will have multiple workloads defined. -## Enable KLT for your cluster +You can create the YAML file to define the resource manually +but the easier approach is to let KLT create this definition for you. +This requires that you annotate all your workloads +(`Deployments`, `Pods`, `StatefulSets`, `DaemonSets`, and `ReplicaSets` +as described in +[Use Keptn automatic app discovery](../../implementing/integrate/#use-keptn-automatic-app-discovery). -To enable KLT for your cluster, annotate the -[Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) -resource -In this example, this is defined in the -[simplenode-dev-ns.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-ns.yaml) -file, which looks like this: +### Annotate your Deployment resource -```yaml -apiVersion: v1 -kind: Namespace -metadata: - name: simplenode-dev - annotations: - keptn.sh/lifecycle-toolkit: "enabled" -``` +Follow the instructions in +[Annotate workload](../../implementing/integrate/#basic-annotations) +to apply basic annotations to your `Deployment` resource. -You see the annotation line that enables `lifecycle-toolkit`. -This line tells the webhook to handle the namespace +The +[simplenode-dev-deployment.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-deployment.yaml/) +file defines the `Deployment` resource for our example. +You see that the `metadata` specifies the same +`name` and `namespace` values defined in the `KeptnApp` resource. -## Integrate KLT with your cluster +The example file also includes annotations for +pre- and post-deployment activities. +We will discuss those in a separate exercise. -To integrate KLT with your cluster, annotate the Kubernetes -[Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) -resource. -In this example, this is defined in the -[simplenode-dev-deployment.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-deployment.yaml) -file, which includes the following lines: +## DORA metrics -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: simplenode - namespace: simplenode-dev -... -template: - metadata: - labels: - app: simplenode - app.kubernetes.io/name: simplenodeservice - annotations: - # keptn.sh/app: simpleapp - keptn.sh/workload: simplenode - keptn.sh/version: 1.0.2 - keptn.sh/pre-deployment-evaluations: evaluate-dependencies - keptn.sh/pre-deployment-tasks: notify - keptn.sh/post-deployment-evaluations: evaluate-deployment - keptn.sh/post-deployment-tasks: notify -... -``` +DORA metrics are an industry-standard set of measurements; +see the following for a description: -For more information about using annotations and labels -to integrate KLT into your deployment cluster, see -[Integrate KLT with your applications](../../implementing/integrate/_index.md). +- [What are DORA Metrics and Why Do They Matter?](https://codeclimate.com/blog/dora-metrics) +- [Are you an Elite DevOps Performer? + Find out with the Four Keys Project](https://cloud.google.com/blog/products/devops-sre/using-the-four-keys-to-measure-your-devops-performance) -## Define evaluations to be performed pre- and post-deployment +DORA metrics provide information such as: -An `evaluation` is a KeptnMetric that has a defined target value. -Evaluations are resources that are defined in a -[KeptinEvaluationDefinition](../../yaml-crd-ref/evaluationdefinition.md) -yaml file. -In our example, evaluations are defined in the -[keptn-evaluations.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-evaluations.yaml) -file. -For example, the definition of the `evaluate-dependencies` evaluation -looks like this: +- How many deployments happened in the last six hours? +- Time between deployments +- Deployment time between versions +- Average time between versions. -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha3 -kind: KeptnEvaluationDefinition -metadata: - name: evaluate-dependencies - namespace: simplenode-dev -spec: - objectives: - - keptnMetricRef: - name: available-cpus - namespace: simplenode-dev - evaluationTarget: ">4" -``` +The Keptn Lifecycle Toolkit starts collecting these metrics +as soon as you annotate the `Deployment` resource. +Metrics are collected only for the `Deployment` resources +that are annotated. -You see that the `available-cpus` metric is defined in the -[keptn-metric.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-metric.yaml) -file. -The `evaluationTarget` is set to be `>4`, -so this evaluation makes sure that more than 4 CPUs are available. -You could include objectives and additional metrics in this evaluation. +To view DORA metrics, run the following command: -## Define tasks to be performed pre- and post-deployment +```shell +kubectl port-forward -n keptn-lifecycle-toolkit-system \ + svc/lifecycle-operator-metrics-service 2222 +``` -Tasks are resources that are defined in a -[KeptnTaskDefinition](../../yaml-crd-ref/taskdefinition.md) -file. -In our example, the tasks are defined in the -[keptn-tasks.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-tasks.yaml) -file -As an example, -we have a `notify` task that composes some markdown text -to be sent as Slack notifications -The `KeptnTaskDefinition` looks like this: +Then view the metrics at: -```yaml -apiVersion: lifecycle.keptn.sh/v1alpha3 -kind: KeptnTaskDefinition -metadata: - name: notify -spec: - function: - inline: - code: | - - secureParameters: - secret: slack-notification +```shell +http://localhost:2222/metrics ``` -For more information about sending Slack notifications with KLT, see -[Implement Slack notifications](../../implementing/slack.md). -The code to be executed is expressed as a -[Deno](https://deno.land/) -script, which uses JavaScript syntax. -It can be embedded in the definition file -or pulled in from a remote webserver that is specified. -For this example, the code to be executed is embedded in this file -although, in practice, -this script would probably be located on a remote webserver. - -You can view the actual JavaScript code for the task in the repository. -You see that "context" is important in this code. -This refers to the context in which this code executes -- -for which application, for which version, for which Workload. - -Because the slack server that is required to execute this task -is protected by a secret, the task definition also specifies that secret. +DORA metrics are also displayed on Grafana +or whatever dashboard application you choose. +For example: + +![DORA metrics](assets/dynatrace_dora_dashboard.png) + +## Using OpenTelemetry + +The Keptn Lifecycle Toolkit extends the Kubernetes +primitives to create OpenTelemetry data +that connects all your deployment and observability tools +without worrying about where it is stored and where it is managed. +OpenTelemetry traces collect data as Kubernetes is deploying the changes, +which allows you to trace everything done in the context of that deployment. + +- You must have an OpenTelemetry collector installed on your cluster. + See + [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) + for more information. +- Follow the instructions in + [OpenTelemetry observability](../../implementing/otel.md) + to configure where your OpenTelemetry data is sent. + - Define a [KeptnConfig](../../yaml-crd-ref/config.md) resource + that defines the URL and port of the OpenTelemetry collector. + For our example, this is in the + [keptnconfig.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/setup/keptn/keptnconfig.yaml) + file. +- Set the `EXPOSE_KEPTN_METRICS` environment variable + in the `metrics-operator` + +TODO: How to set this env variable in `metrics-operator` + or where is it set in the example? + +## Keptn metrics + +You can supplement the DORA Metrics and OpenTelemetry information +with information you explicitly define using Keptn metrics. +The +[Getting started with Keptn metrics](../metrics) +exercise discusses how to define Keptn metrics. + +## View the results + +To start feeding observability data for your deployments +onto a dashboard of your choice, +modify either your `Deployment` or `KeptnApp` resource yaml file +to increment the version number +and commit that change to your repository. +Note that, from the `KeptnApp` YAML file, +you can either increment the version number of the application +(which causes all workloads to be rerun and produce observability data) +or you can increment the version number of a single workload, +(which causes just that workload to be rerun and produce data). + +The videos that go with this exercise show how the +DORA, OpenTelemetry, and Keptn metrics information +appears on a Grafana dashboard with +[Jaeger](https://grafana.com/docs/grafana-cloud/data-configuration/metrics/prometheus-config-examples/the-jaeger-authors-jaeger/). + +If you also have Jaeger extension for Grafana installed on your cluster, +you can view full end-to-end trace for everything +that happens in your deployment. +For more information, see +[Monitoring Jaeger](https://www.jaegertracing.io/docs/1.45/monitoring/). diff --git a/docs/content/en/docs/getting-started/orchestrate/_index.md b/docs/content/en/docs/getting-started/orchestrate/_index.md index b87b7241bd..4549f1f085 100644 --- a/docs/content/en/docs/getting-started/orchestrate/_index.md +++ b/docs/content/en/docs/getting-started/orchestrate/_index.md @@ -1,6 +1,216 @@ --- -title: Orchestrate deployment checks +title: Orchestrating pre- and post-deployment tasks and evaluations description: Learn how the Keptn Lifecycle Toolkit can orchestrate deployment checks. weight: 55 --- +In this exercise, we will configure the Keptn Lifecyle Toolkit +to run deployment checks as part of your deployment. +Whether you are deploying your software with +Argo, Flux, another deployment engine, or even `kubectl apply`, +the Lifecycle Toolkit can do the following: + +* Pre-deploy: Validate external dependencies, + confirm that images are scanned, and so forth + +* Post-deply: Execute tests, notify stakeholders, + promote to the next stage + +* Automatically validate against your SLO (Service Level Objectives) + +KLT sits in the Kubernetes scheduler and can trace the deployment +from start to end. +KLT is also application aware, +so we can extend the deployment +with tasks and evaluations that +are run either before or after your whole application starts the deployment +or even at the individual workload level. +You can also validate any metric, +either pre- or post-deployment, +using the metrics from the Keptn Metrics Server introduced in +[Getting started with Keptn metrics](../metrics). +This means that you can be sure that the environment is healthy +and has adequate resources before you begin the deployment. +After the deployment succeeds, +use Keptn metrics to confirm that your deployed software is really healthy -- +not just that the pods are running but validate against SLOs +such as performance and user experience. +You can also check for new logs that came in from a log monitoring solution. + +## Using this exercise + +This exercise is based on the +[simplenode-dev](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd) +example. +You can clone that repo to access it locally +or just look at it for examples +as you implement the functionality "from scratch" +on your local Kubernetes deployment cluster. + +The steps to implement pre- and post-deployment orchestration are: + +1. [Bring or create a Kubernetes cluster](../../install/k8s.md) +1. [Install the Keptn Lifecycle Toolkit and enable it](../../install/install.md) +1. [Integrate KLT with your cluster](../../implementing/integrate/) +1. [Define evaluations to be performed pre- and post-deployment](#define-evaluations-to-be-performed-pre--and-post-deployment) +1. [Define tasks to be performed pre- and post-deployment](#define-tasks-to-be-performed-pre--and-post-deployment) + +## Bring or create a Kubernetes deployment cluster + +You can run this exercise on an existing Kubernetes cluster +or you can create a new cluster. +For personal study and demonstrations, +this exercise runs well on a local Kubernetes cluster. +See [Bring or Install a Kubernetes Cluster](../../install/k8s.md). + +## Install KLT on your cluster + +Install the Keptn Lifecycle Toolkit on your cluster +by executing the following command sequence: + +```shell +helm repo add klt https://charts.lifecycle.keptn.sh +helm repo update +helm upgrade --install keptn klt/klt \ + -n keptn-lifecycle-toolkit-system --create-namespace --wait +``` + +See +[Install KLT](../../install/install.md) +for more information about installing the Lifecycle Toolkit. + +## Enable KLT for your cluster + +To enable KLT for your cluster, annotate the +[Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) +resource +In this example, this is defined in the +[simplenode-dev-ns.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-ns.yaml) +file, which looks like this: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: simplenode-dev + annotations: + keptn.sh/lifecycle-toolkit: "enabled" +``` + +You see the annotation line that enables `lifecycle-toolkit`. +This line tells the webhook to handle the namespace + +## Integrate KLT with your cluster + +To integrate KLT with your cluster, annotate the Kubernetes +[Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) +resource. +In this example, this is defined in the +[simplenode-dev-deployment.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-deployment.yaml) +file, which includes the following lines: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: simplenode + namespace: simplenode-dev +... +template: + metadata: + labels: + app: simplenode + app.kubernetes.io/name: simplenodeservice + annotations: + # keptn.sh/app: simpleapp + keptn.sh/workload: simplenode + keptn.sh/version: 1.0.2 + keptn.sh/pre-deployment-evaluations: evaluate-dependencies + keptn.sh/pre-deployment-tasks: notify + keptn.sh/post-deployment-evaluations: evaluate-deployment + keptn.sh/post-deployment-tasks: notify +... +``` + +For more information about using annotations and labels +to integrate KLT into your deployment cluster, see +[Integrate KLT with your applications](../../implementing/integrate/_index.md). + +## Define evaluations to be performed pre- and post-deployment + +An `evaluation` is a KeptnMetric that has a defined target value. +Evaluations are resources that are defined in a +[KeptinEvaluationDefinition](../../yaml-crd-ref/evaluationdefinition.md) +yaml file. +In our example, evaluations are defined in the +[keptn-evaluations.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-evaluations.yaml) +file. +For example, the definition of the `evaluate-dependencies` evaluation +looks like this: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnEvaluationDefinition +metadata: + name: evaluate-dependencies + namespace: simplenode-dev +spec: + objectives: + - keptnMetricRef: + name: available-cpus + namespace: simplenode-dev + evaluationTarget: ">4" +``` + +You see that the `available-cpus` metric is defined in the +[keptn-metric.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-metric.yaml) +file. +The `evaluationTarget` is set to be `>4`, +so this evaluation makes sure that more than 4 CPUs are available. +You could include objectives and additional metrics in this evaluation. + +## Define tasks to be performed pre- and post-deployment + +Tasks are resources that are defined in a +[KeptnTaskDefinition](../../yaml-crd-ref/taskdefinition.md) +file. +In our example, the tasks are defined in the +[keptn-tasks.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-tasks.yaml) +file +As an example, +we have a `notify` task that composes some markdown text +to be sent as Slack notifications +The `KeptnTaskDefinition` looks like this: + +```yaml +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: notify +spec: + function: + inline: + code: | + + secureParameters: + secret: slack-notification +``` + +For more information about sending Slack notifications with KLT, see +[Implement Slack notifications](../../implementing/slack.md). +The code to be executed is expressed as a +[Deno](https://deno.land/) +script, which uses JavaScript syntax. +It can be embedded in the definition file +or pulled in from a remote webserver that is specified. +For this example, the code to be executed is embedded in this file +although, in practice, +this script would probably be located on a remote webserver. + +You can view the actual JavaScript code for the task in the repository. +You see that "context" is important in this code. +This refers to the context in which this code executes -- +for which application, for which version, for which Workload. + +Because the slack server that is required to execute this task +is protected by a secret, the task definition also specifies that secret. From 28dd6b77c4cacd038539e30ac8275d6f63d39155 Mon Sep 17 00:00:00 2001 From: odubajDT <93584209+odubajDT@users.noreply.github.com> Date: Tue, 23 May 2023 13:03:56 +0200 Subject: [PATCH 38/62] chore(operator): make use of status.jobName when searching for job in KeptnTask controller (#1436) Signed-off-by: odubajDT --- .../apis/lifecycle/v1alpha3/keptntask_test.go | 8 ++--- .../lifecycle/v1alpha3/keptntask_types.go | 6 ++-- .../lifecycle/keptntask/controller.go | 31 ++----------------- .../lifecycle/keptntask/function_utils.go | 4 +-- .../lifecycle/keptntask/job_utils.go | 2 +- .../lifecycle/keptntask/job_utils_test.go | 16 +++++----- operator/test/component/task/task_test.go | 10 +++--- 7 files changed, 26 insertions(+), 51 deletions(-) diff --git a/operator/apis/lifecycle/v1alpha3/keptntask_test.go b/operator/apis/lifecycle/v1alpha3/keptntask_test.go index df541ca84f..ff3865c8a3 100644 --- a/operator/apis/lifecycle/v1alpha3/keptntask_test.go +++ b/operator/apis/lifecycle/v1alpha3/keptntask_test.go @@ -95,8 +95,8 @@ func TestKeptnTask(t *testing.T) { "keptn.sh/app": "app", "keptn.sh/task-name": "task", "keptn.sh/version": "appversion", - "label1": "label2", - }, task.CreateKeptnLabels()) + "annotation1": "annotation2", + }, task.CreateKeptnAnnotations()) task.Spec.Workload = "workload" task.Spec.WorkloadVersion = "workloadversion" @@ -106,8 +106,8 @@ func TestKeptnTask(t *testing.T) { "keptn.sh/workload": "workload", "keptn.sh/task-name": "task", "keptn.sh/version": "workloadversion", - "label1": "label2", - }, task.CreateKeptnLabels()) + "annotation1": "annotation2", + }, task.CreateKeptnAnnotations()) require.Equal(t, []attribute.KeyValue{ common.AppName.String("app"), diff --git a/operator/apis/lifecycle/v1alpha3/keptntask_types.go b/operator/apis/lifecycle/v1alpha3/keptntask_types.go index 5112b468bc..182cd4512b 100644 --- a/operator/apis/lifecycle/v1alpha3/keptntask_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptntask_types.go @@ -206,16 +206,16 @@ func (t KeptnTask) SetSpanAttributes(span trace.Span) { span.SetAttributes(t.GetSpanAttributes()...) } -func (t KeptnTask) CreateKeptnLabels() map[string]string { +func (t KeptnTask) CreateKeptnAnnotations() map[string]string { if t.Spec.Workload != "" { - return common.MergeMaps(t.Labels, map[string]string{ + return common.MergeMaps(t.Annotations, map[string]string{ common.AppAnnotation: t.Spec.AppName, common.WorkloadAnnotation: t.Spec.Workload, common.VersionAnnotation: t.Spec.WorkloadVersion, common.TaskNameAnnotation: t.Name, }) } - return common.MergeMaps(t.Labels, map[string]string{ + return common.MergeMaps(t.Annotations, map[string]string{ common.AppAnnotation: t.Spec.AppName, common.VersionAnnotation: t.Spec.AppVersion, common.TaskNameAnnotation: t.Name, diff --git a/operator/controllers/lifecycle/keptntask/controller.go b/operator/controllers/lifecycle/keptntask/controller.go index 9a0482197d..c6100ccc17 100644 --- a/operator/controllers/lifecycle/keptntask/controller.go +++ b/operator/controllers/lifecycle/keptntask/controller.go @@ -18,14 +18,12 @@ package keptntask import ( "context" - "fmt" "time" "github.com/go-logr/logr" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" - controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" @@ -91,14 +89,14 @@ func (r *KeptnTaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } }(task) - jobExists, err := r.JobExists(ctx, *task, req.Namespace) - if err != nil { + job, err := r.getJob(ctx, task.Status.JobName, req.Namespace) + if err != nil && !errors.IsNotFound(err) { r.Log.Error(err, "Could not check if job is running") span.SetStatus(codes.Error, err.Error()) return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil } - if !jobExists { + if job == nil { err = r.createJob(ctx, req, task) if err != nil { span.SetStatus(codes.Error, err.Error()) @@ -146,29 +144,6 @@ func (r *KeptnTaskReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func (r *KeptnTaskReconciler) JobExists(ctx context.Context, task klcv1alpha3.KeptnTask, namespace string) (bool, error) { - jobList := &batchv1.JobList{} - - jobLabels := client.MatchingLabels{} - for k, v := range task.CreateKeptnLabels() { - jobLabels[k] = v - } - - if len(jobLabels) == 0 { - return false, fmt.Errorf(controllererrors.ErrNoLabelsFoundTask, task.Name) - } - - if err := r.Client.List(ctx, jobList, client.InNamespace(namespace), jobLabels); err != nil { - return false, err - } - - if len(jobList.Items) > 0 { - return true, nil - } - - return false, nil -} - func (r *KeptnTaskReconciler) getTracer() controllercommon.ITracer { return r.TracerFactory.GetTracer(traceComponentName) } diff --git a/operator/controllers/lifecycle/keptntask/function_utils.go b/operator/controllers/lifecycle/keptntask/function_utils.go index 877d0467a3..66682f44c1 100644 --- a/operator/controllers/lifecycle/keptntask/function_utils.go +++ b/operator/controllers/lifecycle/keptntask/function_utils.go @@ -30,8 +30,8 @@ func (r *KeptnTaskReconciler) generateFunctionJob(task *klcv1alpha3.KeptnTask, p ObjectMeta: metav1.ObjectMeta{ Name: jobId, Namespace: task.Namespace, - Labels: task.CreateKeptnLabels(), - Annotations: task.Annotations, + Labels: task.Labels, + Annotations: task.CreateKeptnAnnotations(), }, Spec: batchv1.JobSpec{ Template: corev1.PodTemplateSpec{ diff --git a/operator/controllers/lifecycle/keptntask/job_utils.go b/operator/controllers/lifecycle/keptntask/job_utils.go index a57874927b..c9847c7a47 100644 --- a/operator/controllers/lifecycle/keptntask/job_utils.go +++ b/operator/controllers/lifecycle/keptntask/job_utils.go @@ -105,7 +105,7 @@ func (r *KeptnTaskReconciler) getJob(ctx context.Context, jobName string, namesp job := &batchv1.Job{} err := r.Client.Get(ctx, types.NamespacedName{Name: jobName, Namespace: namespace}, job) if err != nil { - return job, err + return nil, err } return job, nil } diff --git a/operator/controllers/lifecycle/keptntask/job_utils_test.go b/operator/controllers/lifecycle/keptntask/job_utils_test.go index 3ed1e2c4b5..ec242e07e1 100644 --- a/operator/controllers/lifecycle/keptntask/job_utils_test.go +++ b/operator/controllers/lifecycle/keptntask/job_utils_test.go @@ -80,14 +80,14 @@ func TestKeptnTaskReconciler_createJob(t *testing.T) { require.Len(t, resultingJob.Spec.Template.Spec.Containers, 1) require.Len(t, resultingJob.Spec.Template.Spec.Containers[0].Env, 4) require.Equal(t, map[string]string{ - "label1": "label2", + "label1": "label2", + }, resultingJob.Labels) + require.Equal(t, map[string]string{ + "annotation1": "annotation2", "keptn.sh/app": "my-app", "keptn.sh/task-name": "my-task", "keptn.sh/version": "", "keptn.sh/workload": "my-workload", - }, resultingJob.Labels) - require.Equal(t, map[string]string{ - "annotation1": "annotation2", }, resultingJob.Annotations) } @@ -154,14 +154,14 @@ func TestKeptnTaskReconciler_createJob_withTaskDefInDefaultNamespace(t *testing. require.Len(t, resultingJob.Spec.Template.Spec.Containers, 1) require.Len(t, resultingJob.Spec.Template.Spec.Containers[0].Env, 4) require.Equal(t, map[string]string{ - "label1": "label2", + "label1": "label2", + }, resultingJob.Labels) + require.Equal(t, map[string]string{ + "annotation1": "annotation2", "keptn.sh/app": "my-app", "keptn.sh/task-name": "my-task", "keptn.sh/version": "", "keptn.sh/workload": "my-workload", - }, resultingJob.Labels) - require.Equal(t, map[string]string{ - "annotation1": "annotation2", }, resultingJob.Annotations) } diff --git a/operator/test/component/task/task_test.go b/operator/test/component/task/task_test.go index 9954f9f7d2..b86997c559 100644 --- a/operator/test/component/task/task_test.go +++ b/operator/test/component/task/task_test.go @@ -156,17 +156,17 @@ var _ = Describe("Task", Ordered, func() { Expect(err).To(BeNil()) Expect(createdJob.Annotations).To(Equal(map[string]string{ - "annotation1": "annotation2", - })) - - Expect(createdJob.Labels).To(Equal(map[string]string{ + "annotation1": "annotation2", "keptn.sh/task-name": task.Name, "keptn.sh/version": "", "keptn.sh/workload": "my-workload", - "label1": "label2", "keptn.sh/app": "my-app", })) + Expect(createdJob.Labels).To(Equal(map[string]string{ + "label1": "label2", + })) + val, ok := createdJob.Spec.Template.Labels["label1"] Expect(ok && val == "label2").To(BeTrue()) From f9d609c47545c8fa772329056606891534a6eed6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 15:01:39 +0200 Subject: [PATCH 39/62] deps: update checkmarx/kics-github-action action to v1.7.0 (#1435) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/security-scans.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/security-scans.yml b/.github/workflows/security-scans.yml index 81d9349d70..dd88c12b46 100644 --- a/.github/workflows/security-scans.yml +++ b/.github/workflows/security-scans.yml @@ -123,7 +123,7 @@ jobs: - name: KICS Scan if: matrix.tool == 'kics' - uses: Checkmarx/kics-github-action@v1.6.3 + uses: Checkmarx/kics-github-action@v1.7.0 with: path: scans config_path: .github/kics-config.yml From 9e90d17c211709b357b40ef8f0843a9e1bf0364f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 15:08:02 +0200 Subject: [PATCH 40/62] deps: update curlimages/curl docker tag to v8.1.0 (#1439) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- test/integration/expose-keptn-metric/job-existing-metric.yaml | 4 ++-- test/integration/expose-keptn-metric/job-no-metric.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/expose-keptn-metric/job-existing-metric.yaml b/test/integration/expose-keptn-metric/job-existing-metric.yaml index fadc409bb9..fda1cc39f2 100644 --- a/test/integration/expose-keptn-metric/job-existing-metric.yaml +++ b/test/integration/expose-keptn-metric/job-existing-metric.yaml @@ -20,7 +20,7 @@ spec: spec: containers: - name: test-prometheus - image: curlimages/curl:8.00.1 + image: curlimages/curl:8.1.0 args: - /bin/sh - -ec @@ -33,7 +33,7 @@ spec: fi exit 1 - name: test-api-endpoint - image: curlimages/curl:8.00.1 + image: curlimages/curl:8.1.0 # yamllint disable rule:line-length args: - /bin/sh diff --git a/test/integration/expose-keptn-metric/job-no-metric.yaml b/test/integration/expose-keptn-metric/job-no-metric.yaml index 4dd12ce3fd..0c3e6b9b6d 100644 --- a/test/integration/expose-keptn-metric/job-no-metric.yaml +++ b/test/integration/expose-keptn-metric/job-no-metric.yaml @@ -9,7 +9,7 @@ spec: spec: containers: - name: test-prometheus - image: curlimages/curl:8.00.1 + image: curlimages/curl:8.1.0 args: - /bin/sh - -ec @@ -20,7 +20,7 @@ spec: exit 1 fi - name: test-api-endpoint - image: curlimages/curl:8.00.1 + image: curlimages/curl:8.1.0 args: - /bin/sh - -ec From aff755d4539310787d47a448f1fc6600ffd04c33 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 15:08:39 +0200 Subject: [PATCH 41/62] deps: update dependency helm/helm to v3.12.0 (#1440) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8c3dc8cc31..729f3ec10c 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # renovate: datasource=github-tags depName=kubernetes-sigs/kustomize KUSTOMIZE_VERSION?=v5.0.3 # renovate: datasource=github-tags depName=helm/helm -HELM_VERSION ?= v3.11.3 +HELM_VERSION ?= v3.12.0 CHART_APPVERSION ?= v0.7.1 # x-release-please-version # renovate: datasource=docker depName=cytopia/yamllint From 1fba2b4985a424c728ca02747c56a343fcf3fdbe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 15:08:54 +0200 Subject: [PATCH 42/62] deps: update sigstore/cosign-installer action to v3.0.5 (#1438) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c5062880a9..adc13e7b71 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,7 +79,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Cosign - uses: sigstore/cosign-installer@v3.0.3 + uses: sigstore/cosign-installer@v3.0.5 - name: Build Docker Image id: docker_build_image From 9ba5cae8b3f0be8b380e28883530f97db76773dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 15:11:21 +0200 Subject: [PATCH 43/62] deps: update busybox docker tag to v1.36.1 (#1437) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../assets/podtatohead-deployment-evaluation/manifest.yaml | 2 +- .../podtato-head-application-auto-app-discovery/00-install.yaml | 2 +- test/integration/podtato-head-application/00-install.yaml | 2 +- test/integration/restartable-app/00-install.yaml | 2 +- test/integration/simple-deployment-annotated/00-install.yaml | 2 +- test/integration/simple-deployment-evaluation/00-install.yaml | 2 +- test/integration/simple-deployment/00-install.yaml | 2 +- test/integration/simple-statefulset-annotated/00-install.yaml | 2 +- test/integration/simple-statefulset-annotated/02-install.yaml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/support/observability/assets/podtatohead-deployment-evaluation/manifest.yaml b/examples/support/observability/assets/podtatohead-deployment-evaluation/manifest.yaml index d4a51c4ae7..ab5f08ebc8 100644 --- a/examples/support/observability/assets/podtatohead-deployment-evaluation/manifest.yaml +++ b/examples/support/observability/assets/podtatohead-deployment-evaluation/manifest.yaml @@ -29,7 +29,7 @@ spec: terminationGracePeriodSeconds: 5 initContainers: - name: init-myservice - image: busybox:1.36.0 + image: busybox:1.36.1 command: ['sh', '-c', 'sleep 30'] containers: - name: server diff --git a/test/integration/podtato-head-application-auto-app-discovery/00-install.yaml b/test/integration/podtato-head-application-auto-app-discovery/00-install.yaml index 255636461f..0d9c78e535 100644 --- a/test/integration/podtato-head-application-auto-app-discovery/00-install.yaml +++ b/test/integration/podtato-head-application-auto-app-discovery/00-install.yaml @@ -20,7 +20,7 @@ spec: terminationGracePeriodSeconds: 5 initContainers: - name: init-myservice - image: busybox:1.36.0 + image: busybox:1.36.1 command: ['sh', '-c', 'sleep 30'] containers: - name: server diff --git a/test/integration/podtato-head-application/00-install.yaml b/test/integration/podtato-head-application/00-install.yaml index 3282ad348d..b8a6f95c68 100644 --- a/test/integration/podtato-head-application/00-install.yaml +++ b/test/integration/podtato-head-application/00-install.yaml @@ -52,7 +52,7 @@ spec: terminationGracePeriodSeconds: 5 initContainers: - name: init-myservice - image: busybox:1.36.0 + image: busybox:1.36.1 command: ['sh', '-c', 'sleep 30'] containers: - name: server diff --git a/test/integration/restartable-app/00-install.yaml b/test/integration/restartable-app/00-install.yaml index 40ba6a75d3..5c2ea28ded 100644 --- a/test/integration/restartable-app/00-install.yaml +++ b/test/integration/restartable-app/00-install.yaml @@ -45,7 +45,7 @@ spec: terminationGracePeriodSeconds: 5 initContainers: - name: init-myservice - image: busybox:1.36.0 + image: busybox:1.36.1 command: ['sh', '-c', 'sleep 30'] containers: - name: server diff --git a/test/integration/simple-deployment-annotated/00-install.yaml b/test/integration/simple-deployment-annotated/00-install.yaml index 2eb550a682..76231e3a64 100644 --- a/test/integration/simple-deployment-annotated/00-install.yaml +++ b/test/integration/simple-deployment-annotated/00-install.yaml @@ -37,5 +37,5 @@ spec: command: ['sh', '-c', 'echo The app is running! && sleep infinity'] initContainers: - name: init-myservice - image: busybox:1.36.0 + image: busybox:1.36.1 command: ['sh', '-c', 'sleep 30'] diff --git a/test/integration/simple-deployment-evaluation/00-install.yaml b/test/integration/simple-deployment-evaluation/00-install.yaml index f0a22ddc8d..bc4f50f190 100644 --- a/test/integration/simple-deployment-evaluation/00-install.yaml +++ b/test/integration/simple-deployment-evaluation/00-install.yaml @@ -55,5 +55,5 @@ spec: command: ['sh', '-c', 'echo The app is running! && sleep infinity'] initContainers: - name: init-myservice - image: busybox:1.36.0 + image: busybox:1.36.1 command: ['sh', '-c', 'sleep 10'] diff --git a/test/integration/simple-deployment/00-install.yaml b/test/integration/simple-deployment/00-install.yaml index 1374332978..c96eff8c86 100644 --- a/test/integration/simple-deployment/00-install.yaml +++ b/test/integration/simple-deployment/00-install.yaml @@ -36,5 +36,5 @@ spec: command: ['sh', '-c', 'echo The app is running! && sleep infinity'] initContainers: - name: init-myservice - image: busybox:1.36.0 + image: busybox:1.36.1 command: ['sh', '-c', 'sleep 30'] diff --git a/test/integration/simple-statefulset-annotated/00-install.yaml b/test/integration/simple-statefulset-annotated/00-install.yaml index dd52f386fd..66e8cc0b1f 100644 --- a/test/integration/simple-statefulset-annotated/00-install.yaml +++ b/test/integration/simple-statefulset-annotated/00-install.yaml @@ -40,5 +40,5 @@ spec: command: ['sh', '-c', 'echo The app is running! && sleep infinity'] initContainers: - name: init-myservice - image: busybox:1.36.0 + image: busybox:1.36.1 command: ['sh', '-c', 'sleep 30'] diff --git a/test/integration/simple-statefulset-annotated/02-install.yaml b/test/integration/simple-statefulset-annotated/02-install.yaml index 560512bf7d..566928e835 100644 --- a/test/integration/simple-statefulset-annotated/02-install.yaml +++ b/test/integration/simple-statefulset-annotated/02-install.yaml @@ -27,5 +27,5 @@ spec: command: ['sh', '-c', 'echo The app is running! && sleep infinity'] initContainers: - name: init-myservice - image: busybox:1.36.0 + image: busybox:1.36.1 command: ['sh', '-c', 'sleep 30'] From fcdd9fea3860ba9d5ec52b3733a48258df4e8549 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 15:17:22 +0200 Subject: [PATCH 44/62] deps: update module github.com/onsi/ginkgo/v2 to v2.9.5 (#1433) Signed-off-by: Renovate Bot Signed-off-by: odubajDT Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: odubajDT --- operator/go.mod | 8 ++++---- operator/go.sum | 12 +++++++----- scheduler/go.mod | 10 +++++----- scheduler/go.sum | 15 +++++++++------ 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/operator/go.mod b/operator/go.mod index 6f3f8fc81c..4846d2d557 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -11,7 +11,7 @@ require ( github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230517134932-24a60f5e6f8f github.com/magiconair/properties v1.8.7 - github.com/onsi/ginkgo/v2 v2.9.4 + github.com/onsi/ginkgo/v2 v2.9.5 github.com/onsi/gomega v1.27.6 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 @@ -36,9 +36,9 @@ require ( require ( github.com/prometheus/common v0.42.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/net v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.7.0 // indirect golang.org/x/text v0.9.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -87,7 +87,7 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.8.0 // indirect + golang.org/x/tools v0.9.1 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect diff --git a/operator/go.sum b/operator/go.sum index ae45c3cd86..a935fda16c 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -234,8 +234,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -363,6 +363,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -426,8 +427,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= @@ -499,8 +501,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= -golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/scheduler/go.mod b/scheduler/go.mod index f46c7fc65d..ee9986a978 100644 --- a/scheduler/go.mod +++ b/scheduler/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/kelseyhightower/envconfig v1.4.0 - github.com/onsi/ginkgo/v2 v2.9.4 + github.com/onsi/ginkgo/v2 v2.9.5 github.com/onsi/gomega v1.27.6 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.2 @@ -91,14 +91,14 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.21.0 // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect - golang.org/x/net v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.7.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect - golang.org/x/tools v0.8.0 // indirect + golang.org/x/tools v0.9.1 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect google.golang.org/protobuf v1.28.1 // indirect diff --git a/scheduler/go.sum b/scheduler/go.sum index ece29ffc50..9710aa8e1e 100644 --- a/scheduler/go.sum +++ b/scheduler/go.sum @@ -270,8 +270,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -439,6 +439,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -461,8 +462,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -506,8 +508,9 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= @@ -573,8 +576,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= -golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 7f5b3abb87ebe8e0c040e415f69ed12e25ebb7fd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 15:17:42 +0200 Subject: [PATCH 45/62] deps: update kubernetes packages (patch) (#1432) Signed-off-by: Renovate Bot Signed-off-by: odubajDT Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: odubajDT --- klt-cert-manager/go.mod | 12 ++--- klt-cert-manager/go.sum | 99 +++++++---------------------------------- metrics-operator/go.mod | 20 ++++----- metrics-operator/go.sum | 36 +++++++-------- operator/go.mod | 12 ++--- operator/go.sum | 24 +++++----- scheduler/go.mod | 62 +++++++++++++------------- scheduler/go.sum | 44 +++++++++--------- 8 files changed, 120 insertions(+), 189 deletions(-) diff --git a/klt-cert-manager/go.mod b/klt-cert-manager/go.mod index 96316b35ba..69bccad70f 100644 --- a/klt-cert-manager/go.mod +++ b/klt-cert-manager/go.mod @@ -8,10 +8,10 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/afero v1.9.5 github.com/stretchr/testify v1.8.2 - k8s.io/api v0.26.4 - k8s.io/apiextensions-apiserver v0.26.4 - k8s.io/apimachinery v0.26.4 - k8s.io/client-go v0.26.4 + k8s.io/api v0.26.5 + k8s.io/apiextensions-apiserver v0.26.5 + k8s.io/apimachinery v0.26.5 + k8s.io/client-go v0.26.5 sigs.k8s.io/controller-runtime v0.14.6 ) @@ -51,7 +51,7 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/net v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sys v0.7.0 // indirect golang.org/x/term v0.7.0 // indirect @@ -63,7 +63,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.26.4 // indirect + k8s.io/component-base v0.26.5 // indirect k8s.io/klog/v2 v2.80.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect diff --git a/klt-cert-manager/go.sum b/klt-cert-manager/go.sum index 0d0b73d95f..d9ef9350c6 100644 --- a/klt-cert-manager/go.sum +++ b/klt-cert-manager/go.sum @@ -38,28 +38,21 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.3 h1:g+rSsSaAzhHJYcIQE78hJ3AhyjjtQvleKDjlhdBnIhc= -github.com/benbjohnson/clock v1.3.3/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -67,7 +60,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -75,26 +67,19 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -120,10 +105,7 @@ github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXym github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -157,14 +139,10 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= -github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -180,8 +158,6 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -195,7 +171,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -203,15 +178,12 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -235,7 +207,6 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -244,13 +215,9 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -262,11 +229,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= -github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= +github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -281,8 +247,6 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= -github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -295,8 +259,6 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -304,16 +266,10 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -333,9 +289,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -347,17 +300,12 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= @@ -417,8 +365,6 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -453,7 +399,6 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -470,7 +415,6 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -488,7 +432,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= @@ -550,7 +493,6 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -607,7 +549,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -622,7 +563,6 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -636,12 +576,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -654,23 +591,19 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -690,20 +623,18 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.4 h1:qSG2PmtcD23BkYiWfoYAcak870eF/hE7NNYBYavTT94= -k8s.io/api v0.26.4/go.mod h1:WwKEXU3R1rgCZ77AYa7DFksd9/BAIKyOmRlbVxgvjCk= -k8s.io/apiextensions-apiserver v0.26.4 h1:9D2RTxYGxrG5uYg6D7QZRcykXvavBvcA59j5kTaedQI= -k8s.io/apiextensions-apiserver v0.26.4/go.mod h1:cd4uGFGIgzEqUghWpRsr9KE8j2KNTjY8Ji8pnMMazyw= -k8s.io/apimachinery v0.26.4 h1:rZccKdBLg9vP6J09JD+z8Yr99Ce8gk3Lbi9TCx05Jzs= -k8s.io/apimachinery v0.26.4/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/client-go v0.26.4 h1:/7P/IbGBuT73A+G97trf44NTPSNqvuBREpOfdLbHvD4= -k8s.io/client-go v0.26.4/go.mod h1:6qOItWm3EwxJdl/8p5t7FWtWUOwyMdA8N9ekbW4idpI= -k8s.io/component-base v0.26.4 h1:Bg2xzyXNKL3eAuiTEu3XE198d6z22ENgFgGQv2GGOUk= -k8s.io/component-base v0.26.4/go.mod h1:lTuWL1Xz/a4e80gmIC3YZG2JCO4xNwtKWHJWeJmsq20= +k8s.io/api v0.26.5 h1:Npao/+sMSng6nkEcNydgH3BNo4s5YoBg7iw35HM7Hcw= +k8s.io/api v0.26.5/go.mod h1:O7ICW7lj6+ZQQQ3cxekgCoW+fnGo5kWT0nTHkLZ5grc= +k8s.io/apiextensions-apiserver v0.26.5 h1:VJ946z9RjyCPn3qiz4Kus/UYjCRrdn1xUvEsJFvN5Yo= +k8s.io/apiextensions-apiserver v0.26.5/go.mod h1:Olsde7ZNWnyz9rsL13iXYXmL1h7kWujtKeC3yWVCDPo= +k8s.io/apimachinery v0.26.5 h1:hTQVhJao2piX7vSgCn4Lwd6E0o/+TJIH4NqRf+q4EmE= +k8s.io/apimachinery v0.26.5/go.mod h1:HUvk6wrOP4v22AIYqeCGSQ6xWCHo41J9d6psb3temAg= +k8s.io/client-go v0.26.5 h1:e8Z44pafL/c6ayF/6qYEypbJoDSakaFxhJ9lqULEJEo= +k8s.io/client-go v0.26.5/go.mod h1:/CYyNt+ZLMvWqMF8h1SvkUXz2ujFWQLwdDrdiQlZ5X0= +k8s.io/component-base v0.26.5 h1:nHAzDvXQ4whYpOqrQGWrDIYI/GIeXkuxzqC/iVICfZo= +k8s.io/component-base v0.26.5/go.mod h1:wvfNAS05EtKdPeUxFceo8WNh8bGPcFY8QfPhv5MYjA4= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index 5ddbfba8ef..ae44889509 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -14,14 +14,14 @@ require ( github.com/prometheus/client_golang v1.15.1 github.com/prometheus/common v0.42.0 github.com/stretchr/testify v1.8.2 - k8s.io/api v0.26.4 - k8s.io/apiextensions-apiserver v0.26.4 - k8s.io/apimachinery v0.26.4 - k8s.io/apiserver v0.26.4 - k8s.io/client-go v0.26.4 - k8s.io/component-base v0.26.4 + k8s.io/api v0.26.5 + k8s.io/apiextensions-apiserver v0.26.5 + k8s.io/apimachinery v0.26.5 + k8s.io/apiserver v0.26.5 + k8s.io/client-go v0.26.5 + k8s.io/component-base v0.26.5 k8s.io/klog/v2 v2.100.1 - k8s.io/metrics v0.26.4 + k8s.io/metrics v0.26.5 sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/custom-metrics-apiserver v1.25.1-0.20230116101851-63817c8ac8f2 ) @@ -94,7 +94,7 @@ require ( go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/net v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.7.0 // indirect @@ -110,10 +110,10 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/kms v0.26.4 // indirect + k8s.io/kms v0.26.5 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.37 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index 9d49047e16..1c30fe8180 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -765,33 +765,33 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.4 h1:qSG2PmtcD23BkYiWfoYAcak870eF/hE7NNYBYavTT94= -k8s.io/api v0.26.4/go.mod h1:WwKEXU3R1rgCZ77AYa7DFksd9/BAIKyOmRlbVxgvjCk= -k8s.io/apiextensions-apiserver v0.26.4 h1:9D2RTxYGxrG5uYg6D7QZRcykXvavBvcA59j5kTaedQI= -k8s.io/apiextensions-apiserver v0.26.4/go.mod h1:cd4uGFGIgzEqUghWpRsr9KE8j2KNTjY8Ji8pnMMazyw= -k8s.io/apimachinery v0.26.4 h1:rZccKdBLg9vP6J09JD+z8Yr99Ce8gk3Lbi9TCx05Jzs= -k8s.io/apimachinery v0.26.4/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/apiserver v0.26.4 h1:3Oq4mnJv0mzVX7BR/Nod+8KjlELf/3Ljvu9ZWDyLUoA= -k8s.io/apiserver v0.26.4/go.mod h1:yAY3O1vBM4/0OIGAGeWcdfzQvgdwJ188VirLcuSAVnw= -k8s.io/client-go v0.26.4 h1:/7P/IbGBuT73A+G97trf44NTPSNqvuBREpOfdLbHvD4= -k8s.io/client-go v0.26.4/go.mod h1:6qOItWm3EwxJdl/8p5t7FWtWUOwyMdA8N9ekbW4idpI= -k8s.io/component-base v0.26.4 h1:Bg2xzyXNKL3eAuiTEu3XE198d6z22ENgFgGQv2GGOUk= -k8s.io/component-base v0.26.4/go.mod h1:lTuWL1Xz/a4e80gmIC3YZG2JCO4xNwtKWHJWeJmsq20= +k8s.io/api v0.26.5 h1:Npao/+sMSng6nkEcNydgH3BNo4s5YoBg7iw35HM7Hcw= +k8s.io/api v0.26.5/go.mod h1:O7ICW7lj6+ZQQQ3cxekgCoW+fnGo5kWT0nTHkLZ5grc= +k8s.io/apiextensions-apiserver v0.26.5 h1:VJ946z9RjyCPn3qiz4Kus/UYjCRrdn1xUvEsJFvN5Yo= +k8s.io/apiextensions-apiserver v0.26.5/go.mod h1:Olsde7ZNWnyz9rsL13iXYXmL1h7kWujtKeC3yWVCDPo= +k8s.io/apimachinery v0.26.5 h1:hTQVhJao2piX7vSgCn4Lwd6E0o/+TJIH4NqRf+q4EmE= +k8s.io/apimachinery v0.26.5/go.mod h1:HUvk6wrOP4v22AIYqeCGSQ6xWCHo41J9d6psb3temAg= +k8s.io/apiserver v0.26.5 h1:SBzyDpIXXPR4v+mpSU44p9fQerBMkpOH6lmSPCD1wmo= +k8s.io/apiserver v0.26.5/go.mod h1:OSbw98Y1bDSbA2izYIKqhi10vb4KWP9b4siiCRFkBVE= +k8s.io/client-go v0.26.5 h1:e8Z44pafL/c6ayF/6qYEypbJoDSakaFxhJ9lqULEJEo= +k8s.io/client-go v0.26.5/go.mod h1:/CYyNt+ZLMvWqMF8h1SvkUXz2ujFWQLwdDrdiQlZ5X0= +k8s.io/component-base v0.26.5 h1:nHAzDvXQ4whYpOqrQGWrDIYI/GIeXkuxzqC/iVICfZo= +k8s.io/component-base v0.26.5/go.mod h1:wvfNAS05EtKdPeUxFceo8WNh8bGPcFY8QfPhv5MYjA4= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kms v0.26.4 h1:mQ+DeOvgAHC6+heZcozPkEd3rWtP4DVVjo1hLSih9w4= -k8s.io/kms v0.26.4/go.mod h1:69qGnf1NsFOQP07fBYqNLZklqEHSJF024JqYCaeVxHg= +k8s.io/kms v0.26.5 h1:Yjgvlxc3KBTAUWuxOIkMUB6YEmqR+rKBkRquBioP8YY= +k8s.io/kms v0.26.5/go.mod h1:AYuV9ZebRhr6cb1eT9L6kZVxvgIUxmE1Fe6kPhqYvuc= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/metrics v0.26.4 h1:ijyerycmjVp9EVPfDqha8eb+s9jw5c+A9MkTvuRBdms= -k8s.io/metrics v0.26.4/go.mod h1:0InNj7+/aS5POa0dDHuSleIDr5MHXaQQSpMc0mm17wE= +k8s.io/metrics v0.26.5 h1:J2vPw1u49iA1rAByeAObffn60WvcxZwTCmMTB3+LWAM= +k8s.io/metrics v0.26.5/go.mod h1:g3YZfYetr4JJ+uA2q2Vdkr/D9bswPgQDOvost7ZTLHQ= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36 h1:PUuX1qIFv309AT8hF/CdPKDmsG/hn/L8zRX7VvISM3A= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.37 h1:fAPTNEpzQMOLMGwOHNbUkR2xXTQwMJOZYNx+/mLlOh0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.37/go.mod h1:vfnxT4FXNT8eGvO+xi/DsyC/qHmdujqwrUa1WSspCsk= sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/custom-metrics-apiserver v1.25.1-0.20230116101851-63817c8ac8f2 h1:D49r2VoxIdm3s1yVoCMbHrLH0qxDj5TiaWB0XYdqgcI= diff --git a/operator/go.mod b/operator/go.mod index 4846d2d557..e36ef1f855 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -25,11 +25,11 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.38.1 go.opentelemetry.io/otel/trace v1.15.1 google.golang.org/grpc v1.54.1 - k8s.io/api v0.26.4 - k8s.io/apiextensions-apiserver v0.26.4 - k8s.io/apimachinery v0.26.4 - k8s.io/apiserver v0.26.4 - k8s.io/client-go v0.26.4 + k8s.io/api v0.26.5 + k8s.io/apiextensions-apiserver v0.26.5 + k8s.io/apimachinery v0.26.5 + k8s.io/apiserver v0.26.5 + k8s.io/client-go v0.26.5 sigs.k8s.io/controller-runtime v0.14.6 ) @@ -94,7 +94,7 @@ require ( google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.26.4 // indirect + k8s.io/component-base v0.26.5 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect diff --git a/operator/go.sum b/operator/go.sum index a935fda16c..b173d42a3b 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -638,18 +638,18 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.4 h1:qSG2PmtcD23BkYiWfoYAcak870eF/hE7NNYBYavTT94= -k8s.io/api v0.26.4/go.mod h1:WwKEXU3R1rgCZ77AYa7DFksd9/BAIKyOmRlbVxgvjCk= -k8s.io/apiextensions-apiserver v0.26.4 h1:9D2RTxYGxrG5uYg6D7QZRcykXvavBvcA59j5kTaedQI= -k8s.io/apiextensions-apiserver v0.26.4/go.mod h1:cd4uGFGIgzEqUghWpRsr9KE8j2KNTjY8Ji8pnMMazyw= -k8s.io/apimachinery v0.26.4 h1:rZccKdBLg9vP6J09JD+z8Yr99Ce8gk3Lbi9TCx05Jzs= -k8s.io/apimachinery v0.26.4/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/apiserver v0.26.4 h1:3Oq4mnJv0mzVX7BR/Nod+8KjlELf/3Ljvu9ZWDyLUoA= -k8s.io/apiserver v0.26.4/go.mod h1:yAY3O1vBM4/0OIGAGeWcdfzQvgdwJ188VirLcuSAVnw= -k8s.io/client-go v0.26.4 h1:/7P/IbGBuT73A+G97trf44NTPSNqvuBREpOfdLbHvD4= -k8s.io/client-go v0.26.4/go.mod h1:6qOItWm3EwxJdl/8p5t7FWtWUOwyMdA8N9ekbW4idpI= -k8s.io/component-base v0.26.4 h1:Bg2xzyXNKL3eAuiTEu3XE198d6z22ENgFgGQv2GGOUk= -k8s.io/component-base v0.26.4/go.mod h1:lTuWL1Xz/a4e80gmIC3YZG2JCO4xNwtKWHJWeJmsq20= +k8s.io/api v0.26.5 h1:Npao/+sMSng6nkEcNydgH3BNo4s5YoBg7iw35HM7Hcw= +k8s.io/api v0.26.5/go.mod h1:O7ICW7lj6+ZQQQ3cxekgCoW+fnGo5kWT0nTHkLZ5grc= +k8s.io/apiextensions-apiserver v0.26.5 h1:VJ946z9RjyCPn3qiz4Kus/UYjCRrdn1xUvEsJFvN5Yo= +k8s.io/apiextensions-apiserver v0.26.5/go.mod h1:Olsde7ZNWnyz9rsL13iXYXmL1h7kWujtKeC3yWVCDPo= +k8s.io/apimachinery v0.26.5 h1:hTQVhJao2piX7vSgCn4Lwd6E0o/+TJIH4NqRf+q4EmE= +k8s.io/apimachinery v0.26.5/go.mod h1:HUvk6wrOP4v22AIYqeCGSQ6xWCHo41J9d6psb3temAg= +k8s.io/apiserver v0.26.5 h1:SBzyDpIXXPR4v+mpSU44p9fQerBMkpOH6lmSPCD1wmo= +k8s.io/apiserver v0.26.5/go.mod h1:OSbw98Y1bDSbA2izYIKqhi10vb4KWP9b4siiCRFkBVE= +k8s.io/client-go v0.26.5 h1:e8Z44pafL/c6ayF/6qYEypbJoDSakaFxhJ9lqULEJEo= +k8s.io/client-go v0.26.5/go.mod h1:/CYyNt+ZLMvWqMF8h1SvkUXz2ujFWQLwdDrdiQlZ5X0= +k8s.io/component-base v0.26.5 h1:nHAzDvXQ4whYpOqrQGWrDIYI/GIeXkuxzqC/iVICfZo= +k8s.io/component-base v0.26.5/go.mod h1:wvfNAS05EtKdPeUxFceo8WNh8bGPcFY8QfPhv5MYjA4= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= diff --git a/scheduler/go.mod b/scheduler/go.mod index ee9986a978..5cf1a2950c 100644 --- a/scheduler/go.mod +++ b/scheduler/go.mod @@ -14,11 +14,11 @@ require ( go.opentelemetry.io/otel/sdk v0.20.0 go.opentelemetry.io/otel/trace v0.20.0 google.golang.org/grpc v1.54.1 - k8s.io/api v0.25.9 - k8s.io/apimachinery v0.25.9 - k8s.io/apiserver v0.25.9 - k8s.io/client-go v0.25.9 - k8s.io/component-base v0.25.9 + k8s.io/api v0.25.10 + k8s.io/apimachinery v0.25.10 + k8s.io/apiserver v0.25.10 + k8s.io/client-go v0.25.10 + k8s.io/component-base v0.25.10 k8s.io/klog/v2 v2.100.1 k8s.io/kubernetes v1.25.9 sigs.k8s.io/controller-runtime v0.13.1 @@ -108,13 +108,13 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.25.0 // indirect k8s.io/cloud-provider v0.25.4 // indirect - k8s.io/component-helpers v0.25.9 // indirect + k8s.io/component-helpers v0.25.10 // indirect k8s.io/csi-translation-lib v0.25.4 // indirect k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect k8s.io/kube-scheduler v0.0.0 // indirect k8s.io/mount-utils v0.25.4 // indirect k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.37 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect @@ -123,30 +123,30 @@ require ( replace ( github.com/keptn/lifecycle-toolkit/scheduler/pkg/klcpermit => /pkg/klcpermit golang.org/x/net => golang.org/x/net v0.9.0 - k8s.io/api => k8s.io/api v0.25.9 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.25.9 - k8s.io/apimachinery => k8s.io/apimachinery v0.25.9 - k8s.io/apiserver => k8s.io/apiserver v0.25.9 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.25.9 - k8s.io/client-go => k8s.io/client-go v0.25.9 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.25.9 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.25.9 - k8s.io/code-generator => k8s.io/code-generator v0.25.9 - k8s.io/component-base => k8s.io/component-base v0.25.9 + k8s.io/api => k8s.io/api v0.25.10 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.25.10 + k8s.io/apimachinery => k8s.io/apimachinery v0.25.10 + k8s.io/apiserver => k8s.io/apiserver v0.25.10 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.25.10 + k8s.io/client-go => k8s.io/client-go v0.25.10 + k8s.io/cloud-provider => k8s.io/cloud-provider v0.25.10 + k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.25.10 + k8s.io/code-generator => k8s.io/code-generator v0.25.10 + k8s.io/component-base => k8s.io/component-base v0.25.10 k8s.io/component-helpers => k8s.io/component-helpers v0.25.9 - k8s.io/controller-manager => k8s.io/controller-manager v0.25.9 - k8s.io/cri-api => k8s.io/cri-api v0.25.9 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.25.9 - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.25.9 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.25.9 - k8s.io/kube-proxy => k8s.io/kube-proxy v0.25.9 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.25.9 - k8s.io/kubectl => k8s.io/kubectl v0.25.9 - k8s.io/kubelet => k8s.io/kubelet v0.25.9 + k8s.io/controller-manager => k8s.io/controller-manager v0.25.10 + k8s.io/cri-api => k8s.io/cri-api v0.25.10 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.25.10 + k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.25.10 + k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.25.10 + k8s.io/kube-proxy => k8s.io/kube-proxy v0.25.10 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.25.10 + k8s.io/kubectl => k8s.io/kubectl v0.25.10 + k8s.io/kubelet => k8s.io/kubelet v0.25.10 k8s.io/kubernetes => k8s.io/kubernetes v1.25.9 - k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.25.9 - k8s.io/metrics => k8s.io/metrics v0.25.9 - k8s.io/mount-utils => k8s.io/mount-utils v0.25.9 - k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.25.9 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.25.9 + k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.25.10 + k8s.io/metrics => k8s.io/metrics v0.25.10 + k8s.io/mount-utils => k8s.io/mount-utils v0.25.10 + k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.25.10 + k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.25.10 ) diff --git a/scheduler/go.sum b/scheduler/go.sum index 9710aa8e1e..036b548953 100644 --- a/scheduler/go.sum +++ b/scheduler/go.sum @@ -708,42 +708,42 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.25.9 h1:XuJ2bz2F52jZmp3YjUcp/pozH8kY1BlBHdXnoOXBP3U= -k8s.io/api v0.25.9/go.mod h1:9YRWzD0cRHzfsnf9e5OQsQ4Un6cbZ//Xv3jo44YKm2Y= -k8s.io/apiextensions-apiserver v0.25.9 h1:Pycd6lm2auABp9wKQHCFSEPG+NPdFSTJXPST6NJFzB8= -k8s.io/apiextensions-apiserver v0.25.9/go.mod h1:ijGxmSG1GLOEaWhTuaEr0M7KUeia3mWCZa6FFQqpt1M= -k8s.io/apimachinery v0.25.9 h1:MPjgTz4dbAKJ/KiHIvDeYkFfIn7ueihqvT520HkV7v4= -k8s.io/apimachinery v0.25.9/go.mod h1:ZTl0drTQaFi5gMM3snYI5tWV1XJmRH1gfnDx2QCLsxk= -k8s.io/apiserver v0.25.9 h1:1tuxeA28SnoK30bhOa48c6tOCQypcJJYlsGE8BJpUko= -k8s.io/apiserver v0.25.9/go.mod h1:FHU743u4KKL79IpiQU/d8MiwA+JdHX26vfhG7gBJSYo= -k8s.io/client-go v0.25.9 h1:U0S3nc71NRfHXiA0utyCkPt3Mv1SWpQw0g5VfBCv5xg= -k8s.io/client-go v0.25.9/go.mod h1:tmPyOtpbbkneXj65EYZ4sXun1BE/2F2XlRABVj9CBgc= -k8s.io/cloud-provider v0.25.9 h1:o8yJ3E5PmCTZ9jh86Nob5wYJk8TjtY705j6NpnReyaQ= -k8s.io/cloud-provider v0.25.9/go.mod h1:X5gJ477bpXjgRkx4OJy5jgheMY6wQOVgstjklu4HiLw= -k8s.io/component-base v0.25.9 h1:4ZZX907RLQus98m6O66FEp2mrh6ul2+xYcW17dLx/0s= -k8s.io/component-base v0.25.9/go.mod h1:h/Sf4qyuoIwFA14ff4TT6anxT3ugtWjh8J9NQj/U0hc= +k8s.io/api v0.25.10 h1:YfcmWMKDnWpzKV2byP+fu0v00yNTS4+cqw4g0ndUsJA= +k8s.io/api v0.25.10/go.mod h1:7inWacs1rgsi5uLOONfUmo4on+tVkkuJZNsMLouGAhA= +k8s.io/apiextensions-apiserver v0.25.10 h1:10aKN++Nrzql7/nw9X+OdALaPq2okw1Bj9KxKm5CpsM= +k8s.io/apiextensions-apiserver v0.25.10/go.mod h1:MpvczMBurvgfZ7MPVrUt1IOGvi/K3eJipATUoLWNfFw= +k8s.io/apimachinery v0.25.10 h1:uvPXar0BVg9g2R5a5kTjMuHCjLxC5LiAclSrLOP8Q20= +k8s.io/apimachinery v0.25.10/go.mod h1:PJ+6cm50BMETqCCJx1RXQIXaq937SUdAq2vVKCGDZXU= +k8s.io/apiserver v0.25.10 h1:fhyXPhSK5Gl4eOE4GHl9U7zjTDHySed5BVQymehTqwE= +k8s.io/apiserver v0.25.10/go.mod h1:+1kB0D6V6WGlX0OaRZ/og2dBtC4+fvvjBmnG0nNS9tI= +k8s.io/client-go v0.25.10 h1:FhTgEpCDboGjByXnoEj/kiHK12SC+fjRMrkNKn72/aU= +k8s.io/client-go v0.25.10/go.mod h1:zqpG8XvdsDK7q/Dh83v2M3LgTVj8sAbT3BT0JnANjME= +k8s.io/cloud-provider v0.25.10 h1:PKJzAjQakHS6oxkXF4ZE7cHUbx7hJu/mPEmfdXUovZU= +k8s.io/cloud-provider v0.25.10/go.mod h1:sRDRQDxyQyfluYs/KxmfpyrBx7cq+x9q8olsM3+FQ2I= +k8s.io/component-base v0.25.10 h1:OjsAFfzmeDqiB9rZyGOqv72KcPMNpStViwNpEg8F0/k= +k8s.io/component-base v0.25.10/go.mod h1:cWsBkPnnIX6v7WDjCs72P8q3l0b00KIDgyfcjLXT/eQ= k8s.io/component-helpers v0.25.9 h1:WfuDvS0xO+ADmIacqYuM2O8qHq9bUZCYdYCjTCB2jwM= k8s.io/component-helpers v0.25.9/go.mod h1:o9yuVdUiyKe0ubYP78veYtYxThJx66PsGzpRandVwf0= -k8s.io/csi-translation-lib v0.25.9 h1:82oc8yYCapshWTMQVmecBEWSdbhrmpuEOBQUSlh1x5I= -k8s.io/csi-translation-lib v0.25.9/go.mod h1:oWiY3igOmast1WmTreVezMXyOVRMoB1Eaa5j8CwtwEI= +k8s.io/csi-translation-lib v0.25.10 h1:aPA/UAGffC5+4cHgeWORZe2w1ytrpXGg96TQyxLf2zU= +k8s.io/csi-translation-lib v0.25.10/go.mod h1:kCLPsbj4GvdyjlYy57obi1wDjAAhuQ9wH+EqaV0qcr4= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= -k8s.io/kube-scheduler v0.25.9 h1:1niibmG0cU/yGG8O+ORRUhbisQ6k7d+g7O6Fo1CZSNs= -k8s.io/kube-scheduler v0.25.9/go.mod h1:diz6iLAyovBUF/tqgb+Pw/mmutfLb8p3GI+LxmlVSFY= +k8s.io/kube-scheduler v0.25.10 h1:wpQZ8G2AE+7tC5ue3FVQox/UgpKSVDeUaE0EVxN0mQg= +k8s.io/kube-scheduler v0.25.10/go.mod h1:qxG3EaVXCNzYhu0aOt20gMjgYKTCzDfdMU430XpMLLg= k8s.io/kubernetes v1.25.9 h1:ecLjIq630FVMRgisW7jO0rr+vf2PLSbIGKgol2xlAx4= k8s.io/kubernetes v1.25.9/go.mod h1:wP+j1DisuLPNLGK0YrsFIQdBtz04xQB7wwM7IbmrKRw= -k8s.io/mount-utils v0.25.9 h1:gGWKc7u+gVppGSBPVMs1rh7/w1ElM9XA1EVfpGPdOkY= -k8s.io/mount-utils v0.25.9/go.mod h1:vojU37mpuNwtcRmJiVso5mgFvdo5wI8No6RLp2O1yhg= +k8s.io/mount-utils v0.25.10 h1:ZlMrV9cs6YAyiZrYCpUcLZMv6keq5cXi+50SZMXJNTo= +k8s.io/mount-utils v0.25.10/go.mod h1:IM9QOFh15E1a4Nb6Rcn8FJ9Z1PbBpuyAPCty/qvKSAw= k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36 h1:PUuX1qIFv309AT8hF/CdPKDmsG/hn/L8zRX7VvISM3A= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.37 h1:fAPTNEpzQMOLMGwOHNbUkR2xXTQwMJOZYNx+/mLlOh0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.37/go.mod h1:vfnxT4FXNT8eGvO+xi/DsyC/qHmdujqwrUa1WSspCsk= sigs.k8s.io/controller-runtime v0.13.1 h1:tUsRCSJVM1QQOOeViGeX3GMT3dQF1eePPw6sEE3xSlg= sigs.k8s.io/controller-runtime v0.13.1/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= From 65b41399b2e0d5c4109af484a80d4bb2c56f9215 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 15:39:47 +0200 Subject: [PATCH 46/62] deps: update module github.com/stretchr/testify to v1.8.3 (#1434) Signed-off-by: Renovate Bot Signed-off-by: odubajDT Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: odubajDT --- klt-cert-manager/go.mod | 2 +- klt-cert-manager/go.sum | 8 ++------ metrics-operator/go.mod | 2 +- metrics-operator/go.sum | 8 ++------ operator/go.mod | 2 +- operator/go.sum | 8 ++------ scheduler/go.mod | 2 +- scheduler/go.sum | 8 ++------ 8 files changed, 12 insertions(+), 28 deletions(-) diff --git a/klt-cert-manager/go.mod b/klt-cert-manager/go.mod index 69bccad70f..616046294c 100644 --- a/klt-cert-manager/go.mod +++ b/klt-cert-manager/go.mod @@ -7,7 +7,7 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/pkg/errors v0.9.1 github.com/spf13/afero v1.9.5 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 k8s.io/api v0.26.5 k8s.io/apiextensions-apiserver v0.26.5 k8s.io/apimachinery v0.26.5 diff --git a/klt-cert-manager/go.sum b/klt-cert-manager/go.sum index d9ef9350c6..0f2baa0abc 100644 --- a/klt-cert-manager/go.sum +++ b/klt-cert-manager/go.sum @@ -277,18 +277,14 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index ae44889509..0de21de900 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -13,7 +13,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 github.com/prometheus/common v0.42.0 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 k8s.io/api v0.26.5 k8s.io/apiextensions-apiserver v0.26.5 k8s.io/apimachinery v0.26.5 diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index 1c30fe8180..29efdfcd82 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -353,17 +353,13 @@ github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ai github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= diff --git a/operator/go.mod b/operator/go.mod index e36ef1f855..8436d6ad11 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -15,7 +15,7 @@ require ( github.com/onsi/gomega v1.27.6 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 go.opentelemetry.io/otel v1.15.1 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1 go.opentelemetry.io/otel/exporters/prometheus v0.38.1 diff --git a/operator/go.sum b/operator/go.sum index b173d42a3b..11e1bb8c92 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -263,17 +263,13 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= diff --git a/scheduler/go.mod b/scheduler/go.mod index 5cf1a2950c..a6cfda5da9 100644 --- a/scheduler/go.mod +++ b/scheduler/go.mod @@ -7,7 +7,7 @@ require ( github.com/onsi/ginkgo/v2 v2.9.5 github.com/onsi/gomega v1.27.6 github.com/pkg/errors v0.9.1 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 go.opentelemetry.io/otel v0.20.0 go.opentelemetry.io/otel/exporters/otlp v0.20.0 go.opentelemetry.io/otel/exporters/stdout v0.20.0 diff --git a/scheduler/go.sum b/scheduler/go.sum index 036b548953..5ff44c86e5 100644 --- a/scheduler/go.sum +++ b/scheduler/go.sum @@ -325,18 +325,14 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From 57fdcddcf73c71dde07641cb13f9c7b16cff6cf5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 16:00:51 +0200 Subject: [PATCH 47/62] deps: update github.com/keptn/lifecycle-toolkit/klt-cert-manager digest to 65b4139 (#1429) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- metrics-operator/go.mod | 2 +- metrics-operator/go.sum | 4 ++-- operator/go.mod | 2 +- operator/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index 0de21de900..695442ec5e 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-logr/logr v1.2.4 github.com/gorilla/mux v1.8.0 github.com/kelseyhightower/envconfig v1.4.0 - github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 + github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230523133947-65b41399b2e0 github.com/open-feature/go-sdk v1.3.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index 29efdfcd82..54a43f5cc1 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -263,8 +263,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 h1:V44aeshIPrEu2L4ElKJ9Y0VPDp5j9BhT8eMzhjICyGU= -github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230523133947-65b41399b2e0 h1:utqqOMPptCXMBUaU9oRBsXAQE1US5Bz9fbXxzOAk1YI= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230523133947-65b41399b2e0/go.mod h1:sZUFv7ZVPq4cMxcMQc77vjhEtem48BzV6fHbmXdVjQg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/operator/go.mod b/operator/go.mod index 8436d6ad11..03c55abd4b 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-logr/logr v1.2.4 github.com/imdario/mergo v0.3.15 github.com/kelseyhightower/envconfig v1.4.0 - github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 + github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230523133947-65b41399b2e0 github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230517134932-24a60f5e6f8f github.com/magiconair/properties v1.8.7 github.com/onsi/ginkgo/v2 v2.9.5 diff --git a/operator/go.sum b/operator/go.sum index 11e1bb8c92..94c42faf2c 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -206,8 +206,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79 h1:V44aeshIPrEu2L4ElKJ9Y0VPDp5j9BhT8eMzhjICyGU= -github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230517124521-e381f7fc6d79/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230523133947-65b41399b2e0 h1:utqqOMPptCXMBUaU9oRBsXAQE1US5Bz9fbXxzOAk1YI= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230523133947-65b41399b2e0/go.mod h1:sZUFv7ZVPq4cMxcMQc77vjhEtem48BzV6fHbmXdVjQg= github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230517134932-24a60f5e6f8f h1:FDUUg4O36+nQFpr6L4M4Xz6ZcxG/YUr1/crE90DfjaE= github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230517134932-24a60f5e6f8f/go.mod h1:aWxBCTaDbsZWnA3gzV4X4e3CgX4lps5fmIyi5pwPKxc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= From 54a93840094e7dd3c9799e5e0a8ae889d51bb2ac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 07:55:54 +0200 Subject: [PATCH 48/62] deps: update github.com/keptn/lifecycle-toolkit/metrics-operator digest to 57fdcdd (#1430) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- operator/go.mod | 2 +- operator/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/operator/go.mod b/operator/go.mod index 03c55abd4b..69fcd2f9aa 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -9,7 +9,7 @@ require ( github.com/imdario/mergo v0.3.15 github.com/kelseyhightower/envconfig v1.4.0 github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230523133947-65b41399b2e0 - github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230517134932-24a60f5e6f8f + github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230523140051-57fdcddcf73c github.com/magiconair/properties v1.8.7 github.com/onsi/ginkgo/v2 v2.9.5 github.com/onsi/gomega v1.27.6 diff --git a/operator/go.sum b/operator/go.sum index 94c42faf2c..1a3cfaa7ca 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -208,8 +208,8 @@ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dv github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230523133947-65b41399b2e0 h1:utqqOMPptCXMBUaU9oRBsXAQE1US5Bz9fbXxzOAk1YI= github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230523133947-65b41399b2e0/go.mod h1:sZUFv7ZVPq4cMxcMQc77vjhEtem48BzV6fHbmXdVjQg= -github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230517134932-24a60f5e6f8f h1:FDUUg4O36+nQFpr6L4M4Xz6ZcxG/YUr1/crE90DfjaE= -github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230517134932-24a60f5e6f8f/go.mod h1:aWxBCTaDbsZWnA3gzV4X4e3CgX4lps5fmIyi5pwPKxc= +github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230523140051-57fdcddcf73c h1:to6Fy5UHqL1qL+2C5/kSNst0O2saTU1Zp4tE3ibv3CQ= +github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230523140051-57fdcddcf73c/go.mod h1:47Sx5OCHdEkQmU55/ymmOYw3me5nUAMgEPeaO84AP0E= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= From a20b2e707fd2c0bb03b661c6a6cca272eb088ee1 Mon Sep 17 00:00:00 2001 From: RealAnna <89971034+RealAnna@users.noreply.github.com> Date: Wed, 24 May 2023 08:36:16 +0200 Subject: [PATCH 49/62] fix: remove scarf redirect from containers images (#1443) Signed-off-by: realanna --- .github/.kubescape/controls-inputs.json | 2 +- .../actions/deploy-klt-on-cluster/action.yml | 2 +- .github/workflows/CI.yaml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/validate-helm-chart.yml | 6 +- functions-runtime/README.md | 2 +- helm/chart/README.md | 144 +++++++++--------- helm/chart/values.yaml | 10 +- operator/config/manager/manager.yaml | 2 +- renovate.json | 10 +- 10 files changed, 91 insertions(+), 91 deletions(-) diff --git a/.github/.kubescape/controls-inputs.json b/.github/.kubescape/controls-inputs.json index 8ba5106cf2..fd2e21e9a3 100644 --- a/.github/.kubescape/controls-inputs.json +++ b/.github/.kubescape/controls-inputs.json @@ -5,7 +5,7 @@ "*.ghcr.io", ".*azurecr.io", "docker.io", - "ghcr.keptn.sh" + "ghcr.io" ], "max_critical_vulnerabilities": [ "5" diff --git a/.github/actions/deploy-klt-on-cluster/action.yml b/.github/actions/deploy-klt-on-cluster/action.yml index 35df992e44..aa64cd65d6 100644 --- a/.github/actions/deploy-klt-on-cluster/action.yml +++ b/.github/actions/deploy-klt-on-cluster/action.yml @@ -61,7 +61,7 @@ runs: run: | echo "Installing KLT using manifests" sed -i 's/imagePullPolicy: Always/imagePullPolicy: Never/g' ~/download/artifacts/lifecycle-operator-manifest-test/release.yaml - sed -i 's/ghcr.keptn.sh\/keptn\/functions-runtime:.*/localhost:5000\/keptn\/functions-runtime:${{ inputs.functions_runtime_tag }}/g' \ + sed -i 's/ghcr.io\/keptn\/functions-runtime:.*/localhost:5000\/keptn\/functions-runtime:${{ inputs.functions_runtime_tag }}/g' \ ~/download/artifacts/lifecycle-operator-manifest-test/release.yaml kubectl create namespace keptn-lifecycle-toolkit-system kubectl apply -f ~/download/artifacts/lifecycle-operator-manifest-test diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 78ebebd3f4..806961b7ec 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -238,7 +238,7 @@ jobs: - name: Generate helm charts env: - RELEASE_REGISTRY: ghcr.keptn.sh/keptn + RELEASE_REGISTRY: ghcr.io/keptn run: make helm-package - name: Copy charts from klt to helm repo diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index adc13e7b71..a85b6920fb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -162,7 +162,7 @@ jobs: - name: Create manifests env: - RELEASE_REGISTRY: ghcr.keptn.sh/keptn + RELEASE_REGISTRY: ghcr.io/keptn CHART_APPVERSION: ${{ needs.release-please.outputs.tag_name }} run: | cd scheduler diff --git a/.github/workflows/validate-helm-chart.yml b/.github/workflows/validate-helm-chart.yml index e0e69dca62..2edce0b758 100644 --- a/.github/workflows/validate-helm-chart.yml +++ b/.github/workflows/validate-helm-chart.yml @@ -26,7 +26,7 @@ jobs: - name: Generate helm charts env: - RELEASE_REGISTRY: ghcr.keptn.sh/keptn + RELEASE_REGISTRY: ghcr.io/keptn run: make helm-package - name: Install readme generator @@ -71,7 +71,7 @@ jobs: - name: Generate helm charts env: - RELEASE_REGISTRY: ghcr.keptn.sh/keptn + RELEASE_REGISTRY: ghcr.io/keptn run: make helm-package - name: Compare YAML file changes @@ -81,7 +81,7 @@ jobs: echo "There are no changes in the manifests" else echo "" - echo "Helm charts were not re-generated. Please regenerate them using make helm-package RELEASE_REGISTRY=ghcr.keptn.sh/keptn" + echo "Helm charts were not re-generated. Please regenerate them using make helm-package RELEASE_REGISTRY=ghcr.io/keptn" echo "" echo "=========== Diff ===========" git diff diff --git a/functions-runtime/README.md b/functions-runtime/README.md index 134c28b638..3d2076d1ef 100644 --- a/functions-runtime/README.md +++ b/functions-runtime/README.md @@ -68,7 +68,7 @@ docker run \ -e SCRIPT=https://raw.githubusercontent.com/keptn/lifecycle-toolkit/main/functions-runtime/samples/ts/prometheus.ts \ -e DATA='{ "url":"http://localhost:9090", "metrics": "up{service=\"kubernetes\"}", "expected_value": "1" }' \ -it \ - ghcr.keptn.sh/keptn/functions-runtime:${VERSION} + ghcr.io/keptn/functions-runtime:${VERSION} ``` diff --git a/helm/chart/README.md b/helm/chart/README.md index 5da5d16f37..20901aa590 100644 --- a/helm/chart/README.md +++ b/helm/chart/README.md @@ -8,23 +8,23 @@ checks ### Keptn Scheduler -| Name | Description | Value | -| -------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------- | -| `scheduler.scheduler.containerSecurityContext` | Sets security context | | -| `scheduler.scheduler.env.otelCollectorUrl` | sets url for open telemetry collector | `otel-collector:4317` | -| `scheduler.scheduler.image.repository` | set image repository for scheduler | `ghcr.keptn.sh/keptn/scheduler` | -| `scheduler.scheduler.image.tag` | set image tag for scheduler | `v0.7.1` | -| `scheduler.scheduler.imagePullPolicy` | set image pull policy for scheduler | `Always` | -| `scheduler.scheduler.livenessProbe` | customizable liveness probe for the scheduler | | -| `scheduler.scheduler.readinessProbe` | customizable readiness probe for the scheduler | | -| `scheduler.scheduler.resources` | sets cpu and memory resurces/limits for scheduler | | -| `schedulerConfig.schedulerConfigYaml.leaderElection.leaderElect` | enables leader election for multiple replicas of the scheduler | `false` | -| `schedulerConfig.schedulerConfigYaml.profiles[0].plugins.permit.enabled[0].name` | enables permit plugin | `KLCPermit` | -| `schedulerConfig.schedulerConfigYaml.profiles[0].schedulerName` | changes scheduler name | `keptn-scheduler` | -| `scheduler.nodeSelector` | adds node selectors for scheduler | `{}` | -| `scheduler.replicas` | modifies replicas | `1` | -| `scheduler.tolerations` | adds tolerations for scheduler | `[]` | -| `scheduler.topologySpreadConstraints` | add topology constraints for scheduler | `[]` | +| Name | Description | Value | +| -------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------- | +| `scheduler.scheduler.containerSecurityContext` | Sets security context | | +| `scheduler.scheduler.env.otelCollectorUrl` | sets url for open telemetry collector | `otel-collector:4317` | +| `scheduler.scheduler.image.repository` | set image repository for scheduler | `ghcr.io/keptn/scheduler` | +| `scheduler.scheduler.image.tag` | set image tag for scheduler | `v0.7.1` | +| `scheduler.scheduler.imagePullPolicy` | set image pull policy for scheduler | `Always` | +| `scheduler.scheduler.livenessProbe` | customizable liveness probe for the scheduler | | +| `scheduler.scheduler.readinessProbe` | customizable readiness probe for the scheduler | | +| `scheduler.scheduler.resources` | sets cpu and memory resurces/limits for scheduler | | +| `schedulerConfig.schedulerConfigYaml.leaderElection.leaderElect` | enables leader election for multiple replicas of the scheduler | `false` | +| `schedulerConfig.schedulerConfigYaml.profiles[0].plugins.permit.enabled[0].name` | enables permit plugin | `KLCPermit` | +| `schedulerConfig.schedulerConfigYaml.profiles[0].schedulerName` | changes scheduler name | `keptn-scheduler` | +| `scheduler.nodeSelector` | adds node selectors for scheduler | `{}` | +| `scheduler.replicas` | modifies replicas | `1` | +| `scheduler.tolerations` | adds tolerations for scheduler | `[]` | +| `scheduler.topologySpreadConstraints` | add topology constraints for scheduler | `[]` | ### Keptn Certificate Operator common @@ -42,17 +42,17 @@ checks ### Keptn Certificate Operator controller -| Name | Description | Value | -| ------------------------------------------------------ | ------------------------------------------------------------------------- | ------------------------------------------ | -| `certificateOperator.manager.containerSecurityContext` | Sets security context for the cert manager | | -| `certificateOperator.manager.image.repository` | specify repo for manager image | `ghcr.keptn.sh/keptn/certificate-operator` | -| `certificateOperator.manager.image.tag` | select tag for manager container | `v0.7.1` | -| `certificateOperator.manager.imagePullPolicy` | select image pull policy for manager container | `Always` | -| `certificateOperator.manager.env.labelSelectorKey` | specify the label selector to find resources to generate certificates for | `keptn.sh/inject-cert` | -| `certificateOperator.manager.env.labelSelectorValue` | specify the value for the label selector | `true` | -| `certificateOperator.manager.livenessProbe` | custom RBAC proxy liveness probe | | -| `certificateOperator.manager.readinessProbe` | custom manager readiness probe | | -| `certificateOperator.manager.resources` | custom limits and requests for manager container | | +| Name | Description | Value | +| ------------------------------------------------------ | ------------------------------------------------------------------------- | ------------------------------------ | +| `certificateOperator.manager.containerSecurityContext` | Sets security context for the cert manager | | +| `certificateOperator.manager.image.repository` | specify repo for manager image | `ghcr.io/keptn/certificate-operator` | +| `certificateOperator.manager.image.tag` | select tag for manager container | `v0.7.1` | +| `certificateOperator.manager.imagePullPolicy` | select image pull policy for manager container | `Always` | +| `certificateOperator.manager.env.labelSelectorKey` | specify the label selector to find resources to generate certificates for | `keptn.sh/inject-cert` | +| `certificateOperator.manager.env.labelSelectorValue` | specify the value for the label selector | `true` | +| `certificateOperator.manager.livenessProbe` | custom RBAC proxy liveness probe | | +| `certificateOperator.manager.readinessProbe` | custom manager readiness probe | | +| `certificateOperator.manager.resources` | custom limits and requests for manager container | | ### Keptn Lifecycle Operator common @@ -71,33 +71,33 @@ checks ### Keptn Lifecycle Operator controller -| Name | Description | Value | -| ----------------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------- | -| `lifecycleOperator.manager.containerSecurityContext` | Sets security context privileges | | -| `lifecycleOperator.manager.containerSecurityContext.allowPrivilegeEscalation` | | `false` | -| `lifecycleOperator.manager.containerSecurityContext.capabilities.drop` | | `["ALL"]` | -| `lifecycleOperator.manager.containerSecurityContext.privileged` | | `false` | -| `lifecycleOperator.manager.containerSecurityContext.runAsGroup` | | `65532` | -| `lifecycleOperator.manager.containerSecurityContext.runAsNonRoot` | | `true` | -| `lifecycleOperator.manager.containerSecurityContext.runAsUser` | | `65532` | -| `lifecycleOperator.manager.containerSecurityContext.seccompProfile.type` | | `RuntimeDefault` | -| `lifecycleOperator.manager.env.keptnAppControllerLogLevel` | sets the log level of Keptn App Controller | `0` | -| `lifecycleOperator.manager.env.keptnAppCreationRequestControllerLogLevel` | sets the log level of Keptn App Creation Request Controller | `0` | -| `lifecycleOperator.manager.env.keptnAppVersionControllerLogLevel` | sets the log level of Keptn AppVersion Controller | `0` | -| `lifecycleOperator.manager.env.keptnEvaluationControllerLogLevel` | sets the log level of Keptn Evaluation Controller | `0` | -| `lifecycleOperator.manager.env.keptnTaskControllerLogLevel` | sets the log level of Keptn Task Controller | `0` | -| `lifecycleOperator.manager.env.keptnTaskDefinitionControllerLogLevel` | sets the log level of Keptn TaskDefinition Controller | `0` | -| `lifecycleOperator.manager.env.keptnWorkloadControllerLogLevel` | sets the log level of Keptn Workload Controller | `0` | -| `lifecycleOperator.manager.env.keptnWorkloadInstanceControllerLogLevel` | sets the log level of Keptn WorkloadInstance Controller | `0` | -| `lifecycleOperator.manager.env.optionsControllerLogLevel` | sets the log level of Keptn Options Controller | `0` | -| `lifecycleOperator.manager.env.otelCollectorUrl` | Sets the URL for the open telemetry collector | `otel-collector:4317` | -| `lifecycleOperator.manager.env.functionRunnerImage` | specify image for task runtime | `ghcr.keptn.sh/keptn/functions-runtime:v0.7.1` | -| `lifecycleOperator.manager.image.repository` | specify registry for manager image | `ghcr.keptn.sh/keptn/lifecycle-operator` | -| `lifecycleOperator.manager.image.tag` | select tag for manager image | `v0.7.1` | -| `lifecycleOperator.manager.imagePullPolicy` | specify pull policy for manager image | `Always` | -| `lifecycleOperator.manager.livenessProbe` | custom livenessprobe for manager container | | -| `lifecycleOperator.manager.readinessProbe` | custom readinessprobe for manager container | | -| `lifecycleOperator.manager.resources` | specify limits and requests for manager container | | +| Name | Description | Value | +| ----------------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------- | +| `lifecycleOperator.manager.containerSecurityContext` | Sets security context privileges | | +| `lifecycleOperator.manager.containerSecurityContext.allowPrivilegeEscalation` | | `false` | +| `lifecycleOperator.manager.containerSecurityContext.capabilities.drop` | | `["ALL"]` | +| `lifecycleOperator.manager.containerSecurityContext.privileged` | | `false` | +| `lifecycleOperator.manager.containerSecurityContext.runAsGroup` | | `65532` | +| `lifecycleOperator.manager.containerSecurityContext.runAsNonRoot` | | `true` | +| `lifecycleOperator.manager.containerSecurityContext.runAsUser` | | `65532` | +| `lifecycleOperator.manager.containerSecurityContext.seccompProfile.type` | | `RuntimeDefault` | +| `lifecycleOperator.manager.env.keptnAppControllerLogLevel` | sets the log level of Keptn App Controller | `0` | +| `lifecycleOperator.manager.env.keptnAppCreationRequestControllerLogLevel` | sets the log level of Keptn App Creation Request Controller | `0` | +| `lifecycleOperator.manager.env.keptnAppVersionControllerLogLevel` | sets the log level of Keptn AppVersion Controller | `0` | +| `lifecycleOperator.manager.env.keptnEvaluationControllerLogLevel` | sets the log level of Keptn Evaluation Controller | `0` | +| `lifecycleOperator.manager.env.keptnTaskControllerLogLevel` | sets the log level of Keptn Task Controller | `0` | +| `lifecycleOperator.manager.env.keptnTaskDefinitionControllerLogLevel` | sets the log level of Keptn TaskDefinition Controller | `0` | +| `lifecycleOperator.manager.env.keptnWorkloadControllerLogLevel` | sets the log level of Keptn Workload Controller | `0` | +| `lifecycleOperator.manager.env.keptnWorkloadInstanceControllerLogLevel` | sets the log level of Keptn WorkloadInstance Controller | `0` | +| `lifecycleOperator.manager.env.optionsControllerLogLevel` | sets the log level of Keptn Options Controller | `0` | +| `lifecycleOperator.manager.env.otelCollectorUrl` | Sets the URL for the open telemetry collector | `otel-collector:4317` | +| `lifecycleOperator.manager.env.functionRunnerImage` | specify image for task runtime | `ghcr.io/keptn/functions-runtime:v0.7.1` | +| `lifecycleOperator.manager.image.repository` | specify registry for manager image | `ghcr.io/keptn/lifecycle-operator` | +| `lifecycleOperator.manager.image.tag` | select tag for manager image | `v0.7.1` | +| `lifecycleOperator.manager.imagePullPolicy` | specify pull policy for manager image | `Always` | +| `lifecycleOperator.manager.livenessProbe` | custom livenessprobe for manager container | | +| `lifecycleOperator.manager.readinessProbe` | custom readinessprobe for manager container | | +| `lifecycleOperator.manager.resources` | specify limits and requests for manager container | | ### Keptn Metrics Operator common @@ -135,23 +135,23 @@ checks ### Keptn Metrics Operator controller -| Name | Description | Value | -| --------------------------------------------------------------------------- | ------------------------------------------------------------- | -------------------------------------- | -| `metricsOperator.manager.containerSecurityContext` | Sets security context privileges | | -| `metricsOperator.manager.containerSecurityContext.allowPrivilegeEscalation` | | `false` | -| `metricsOperator.manager.containerSecurityContext.capabilities.drop` | | `["ALL"]` | -| `metricsOperator.manager.containerSecurityContext.privileged` | | `false` | -| `metricsOperator.manager.containerSecurityContext.runAsGroup` | | `65532` | -| `metricsOperator.manager.containerSecurityContext.runAsNonRoot` | | `true` | -| `metricsOperator.manager.containerSecurityContext.runAsUser` | | `65532` | -| `metricsOperator.manager.containerSecurityContext.seccompProfile.type` | | `RuntimeDefault` | -| `metricsOperator.manager.image.repository` | specify registry for manager image | `ghcr.keptn.sh/keptn/metrics-operator` | -| `metricsOperator.manager.image.tag` | select tag for manager image | `v0.7.1` | -| `metricsOperator.manager.env.exposeKeptnMetrics` | enable metrics exporter | `true` | -| `metricsOperator.manager.env.metricsControllerLogLevel` | sets the log level of Metrics Controller | `0` | -| `metricsOperator.manager.livenessProbe` | custom livenessprobe for manager container | | -| `metricsOperator.manager.readinessProbe` | custom readinessprobe for manager container | | -| `metricsOperator.manager.resources` | specify limits and requests for manager container | | +| Name | Description | Value | +| --------------------------------------------------------------------------- | ------------------------------------------------------------- | -------------------------------- | +| `metricsOperator.manager.containerSecurityContext` | Sets security context privileges | | +| `metricsOperator.manager.containerSecurityContext.allowPrivilegeEscalation` | | `false` | +| `metricsOperator.manager.containerSecurityContext.capabilities.drop` | | `["ALL"]` | +| `metricsOperator.manager.containerSecurityContext.privileged` | | `false` | +| `metricsOperator.manager.containerSecurityContext.runAsGroup` | | `65532` | +| `metricsOperator.manager.containerSecurityContext.runAsNonRoot` | | `true` | +| `metricsOperator.manager.containerSecurityContext.runAsUser` | | `65532` | +| `metricsOperator.manager.containerSecurityContext.seccompProfile.type` | | `RuntimeDefault` | +| `metricsOperator.manager.image.repository` | specify registry for manager image | `ghcr.io/keptn/metrics-operator` | +| `metricsOperator.manager.image.tag` | select tag for manager image | `v0.7.1` | +| `metricsOperator.manager.env.exposeKeptnMetrics` | enable metrics exporter | `true` | +| `metricsOperator.manager.env.metricsControllerLogLevel` | sets the log level of Metrics Controller | `0` | +| `metricsOperator.manager.livenessProbe` | custom livenessprobe for manager container | | +| `metricsOperator.manager.readinessProbe` | custom readinessprobe for manager container | | +| `metricsOperator.manager.resources` | specify limits and requests for manager container | | ### Global diff --git a/helm/chart/values.yaml b/helm/chart/values.yaml index 3249d7393d..0d3daeb98c 100644 --- a/helm/chart/values.yaml +++ b/helm/chart/values.yaml @@ -14,7 +14,7 @@ certificateOperator: labelSelectorKey: keptn.sh/inject-cert labelSelectorValue: "true" image: - repository: ghcr.keptn.sh/keptn/certificate-operator + repository: ghcr.io/keptn/certificate-operator tag: v0.7.1 imagePullPolicy: Always livenessProbe: @@ -67,7 +67,7 @@ lifecycleOperator: seccompProfile: type: RuntimeDefault env: - functionRunnerImage: ghcr.keptn.sh/keptn/functions-runtime:v0.7.1 + functionRunnerImage: ghcr.io/keptn/functions-runtime:v0.7.1 keptnAppControllerLogLevel: "0" keptnAppCreationRequestControllerLogLevel: "0" keptnAppVersionControllerLogLevel: "0" @@ -79,7 +79,7 @@ lifecycleOperator: optionsControllerLogLevel: "0" otelCollectorUrl: otel-collector:4317 image: - repository: ghcr.keptn.sh/keptn/lifecycle-operator + repository: ghcr.io/keptn/lifecycle-operator tag: v0.7.1 imagePullPolicy: Always livenessProbe: @@ -146,7 +146,7 @@ metricsOperator: exposeKeptnMetrics: "true" metricsControllerLogLevel: "0" image: - repository: ghcr.keptn.sh/keptn/metrics-operator + repository: ghcr.io/keptn/metrics-operator tag: v0.7.1 livenessProbe: httpGet: @@ -209,7 +209,7 @@ scheduler: env: otelCollectorUrl: otel-collector:4317 image: - repository: ghcr.keptn.sh/keptn/scheduler + repository: ghcr.io/keptn/scheduler tag: v0.7.1 imagePullPolicy: Always livenessProbe: diff --git a/operator/config/manager/manager.yaml b/operator/config/manager/manager.yaml index 1c6b9ecd41..1bd9d121e6 100644 --- a/operator/config/manager/manager.yaml +++ b/operator/config/manager/manager.yaml @@ -66,7 +66,7 @@ spec: fieldRef: fieldPath: metadata.name - name: FUNCTION_RUNNER_IMAGE - value: ghcr.keptn.sh/keptn/functions-runtime:v0.7.1 # x-release-please-version + value: ghcr.io/keptn/functions-runtime:v0.7.1 # x-release-please-version - name: OTEL_COLLECTOR_URL value: otel-collector:4317 - name: KEPTN_APP_CONTROLLER_LOG_LEVEL diff --git a/renovate.json b/renovate.json index 25af678023..94473248ed 100644 --- a/renovate.json +++ b/renovate.json @@ -23,11 +23,11 @@ "**/tests/**" ], "ignoreDeps": [ - "ghcr.keptn.sh/keptn/lifecycle-operator", - "ghcr.keptn.sh/keptn/scheduler", - "ghcr.keptn.sh/keptn/functions-runtime", - "ghcr.keptn.sh/keptn/certificate-operator", - "ghcr.keptn.sh/keptn/metrics-operator" + "ghcr.io/keptn/lifecycle-operator", + "ghcr.io/keptn/scheduler", + "ghcr.io/keptn/functions-runtime", + "ghcr.io/keptn/certificate-operator", + "ghcr.io/keptn/metrics-operator" ], "packageRules": [ { From 30e664703c3b42aa5c2049535d528f69cbcfe4b4 Mon Sep 17 00:00:00 2001 From: Meg McRoberts Date: Thu, 25 May 2023 00:36:18 -0700 Subject: [PATCH 50/62] docs: final polish of getting started guides (#1449) Signed-off-by: Meg McRoberts Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> Co-authored-by: Florian Bacher Co-authored-by: Giovanni Liva --- .../content/en/docs/getting-started/_index.md | 50 ++-- .../en/docs/getting-started/metrics/_index.md | 57 +++-- .../getting-started/observability/_index.md | 236 ++++++------------ .../getting-started/orchestrate/_index.md | 173 ++++++------- .../en/docs/implementing/integrate/_index.md | 79 +++--- docs/content/en/docs/implementing/otel.md | 33 ++- docs/content/en/docs/install/_index.md | 37 ++- docs/content/en/docs/install/install.md | 20 +- docs/content/en/docs/install/k8s.md | 2 +- 9 files changed, 337 insertions(+), 350 deletions(-) diff --git a/docs/content/en/docs/getting-started/_index.md b/docs/content/en/docs/getting-started/_index.md index 2f6b5ad331..f6ebc7704c 100644 --- a/docs/content/en/docs/getting-started/_index.md +++ b/docs/content/en/docs/getting-started/_index.md @@ -5,25 +5,47 @@ weight: 15 hidechildren: false # this flag hides all sub-pages in the sidebar-multicard.html --- -> **Note** -This section is under development. -We welcome your input!** - This section provides excercises to introduce you to the Keptn Lifecycle Toolkit. -Choose the exercise that interest you the most. +Choose the exercise that interests you the most. + +## Introducing the Keptn Lifecycle Toolkit -The [Introducing Keptn Lifecycle Toolkit](https://youtu.be/449HAFYkUlY) -video discusses three common use cases for the Lifecycle Toolkit +This exercise introduces three common use cases for the Lifecycle Toolkit and walks you through the implementation process for each. -The following exercises provide more detailed instructions -for implementing these use cases: +We recommend that you do all three exercises in order +but you can do any of them independently. * [Getting started with Keptn metrics](metrics) * [Standardize access to observability data](observability) -* [Orchestrate deployment checks](orchestrate) +* [Manage the release cycle](orchestrate) + +These exercises are based on the +[simplenode-dev](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd) +example. +You can clone that repo to access it locally +or just look at it for examples +as you implement the functionality "from scratch" +on your local Kubernetes cluster. +The +[README](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/setup/observability/README.md) +file for that repo contains additional information. + +You can run these exercises on an existing Kubernetes cluster +or you can create a new cluster. +For personal study and demonstrations, +these exercises run well on a local Kubernetes cluster. +See [Create Local Kubernetes Cluster](../install/k8s.md/#create-local-kubernetes-cluster). + +Two videos are available to introduce these exercises: + +* [Introducing Keptn Lifecycle Toolkit](https://youtu.be/449HAFYkUlY) +* [Use SLOs and get DORA the Native K8s way!](https://www.youtube.com/watch?v=zeEC0475SOU) + +## Getting started with Lifecycle Toolkit -If you prefer, -[Getting started with Lifecycle Toolkit](generic-gs) -provides an exercise that introduces you to -all the things KLT can do. +The +[KLT End-to-end exercise](generic-gs) +exercise provides a more detailed exercise +for managing the deployment lifecycle +with pre- and post-deployment evaluations and tasks. diff --git a/docs/content/en/docs/getting-started/metrics/_index.md b/docs/content/en/docs/getting-started/metrics/_index.md index 22b7388d6b..edeac97ea5 100644 --- a/docs/content/en/docs/getting-started/metrics/_index.md +++ b/docs/content/en/docs/getting-started/metrics/_index.md @@ -1,21 +1,22 @@ --- -title: Getting started with Keptn metrics -description: Learn how Keptn metrics enhances your deployment +title: Custom Keptn metrics +description: Enhance your deployment with custom Keptn metrics weight: 25 --- -The Keptn metrics component of the Keptn Lifecycle Toolkit -allow you to define any type of metric +The Custom Keptn metrics component of the Keptn Lifecycle Toolkit +allows you to define any type of metric from multiple instances of any type of data source in your Kubernetes cluster. You may have deployment tools like Argo, Flux, KEDA, HPA, or Keptn that need observability data to make automated decisions -such as whether a rollout is good, whether to scale up or down. +such as whether a rollout is good, or whether to scale up or down. + Your observability data may come from multiple observability solutions -- Prometheus, Dynatrace, Datadog and others -- -or data directly from your cloud provider such as AWS, Google, or Azure. - +or may be data that comes directly +from your cloud provider such as AWS, Google, or Azure. The Keptn Metrics Server unifies and standardizes access to all this data. Minimal configuration is required because the Keptn Lifecycle Toolkit hooks directly into Kubernetes primitives. @@ -27,7 +28,9 @@ from Argo Rollouts, Flux, KEDA, and HPA. Each has plugins but it is difficult to maintain them, especially if you are using multiple tools, and multible observability platforms, -and multiple instance of some tools or observability platforms. +and multiple instances of some tools or observability platforms. +The Custom Keptn metrics feature unites all these metrics +integrates metrics from all these sources into a single set of metrics. ## Using this exercise @@ -39,10 +42,26 @@ or just look at it for examples as you implement the functionality "from scratch" on your local Kubernetes deployment cluster. +This is the first of three exercises in the +[Introducing the Keptn Lifecycle Toolkit](../#introducing-the-keptn-lifecycle-toolkit) +series. +After completing this exercise, +you may want to do the other exercises: + +- In [Standardize observability](../observability), + you learn how to standardize access + to the observability data for your cluster. +- In + [Manage release lifecycle](../orchestrate), + you learn how to implement + pre- and post-deployment tasks and evaluations + to orchestrate the flow of all the `workloads` + that are part of your `application`. + The steps to implement metrics in an existing cluster are: 1. [Install the Keptn Lifecycle Toolkit](../../install/install.md) -1. Configure metrics to use +1. Configure the metrics you want to use: - [Define metrics providers](#define-metrics-providers) - [Define KeptnMetric information](#define-keptnmetric-information) - [View available metrics](#view-available-metrics) @@ -50,10 +69,6 @@ The steps to implement metrics in an existing cluster are: If you want to create your own cluster to run this exercise, follow the instructions in [Installation](../../install). -See the -[Introducing Keptn Lifecycle Toolkit](https://youtu.be/449HAFYkUlY) -video for a demonstration of this exercise. - ## Define metrics to use You need to define the external observability platforms @@ -79,11 +94,14 @@ You can specify a virtually unlimited number of providers, including multiple instances of each observability platform. Each one must be assigned a unique name, identified by the type of platform it is -and the URL. +and the URL of the target server. +If the target server is protected by a `secret`, +provide information about the token and key. > Note: The video and example application use an older syntax of the `KeptnMetricsProvider` and `KeptnMetric` resources. - The syntax shown in this document is correct for v0.7.1 and later. + The syntax shown in this document and the reference page + is correct for v0.7.1 and later. Definition of [dev-prometheus](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-prometheus-provider.yaml) @@ -170,10 +188,11 @@ Note the following: then apply all of them. - Each metric is assigned a unique `name`. - The value of the `spec.provider.name` field - must correspond to the name assigned in a + must correspond to the name assigned in the `metadata.name` field of a `KeptnMetricsProvider` resource. - Information is fetched in on a continuous basis -at a rate specified by the value of the `spec.fetchIntervalSeconds` field. + at a rate specified + by the value of the `spec.fetchIntervalSeconds` field. ### View available metrics @@ -251,13 +270,13 @@ $ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta2/namespaces/simplenode-d } ``` -You can also display the graphics using a dashboard such as Grafana. +You can also display the metrics graphically using a dashboard such as Grafana. ## Implementing autoscaling with HPA The Kubernetes HorizontalPodAutoscaler (HPA) uses metrics to provide autoscaling for the cluster. -HPA can retrieve KeptnMetrics and use it to implement HPA. +HPA can retrieve KeptnMetrics and use those metrics to implement HPA. See Using the [HorizontalPodAutoscaler](../../implementing/evaluatemetrics.md/#using-the-horizontalpodautoscaler) for detailed information. diff --git a/docs/content/en/docs/getting-started/observability/_index.md b/docs/content/en/docs/getting-started/observability/_index.md index cccb7c8fbe..274dcb2d88 100644 --- a/docs/content/en/docs/getting-started/observability/_index.md +++ b/docs/content/en/docs/getting-started/observability/_index.md @@ -1,37 +1,39 @@ --- -title: Standardize access to observability data -description: Learn how the Keptn Lifecycle Toolkit provides observability for Kubernetes deployments +title: Standardize observability +description: How the KLT standardizes access to observability data for Kubernetes deployments weight: 45 --- The Keptn Lifecycle Toolkit (KLT) makes any Kubernetes deployment observable. -You can readily see why a deployment takes so long or why it fails, -even when using multiple deployment tools. -Keptn introduces a concept of an application -which is an abstraction that connects multiple -Workloads belonging together. -In other words, KLT, creates a distributed end-to-end trace +In other words, it creates a distributed, end-to-end trace of everything Kubernetes does in the context of a Deployment. +It provides this information +for all applications running in your cluster, +and includes information about +everything Kubernetes does in the context of a deployment. +To do this, +Keptn introduces the concept of an `application`, +which is an abstraction that connects multiple +Workloads that logically belong together, +even if they use different deployment strategies. + +This means that: + +- You can readily see why a deployment takes so long + or why it fails, even when using multiple deployment strategies. +- KLT can capture DORA metrics and expose them as OpenTelemetry metrics The observability data is an amalgamation of the following: - DORA metrics are collected out of the box when the Lifecycle Toolkit is enabled -- OpenTelemetry runs traces that show everything that happens in the Kubernetes cluster - and can display this information with dashboard tools - such as Grafana. -- Specific metrics that you can define to monitor - information from all the data providers configured in your cluster. +- OpenTelemetry runs traces that show + everything that happens in the Kubernetes cluster +- Custom Keptn metrics that you can use to monitor + information from all the data providers configured in your cluster -The Keptn Lifecycle Toolkit can provide this information -for all applications running in your cluster, -even if they are using different deployment tools. -And it can capture metrics from multiple data sources -using multiple data platforms. -With KLT deployed on your cluster, -you can easily monitor what is happening during a deployment into your Kuberenetes cluster, -and quickly get data to help you understand issues such as -why a deployment took so long or why it failed. +All this information can be displayed with dashboard tools +such as Grafana. ## Using this exercise @@ -40,145 +42,50 @@ to the observability data for your cluster. It is based on the [simplenode-dev](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd) example. -You can clone that repo to access it locally -or just look at it for examples -as you implement the functionality "from scratch" -on your local Kubernetes deployment cluster. -The -[README](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/setup/observability/README.md) -file for that repo contains useful information. -Two videos are available -to walk you through this exercise if you prefer: - -- [Introducing Keptn Lifecycle Toolkit](https://youtu.be/449HAFYkUlY) -- [Use SLOs and get DORA the Native K8s way!](https://www.youtube.com/watch?v=zeEC0475SOU) - -In the -[Getting started with Keptn metrics](../metrics) -exercise, you learn how to define and use Keptn metrics. -You may want to complete that exercise before doing this exercise -although that is not required. +This is the second of three exercises in the +[Introducing the Keptn Lifecycle Toolkit](../#introducing-the-keptn-lifecycle-toolkit) +series: + +- In the + [Getting started with Keptn metrics](../metrics) + exercise, you learn how to define and use Keptn metrics. + You may want to complete that exercise before doing this exercise + although that is not required. +- In + [Manage release lifecycle](../orchestrate), + you learn how to implement + pre- and post-deployment tasks and evaluations + to orchestrate the flow of all the `workloads` + that are part of your `application`. This exercise shows how to standardize access to the observability data for your cluster. -The steps are: -1. [Install and enable]( #install-and-enable-klt) - the Lifecycle Toolkit on your cluster -1. [Integrate the Lifecycle Toolkit with your applications](#integrate-the-lifecycle-toolkit-with-your-applications) -1. [DORA metrics](#dora-metrics) -1. [Using OpenTelemetry](#using-opentelemetry) -1. [Keptn metrics](#keptn-metrics) -1. [View the results](#view-the-results) +If you are installing the Keptn Lifecycle Toolkit on an existing cluster +or on a local cluster you are creating for this exercise, +you need to do the following: -## Install and enable KLT - -To install and enable the Keptn Lifecycle Toolkit on your cluster: - -1. Be sure that your cluster includes the components discussed in - [Prepare your cluster for KLT](../../install/k8s.md/#prepare-your-cluster-for-klt) 1. Follow the instructions in - [Install the Keptn Lifecycle Toolkit](../../install/install.md/#use-helm-chart) - to install KLT on your cluster using the Helm chart - - If you installed KLT on your cluster for the - [Getting started with Keptn metrics](../metrics) - exercise, you do not need to re-install it for this exercise. - However, if you only installed the `metrics-operator` for that exercise, - you now need to install the full KLT. - + [Install and update](../../install) + to install and enable KLT on your cluster. 1. Follow the instructions in - [Enable KLT for your cluster](../../install/install.md/#enable-klt-for-your-cluster) - to enable KLT on your cluster - by annotating the `Namespace` resource.. - See the - [simplenode-dev-ns.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-ns.yaml) - file for an example - -1. Run the following command to ensure that your Kuberetes cluster - is ready to complete this exercise: - - ```shell - kubectl get pods -n keptn-lifecycle-toolkit-system - ``` - - You should see pods for the following components: - - certificate-operator (or another cert manager) - - lifecycle-operator - - scheduler - - metrics-operator - -## Integrate the Lifecycle Toolkit with your applications - -The Keptn Lifecycle Toolkit sits in the scheduler -so it can trace all activities of all deployment workloads on the cluster, -no matter what tool is used for the deployment. -This same mechanism allows KLT to inject pre- and post-deployment checks -into all deployment workloads; -we discuss this in another exercise. - -KLT uses metadata to identify the workloads of interest. -To integrate KLT with your applications, -you need to populate the metadata it needs. -This requires the following steps: - -- Define a Keptn application -- Annotate the `Deployment` resource to recognize your Keptn application - -### Define the Keptn application - -A Keptn application defines the workloads -to be included in your Keptn Application. -We will use the application discovery feature -to automatically generate a Keptn Application -that includes all workloads on the cluster, -regardless of the tools being used. - -A Keptn application aggregates multiple workloads -that belong to a logical app into a single -[KeptnApp](../../yaml-crd-ref/app.md) -resource. - -You can view a sample of this file in the -[keptn-app.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-app.yaml.tmp) -file. -You see the metadata that names this `KeptnApp` -and identifies the namespace where it lives: - -```yaml -metadata: - name: simpleapp - namespace: simplenode-dev -``` - -You can also see the `spec.workloads` list. -In this simple example, -we only have one workload defined -but most production apps will have multiple workloads defined. - -You can create the YAML file to define the resource manually -but the easier approach is to let KLT create this definition for you. -This requires that you annotate all your workloads -(`Deployments`, `Pods`, `StatefulSets`, `DaemonSets`, and `ReplicaSets` -as described in -[Use Keptn automatic app discovery](../../implementing/integrate/#use-keptn-automatic-app-discovery). - -### Annotate your Deployment resource - -Follow the instructions in -[Annotate workload](../../implementing/integrate/#basic-annotations) -to apply basic annotations to your `Deployment` resource. - -The -[simplenode-dev-deployment.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-deployment.yaml/) -file defines the `Deployment` resource for our example. -You see that the `metadata` specifies the same -`name` and `namespace` values defined in the `KeptnApp` resource. - -The example file also includes annotations for -pre- and post-deployment activities. -We will discuss those in a separate exercise. + [Integrate KLT with your applications](../../implementing/integrate) + to integrate KLT with your Kubernetes cluster. + This requires the following: + + - Follow the instructions in + [Annotate workload](../../implementing/integrate/#basic-annotations) + to integrate the Lifecycle Toolkit into your Kubernetes cluster + by applying basic annotations to your `Deployment` resource. + - Follow the instructions in + [Define a Keptn application](../../implementing/integrate/#define-a-keptn-application) + to create a Keptn application that aggragates + all the `workloads` for your deployment into a single + [KeptnApp](../../yaml-crd-ref/app.md) resource. + For this exercise, we recommend that you use + [Keptn automatic app discovery](../../implementing/integrate/#use-keptn-automatic-app-discovery) + to automatically generate a Keptn Application. ## DORA metrics @@ -236,16 +143,11 @@ which allows you to trace everything done in the context of that deployment. - Follow the instructions in [OpenTelemetry observability](../../implementing/otel.md) to configure where your OpenTelemetry data is sent. - - Define a [KeptnConfig](../../yaml-crd-ref/config.md) resource + This requires you to define a [KeptnConfig](../../yaml-crd-ref/config.md) resource that defines the URL and port of the OpenTelemetry collector. For our example, this is in the [keptnconfig.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/setup/keptn/keptnconfig.yaml) file. -- Set the `EXPOSE_KEPTN_METRICS` environment variable - in the `metrics-operator` - -TODO: How to set this env variable in `metrics-operator` - or where is it set in the example? ## Keptn metrics @@ -258,23 +160,25 @@ exercise discusses how to define Keptn metrics. ## View the results To start feeding observability data for your deployments -onto a dashboard of your choice, -modify either your `Deployment` or `KeptnApp` resource yaml file -to increment the version number -and commit that change to your repository. +onto a dashboard of your choice: + +1. Modify either your `Deployment` or `KeptnApp` resource yaml file + to increment the version number +1. Commit that change to your repository. + Note that, from the `KeptnApp` YAML file, you can either increment the version number of the application (which causes all workloads to be rerun and produce observability data) or you can increment the version number of a single workload, -(which causes just that workload to be rerun and produce data). +(which causes just that workload to be rerun and produce observability data). The videos that go with this exercise show how the DORA, OpenTelemetry, and Keptn metrics information appears on a Grafana dashboard with [Jaeger](https://grafana.com/docs/grafana-cloud/data-configuration/metrics/prometheus-config-examples/the-jaeger-authors-jaeger/). -If you also have Jaeger extension for Grafana installed on your cluster, -you can view full end-to-end trace for everything +If you also have the Jaeger extension for Grafana installed on your cluster, +you can view the full end-to-end trace for everything that happens in your deployment. For more information, see [Monitoring Jaeger](https://www.jaegertracing.io/docs/1.45/monitoring/). diff --git a/docs/content/en/docs/getting-started/orchestrate/_index.md b/docs/content/en/docs/getting-started/orchestrate/_index.md index 4549f1f085..c4ebafb1fa 100644 --- a/docs/content/en/docs/getting-started/orchestrate/_index.md +++ b/docs/content/en/docs/getting-started/orchestrate/_index.md @@ -1,6 +1,6 @@ --- -title: Orchestrating pre- and post-deployment tasks and evaluations -description: Learn how the Keptn Lifecycle Toolkit can orchestrate deployment checks. +title: Manage release lifecycle +description: How KLT orchestrates pre- and post-deployment evaluations and tasks weight: 55 --- @@ -18,17 +18,19 @@ the Lifecycle Toolkit can do the following: * Automatically validate against your SLO (Service Level Objectives) -KLT sits in the Kubernetes scheduler and can trace the deployment -from start to end. -KLT is also application aware, -so we can extend the deployment -with tasks and evaluations that -are run either before or after your whole application starts the deployment -or even at the individual workload level. -You can also validate any metric, -either pre- or post-deployment, -using the metrics from the Keptn Metrics Server introduced in -[Getting started with Keptn metrics](../metrics). +KLT sits on top of the Kubernetes scheduler +and can do the following: + +* Trace the deployment from start to end +* KLT is application aware, + so can extend the deployment with tasks and evaluations that + are run either before or after your whole application starts the deployment + or at the individual workload level. +* Validate any Keptn metric, + either pre- or post-deployment, + using the metrics from the Keptn Metrics Server introduced in + [Getting started with Keptn metrics](../metrics). + This means that you can be sure that the environment is healthy and has adequate resources before you begin the deployment. After the deployment succeeds, @@ -39,102 +41,58 @@ You can also check for new logs that came in from a log monitoring solution. ## Using this exercise -This exercise is based on the +This exercise shows how to implement +pre- and post-deployment evaluations and tasks +for your application. +It is based on the [simplenode-dev](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd) example. -You can clone that repo to access it locally -or just look at it for examples -as you implement the functionality "from scratch" -on your local Kubernetes deployment cluster. The steps to implement pre- and post-deployment orchestration are: -1. [Bring or create a Kubernetes cluster](../../install/k8s.md) -1. [Install the Keptn Lifecycle Toolkit and enable it](../../install/install.md) -1. [Integrate KLT with your cluster](../../implementing/integrate/) 1. [Define evaluations to be performed pre- and post-deployment](#define-evaluations-to-be-performed-pre--and-post-deployment) 1. [Define tasks to be performed pre- and post-deployment](#define-tasks-to-be-performed-pre--and-post-deployment) - -## Bring or create a Kubernetes deployment cluster - -You can run this exercise on an existing Kubernetes cluster -or you can create a new cluster. -For personal study and demonstrations, -this exercise runs well on a local Kubernetes cluster. -See [Bring or Install a Kubernetes Cluster](../../install/k8s.md). - -## Install KLT on your cluster - -Install the Keptn Lifecycle Toolkit on your cluster -by executing the following command sequence: - -```shell -helm repo add klt https://charts.lifecycle.keptn.sh -helm repo update -helm upgrade --install keptn klt/klt \ - -n keptn-lifecycle-toolkit-system --create-namespace --wait -``` - -See -[Install KLT](../../install/install.md) -for more information about installing the Lifecycle Toolkit. - -## Enable KLT for your cluster - -To enable KLT for your cluster, annotate the -[Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) -resource -In this example, this is defined in the -[simplenode-dev-ns.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-ns.yaml) -file, which looks like this: - -```yaml -apiVersion: v1 -kind: Namespace -metadata: - name: simplenode-dev - annotations: - keptn.sh/lifecycle-toolkit: "enabled" -``` - -You see the annotation line that enables `lifecycle-toolkit`. -This line tells the webhook to handle the namespace - -## Integrate KLT with your cluster - -To integrate KLT with your cluster, annotate the Kubernetes -[Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) -resource. -In this example, this is defined in the -[simplenode-dev-deployment.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-deployment.yaml) -file, which includes the following lines: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: simplenode - namespace: simplenode-dev -... -template: - metadata: - labels: - app: simplenode - app.kubernetes.io/name: simplenodeservice - annotations: - # keptn.sh/app: simpleapp - keptn.sh/workload: simplenode - keptn.sh/version: 1.0.2 - keptn.sh/pre-deployment-evaluations: evaluate-dependencies - keptn.sh/pre-deployment-tasks: notify - keptn.sh/post-deployment-evaluations: evaluate-deployment - keptn.sh/post-deployment-tasks: notify -... -``` - -For more information about using annotations and labels -to integrate KLT into your deployment cluster, see -[Integrate KLT with your applications](../../implementing/integrate/_index.md). +1. [Integrate evaluations and tasks into the cluster](#integrate-evaluations-and-tasks-into-the-cluster) + +This is the third of three exercises in the +[Introducing the Keptn Lifecycle Toolkit](../#introducing-the-keptn-lifecycle-toolkit) +series. +You may want to complete the other exercises before doing this exercise +although that is not required: + +* In the + [Getting started with Keptn metrics](../metrics) + exercise, you learn how to define and use Keptn metrics. +* In [Standardize observability](../observability), + you learn how to standardize access + to the observability data for your cluster. + +If you are installing the Keptn Lifecycle Toolkit on an existing cluster +or in a local cluster you are creating for this exercise +and did not previously set up your cluster for the +[Standardize observability](../observability) exercise, +you need to do the following: + +1. Follow the instructions in + [Install and update](../../install) + to install and enable KLT on your cluster. +1. Follow the instructions in + [Integrate KLT with your applications](../../implementing/integrate) + to integrate KLT with your Kubernetes cluster: + + * Follow the instructions in + [Annotate workload](../../implementing/integrate/#basic-annotations) + to integrate the Lifecycle Toolkit into your Kubernetes cluster + by applying basic annotations to your `Deployment` resource. + * Follow the instructions in + [Define a Keptn application](../../implementing/integrate/#define-a-keptn-application) + to create a + [KeptnApp](../../yaml-crd-ref/app.md) resource + that includes all workloads on the cluster, + regardless of the tools being used. + For this exercise, we recommend that you + [Use Keptn automatic app discovery](../../implementing/integrate/#use-keptn-automatic-app-discovery) + to automatically generate a Keptn Application. ## Define evaluations to be performed pre- and post-deployment @@ -214,3 +172,14 @@ for which application, for which version, for which Workload. Because the slack server that is required to execute this task is protected by a secret, the task definition also specifies that secret. + +## Integrate evaluations and tasks into the cluster + +Follow the instructions in +[Annotate workload](../../implementing/integrate/#pre--and-post-deployment-checks) +to integrate the evaluations and tasks you defined +into the cluster +by applying annotations to the `Deployment` resource. +See the +[simplenode-dev-deployment.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-deployment.yaml) +file for an example. diff --git a/docs/content/en/docs/implementing/integrate/_index.md b/docs/content/en/docs/implementing/integrate/_index.md index 800b504a50..d674ecd612 100644 --- a/docs/content/en/docs/implementing/integrate/_index.md +++ b/docs/content/en/docs/implementing/integrate/_index.md @@ -7,44 +7,35 @@ weight: 45 hidechildren: false # this flag hides all sub-pages in the sidebar-multicard.html --- -Use Kubernetes annotations and labels -to integrate the Keptn Lifecycle Toolkit into your Kubernetes cluster. - -The Keptn Lifecycle Toolkit monitors resources +The Keptn Lifecycle Toolkit works +on top of the default scheduler for the cluster +so it can trace all activities of all deployment workloads on the cluster, +no matter what tool is used for the deployment. +This same mechanism allows KLT to inject pre- and post-deployment checks +into all deployment workloads. +KLT monitors resources that have been applied into the Kubernetes cluster and reacts if it finds a workload with special annotations/labels. -This is a four-step process: +The Keptn Lifecycle Toolkit uses metadata +to identify the workloads of interest. + +To integrate KLT with your applications, +you need to populate the metadata it needs +with either Keptn or Kubernetes annotations and labels. + +This requires two steps: -* [Enable the target namespace](#enable-target-namespace) * [Annotate your workload(s)](#annotate-workloads) * Define a Keptn application that references those workloads. You have two options: - * [Define Keptn resources](#define-keptn-custom-resources-for-the-application) + * [Define KeptnApp manually](#define-keptnapp-manually) for the application * [Use the Keptn automatic app discovery capability](#use-keptn-automatic-app-discovery) that enables the observability features provided by the Lifecycle Toolkit for existing applications, without requiring you to manually create any KeptnApp resources. -## Enable target namespace - -To enable the Keptn Lifecycle Controller in your cluster, -annotate the Kubernetes -[Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/); -for example: - -```yaml -apiVersion: v1 -kind: Namespace -metadata: - name: simplenode-dev - annotations: - keptn.sh/lifecycle-toolkit: "enabled" -``` - -This annotation tells the webhook to handle the namespace. - ## Annotate workload(s) To annotate your @@ -64,6 +55,10 @@ If KLT finds any of hese resources and the resource has either the keptn.sh or the kubernetes recommended labels, it creates a `KeptnWorkload` resource for the version it detects. +> Note: Annotations are not required if you are only using the + `metrics-operator` component of KLT + to observe Keptn metrics. + ### Basic annotations The basic keptn.sh annotations are: @@ -145,6 +140,13 @@ the post-deployment checks start. ## Define a Keptn application +A Keptn application defines the workloads +to be included in your Keptn Application. +It does this by aggregating multiple workloads +that belong to a logical app into a single +[KeptnApp](../../yaml-crd-ref/app.md) +resource. + You have two options: * Create a [KeptnApp](../../yaml-crd-ref/app.md) resource @@ -158,15 +160,36 @@ the post-deployment checks start. for existing applications, without requiring you to manually create any KeptnApp resources -### Define Keptn custom resources for the application +### Define KeptnApp manually -Manually create a YAML file for the +You can manually create a YAML file for the [KeptnApp](../../yaml-crd-ref/app.md) resource that references the workloads to be included along with any [KeptnTaskDefinition](../../yaml-crd-ref/taskdefinition.md) and [KeptnEvaluationDefinition](../../yaml-crd-ref/evaluationdefinition.md) -resource that you want +resources that you want. + +See the +[keptn-app.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/keptn-app.yaml.tmp) +file for an example. +You see the `metadata` that names this `KeptnApp` +and identifies the namespace where it lives: + +```yaml +metadata: + name: simpleapp + namespace: simplenode-dev +``` + +You can also see the `spec.workloads` list +that defines the workloads to be included +and any pre-/post-deployment +tasks and evaluations to be performed. +In this simple example, +we only have one workload and one evaluation defined +but most production apps will have multiple workloads, +multiple tasks, and multiple evaluations defined. ### Use Keptn automatic app discovery diff --git a/docs/content/en/docs/implementing/otel.md b/docs/content/en/docs/implementing/otel.md index 65d62e2d17..88037c198e 100644 --- a/docs/content/en/docs/implementing/otel.md +++ b/docs/content/en/docs/implementing/otel.md @@ -3,19 +3,30 @@ title: OpenTelemetry observability description: How to standardize access to OpenTelemetry observability data weight: 140 --- -## Using OpenTelemetry with Keptn metrics -Keptn metrics can be exposed as OpenTelemetry (OTel) metrics -via port `9999` of the KLT metrics-operator. +To access OpenTelemetry metrics with the Keptn Lifecycle Toolkit, +you must: + +- Install an OpenTelemetry collector on your cluster. + See + [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) + for more information. +- Apply + [basic annotations](../implementing/integrate/#basic-annotations) + for your `Deployment` resource + to integrate the Lifecycle Toolkit into your Kubernetes cluster. -To expose OTel metrics: +KLT begins to collect OpenTelemetry metrics +as soon as the `Deployment` resource +has the basic annotations to integrate KLT in the cluster. -* Be sure that the `EXPOSE_KEPTN_METRICS` environment variable - in the `metrics-operator` manifest is set to `true`, - which is the default value. -* Define a [KeptnConfig](../yaml-crd-ref/config.md) CRD - that has the `spec.OTelCollectorUrl` field populated - with the URL of the OpenTelemetry collector. +To expose OpenTelemetry metrics, +define a [KeptnConfig](../yaml-crd-ref/config.md) resource +that has the `spec.OTelCollectorUrl` field populated +with the URL of the OpenTelemetry collector. + +Keptn metrics can be exposed as OpenTelemetry (OTel) metrics +via port `9999` of the KLT metrics-operator. To access the metrics, use the following command: @@ -26,5 +37,5 @@ kubectl port-forward deployment/metrics-operator 9999 -n keptn-lifecycle-toolkit You can access the metrics from your browser at: `http://localhost:9999` For an introduction to using OpenTelemetry with Keptn metrics, see the -[Standardize access to observability data](../getting-started/observability) +[Standardize observability](../getting-started/observability) getting started guide. diff --git a/docs/content/en/docs/install/_index.md b/docs/content/en/docs/install/_index.md index 5aa6ae0655..70c56ff443 100644 --- a/docs/content/en/docs/install/_index.md +++ b/docs/content/en/docs/install/_index.md @@ -1,14 +1,10 @@ --- -title: Installation +title: Installation and upgrade description: Learn how to install, configure, and upgrade the Keptn Lifecycle Toolkit weight: 15 hidechildren: false # this flag hides all sub-pages in the sidebar-multicard.html --- -> **Note** This section is under development. -All material presented here has been reviewed for accuracy -but the content is not yet complete. - This section provides details about how to install and configure the components of the Keptn Lifecycle Toolkit either as a local cluster you use for study, testing, and demonstrations @@ -17,13 +13,42 @@ The steps are: 1. Understand the [Software versions and resources](reqs.md) that are required. +1. Be sure that your cluster includes the components discussed in + [Prepare your cluster for KLT](k8s.md/#prepare-your-cluster-for-klt). 1. [Bring or create your Kubernetes cluster](k8s.md). 1. [Replace the default cert-manager](cert-manager.md) (optional). This step is only required if you want to replace the default KLT cert-manager with another cert-manager. 1. [Install the Keptn Lifecycle Toolkit](install.md). 1. [Enable Keptn Lifecycle Toolkit](install.md/#enable-klt-for-your-cluster). - This step is not required if you only want to run Keptn Metrics. + This step is not required if you only want to run Keptn Metrics + but is required for all other KLT features. + +1. Run the following command to ensure that your Kuberetes cluster + is ready to implement the Lifecycle Toolkit: + + ```shell + kubectl get pods -n keptn-lifecycle-toolkit-system + ``` + + You should see pods for the following components: + - certificate-operator (or another cert manager) + - lifecycle-operator + - scheduler + - metrics-operator + +Unless you are only using the customized Keptn metrics feature, +you now need to: + +- Follow the instructions in + [Annotate workload](../implementing/integrate/#basic-annotations) + to integrate the Lifecycle Toolkit into your Kubernetes cluster + by applying basic annotations to your `Deployment` resource. +- Follow the instructions in + [Define a Keptn application](../implementing/integrate/#define-a-keptn-application) + to create a Keptn application that aggragates + all the `workloads` for your deployment into a single + [KeptnApp](../yaml-crd-ref/app.md) resource. This section also includes: diff --git a/docs/content/en/docs/install/install.md b/docs/content/en/docs/install/install.md index 5b762e75a3..f021780190 100644 --- a/docs/content/en/docs/install/install.md +++ b/docs/content/en/docs/install/install.md @@ -5,6 +5,14 @@ weight: 35 hidechildren: false # this flag hides all sub-pages in the sidebar-multicard.html --- +The Keptn Lifecycle Toolkit must be installed, enabled, and integrated +into each cluster you want to monitor. +This is because KLT communicates with the Kubernetes scheduler +for tasks such as enforcing checks natively, +stopping a deployment from proceeding when criteria are not met, +doing post-deployment evaluations +and tracing all activities of all deployment workloads on the cluster. + Two methods are supported for installing the Keptn Lifecycle Toolkit (KLT): * Releases v0.7.0 and later can be installed using @@ -61,6 +69,11 @@ Some helpful hints: ### Modify Helm configuration options +Helm chart values can be modified before the installation. +This is useful if you want to install only the `metrics-operator` +rather than the full Toolkit +or if you need to change the size of the installation. + To modify configuration options, download a copy of the [helm/chart/values.yaml](https://github.com/keptn/lifecycle-toolkit/blob/main/helm/chart/values.yaml) file, modify some values, and use the modified file to install KLT: @@ -128,10 +141,11 @@ The Lifecycle Toolkit and its dependencies are now installed and ready to use. ## Enable KLT for your cluster -To enable KLT for your cluster, annotate the Kubernetes +To enable the Keptn Lifecycle Controller in your cluster, +annotate the Kubernetes [Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) resource. -In this example, this is defined in the +For an example of this, see [simplenode-dev-ns.yaml](https://github.com/keptn-sandbox/klt-on-k3s-with-argocd/blob/main/simplenode-dev/simplenode-dev-ns.yaml) file, which looks like this: @@ -145,4 +159,4 @@ metadata: ``` You see the annotation line `keptn.sh/lifecycle-toolkit: "enabled"`. -This line tells KLT to handle the namespace +This annotation tells the webhook to handle the namespace. diff --git a/docs/content/en/docs/install/k8s.md b/docs/content/en/docs/install/k8s.md index 074d34028e..a1a5195fda 100644 --- a/docs/content/en/docs/install/k8s.md +++ b/docs/content/en/docs/install/k8s.md @@ -82,7 +82,7 @@ Your cluster should include the following: * If you want to use the standardized observability feature, install an OpenTelemetry collector on your cluster. - See + See [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) for more information. From ecd8c487b22b11bea0646a3c5b2a1f9a22c80d2f Mon Sep 17 00:00:00 2001 From: odubajDT <93584209+odubajDT@users.noreply.github.com> Date: Thu, 25 May 2023 16:01:37 +0200 Subject: [PATCH 51/62] chore(operator): use List() when fetching KeptnWorkloadInstances for KeptnAppVersion (#1456) Signed-off-by: odubajDT --- .../controllers/common/fake/fakeclient.go | 6 +- .../keptnappversion/controller_test.go | 11 +- .../reconcile_workloadsstate.go | 63 +++-- .../reconcile_workloadstate_test.go | 220 ++++++++++++++++++ .../keptnworkloadinstance/controller.go | 6 + 5 files changed, 283 insertions(+), 23 deletions(-) create mode 100644 operator/controllers/lifecycle/keptnappversion/reconcile_workloadstate_test.go diff --git a/operator/controllers/common/fake/fakeclient.go b/operator/controllers/common/fake/fakeclient.go index dd4dc356a1..0039bd514b 100644 --- a/operator/controllers/common/fake/fakeclient.go +++ b/operator/controllers/common/fake/fakeclient.go @@ -15,16 +15,14 @@ import ( // NewClient returns a new controller-runtime fake Client configured with the Operator's scheme, and initialized with objs. func NewClient(objs ...client.Object) client.Client { - setupSchemes() + SetupSchemes() return fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(objs...).Build() } -func setupSchemes() { +func SetupSchemes() { utilruntime.Must(clientgoscheme.AddToScheme(scheme.Scheme)) utilruntime.Must(corev1.AddToScheme(scheme.Scheme)) utilruntime.Must(apiv1.AddToScheme(scheme.Scheme)) - // utilruntime.Must(lfcv1alpha1.AddToScheme(scheme.Scheme)) - // utilruntime.Must(lfcv1alpha2.AddToScheme(scheme.Scheme)) utilruntime.Must(lfcv1alpha3.AddToScheme(scheme.Scheme)) utilruntime.Must(optionsv1alpha1.AddToScheme(scheme.Scheme)) utilruntime.Must(metricsapi.AddToScheme(scheme.Scheme)) diff --git a/operator/controllers/lifecycle/keptnappversion/controller_test.go b/operator/controllers/lifecycle/keptnappversion/controller_test.go index 2b022a25f6..85cb565d05 100644 --- a/operator/controllers/lifecycle/keptnappversion/controller_test.go +++ b/operator/controllers/lifecycle/keptnappversion/controller_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + k8sfake "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/log/zap" ) @@ -160,7 +161,7 @@ func setupReconcilerWithMeters() *KeptnAppVersionReconciler { return r } -func setupReconciler() (*KeptnAppVersionReconciler, chan string, *fake.ITracerMock, *fake.ISpanHandlerMock) { +func setupReconciler(objs ...client.Object) (*KeptnAppVersionReconciler, chan string, *fake.ITracerMock, *fake.ISpanHandlerMock) { //setup logger opts := zap.Options{ Development: true, @@ -185,7 +186,13 @@ func setupReconciler() (*KeptnAppVersionReconciler, chan string, *fake.ITracerMo UnbindSpanFunc: func(reconcileObject client.Object, phase string) error { return nil }, } - fakeClient := fake.NewClient() + workloadInstanceIndexer := func(obj client.Object) []string { + workloadInstance, _ := obj.(*lfcv1alpha3.KeptnWorkloadInstance) + return []string{workloadInstance.Spec.AppName} + } + + fake.SetupSchemes() + fakeClient := k8sfake.NewClientBuilder().WithObjects(objs...).WithScheme(scheme.Scheme).WithObjects().WithIndex(&lfcv1alpha3.KeptnWorkloadInstance{}, "spec.app", workloadInstanceIndexer).Build() recorder := record.NewFakeRecorder(100) r := &KeptnAppVersionReconciler{ diff --git a/operator/controllers/lifecycle/keptnappversion/reconcile_workloadsstate.go b/operator/controllers/lifecycle/keptnappversion/reconcile_workloadsstate.go index 1ca2a9ca3f..6b303e86d6 100644 --- a/operator/controllers/lifecycle/keptnappversion/reconcile_workloadsstate.go +++ b/operator/controllers/lifecycle/keptnappversion/reconcile_workloadsstate.go @@ -6,8 +6,7 @@ import ( klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" ) func (r *KeptnAppVersionReconciler) reconcileWorkloads(ctx context.Context, appVersion *klcv1alpha3.KeptnAppVersion) (apicommon.KeptnState, error) { @@ -20,18 +19,33 @@ func (r *KeptnAppVersionReconciler) reconcileWorkloads(ctx context.Context, appV LongName: "Reconcile Workloads", } - var newStatus []klcv1alpha3.WorkloadStatus + workloadInstanceList, err := r.getWorkloadInstanceList(ctx, appVersion.Namespace, appVersion.Spec.AppName) + if err != nil { + r.Log.Error(err, "Could not get workloads") + return apicommon.StateUnknown, r.handleUnaccessibleWorkloadInstanceList(ctx, appVersion) + } + + newStatus := make([]klcv1alpha3.WorkloadStatus, 0, len(appVersion.Spec.Workloads)) for _, w := range appVersion.Spec.Workloads { r.Log.Info("Reconciling workload " + w.Name) - workload, err := r.getWorkloadInstance(ctx, getWorkloadInstanceName(appVersion.Namespace, appVersion.Spec.AppName, w.Name, w.Version)) - if err != nil && errors.IsNotFound(err) { + workloadStatus := apicommon.StatePending + found := false + instanceName := getWorkloadInstanceName(appVersion.Spec.AppName, w.Name, w.Version) + for _, i := range workloadInstanceList.Items { + r.Log.Info("No WorkloadInstance found for KeptnApp " + appVersion.Spec.AppName) + // additional filtering of the retrieved WIs is needed, as the List() method retrieves all + // WIs for a specific KeptnApp. The result can contain also WIs, that are not part of the + // latest KeptnAppVersion, so it's needed to double check them + // no need to compare version, as it is part of WI name + if instanceName == i.Name { + found = true + workloadStatus = i.Status.Status + } + } + + if !found { controllercommon.RecordEvent(r.Recorder, phase, "Warning", appVersion, "NotFound", "workloadInstance not found", appVersion.GetVersion()) - workload.Status.Status = apicommon.StatePending - } else if err != nil { - r.Log.Error(err, "Could not get workload") - workload.Status.Status = apicommon.StateUnknown } - workloadStatus := workload.Status.Status newStatus = append(newStatus, klcv1alpha3.WorkloadStatus{ Workload: w, @@ -48,16 +62,31 @@ func (r *KeptnAppVersionReconciler) reconcileWorkloads(ctx context.Context, appV r.Log.Info("Workload status", "status", appVersion.Status.WorkloadStatus) // Write Status Field - err := r.Client.Status().Update(ctx, appVersion) + err = r.Client.Status().Update(ctx, appVersion) return overallState, err } -func (r *KeptnAppVersionReconciler) getWorkloadInstance(ctx context.Context, workload types.NamespacedName) (klcv1alpha3.KeptnWorkloadInstance, error) { - workloadInstance := &klcv1alpha3.KeptnWorkloadInstance{} - err := r.Get(ctx, workload, workloadInstance) - return *workloadInstance, err +func (r *KeptnAppVersionReconciler) getWorkloadInstanceList(ctx context.Context, namespace string, appName string) (*klcv1alpha3.KeptnWorkloadInstanceList, error) { + workloadInstanceList := &klcv1alpha3.KeptnWorkloadInstanceList{} + err := r.Client.List(ctx, workloadInstanceList, client.InNamespace(namespace), client.MatchingFields{ + "spec.app": appName, + }) + return workloadInstanceList, err +} + +func (r *KeptnAppVersionReconciler) handleUnaccessibleWorkloadInstanceList(ctx context.Context, appVersion *klcv1alpha3.KeptnAppVersion) error { + newStatus := make([]klcv1alpha3.WorkloadStatus, 0, len(appVersion.Spec.Workloads)) + for _, w := range appVersion.Spec.Workloads { + newStatus = append(newStatus, klcv1alpha3.WorkloadStatus{ + Workload: w, + Status: apicommon.StateUnknown, + }) + } + appVersion.Status.WorkloadOverallStatus = apicommon.StateUnknown + appVersion.Status.WorkloadStatus = newStatus + return r.Client.Status().Update(ctx, appVersion) } -func getWorkloadInstanceName(namespace string, appName string, workloadName string, version string) types.NamespacedName { - return types.NamespacedName{Namespace: namespace, Name: appName + "-" + workloadName + "-" + version} +func getWorkloadInstanceName(appName string, workloadName string, version string) string { + return appName + "-" + workloadName + "-" + version } diff --git a/operator/controllers/lifecycle/keptnappversion/reconcile_workloadstate_test.go b/operator/controllers/lifecycle/keptnappversion/reconcile_workloadstate_test.go new file mode 100644 index 0000000000..40edc38a48 --- /dev/null +++ b/operator/controllers/lifecycle/keptnappversion/reconcile_workloadstate_test.go @@ -0,0 +1,220 @@ +package keptnappversion + +import ( + "context" + "testing" + + lfcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" + apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" + "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +//nolint:dogsled +func TestKeptnAppVersionReconciler_reconcileWorkloads_noWorkloads(t *testing.T) { + appVersion := &lfcv1alpha3.KeptnAppVersion{ + ObjectMeta: v1.ObjectMeta{ + Name: "appversion", + Namespace: "default", + }, + Spec: lfcv1alpha3.KeptnAppVersionSpec{ + AppName: "app", + }, + } + r, _, _, _ := setupReconciler(appVersion) + + state, err := r.reconcileWorkloads(context.TODO(), appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StateSucceeded, state) + + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StateSucceeded, appVersion.Status.WorkloadOverallStatus) + require.Len(t, appVersion.Status.WorkloadStatus, 0) +} + +//nolint:dogsled +func TestKeptnAppVersionReconciler_reconcileWorkloads(t *testing.T) { + appVersion := &lfcv1alpha3.KeptnAppVersion{ + ObjectMeta: v1.ObjectMeta{ + Name: "appversion", + Namespace: "default", + }, + Spec: lfcv1alpha3.KeptnAppVersionSpec{ + KeptnAppSpec: lfcv1alpha3.KeptnAppSpec{ + Workloads: []lfcv1alpha3.KeptnWorkloadRef{ + { + Name: "workload", + Version: "ver1", + }, + }, + }, + AppName: "app", + }, + } + r, _, _, _ := setupReconciler(appVersion) + + // No workloadInstances are created yet, should stay in Pending state + + state, err := r.reconcileWorkloads(context.TODO(), appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StatePending, state) + + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StatePending, appVersion.Status.WorkloadOverallStatus) + require.Len(t, appVersion.Status.WorkloadStatus, 1) + require.Equal(t, []lfcv1alpha3.WorkloadStatus{ + { + Workload: lfcv1alpha3.KeptnWorkloadRef{ + Name: "workload", + Version: "ver1", + }, + Status: apicommon.StatePending, + }, + }, appVersion.Status.WorkloadStatus) + + // Creating WorkloadInstace that is not part of the App -> should stay Pending + + wi1 := &lfcv1alpha3.KeptnWorkloadInstance{ + ObjectMeta: v1.ObjectMeta{ + Name: "workload", + Namespace: "default", + }, + Spec: lfcv1alpha3.KeptnWorkloadInstanceSpec{ + KeptnWorkloadSpec: lfcv1alpha3.KeptnWorkloadSpec{ + AppName: "app2", + }, + }, + } + + err = r.Client.Create(context.TODO(), wi1) + require.Nil(t, err) + + state, err = r.reconcileWorkloads(context.TODO(), appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StatePending, state) + + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StatePending, appVersion.Status.WorkloadOverallStatus) + require.Len(t, appVersion.Status.WorkloadStatus, 1) + require.Equal(t, []lfcv1alpha3.WorkloadStatus{ + { + Workload: lfcv1alpha3.KeptnWorkloadRef{ + Name: "workload", + Version: "ver1", + }, + Status: apicommon.StatePending, + }, + }, appVersion.Status.WorkloadStatus) + + // Creating WorkloadInstance of App with progressing state -> appVersion should be Progressing + + wi2 := &lfcv1alpha3.KeptnWorkloadInstance{ + ObjectMeta: v1.ObjectMeta{ + Name: "app-workload-ver1", + Namespace: "default", + }, + Spec: lfcv1alpha3.KeptnWorkloadInstanceSpec{ + KeptnWorkloadSpec: lfcv1alpha3.KeptnWorkloadSpec{ + AppName: "app", + }, + }, + } + + err = r.Client.Create(context.TODO(), wi2) + require.Nil(t, err) + + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: wi2.Namespace, Name: wi2.Name}, wi2) + require.Nil(t, err) + + wi2.Status.Status = apicommon.StateProgressing + err = r.Client.Update(context.TODO(), wi2) + require.Nil(t, err) + + state, err = r.reconcileWorkloads(context.TODO(), appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StateProgressing, state) + + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StateProgressing, appVersion.Status.WorkloadOverallStatus) + require.Len(t, appVersion.Status.WorkloadStatus, 1) + require.Equal(t, []lfcv1alpha3.WorkloadStatus{ + { + Workload: lfcv1alpha3.KeptnWorkloadRef{ + Name: "workload", + Version: "ver1", + }, + Status: apicommon.StateProgressing, + }, + }, appVersion.Status.WorkloadStatus) + + // Updating WorkloadInstance of App with succeeded state -> appVersion should be Succeeded + + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: wi2.Namespace, Name: wi2.Name}, wi2) + require.Nil(t, err) + + wi2.Status.Status = apicommon.StateSucceeded + err = r.Client.Update(context.TODO(), wi2) + require.Nil(t, err) + + state, err = r.reconcileWorkloads(context.TODO(), appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StateSucceeded, state) + + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StateSucceeded, appVersion.Status.WorkloadOverallStatus) + require.Len(t, appVersion.Status.WorkloadStatus, 1) + require.Equal(t, []lfcv1alpha3.WorkloadStatus{ + { + Workload: lfcv1alpha3.KeptnWorkloadRef{ + Name: "workload", + Version: "ver1", + }, + Status: apicommon.StateSucceeded, + }, + }, appVersion.Status.WorkloadStatus) +} + +//nolint:dogsled +func TestKeptnAppVersionReconciler_handleUnaccessibleWorkloadInstanceList(t *testing.T) { + appVersion := &lfcv1alpha3.KeptnAppVersion{ + ObjectMeta: v1.ObjectMeta{ + Name: "appversion", + Namespace: "default", + }, + Spec: lfcv1alpha3.KeptnAppVersionSpec{ + KeptnAppSpec: lfcv1alpha3.KeptnAppSpec{ + Workloads: []lfcv1alpha3.KeptnWorkloadRef{ + { + Name: "workload", + Version: "ver1", + }, + }, + }, + AppName: "app", + }, + } + r, _, _, _ := setupReconciler(appVersion) + + err := r.handleUnaccessibleWorkloadInstanceList(context.TODO(), appVersion) + require.Nil(t, err) + + err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion) + require.Nil(t, err) + require.Equal(t, apicommon.StateUnknown, appVersion.Status.WorkloadOverallStatus) + require.Len(t, appVersion.Status.WorkloadStatus, 1) + require.Equal(t, []lfcv1alpha3.WorkloadStatus{ + { + Workload: lfcv1alpha3.KeptnWorkloadRef{ + Name: "workload", + Version: "ver1", + }, + Status: apicommon.StateUnknown, + }, + }, appVersion.Status.WorkloadStatus) +} diff --git a/operator/controllers/lifecycle/keptnworkloadinstance/controller.go b/operator/controllers/lifecycle/keptnworkloadinstance/controller.go index 72dfc0513d..4548caf6fa 100644 --- a/operator/controllers/lifecycle/keptnworkloadinstance/controller.go +++ b/operator/controllers/lifecycle/keptnworkloadinstance/controller.go @@ -212,6 +212,12 @@ func (r *KeptnWorkloadInstanceReconciler) finishKeptnWorkloadInstanceReconcile(c // SetupWithManager sets up the controller with the Manager. func (r *KeptnWorkloadInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &klcv1alpha3.KeptnWorkloadInstance{}, "spec.app", func(rawObj client.Object) []string { + workloadInstance := rawObj.(*klcv1alpha3.KeptnWorkloadInstance) + return []string{workloadInstance.Spec.AppName} + }); err != nil { + return err + } return ctrl.NewControllerManagedBy(mgr). // predicate disabling the auto reconciliation after updating the object status For(&klcv1alpha3.KeptnWorkloadInstance{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). From a3f5e5bc3fc8bd8073d264c30c39a38fc09d364e Mon Sep 17 00:00:00 2001 From: RealAnna <89971034+RealAnna@users.noreply.github.com> Date: Fri, 26 May 2023 11:01:58 +0200 Subject: [PATCH 52/62] chore(operator): refactor keptntaskcontroller to use builder interface (#1450) Signed-off-by: realanna --- operator/controllers/errors/errors.go | 3 +- .../lifecycle/keptntask/container_builder.go | 46 ++++ .../lifecycle/keptntask/job_utils.go | 91 ++++--- .../{function_utils.go => js_builder.go} | 130 ++++++---- .../lifecycle/keptntask/js_builder_test.go | 234 ++++++++++++++++++ operator/go.mod | 2 +- .../00-assert.yaml | 8 + .../00-install.yaml | 72 ++++++ .../00-teststep.yaml | 4 + .../01-assert.yaml | 96 +++++++ 10 files changed, 595 insertions(+), 91 deletions(-) create mode 100644 operator/controllers/lifecycle/keptntask/container_builder.go rename operator/controllers/lifecycle/keptntask/{function_utils.go => js_builder.go} (50%) create mode 100644 operator/controllers/lifecycle/keptntask/js_builder_test.go create mode 100644 test/integration/simple-deployment-recursive-task/00-assert.yaml create mode 100644 test/integration/simple-deployment-recursive-task/00-install.yaml create mode 100644 test/integration/simple-deployment-recursive-task/00-teststep.yaml create mode 100644 test/integration/simple-deployment-recursive-task/01-assert.yaml diff --git a/operator/controllers/errors/errors.go b/operator/controllers/errors/errors.go index f998f283b9..e7b742515d 100644 --- a/operator/controllers/errors/errors.go +++ b/operator/controllers/errors/errors.go @@ -12,6 +12,7 @@ var ErrRetryCountExceeded = fmt.Errorf("retryCount for evaluation exceeded") var ErrNoValues = fmt.Errorf("no values") var ErrInvalidOperator = fmt.Errorf("invalid operator") var ErrCannotMarshalParams = fmt.Errorf("could not marshal parameters") +var ErrNoTaskDefinitionSpec = fmt.Errorf("the TaskDefinition specs are empty") var ErrUnsupportedWorkloadInstanceResourceReference = fmt.Errorf("unsupported Resource Reference") var ErrCannotGetKeptnTaskDefinition = fmt.Errorf("cannot retrieve KeptnTaskDefinition") @@ -22,7 +23,7 @@ var ErrCannotFetchAppVersionMsg = "could not retrieve KeptnappVersion: %w" var ErrCannotRetrieveWorkloadInstancesMsg = "could not retrieve KeptnWorkloadInstance: %w" var ErrCannotRetrieveWorkloadMsg = "could not retrieve KeptnWorkload: %w" var ErrNoLabelsFoundTask = "no labels found for task: %s" -var ErrNoConfigMapMsg = "No ConfigMap specified or HTTP source specified in TaskDefinition) / Namespace: %s, Name: %s" +var ErrNoConfigMapMsg = "no ConfigMap specified or HTTP source specified in TaskDefinition) / Namespace: %s, Name: %s" var ErrCannotGetFunctionConfigMap = "could not get function configMap: %w" var ErrCannotFetchAppVersionForWorkloadInstanceMsg = "could not fetch AppVersion for KeptnWorkloadInstance: " var ErrCouldNotUnbindSpan = "could not unbind span for %s" diff --git a/operator/controllers/lifecycle/keptntask/container_builder.go b/operator/controllers/lifecycle/keptntask/container_builder.go new file mode 100644 index 0000000000..e550b9dfac --- /dev/null +++ b/operator/controllers/lifecycle/keptntask/container_builder.go @@ -0,0 +1,46 @@ +package keptntask + +import ( + "reflect" + + "github.com/go-logr/logr" + klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" + "golang.org/x/net/context" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// IContainerBuilder is the interface that describes the operations needed to help build job specs of a task +type IContainerBuilder interface { + // CreateContainerWithVolumes returns a job container and volumes based on the task definition spec + CreateContainerWithVolumes(ctx context.Context) (*corev1.Container, []corev1.Volume, error) +} + +// BuilderOptions contains everything needed to build the current job +type BuilderOptions struct { + client.Client + recorder record.EventRecorder + req ctrl.Request + Log logr.Logger + task *klcv1alpha3.KeptnTask + taskDef *klcv1alpha3.KeptnTaskDefinition +} + +func getContainerBuilder(options BuilderOptions) IContainerBuilder { + if isJSSpecDefined(&options.taskDef.Spec) { + builder := newJSBuilder(options) + return &builder + } + return nil +} + +func specExists(definition *klcv1alpha3.KeptnTaskDefinition) bool { + //TODO when adding new builders add more logic here + return isJSSpecDefined(&definition.Spec) +} + +func isJSSpecDefined(spec *klcv1alpha3.KeptnTaskDefinitionSpec) bool { + return !reflect.DeepEqual(spec.Function, klcv1alpha3.FunctionSpec{}) +} diff --git a/operator/controllers/lifecycle/keptntask/job_utils.go b/operator/controllers/lifecycle/keptntask/job_utils.go index c9847c7a47..c49efbb77a 100644 --- a/operator/controllers/lifecycle/keptntask/job_utils.go +++ b/operator/controllers/lifecycle/keptntask/job_utils.go @@ -3,15 +3,18 @@ package keptntask import ( "context" "fmt" - "reflect" + "math/rand" - "github.com/imdario/mergo" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" + controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) func (r *KeptnTaskReconciler) createJob(ctx context.Context, req ctrl.Request, task *klcv1alpha3.KeptnTask) error { @@ -22,7 +25,7 @@ func (r *KeptnTaskReconciler) createJob(ctx context.Context, req ctrl.Request, t return err } - if !reflect.DeepEqual(definition.Spec.Function, klcv1alpha3.FunctionSpec{}) { + if specExists(definition) { jobName, err = r.createFunctionJob(ctx, req, task, definition) if err != nil { return err @@ -40,31 +43,8 @@ func (r *KeptnTaskReconciler) createJob(ctx context.Context, req ctrl.Request, t } func (r *KeptnTaskReconciler) createFunctionJob(ctx context.Context, req ctrl.Request, task *klcv1alpha3.KeptnTask, definition *klcv1alpha3.KeptnTaskDefinition) (string, error) { - params, hasParent, err := r.parseFunctionTaskDefinition(definition) - if err != nil { - return "", err - } - if hasParent { - if err := r.handleParent(ctx, req, task, definition, params); err != nil { - return "", err - } - } - - params.Context = setupTaskContext(task) - - if len(task.Spec.Parameters.Inline) > 0 { - err = mergo.Merge(¶ms.Parameters, task.Spec.Parameters.Inline) - if err != nil { - controllercommon.RecordEvent(r.Recorder, apicommon.PhaseCreateTask, "Warning", task, "TaskDefinitionMergeFailure", fmt.Sprintf("could not merge KeptnTaskDefinition: %s ", task.Spec.TaskDefinition), "") - return "", err - } - } - - if task.Spec.SecureParameters.Secret != "" { - params.SecureParameters = task.Spec.SecureParameters.Secret - } - job, err := r.generateFunctionJob(task, params) + job, err := r.generateJob(ctx, task, definition, req) if err != nil { return "", err } @@ -127,21 +107,54 @@ func setupTaskContext(task *klcv1alpha3.KeptnTask) klcv1alpha3.TaskContext { return taskContext } -func (r *KeptnTaskReconciler) handleParent(ctx context.Context, req ctrl.Request, task *klcv1alpha3.KeptnTask, definition *klcv1alpha3.KeptnTaskDefinition, params FunctionExecutionParams) error { - var parentJobParams FunctionExecutionParams - parentDefinition, err := controllercommon.GetTaskDefinition(r.Client, r.Log, ctx, definition.Spec.Function.FunctionReference.Name, req.Namespace) +func (r *KeptnTaskReconciler) generateJob(ctx context.Context, task *klcv1alpha3.KeptnTask, definition *klcv1alpha3.KeptnTaskDefinition, request ctrl.Request) (*batchv1.Job, error) { + randomId := rand.Intn(99999-10000) + 10000 + jobId := fmt.Sprintf("klc-%s-%d", apicommon.TruncateString(task.Name, apicommon.MaxTaskNameLength), randomId) + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: jobId, + Namespace: task.Namespace, + Labels: task.Labels, + Annotations: task.CreateKeptnAnnotations(), + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: task.Labels, + Annotations: task.Annotations, + }, + Spec: corev1.PodSpec{ + RestartPolicy: "OnFailure", + }, + }, + BackoffLimit: task.Spec.Retries, + ActiveDeadlineSeconds: task.GetActiveDeadlineSeconds(), + }, + } + err := controllerutil.SetControllerReference(task, job, r.Scheme) if err != nil { - controllercommon.RecordEvent(r.Recorder, apicommon.PhaseCreateTask, "Warning", task, "TaskDefinitionNotFound", fmt.Sprintf("could not find KeptnTaskDefinition: %s ", task.Spec.TaskDefinition), "") - return err + r.Log.Error(err, "could not set controller reference:") } - parentJobParams, _, err = r.parseFunctionTaskDefinition(parentDefinition) - if err != nil { - return err + + builderOpt := BuilderOptions{ + Client: r.Client, + req: request, + Log: r.Log, + task: task, + taskDef: definition, + recorder: r.Recorder, + } + builder := getContainerBuilder(builderOpt) + if builder == nil { + return nil, controllererrors.ErrNoTaskDefinitionSpec } - err = mergo.Merge(¶ms, parentJobParams) + container, volumes, err := builder.CreateContainerWithVolumes(ctx) + if err != nil { - controllercommon.RecordEvent(r.Recorder, apicommon.PhaseCreateTask, "Warning", task, "TaskDefinitionMergeFailure", fmt.Sprintf("could not merge KeptnTaskDefinition: %s ", task.Spec.TaskDefinition), "") - return err + return nil, controllererrors.ErrCannotMarshalParams } - return nil + + job.Spec.Template.Spec.Containers = []corev1.Container{*container} + job.Spec.Template.Spec.Volumes = volumes + return job, nil } diff --git a/operator/controllers/lifecycle/keptntask/function_utils.go b/operator/controllers/lifecycle/keptntask/js_builder.go similarity index 50% rename from operator/controllers/lifecycle/keptntask/function_utils.go rename to operator/controllers/lifecycle/keptntask/js_builder.go index 66682f44c1..5a990f4b3c 100644 --- a/operator/controllers/lifecycle/keptntask/function_utils.go +++ b/operator/controllers/lifecycle/keptntask/js_builder.go @@ -3,18 +3,29 @@ package keptntask import ( "encoding/json" "fmt" - "math/rand" "os" + "github.com/imdario/mergo" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" + controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" - batchv1 "k8s.io/api/batch/v1" + "golang.org/x/net/context" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) +// JSBuilder implements container builder interface for javascript deno +type JSBuilder struct { + options BuilderOptions +} + +func newJSBuilder(options BuilderOptions) JSBuilder { + return JSBuilder{ + options: options, + } +} + +// FunctionExecutionParams stores parametersrelatedto js deno container creation type FunctionExecutionParams struct { ConfigMap string Parameters map[string]string @@ -23,35 +34,7 @@ type FunctionExecutionParams struct { Context klcv1alpha3.TaskContext } -func (r *KeptnTaskReconciler) generateFunctionJob(task *klcv1alpha3.KeptnTask, params FunctionExecutionParams) (*batchv1.Job, error) { - randomId := rand.Intn(99999-10000) + 10000 - jobId := fmt.Sprintf("klc-%s-%d", apicommon.TruncateString(task.Name, apicommon.MaxTaskNameLength), randomId) - job := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: jobId, - Namespace: task.Namespace, - Labels: task.Labels, - Annotations: task.CreateKeptnAnnotations(), - }, - Spec: batchv1.JobSpec{ - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: task.Labels, - Annotations: task.Annotations, - }, - Spec: corev1.PodSpec{ - RestartPolicy: "OnFailure", - }, - }, - BackoffLimit: task.Spec.Retries, - ActiveDeadlineSeconds: task.GetActiveDeadlineSeconds(), - }, - } - err := controllerutil.SetControllerReference(task, job, r.Scheme) - if err != nil { - r.Log.Error(err, "could not set controller reference:") - } - +func (js *JSBuilder) CreateContainerWithVolumes(ctx context.Context) (*corev1.Container, []corev1.Volume, error) { container := corev1.Container{ Name: "keptn-function-runner", Image: os.Getenv("FUNCTION_RUNNER_IMAGE"), @@ -59,17 +42,21 @@ func (r *KeptnTaskReconciler) generateFunctionJob(task *klcv1alpha3.KeptnTask, p var envVars []corev1.EnvVar + params, err := js.getParams(ctx) + if err != nil { + return nil, nil, err + } if len(params.Parameters) > 0 { jsonParams, err := json.Marshal(params.Parameters) if err != nil { - return job, controllererrors.ErrCannotMarshalParams + return nil, nil, err } envVars = append(envVars, corev1.EnvVar{Name: "DATA", Value: string(jsonParams)}) } jsonParams, err := json.Marshal(params.Context) if err != nil { - return job, controllererrors.ErrCannotMarshalParams + return nil, nil, err } envVars = append(envVars, corev1.EnvVar{Name: "CONTEXT", Value: string(jsonParams)}) @@ -84,24 +71,23 @@ func (r *KeptnTaskReconciler) generateFunctionJob(task *klcv1alpha3.KeptnTask, p }, }) } - + var jobVolumes []corev1.Volume // Mount the function code if a ConfigMap is provided // The ConfigMap might be provided manually or created by the TaskDefinition controller if params.ConfigMap != "" { envVars = append(envVars, corev1.EnvVar{Name: "SCRIPT", Value: "/var/data/function.ts"}) - job.Spec.Template.Spec.Volumes = []corev1.Volume{ - { - Name: "function-mount", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: params.ConfigMap, - }, + jobVolumes = append(jobVolumes, corev1.Volume{ + Name: "function-mount", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: params.ConfigMap, }, }, }, - } + }) + container.VolumeMounts = []corev1.VolumeMount{ { Name: "function-mount", @@ -115,13 +101,38 @@ func (r *KeptnTaskReconciler) generateFunctionJob(task *klcv1alpha3.KeptnTask, p } container.Env = envVars - job.Spec.Template.Spec.Containers = []corev1.Container{ - container, + return &container, jobVolumes, nil + +} + +func (js *JSBuilder) getParams(ctx context.Context) (*FunctionExecutionParams, error) { + params, hasParent, err := js.parseFunctionTaskDefinition(js.options.taskDef) + if err != nil { + return nil, err + } + if hasParent { + if err := js.handleParent(ctx, ¶ms); err != nil { + return nil, err + } + } + + params.Context = setupTaskContext(js.options.task) + + if len(js.options.task.Spec.Parameters.Inline) > 0 { + err = mergo.Merge(¶ms.Parameters, js.options.task.Spec.Parameters.Inline) + if err != nil { + controllercommon.RecordEvent(js.options.recorder, apicommon.PhaseCreateTask, "Warning", js.options.task, "TaskDefinitionMergeFailure", fmt.Sprintf("could not merge KeptnTaskDefinition: %s ", js.options.task.Spec.TaskDefinition), "") + return nil, err + } } - return job, nil + + if js.options.task.Spec.SecureParameters.Secret != "" { + params.SecureParameters = js.options.task.Spec.SecureParameters.Secret + } + return ¶ms, nil } -func (r *KeptnTaskReconciler) parseFunctionTaskDefinition(definition *klcv1alpha3.KeptnTaskDefinition) (FunctionExecutionParams, bool, error) { +func (js *JSBuilder) parseFunctionTaskDefinition(definition *klcv1alpha3.KeptnTaskDefinition) (FunctionExecutionParams, bool, error) { params := FunctionExecutionParams{} // Firstly check if this task definition has a parent object @@ -131,7 +142,7 @@ func (r *KeptnTaskReconciler) parseFunctionTaskDefinition(definition *klcv1alpha } if definition.Status.Function.ConfigMap != "" && definition.Spec.Function.HttpReference.Url != "" { - r.Log.Info(fmt.Sprintf("The JobDefinition contains a ConfigMap and a HTTP Reference, ConfigMap is used / Namespace: %s, Name: %s ", definition.Namespace, definition.Name)) + js.options.Log.Info(fmt.Sprintf("The JobDefinition contains a ConfigMap and a HTTP Reference, ConfigMap is used / Namespace: %s, Name: %s ", definition.Namespace, definition.Name)) } // Check if there is a ConfigMap with the function for this object @@ -156,3 +167,22 @@ func (r *KeptnTaskReconciler) parseFunctionTaskDefinition(definition *klcv1alpha } return params, hasParent, nil } + +func (js *JSBuilder) handleParent(ctx context.Context, params *FunctionExecutionParams) error { + var parentJobParams FunctionExecutionParams + parentDefinition, err := controllercommon.GetTaskDefinition(js.options.Client, js.options.Log, ctx, js.options.taskDef.Spec.Function.FunctionReference.Name, js.options.req.Namespace) + if err != nil { + controllercommon.RecordEvent(js.options.recorder, apicommon.PhaseCreateTask, "Warning", js.options.task, "TaskDefinitionNotFound", fmt.Sprintf("could not find KeptnTaskDefinition: %s ", js.options.task.Spec.TaskDefinition), "") + return err + } + parentJobParams, _, err = js.parseFunctionTaskDefinition(parentDefinition) + if err != nil { + return err + } + err = mergo.Merge(params, parentJobParams) + if err != nil { + controllercommon.RecordEvent(js.options.recorder, apicommon.PhaseCreateTask, "Warning", js.options.task, "TaskDefinitionMergeFailure", fmt.Sprintf("could not merge KeptnTaskDefinition: %s ", js.options.task.Spec.TaskDefinition), "") + return err + } + return nil +} diff --git a/operator/controllers/lifecycle/keptntask/js_builder_test.go b/operator/controllers/lifecycle/keptntask/js_builder_test.go new file mode 100644 index 0000000000..b935be9157 --- /dev/null +++ b/operator/controllers/lifecycle/keptntask/js_builder_test.go @@ -0,0 +1,234 @@ +package keptntask + +import ( + "testing" + + "github.com/go-logr/logr/testr" + klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" + "github.com/keptn/lifecycle-toolkit/operator/controllers/common/fake" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" +) + +func TestJSBuilder_handleParent(t *testing.T) { + + def := &klcv1alpha3.KeptnTaskDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mytaskdef", + Namespace: "default", + }, + Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ + Function: klcv1alpha3.FunctionSpec{ + FunctionReference: klcv1alpha3.FunctionReference{ + Name: "mytaskdef", + }}}, + } + paramDef := &klcv1alpha3.KeptnTaskDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mytd", + Namespace: "default", + }, + Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ + Function: klcv1alpha3.FunctionSpec{ + FunctionReference: klcv1alpha3.FunctionReference{ + Name: "mytd"}, + Parameters: klcv1alpha3.TaskParameters{ + Inline: map[string]string{"DATA": "mydata"}, + }, + SecureParameters: klcv1alpha3.SecureParameters{ + Secret: "mysecret", + }, + }, + }, + } + + tests := []struct { + name string + options BuilderOptions + params FunctionExecutionParams + wantErr bool + err string + }{ + { + name: "no definition", + options: BuilderOptions{ + Client: fake.NewClient(), + recorder: &record.FakeRecorder{}, + req: ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default"}, + }, + Log: testr.New(t), + taskDef: def, + task: makeTask("myt", "default", def.Name), + }, + params: FunctionExecutionParams{}, + wantErr: true, + err: "not found", + }, + { + name: "definition exists, recursive", + options: BuilderOptions{ + Client: fake.NewClient(def), + recorder: &record.FakeRecorder{}, + req: ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default"}, + }, + Log: testr.New(t), + taskDef: def, + task: makeTask("myt2", "default", def.Name), + }, + params: FunctionExecutionParams{}, + wantErr: false, + }, + { + name: "definition exists, with parameters and secrets", + options: BuilderOptions{ + Client: fake.NewClient(paramDef, def), + recorder: &record.FakeRecorder{}, + req: ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default"}, + }, + Log: testr.New(t), + taskDef: paramDef, + task: makeTask("myt3", "default", paramDef.Name), + }, + params: FunctionExecutionParams{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + js := &JSBuilder{ + options: tt.options, + } + err := js.handleParent(context.TODO(), &tt.params) + if !tt.wantErr { + require.Nil(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tt.err) + } + + }) + } +} +func TestJSBuilder_hasParams(t *testing.T) { + + def := &klcv1alpha3.KeptnTaskDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mytaskdef", + Namespace: "default", + }, + Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ + Function: klcv1alpha3.FunctionSpec{ + HttpReference: klcv1alpha3.HttpReference{Url: "donothing"}, + Parameters: klcv1alpha3.TaskParameters{ + Inline: map[string]string{"DATA2": "mydata2"}, + }, + SecureParameters: klcv1alpha3.SecureParameters{ + Secret: "mysecret2", + }, + }}, + } + paramDef := &klcv1alpha3.KeptnTaskDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mytd", + Namespace: "default", + }, + Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ + Function: klcv1alpha3.FunctionSpec{ + HttpReference: klcv1alpha3.HttpReference{Url: "something"}, + FunctionReference: klcv1alpha3.FunctionReference{ + Name: "mytaskdef"}, + Parameters: klcv1alpha3.TaskParameters{ + Inline: map[string]string{"DATA1": "user"}, + }, + SecureParameters: klcv1alpha3.SecureParameters{ + Secret: "pw", + }, + }, + }, + } + + tests := []struct { + name string + options BuilderOptions + params *FunctionExecutionParams + wantErr bool + err string + }{ + { + name: "definition exists, no parent", + options: BuilderOptions{ + Client: fake.NewClient(def), + recorder: &record.FakeRecorder{}, + req: ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default"}, + }, + Log: testr.New(t), + taskDef: def, + task: makeTask("myt2", "default", def.Name), + }, + params: &FunctionExecutionParams{ + ConfigMap: "", + Parameters: map[string]string{ + "DATA2": "mydata2", + }, + SecureParameters: "mysecret2", + URL: "donothing", + Context: klcv1alpha3.TaskContext{ + WorkloadName: "my-workload", + AppName: "my-app", + ObjectType: "Workload"}, + }, + wantErr: false, + }, + { + name: "definition exists, parent with parameters and secrets", + options: BuilderOptions{ + Client: fake.NewClient(paramDef, def), + recorder: &record.FakeRecorder{}, + req: ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default"}, + }, + Log: testr.New(t), + taskDef: paramDef, + task: makeTask("myt3", "default", paramDef.Name), + }, + params: &FunctionExecutionParams{ + ConfigMap: "", + Parameters: map[string]string{ //maps should be merged + "DATA2": "mydata2", + "DATA1": "user", + }, + URL: "something", //we support a single URL so the original should be taken not the parent one + SecureParameters: "pw", //we support a single secret so the original task secret should be taken not the parent one + Context: klcv1alpha3.TaskContext{ + WorkloadName: "my-workload", + AppName: "my-app", + ObjectType: "Workload"}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + js := &JSBuilder{ + options: tt.options, + } + params, err := js.getParams(context.TODO()) + if !tt.wantErr { + require.Nil(t, err) + require.Equal(t, tt.params, params) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tt.err) + } + + }) + } +} diff --git a/operator/go.mod b/operator/go.mod index 69fcd2f9aa..45da0c748d 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -36,7 +36,7 @@ require ( require ( github.com/prometheus/common v0.42.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.10.0 golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.7.0 // indirect diff --git a/test/integration/simple-deployment-recursive-task/00-assert.yaml b/test/integration/simple-deployment-recursive-task/00-assert.yaml new file mode 100644 index 0000000000..e6f7dd679b --- /dev/null +++ b/test/integration/simple-deployment-recursive-task/00-assert.yaml @@ -0,0 +1,8 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test + name: test +status: + readyReplicas: 1 diff --git a/test/integration/simple-deployment-recursive-task/00-install.yaml b/test/integration/simple-deployment-recursive-task/00-install.yaml new file mode 100644 index 0000000000..f08baa5dd6 --- /dev/null +++ b/test/integration/simple-deployment-recursive-task/00-install.yaml @@ -0,0 +1,72 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mysecret +type: Opaque +data: + SECURE_DATA: dG9rZW46IG15dG9rZW4= +--- +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: pre-deployment-hello +spec: + function: + functionRef: + name: pre-deployment-parent + secureParameters: + secret: mysecret + parameters: + map: + user: "myuser" + data: "mydata" +--- +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: pre-deployment-parent +spec: + function: + parameters: + map: + user: "myotheruser" + data: "myotherdata" + other: "data" + inline: + code: | + console.log("Parent Task has been executed"); + + let foo = Deno.env.get('DATA'); + console.log(foo); + Deno.exit(0); +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test + name: test +spec: + replicas: 1 + selector: + matchLabels: + app: test + strategy: {} + template: + metadata: + labels: + app: test + annotations: + keptn.sh/workload: waiter + keptn.sh/version: "0.4" + keptn.sh/pre-deployment-tasks: pre-deployment-hello + keptn.sh/post-deployment-tasks: pre-deployment-parent + spec: + containers: + - image: busybox + name: busybox + command: ['sh', '-c', 'echo The app is running! && sleep infinity'] + initContainers: + - name: init-myservice + image: busybox:1.36.1 + command: ['sh', '-c', 'sleep 30'] diff --git a/test/integration/simple-deployment-recursive-task/00-teststep.yaml b/test/integration/simple-deployment-recursive-task/00-teststep.yaml new file mode 100644 index 0000000000..ad4f1d95d5 --- /dev/null +++ b/test/integration/simple-deployment-recursive-task/00-teststep.yaml @@ -0,0 +1,4 @@ +apiVersion: kuttl.dev/v1 +kind: TestStep +commands: + - script: kubectl annotate ns $NAMESPACE keptn.sh/lifecycle-toolkit='enabled' diff --git a/test/integration/simple-deployment-recursive-task/01-assert.yaml b/test/integration/simple-deployment-recursive-task/01-assert.yaml new file mode 100644 index 0000000000..d02b42e28c --- /dev/null +++ b/test/integration/simple-deployment-recursive-task/01-assert.yaml @@ -0,0 +1,96 @@ +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnWorkload +metadata: + name: waiter-waiter + +--- + +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnWorkloadInstance +metadata: + name: waiter-waiter-0.4 +status: + currentPhase: Completed + deploymentStatus: Succeeded + postDeploymentEvaluationStatus: Succeeded + postDeploymentStatus: Succeeded + postDeploymentTaskStatus: + - status: Succeeded + definitionName: pre-deployment-parent + preDeploymentEvaluationStatus: Succeeded + preDeploymentStatus: Succeeded + preDeploymentTaskStatus: + - status: Succeeded + definitionName: pre-deployment-hello + +--- + +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnApp +metadata: + name: waiter + +--- + +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnAppVersion +metadata: + name: waiter-1b899b6ce1-6b86b273 + +--- +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + batch.kubernetes.io/job-tracking: "" + keptn.sh/app: waiter + keptn.sh/version: "0.4" + keptn.sh/workload: waiter-waiter +spec: + template: + spec: + containers: + - env: + - name: DATA + value: '{"data":"myotherdata","other":"data","user":"myotheruser"}' + - name: CONTEXT + value: '{"workloadName":"waiter-waiter","appName":"waiter","appVersion":"","workloadVersion":"0.4","taskType":"","objectType":"Workload"}' + - name: SCRIPT + value: /var/data/function.ts + name: keptn-function-runner +status: + succeeded: 1 + +--- +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + batch.kubernetes.io/job-tracking: "" + keptn.sh/app: waiter + keptn.sh/version: "0.4" + keptn.sh/workload: waiter-waiter + ownerReferences: + - apiVersion: lifecycle.keptn.sh/v1alpha3 + blockOwnerDeletion: true + controller: true + kind: KeptnTask +spec: + template: + spec: + containers: + - env: + - name: DATA + value: '{"data":"mydata","other":"data","user":"myuser"}' + - name: CONTEXT + value: '{"workloadName":"waiter-waiter","appName":"waiter","appVersion":"","workloadVersion":"0.4","taskType":"","objectType":"Workload"}' + - name: SECURE_DATA + valueFrom: + secretKeyRef: + key: SECURE_DATA + name: mysecret + - name: SCRIPT + value: /var/data/function.ts + name: keptn-function-runner +status: + succeeded: 1 From f7abcb096838c0071c07b95bf6ff938de9be4975 Mon Sep 17 00:00:00 2001 From: odubajDT <93584209+odubajDT@users.noreply.github.com> Date: Fri, 26 May 2023 11:48:05 +0200 Subject: [PATCH 53/62] chore: standardize generation of resource names (#1472) Signed-off-by: odubajDT --- .../apis/lifecycle/v1alpha3/common/common.go | 16 ++- .../lifecycle/v1alpha3/common/common_test.go | 35 ++++- .../apis/lifecycle/v1alpha3/keptnapp_types.go | 6 +- .../lifecycle/v1alpha3/keptnworkload_types.go | 3 +- operator/common/common.go | 48 +++++++ operator/common/common_test.go | 100 +++++++++++++++ .../controllers/common/helperfunctions.go | 4 - .../common/helperfunctions_test.go | 53 -------- .../lifecycle/keptnapp/controller.go | 9 +- .../lifecycle/keptnapp/controller_test.go | 5 +- .../reconcile_workloadsstate.go | 6 +- .../lifecycle/keptntask/job_utils.go | 5 +- .../pod_mutator/pod_mutating_webhook.go | 71 ++++------ .../pod_mutator/pod_mutating_webhook_test.go | 121 ++++-------------- scheduler/pkg/klcpermit/workflow_manager.go | 52 +++++++- .../pkg/klcpermit/workflow_manager_test.go | 93 ++++++++++++++ 16 files changed, 393 insertions(+), 234 deletions(-) create mode 100644 operator/common/common.go create mode 100644 operator/common/common_test.go diff --git a/operator/apis/lifecycle/v1alpha3/common/common.go b/operator/apis/lifecycle/v1alpha3/common/common.go index 51e33a2318..1fee458789 100644 --- a/operator/apis/lifecycle/v1alpha3/common/common.go +++ b/operator/apis/lifecycle/v1alpha3/common/common.go @@ -3,10 +3,10 @@ package common import ( "crypto/sha256" "encoding/hex" - "fmt" "math/rand" "strconv" + operatorcommon "github.com/keptn/lifecycle-toolkit/operator/common" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) @@ -30,10 +30,7 @@ const CreateAppEvalSpanName = "create_%s_app_evaluation" const CreateWorkloadEvalSpanName = "create_%s_deployment_evaluation" const AppTypeAnnotation = "keptn.sh/app-type" -const MaxAppNameLength = 25 -const MaxWorkloadNameLength = 25 -const MaxTaskNameLength = 25 -const MaxVersionLength = 12 +const MinKLTNameLen = 80 const MaxK8sObjectLength = 253 type AppType string @@ -178,12 +175,17 @@ const ( func GenerateTaskName(checkType CheckType, taskName string) string { randomId := rand.Intn(99_999-10_000) + 10000 - return fmt.Sprintf("%s-%s-%d", checkType, TruncateString(taskName, 32), randomId) + return operatorcommon.CreateResourceName(MaxK8sObjectLength, MinKLTNameLen, string(checkType), taskName, strconv.Itoa(randomId)) +} + +func GenerateJobName(taskName string) string { + randomId := rand.Intn(99_999-10_000) + 10000 + return operatorcommon.CreateResourceName(MaxK8sObjectLength, MinKLTNameLen, taskName, strconv.Itoa(randomId)) } func GenerateEvaluationName(checkType CheckType, evalName string) string { randomId := rand.Intn(99_999-10_000) + 10000 - return fmt.Sprintf("%s-%s-%d", checkType, TruncateString(evalName, 27), randomId) + return operatorcommon.CreateResourceName(MaxK8sObjectLength, MinKLTNameLen, string(checkType), evalName, strconv.Itoa(randomId)) } // MergeMaps merges two maps into a new map. If a key exists in both maps, the diff --git a/operator/apis/lifecycle/v1alpha3/common/common_test.go b/operator/apis/lifecycle/v1alpha3/common/common_test.go index 9f40c417e4..293f3f9251 100644 --- a/operator/apis/lifecycle/v1alpha3/common/common_test.go +++ b/operator/apis/lifecycle/v1alpha3/common/common_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/require" ) +const ExtraLongName = "loooooooooooooooooooooo00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ooooooo01234567891234567890123456789" + func TestKeptnState_IsCompleted(t *testing.T) { tests := []struct { State KeptnState @@ -292,8 +294,8 @@ func Test_GenerateTaskName(t *testing.T) { }, { Check: PreDeploymentCheckType, - Name: "loooooooooooooooooooooooooooooooooooooong_name", - Want: "pre-looooooooooooooooooooooooooooooo-", + Name: ExtraLongName, + Want: "pre-loooooooooooooooooooooo00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ooooooo0123456789123456-", }, } for _, tt := range tests { @@ -321,8 +323,8 @@ func Test_GenerateEvaluationName(t *testing.T) { }, { Check: PreDeploymentEvaluationCheckType, - Name: "loooooooooooooooooooooooooooooooooooooong_name", - Want: "pre-eval-loooooooooooooooooooooooooo-", + Name: ExtraLongName, + Want: "pre-eval-loooooooooooooooooooooo00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ooooooo01234567891-", }, } for _, tt := range tests { @@ -332,6 +334,31 @@ func Test_GenerateEvaluationName(t *testing.T) { } } +func Test_GenerateJobName(t *testing.T) { + tests := []struct { + Name string + Want string + }{ + { + Name: "short-name", + Want: "short-name-", + }, + { + Name: "", + Want: "-", + }, + { + Name: ExtraLongName, + Want: "loooooooooooooooooooooo00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ooooooo01234567891234567890-", + }, + } + for _, tt := range tests { + t.Run("", func(t *testing.T) { + require.True(t, strings.HasPrefix(GenerateJobName(tt.Name), tt.Want)) + }) + } +} + func Test_MergeMaps(t *testing.T) { tests := []struct { In1 map[string]string diff --git a/operator/apis/lifecycle/v1alpha3/keptnapp_types.go b/operator/apis/lifecycle/v1alpha3/keptnapp_types.go index 4e0ac69875..ede59936bc 100644 --- a/operator/apis/lifecycle/v1alpha3/keptnapp_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptnapp_types.go @@ -17,10 +17,8 @@ limitations under the License. package v1alpha3 import ( - "fmt" - "strings" - "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" + operatorcommon "github.com/keptn/lifecycle-toolkit/operator/common" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -104,7 +102,7 @@ func init() { } func (a KeptnApp) GetAppVersionName() string { - return strings.ToLower(fmt.Sprintf("%s-%s-%s", a.Name, a.Spec.Version, common.Hash(a.Generation))) + return operatorcommon.CreateResourceName(common.MaxK8sObjectLength, common.MinKLTNameLen, a.Name, a.Spec.Version, common.Hash(a.Generation)) } func (a KeptnApp) SetSpanAttributes(span trace.Span) { diff --git a/operator/apis/lifecycle/v1alpha3/keptnworkload_types.go b/operator/apis/lifecycle/v1alpha3/keptnworkload_types.go index 6b6b31ae6c..f550f7179f 100644 --- a/operator/apis/lifecycle/v1alpha3/keptnworkload_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptnworkload_types.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" + operatorcommon "github.com/keptn/lifecycle-toolkit/operator/common" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -102,7 +103,7 @@ func init() { } func (w KeptnWorkload) GetWorkloadInstanceName() string { - return strings.ToLower(w.Name + "-" + w.Spec.Version) + return operatorcommon.CreateResourceName(common.MaxK8sObjectLength, common.MinKLTNameLen, w.Name, w.Spec.Version) } func (w KeptnWorkload) SetSpanAttributes(span trace.Span) { diff --git a/operator/common/common.go b/operator/common/common.go new file mode 100644 index 0000000000..1dde0f56e0 --- /dev/null +++ b/operator/common/common.go @@ -0,0 +1,48 @@ +package operatorcommon + +import "strings" + +// CreateResourceName is a function that concatenates the parts from the +// input and checks, if the resulting string matches the maxLen condition. +// If it does not match, it reduces the subparts, starting with the first +// one (but leaving its length at least in minSubstrLen so it's not deleted +// completely) adn continuing with the rest if needed. +// Let's take WorkloadInstance as an example (3 parts: app, workload, version). +// First the app name is reduced if needed (only to minSubstrLen), +// afterwards workload and the version is not reduced at all. This pattern is +// chosen to not reduce only one part of the name (that can be completely gone +// afterwards), but to include all of the parts in the resulting string. +func CreateResourceName(maxLen int, minSubstrLen int, str ...string) string { + // if the minSubstrLen is too long for the number of parts, + // needs to be reduced + for len(str)*minSubstrLen > maxLen { + minSubstrLen = minSubstrLen / 2 + } + // looping through the subparts and checking if the resulting string + // matches the maxLen condition + for i := 0; i < len(str)-1; i++ { + newStr := strings.Join(str, "-") + if len(newStr) > maxLen { + // if the part is already smaller than the minSubstrLen, + // this part cannot be reduced anymore, so we continue + if len(str[i]) <= minSubstrLen { + continue + } + // part needs to be reduced + cut := len(newStr) - maxLen + // if the needed reduction is bigger than the allowed + // reduction on the part, it's reduced to the minimum + if cut > len(str[i])-minSubstrLen { + str[i] = str[i][:minSubstrLen] + } else { + // the needed reduction can be completed fully on this + // part, so it's reduced accordingly + str[i] = str[i][:len(str[i])-cut] + } + } else { + return strings.ToLower(newStr) + } + } + + return strings.ToLower(strings.Join(str, "-")) +} diff --git a/operator/common/common_test.go b/operator/common/common_test.go new file mode 100644 index 0000000000..7b92bbd935 --- /dev/null +++ b/operator/common/common_test.go @@ -0,0 +1,100 @@ +package operatorcommon + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_CreateResourceName(t *testing.T) { + tests := []struct { + Name string + Input []string + Max int + Min int + Want string + }{ + { + Name: "parts not exceeding max, not min", + Input: []string{ + "str1", + "str2", + "str3", + }, + Max: 20, + Min: 5, + Want: "str1-str2-str3", + }, + { + Name: "1 part exceeding max", + Input: []string{ + "str1111111111111111111111", + "str2", + "str3", + }, + Max: 20, + Min: 5, + Want: "str1111111-str2-str3", + }, + { + Name: "2 part exceeding max", + Input: []string{ + "str1", + "str222222222222222222222222", + "str3", + }, + Max: 20, + Min: 5, + Want: "str1-str2222222-str3", + }, + { + Name: "1 and 2 part exceeding max", + Input: []string{ + "str111111111111111111111", + "str22222222", + "str3", + }, + Max: 20, + Min: 5, + Want: "str11-str222222-str3", + }, + { + Name: "1 and 2 part exceeding max, min needs to be reduced", + Input: []string{ + "str111111111111111111111", + "str22222222", + "str3", + }, + Max: 20, + Min: 10, + Want: "str11-str222222-str3", + }, + { + Name: "1 and 2 part exceeding max, min needs to be reduced", + Input: []string{ + "str111111111111111111111", + "str22222222", + "str3", + }, + Max: 20, + Min: 20, + Want: "str11-str222222-str3", + }, + { + Name: "1 and 2 part exceeding max, min needs to be reduced", + Input: []string{ + "str111111111111111111111", + "str22222222", + "str3", + }, + Max: 20, + Min: 100, + Want: "str111-str22222-str3", + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + require.Equal(t, tt.Want, CreateResourceName(tt.Max, tt.Min, tt.Input...)) + }) + } +} diff --git a/operator/controllers/common/helperfunctions.go b/operator/controllers/common/helperfunctions.go index bdc6792d89..3419fa6274 100644 --- a/operator/controllers/common/helperfunctions.go +++ b/operator/controllers/common/helperfunctions.go @@ -30,10 +30,6 @@ func GetItemStatus(name string, instanceStatus []klcv1alpha3.ItemStatus) klcv1al } } -func GetAppVersionName(namespace string, appName string, version string) types.NamespacedName { - return types.NamespacedName{Namespace: namespace, Name: appName + "-" + version} -} - // GetOldStatus retrieves the state of the task/evaluation func GetOldStatus(name string, statuses []klcv1alpha3.ItemStatus) apicommon.KeptnState { var oldstatus apicommon.KeptnState diff --git a/operator/controllers/common/helperfunctions_test.go b/operator/controllers/common/helperfunctions_test.go index 469082527d..33126e8810 100644 --- a/operator/controllers/common/helperfunctions_test.go +++ b/operator/controllers/common/helperfunctions_test.go @@ -8,7 +8,6 @@ import ( apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" "github.com/stretchr/testify/require" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -69,58 +68,6 @@ func Test_GetItemStatus(t *testing.T) { } } -func Test_GetAppVersionName(t *testing.T) { - tests := []struct { - namespace string - appName string - version string - want types.NamespacedName - }{ - { - namespace: "namespace", - appName: "name", - version: "version", - want: types.NamespacedName{ - Namespace: "namespace", - Name: "name-version", - }, - }, - { - namespace: "", - appName: "name", - version: "version", - want: types.NamespacedName{ - Namespace: "", - Name: "name-version", - }, - }, - { - namespace: "namespace", - appName: "", - version: "version", - want: types.NamespacedName{ - Namespace: "namespace", - Name: "-version", - }, - }, - { - namespace: "namespace", - appName: "name", - version: "", - want: types.NamespacedName{ - Namespace: "namespace", - Name: "name-", - }, - }, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - require.Equal(t, GetAppVersionName(tt.namespace, tt.appName, tt.version), tt.want) - }) - } -} - func Test_GetOldStatus(t *testing.T) { tests := []struct { statuses []klcv1alpha3.ItemStatus diff --git a/operator/controllers/lifecycle/keptnapp/controller.go b/operator/controllers/lifecycle/keptnapp/controller.go index 69a30624e0..4f85e9a75c 100644 --- a/operator/controllers/lifecycle/keptnapp/controller.go +++ b/operator/controllers/lifecycle/keptnapp/controller.go @@ -23,6 +23,7 @@ import ( "github.com/go-logr/logr" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" + operatorcommon "github.com/keptn/lifecycle-toolkit/operator/common" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" "go.opentelemetry.io/otel" @@ -159,9 +160,6 @@ func (r *KeptnAppReconciler) createAppVersion(ctx context.Context, app *klcv1alp } appVersion := app.GenerateAppVersion(previousVersion, traceContextCarrier) - if len(appVersion.ObjectMeta.Name) > common.MaxK8sObjectLength { - appVersion.ObjectMeta.Name = common.TruncateString(appVersion.ObjectMeta.Name, common.MaxK8sObjectLength) - } appVersion.Spec.TraceId = appTraceContextCarrier err := controllerutil.SetControllerReference(app, &appVersion, r.Scheme) if err != nil { @@ -188,9 +186,10 @@ func (r *KeptnAppReconciler) deprecateAppVersions(ctx context.Context, app *klcv lastResultErr = nil for i := app.Generation - 1; i > 0; i-- { deprecatedAppVersion := &klcv1alpha3.KeptnAppVersion{} - if err := r.Get(ctx, types.NamespacedName{Namespace: app.Namespace, Name: app.Name + "-" + app.Spec.Version + "-" + common.Hash(i)}, deprecatedAppVersion); err != nil { + appVersionName := operatorcommon.CreateResourceName(common.MaxK8sObjectLength, common.MinKLTNameLen, app.Name, app.Spec.Version, common.Hash(i)) + if err := r.Get(ctx, types.NamespacedName{Namespace: app.Namespace, Name: appVersionName}, deprecatedAppVersion); err != nil { if !errors.IsNotFound(err) { - r.Log.Error(err, fmt.Sprintf("Could not get KeptnAppVersion: %s", app.Name+"-"+app.Spec.Version+"-"+common.Hash(i))) + r.Log.Error(err, fmt.Sprintf("Could not get KeptnAppVersion: %s", appVersionName)) lastResultErr = err } } else if !deprecatedAppVersion.Status.Status.IsDeprecated() { diff --git a/operator/controllers/lifecycle/keptnapp/controller_test.go b/operator/controllers/lifecycle/keptnapp/controller_test.go index b0566c3cd6..250ea0cdac 100644 --- a/operator/controllers/lifecycle/keptnapp/controller_test.go +++ b/operator/controllers/lifecycle/keptnapp/controller_test.go @@ -51,12 +51,15 @@ func TestKeptnAppReconciler_createAppVersionWithLongName(t *testing.T) { //nolint:gci longName := `loremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremloremax` //nolint:gci - trimmedName := `loremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustrylorem` + trimmedName := `loremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypesettingindustryloremipsumissimplydummytextoftheprintingandtypeset-version-5feceb66` app := &lfcv1alpha3.KeptnApp{ ObjectMeta: metav1.ObjectMeta{ Name: longName, }, + Spec: lfcv1alpha3.KeptnAppSpec{ + Version: "version", + }, } r, _, _ := setupReconciler() diff --git a/operator/controllers/lifecycle/keptnappversion/reconcile_workloadsstate.go b/operator/controllers/lifecycle/keptnappversion/reconcile_workloadsstate.go index 6b303e86d6..367dcbed3b 100644 --- a/operator/controllers/lifecycle/keptnappversion/reconcile_workloadsstate.go +++ b/operator/controllers/lifecycle/keptnappversion/reconcile_workloadsstate.go @@ -5,6 +5,7 @@ import ( klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" + operatorcommon "github.com/keptn/lifecycle-toolkit/operator/common" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -21,7 +22,7 @@ func (r *KeptnAppVersionReconciler) reconcileWorkloads(ctx context.Context, appV workloadInstanceList, err := r.getWorkloadInstanceList(ctx, appVersion.Namespace, appVersion.Spec.AppName) if err != nil { - r.Log.Error(err, "Could not get workloads") + r.Log.Error(err, "Could not get workloads of appVersion '%s'", appVersion.Name) return apicommon.StateUnknown, r.handleUnaccessibleWorkloadInstanceList(ctx, appVersion) } @@ -32,7 +33,6 @@ func (r *KeptnAppVersionReconciler) reconcileWorkloads(ctx context.Context, appV found := false instanceName := getWorkloadInstanceName(appVersion.Spec.AppName, w.Name, w.Version) for _, i := range workloadInstanceList.Items { - r.Log.Info("No WorkloadInstance found for KeptnApp " + appVersion.Spec.AppName) // additional filtering of the retrieved WIs is needed, as the List() method retrieves all // WIs for a specific KeptnApp. The result can contain also WIs, that are not part of the // latest KeptnAppVersion, so it's needed to double check them @@ -88,5 +88,5 @@ func (r *KeptnAppVersionReconciler) handleUnaccessibleWorkloadInstanceList(ctx c } func getWorkloadInstanceName(appName string, workloadName string, version string) string { - return appName + "-" + workloadName + "-" + version + return operatorcommon.CreateResourceName(apicommon.MaxK8sObjectLength, apicommon.MinKLTNameLen, appName, workloadName, version) } diff --git a/operator/controllers/lifecycle/keptntask/job_utils.go b/operator/controllers/lifecycle/keptntask/job_utils.go index c49efbb77a..61eadf5238 100644 --- a/operator/controllers/lifecycle/keptntask/job_utils.go +++ b/operator/controllers/lifecycle/keptntask/job_utils.go @@ -3,7 +3,6 @@ package keptntask import ( "context" "fmt" - "math/rand" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" @@ -108,11 +107,9 @@ func setupTaskContext(task *klcv1alpha3.KeptnTask) klcv1alpha3.TaskContext { } func (r *KeptnTaskReconciler) generateJob(ctx context.Context, task *klcv1alpha3.KeptnTask, definition *klcv1alpha3.KeptnTaskDefinition, request ctrl.Request) (*batchv1.Job, error) { - randomId := rand.Intn(99999-10000) + 10000 - jobId := fmt.Sprintf("klc-%s-%d", apicommon.TruncateString(task.Name, apicommon.MaxTaskNameLength), randomId) job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ - Name: jobId, + Name: apicommon.GenerateJobName(task.Name), Namespace: task.Namespace, Labels: task.Labels, Annotations: task.CreateKeptnAnnotations(), diff --git a/operator/webhooks/pod_mutator/pod_mutating_webhook.go b/operator/webhooks/pod_mutator/pod_mutating_webhook.go index 0d7d4a65ef..447a4dbc77 100644 --- a/operator/webhooks/pod_mutator/pod_mutating_webhook.go +++ b/operator/webhooks/pod_mutator/pod_mutating_webhook.go @@ -14,6 +14,7 @@ import ( klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/semconv" + operatorcommon "github.com/keptn/lifecycle-toolkit/operator/common" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" @@ -83,21 +84,12 @@ func (a *PodMutatingWebhook) Handle(ctx context.Context, req admission.Request) logger.Info(fmt.Sprintf("Pod annotations: %v", pod.Annotations)) - podIsAnnotated, err := a.isPodAnnotated(pod) + podIsAnnotated := a.isPodAnnotated(pod) logger.Info("Checked if pod is annotated.") - if err != nil { - span.SetStatus(codes.Error, InvalidAnnotationMessage) - return admission.Errored(http.StatusBadRequest, err) - } - if !podIsAnnotated { logger.Info("Pod is not annotated, check for parent annotations...") - podIsAnnotated, err = a.copyAnnotationsIfParentAnnotated(ctx, &req, pod) - if err != nil { - span.SetStatus(codes.Error, InvalidAnnotationMessage) - return admission.Errored(http.StatusBadRequest, err) - } + podIsAnnotated = a.copyAnnotationsIfParentAnnotated(ctx, &req, pod) } if podIsAnnotated { @@ -105,11 +97,7 @@ func (a *PodMutatingWebhook) Handle(ctx context.Context, req admission.Request) pod.Spec.SchedulerName = "keptn-scheduler" logger.Info("Annotations", "annotations", pod.Annotations) - isAppAnnotationPresent, err := a.isAppAnnotationPresent(pod) - if err != nil { - span.SetStatus(codes.Error, InvalidAnnotationMessage) - return admission.Errored(http.StatusBadRequest, err) - } + isAppAnnotationPresent := a.isAppAnnotationPresent(pod) semconv.AddAttributeFromAnnotations(span, pod.Annotations) logger.Info("Attributes from annotations set") @@ -144,13 +132,9 @@ func (a *PodMutatingWebhook) InjectDecoder(d *admission.Decoder) error { return nil } -func (a *PodMutatingWebhook) isPodAnnotated(pod *corev1.Pod) (bool, error) { - workload, gotWorkloadAnnotation := getLabelOrAnnotation(&pod.ObjectMeta, apicommon.WorkloadAnnotation, apicommon.K8sRecommendedWorkloadAnnotations) - version, gotVersionAnnotation := getLabelOrAnnotation(&pod.ObjectMeta, apicommon.VersionAnnotation, apicommon.K8sRecommendedVersionAnnotations) - - if len(workload) > apicommon.MaxWorkloadNameLength || len(version) > apicommon.MaxVersionLength { - return false, ErrTooLongAnnotations - } +func (a *PodMutatingWebhook) isPodAnnotated(pod *corev1.Pod) bool { + _, gotWorkloadAnnotation := getLabelOrAnnotation(&pod.ObjectMeta, apicommon.WorkloadAnnotation, apicommon.K8sRecommendedWorkloadAnnotations) + _, gotVersionAnnotation := getLabelOrAnnotation(&pod.ObjectMeta, apicommon.VersionAnnotation, apicommon.K8sRecommendedVersionAnnotations) if gotWorkloadAnnotation { if !gotVersionAnnotation { @@ -159,28 +143,28 @@ func (a *PodMutatingWebhook) isPodAnnotated(pod *corev1.Pod) (bool, error) { } pod.Annotations[apicommon.VersionAnnotation] = a.calculateVersion(pod) } - return true, nil + return true } - return false, nil + return false } -func (a *PodMutatingWebhook) copyAnnotationsIfParentAnnotated(ctx context.Context, req *admission.Request, pod *corev1.Pod) (bool, error) { +func (a *PodMutatingWebhook) copyAnnotationsIfParentAnnotated(ctx context.Context, req *admission.Request, pod *corev1.Pod) bool { podOwner := a.getOwnerReference(&pod.ObjectMeta) if podOwner.UID == "" { - return false, nil + return false } switch podOwner.Kind { case "ReplicaSet": rs := &appsv1.ReplicaSet{} if err := a.Client.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: podOwner.Name}, rs); err != nil { - return false, nil + return false } a.Log.Info("Done fetching RS") rsOwner := a.getOwnerReference(&rs.ObjectMeta) if rsOwner.UID == "" { - return false, nil + return false } if rsOwner.Kind == "Rollout" { @@ -197,13 +181,13 @@ func (a *PodMutatingWebhook) copyAnnotationsIfParentAnnotated(ctx context.Contex ds := &appsv1.DaemonSet{} return a.fetchParentObjectAndCopyLabels(ctx, podOwner.Name, req.Namespace, pod, ds) default: - return false, nil + return false } } -func (a *PodMutatingWebhook) fetchParentObjectAndCopyLabels(ctx context.Context, name string, namespace string, pod *corev1.Pod, objectContainer client.Object) (bool, error) { +func (a *PodMutatingWebhook) fetchParentObjectAndCopyLabels(ctx context.Context, name string, namespace string, pod *corev1.Pod, objectContainer client.Object) bool { if err := a.Client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, objectContainer); err != nil { - return false, nil + return false } objectContainerMetaData := metav1.ObjectMeta{ Labels: objectContainer.GetLabels(), @@ -212,7 +196,7 @@ func (a *PodMutatingWebhook) fetchParentObjectAndCopyLabels(ctx context.Context, return a.copyResourceLabelsIfPresent(&objectContainerMetaData, pod) } -func (a *PodMutatingWebhook) copyResourceLabelsIfPresent(sourceResource *metav1.ObjectMeta, targetPod *corev1.Pod) (bool, error) { +func (a *PodMutatingWebhook) copyResourceLabelsIfPresent(sourceResource *metav1.ObjectMeta, targetPod *corev1.Pod) bool { var workloadName, appName, version, preDeploymentChecks, postDeploymentChecks, preEvaluationChecks, postEvaluationChecks string var gotWorkloadName, gotVersion bool @@ -224,10 +208,6 @@ func (a *PodMutatingWebhook) copyResourceLabelsIfPresent(sourceResource *metav1. preEvaluationChecks, _ = getLabelOrAnnotation(sourceResource, apicommon.PreDeploymentEvaluationAnnotation, "") postEvaluationChecks, _ = getLabelOrAnnotation(sourceResource, apicommon.PostDeploymentEvaluationAnnotation, "") - if len(workloadName) > apicommon.MaxWorkloadNameLength || len(version) > apicommon.MaxVersionLength { - return false, ErrTooLongAnnotations - } - if len(targetPod.Annotations) == 0 { targetPod.Annotations = make(map[string]string) } @@ -247,26 +227,23 @@ func (a *PodMutatingWebhook) copyResourceLabelsIfPresent(sourceResource *metav1. setMapKey(targetPod.Annotations, apicommon.PreDeploymentEvaluationAnnotation, preEvaluationChecks) setMapKey(targetPod.Annotations, apicommon.PostDeploymentEvaluationAnnotation, postEvaluationChecks) - return true, nil + return true } - return false, nil + return false } -func (a *PodMutatingWebhook) isAppAnnotationPresent(pod *corev1.Pod) (bool, error) { - app, gotAppAnnotation := getLabelOrAnnotation(&pod.ObjectMeta, apicommon.AppAnnotation, apicommon.K8sRecommendedAppAnnotations) +func (a *PodMutatingWebhook) isAppAnnotationPresent(pod *corev1.Pod) bool { + _, gotAppAnnotation := getLabelOrAnnotation(&pod.ObjectMeta, apicommon.AppAnnotation, apicommon.K8sRecommendedAppAnnotations) if gotAppAnnotation { - if len(app) > apicommon.MaxAppNameLength { - return false, ErrTooLongAnnotations - } - return true, nil + return true } if len(pod.Annotations) == 0 { pod.Annotations = make(map[string]string) } pod.Annotations[apicommon.AppAnnotation], _ = getLabelOrAnnotation(&pod.ObjectMeta, apicommon.WorkloadAnnotation, apicommon.K8sRecommendedWorkloadAnnotations) - return false, nil + return false } func (a *PodMutatingWebhook) calculateVersion(pod *corev1.Pod) string { @@ -465,7 +442,7 @@ func (a *PodMutatingWebhook) generateAppCreationRequest(ctx context.Context, pod func (a *PodMutatingWebhook) getWorkloadName(pod *corev1.Pod) string { workloadName, _ := getLabelOrAnnotation(&pod.ObjectMeta, apicommon.WorkloadAnnotation, apicommon.K8sRecommendedWorkloadAnnotations) applicationName, _ := getLabelOrAnnotation(&pod.ObjectMeta, apicommon.AppAnnotation, apicommon.K8sRecommendedAppAnnotations) - return strings.ToLower(applicationName + "-" + workloadName) + return operatorcommon.CreateResourceName(apicommon.MaxK8sObjectLength, apicommon.MinKLTNameLen, applicationName, workloadName) } func (a *PodMutatingWebhook) getAppName(pod *corev1.Pod) string { diff --git a/operator/webhooks/pod_mutator/pod_mutating_webhook_test.go b/operator/webhooks/pod_mutator/pod_mutating_webhook_test.go index af4da8548e..1babb0abd2 100644 --- a/operator/webhooks/pod_mutator/pod_mutating_webhook_test.go +++ b/operator/webhooks/pod_mutator/pod_mutating_webhook_test.go @@ -379,24 +379,8 @@ func TestPodMutatingWebhook_isPodAnnotated(t *testing.T) { fields fields args args want bool - wantErr bool wantedPod *corev1.Pod }{ - { - name: "Test error when workload name is too long", - args: args{ - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - apicommon.AppAnnotation: "SOME-APP-NAME-ANNOTATION", - apicommon.WorkloadAnnotation: "workload-name-that-is-too-loooooooooooooooooooooooooooooooooooooooooooooooooong", - }, - }, - }, - }, - want: false, - wantErr: true, - }, { name: "Test return true when pod has workload annotation", args: args{ @@ -408,8 +392,7 @@ func TestPodMutatingWebhook_isPodAnnotated(t *testing.T) { }, }, }, - want: true, - wantErr: false, + want: true, }, { name: "Test return true and initialize annotations when labels are set", @@ -429,8 +412,7 @@ func TestPodMutatingWebhook_isPodAnnotated(t *testing.T) { }, }, }, - want: true, - wantErr: false, + want: true, wantedPod: &corev1.Pod{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ @@ -460,8 +442,7 @@ func TestPodMutatingWebhook_isPodAnnotated(t *testing.T) { }, }, }, - want: false, - wantErr: false, + want: false, }, } for _, tt := range tests { @@ -473,11 +454,7 @@ func TestPodMutatingWebhook_isPodAnnotated(t *testing.T) { Recorder: tt.fields.Recorder, Log: tt.fields.Log, } - got, err := a.isPodAnnotated(tt.args.pod) - if (err != nil) != tt.wantErr { - t.Errorf("isPodAnnotated() error = %v, wantErr %v", err, tt.wantErr) - return - } + got := a.isPodAnnotated(tt.args.pod) if got != tt.want { t.Errorf("isPodAnnotated() got = %v, want %v", got, tt.want) } @@ -571,11 +548,10 @@ func TestPodMutatingWebhook_copyAnnotationsIfParentAnnotated(t *testing.T) { pod *corev1.Pod } tests := []struct { - name string - fields fields - args args - want bool - wantErr bool + name string + fields fields + args args + want bool }{ { name: "Test that nothing happens if owner UID is pod UID", @@ -596,8 +572,7 @@ func TestPodMutatingWebhook_copyAnnotationsIfParentAnnotated(t *testing.T) { }, }, }, - want: false, - wantErr: false, + want: false, }, { name: "Test fetching of replicaset owner of pod and deployment owner of replicaset", @@ -625,8 +600,7 @@ func TestPodMutatingWebhook_copyAnnotationsIfParentAnnotated(t *testing.T) { }, }, }, - want: false, - wantErr: false, + want: false, }, { name: "Test fetching of statefulset owner of pod", @@ -654,8 +628,7 @@ func TestPodMutatingWebhook_copyAnnotationsIfParentAnnotated(t *testing.T) { }, }, }, - want: false, - wantErr: false, + want: false, }, { name: "Test fetching of daemonset owner of pod", @@ -683,8 +656,7 @@ func TestPodMutatingWebhook_copyAnnotationsIfParentAnnotated(t *testing.T) { }, }, }, - want: false, - wantErr: false, + want: false, }, { name: "Test that method returns without doing anything when we get a pod with replicaset without owner", @@ -712,8 +684,7 @@ func TestPodMutatingWebhook_copyAnnotationsIfParentAnnotated(t *testing.T) { }, }, }, - want: false, - wantErr: false, + want: false, }, } for _, tt := range tests { @@ -725,11 +696,7 @@ func TestPodMutatingWebhook_copyAnnotationsIfParentAnnotated(t *testing.T) { Recorder: tt.fields.Recorder, Log: tt.fields.Log, } - got, err := a.copyAnnotationsIfParentAnnotated(tt.args.ctx, tt.args.req, tt.args.pod) - if (err != nil) != tt.wantErr { - t.Errorf("copyAnnotationsIfParentAnnotated() error = %v, wantErr %v", err, tt.wantErr) - return - } + got := a.copyAnnotationsIfParentAnnotated(tt.args.ctx, tt.args.req, tt.args.pod) if got != tt.want { t.Errorf("copyAnnotationsIfParentAnnotated() got = %v, want %v", got, tt.want) } @@ -754,7 +721,6 @@ func TestPodMutatingWebhook_copyResourceLabelsIfPresent(t *testing.T) { fields fields args args want bool - wantErr bool wantedPod *corev1.Pod }{ { @@ -779,8 +745,7 @@ func TestPodMutatingWebhook_copyResourceLabelsIfPresent(t *testing.T) { Status: corev1.PodStatus{}, }, }, - want: true, - wantErr: false, + want: true, wantedPod: &corev1.Pod{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ @@ -813,8 +778,7 @@ func TestPodMutatingWebhook_copyResourceLabelsIfPresent(t *testing.T) { }, targetPod: &corev1.Pod{}, }, - want: true, - wantErr: false, + want: true, wantedPod: &corev1.Pod{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ @@ -857,8 +821,7 @@ func TestPodMutatingWebhook_copyResourceLabelsIfPresent(t *testing.T) { Status: corev1.PodStatus{}, }, }, - want: true, - wantErr: false, + want: true, wantedPod: &corev1.Pod{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ @@ -882,20 +845,6 @@ func TestPodMutatingWebhook_copyResourceLabelsIfPresent(t *testing.T) { Status: corev1.PodStatus{}, }, }, - { - name: "Test that error is return with too long workload name", - args: args{ - sourceResource: &metav1.ObjectMeta{ - Name: "testSourceObject", - Labels: map[string]string{ - apicommon.WorkloadAnnotation: "some-workload-name-that-is-very-looooooooooooooooooooooong", - }, - }, - targetPod: &corev1.Pod{}, - }, - want: false, - wantErr: true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -906,11 +855,7 @@ func TestPodMutatingWebhook_copyResourceLabelsIfPresent(t *testing.T) { Recorder: tt.fields.Recorder, Log: tt.fields.Log, } - got, err := a.copyResourceLabelsIfPresent(tt.args.sourceResource, tt.args.targetPod) - if (err != nil) != tt.wantErr { - t.Errorf("copyResourceLabelsIfPresent() error = %v, wantErr %v", err, tt.wantErr) - return - } + got := a.copyResourceLabelsIfPresent(tt.args.sourceResource, tt.args.targetPod) if got != tt.want { t.Errorf("copyResourceLabelsIfPresent() got = %v, want %v", got, tt.want) } @@ -937,7 +882,6 @@ func TestPodMutatingWebhook_isAppAnnotationPresent(t *testing.T) { fields fields args args want bool - wantErr bool wantedPod *corev1.Pod }{ { @@ -951,30 +895,14 @@ func TestPodMutatingWebhook_isAppAnnotationPresent(t *testing.T) { }, }, }, - want: true, - wantErr: false, + want: true, }, { name: "Test return false when app annotation is not present", args: args{ pod: &corev1.Pod{}, }, - want: false, - wantErr: false, - }, - { - name: "Test return error when app annotation is too long", - args: args{ - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - apicommon.AppAnnotation: "some-app-annotation-that-is-very-looooooooooooooooooooong", - }, - }, - }, - }, - want: false, - wantErr: true, + want: false, }, { name: "Test that app name is copied when only workload name is present", @@ -987,8 +915,7 @@ func TestPodMutatingWebhook_isAppAnnotationPresent(t *testing.T) { }, }, }, - want: false, - wantErr: false, + want: false, wantedPod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ @@ -1008,11 +935,7 @@ func TestPodMutatingWebhook_isAppAnnotationPresent(t *testing.T) { Recorder: tt.fields.Recorder, Log: tt.fields.Log, } - got, err := a.isAppAnnotationPresent(tt.args.pod) - if (err != nil) != tt.wantErr { - t.Errorf("isAppAnnotationPresent() error = %v, wantErr %v", err, tt.wantErr) - return - } + got := a.isAppAnnotationPresent(tt.args.pod) if got != tt.want { t.Errorf("isAppAnnotationPresent() got = %v, want %v", got, tt.want) } diff --git a/scheduler/pkg/klcpermit/workflow_manager.go b/scheduler/pkg/klcpermit/workflow_manager.go index 3a9f0a77ae..e3b49b979d 100644 --- a/scheduler/pkg/klcpermit/workflow_manager.go +++ b/scheduler/pkg/klcpermit/workflow_manager.go @@ -41,6 +41,9 @@ const ( StateDeprecated KeptnState = "Deprecated" ) +const MinKLTNameLen = 80 +const MaxK8sObjectLength = 253 + const WorkloadAnnotation = "keptn.sh/workload" const VersionAnnotation = "keptn.sh/version" const AppAnnotation = "keptn.sh/app" @@ -122,14 +125,59 @@ func (sMgr *WorkloadManager) getSpan(ctx context.Context, crd *unstructured.Unst return ctx, span } +// CreateResourceName is a function that concatenates the parts from the +// input and checks, if the resulting string matches the maxLen condition. +// If it does not match, it reduces the subparts, starting with the first +// one (but leaving its length at least in minSubstrLen so it's not deleted +// completely) adn continuing with the rest if needed. +// Let's take WorkloadInstance as an example (3 parts: app, workload, version). +// First the app name is reduced if needed (only to minSubstrLen), +// afterwards workload and the version is not reduced at all. This pattern is +// chosen to not reduce only one part of the name (that can be completely gone +// afterwards), but to include all of the parts in the resulting string. +func createResourceName(maxLen int, minSubstrLen int, str ...string) string { + // if the minSubstrLen is too long for the number of parts, + // needs to be reduced + for len(str)*minSubstrLen > maxLen { + minSubstrLen = minSubstrLen / 2 + } + // looping through the subparts and checking if the resulting string + // matches the maxLen condition + for i := 0; i < len(str)-1; i++ { + newStr := strings.Join(str, "-") + if len(newStr) > maxLen { + // if the part is already smaller than the minSubstrLen, + // this part cannot be reduced anymore, so we continue + if len(str[i]) <= minSubstrLen { + continue + } + // part needs to be reduced + cut := len(newStr) - maxLen + // if the needed reduction is bigger than the allowed + // reduction on the part, it's reduced to the minimum + if cut > len(str[i])-minSubstrLen { + str[i] = str[i][:minSubstrLen] + } else { + // the needed reduction can be completed fully on this + // part, so it's reduced accordingly + str[i] = str[i][:len(str[i])-cut] + } + } else { + return strings.ToLower(newStr) + } + } + + return strings.ToLower(strings.Join(str, "-")) +} + func getCRDName(pod *corev1.Pod) string { application, _ := getLabelOrAnnotation(pod, AppAnnotation, K8sRecommendedAppAnnotations) - workloadInstance, _ := getLabelOrAnnotation(pod, WorkloadAnnotation, K8sRecommendedWorkloadAnnotations) + workload, _ := getLabelOrAnnotation(pod, WorkloadAnnotation, K8sRecommendedWorkloadAnnotations) version, versionExists := getLabelOrAnnotation(pod, VersionAnnotation, K8sRecommendedVersionAnnotations) if !versionExists { version = calculateVersion(pod) } - return application + "-" + workloadInstance + "-" + version + return createResourceName(MaxK8sObjectLength, MinKLTNameLen, application, workload, version) } func (sMgr *WorkloadManager) unbindSpan(pod *corev1.Pod) { diff --git a/scheduler/pkg/klcpermit/workflow_manager_test.go b/scheduler/pkg/klcpermit/workflow_manager_test.go index e4ac3bae3e..674568e8b5 100644 --- a/scheduler/pkg/klcpermit/workflow_manager_test.go +++ b/scheduler/pkg/klcpermit/workflow_manager_test.go @@ -89,6 +89,99 @@ func Test_getCRDName(t *testing.T) { } } +func Test_CreateResourceName(t *testing.T) { + tests := []struct { + Name string + Input []string + Max int + Min int + Want string + }{ + { + Name: "parts not exceeding max, not min", + Input: []string{ + "str1", + "str2", + "str3", + }, + Max: 20, + Min: 5, + Want: "str1-str2-str3", + }, + { + Name: "1 part exceeding max", + Input: []string{ + "str1111111111111111111111", + "str2", + "str3", + }, + Max: 20, + Min: 5, + Want: "str1111111-str2-str3", + }, + { + Name: "2 part exceeding max", + Input: []string{ + "str1", + "str222222222222222222222222", + "str3", + }, + Max: 20, + Min: 5, + Want: "str1-str2222222-str3", + }, + { + Name: "1 and 2 part exceeding max", + Input: []string{ + "str111111111111111111111", + "str22222222", + "str3", + }, + Max: 20, + Min: 5, + Want: "str11-str222222-str3", + }, + { + Name: "1 and 2 part exceeding max, min needs to be reduced", + Input: []string{ + "str111111111111111111111", + "str22222222", + "str3", + }, + Max: 20, + Min: 10, + Want: "str11-str222222-str3", + }, + { + Name: "1 and 2 part exceeding max, min needs to be reduced", + Input: []string{ + "str111111111111111111111", + "str22222222", + "str3", + }, + Max: 20, + Min: 20, + Want: "str11-str222222-str3", + }, + { + Name: "1 and 2 part exceeding max, min needs to be reduced", + Input: []string{ + "str111111111111111111111", + "str22222222", + "str3", + }, + Max: 20, + Min: 100, + Want: "str111-str22222-str3", + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + require.Equal(t, tt.Want, createResourceName(tt.Max, tt.Min, tt.Input...)) + }) + } +} + func Test_getLabelOrAnnotation(t *testing.T) { tests := []struct { name string From af2268566b74b251da17dec5576af3cd03159482 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 13:36:30 +0200 Subject: [PATCH 54/62] deps: update module github.com/prometheus/common to v0.44.0 (#1452) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- metrics-operator/go.mod | 11 +++++------ metrics-operator/go.sum | 16 ++++++++-------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index 695442ec5e..a1333cc588 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -12,7 +12,7 @@ require ( github.com/open-feature/go-sdk v1.3.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 - github.com/prometheus/common v0.42.0 + github.com/prometheus/common v0.44.0 github.com/stretchr/testify v1.8.3 k8s.io/api v0.26.5 k8s.io/apiextensions-apiserver v0.26.5 @@ -70,9 +70,8 @@ require ( github.com/onsi/ginkgo/v2 v2.8.1 // indirect github.com/onsi/gomega v1.27.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cobra v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -94,10 +93,10 @@ require ( go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.7.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index 54a43f5cc1..e196ee858d 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -319,13 +319,13 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -335,7 +335,6 @@ github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -479,8 +478,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -541,8 +540,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= From d1279a9fe0f09177449b20d4b3fc8f0f3c10d81a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 13:36:42 +0200 Subject: [PATCH 55/62] deps: update curlimages/curl docker tag to v8.1.1 (#1455) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- test/integration/expose-keptn-metric/job-existing-metric.yaml | 4 ++-- test/integration/expose-keptn-metric/job-no-metric.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/expose-keptn-metric/job-existing-metric.yaml b/test/integration/expose-keptn-metric/job-existing-metric.yaml index fda1cc39f2..a2b210fbb4 100644 --- a/test/integration/expose-keptn-metric/job-existing-metric.yaml +++ b/test/integration/expose-keptn-metric/job-existing-metric.yaml @@ -20,7 +20,7 @@ spec: spec: containers: - name: test-prometheus - image: curlimages/curl:8.1.0 + image: curlimages/curl:8.1.1 args: - /bin/sh - -ec @@ -33,7 +33,7 @@ spec: fi exit 1 - name: test-api-endpoint - image: curlimages/curl:8.1.0 + image: curlimages/curl:8.1.1 # yamllint disable rule:line-length args: - /bin/sh diff --git a/test/integration/expose-keptn-metric/job-no-metric.yaml b/test/integration/expose-keptn-metric/job-no-metric.yaml index 0c3e6b9b6d..749f17e3c1 100644 --- a/test/integration/expose-keptn-metric/job-no-metric.yaml +++ b/test/integration/expose-keptn-metric/job-no-metric.yaml @@ -9,7 +9,7 @@ spec: spec: containers: - name: test-prometheus - image: curlimages/curl:8.1.0 + image: curlimages/curl:8.1.1 args: - /bin/sh - -ec @@ -20,7 +20,7 @@ spec: exit 1 fi - name: test-api-endpoint - image: curlimages/curl:8.1.0 + image: curlimages/curl:8.1.1 args: - /bin/sh - -ec From abf10bfaf033a1f57b8f65d3c7127dd962926ed4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 13:55:08 +0200 Subject: [PATCH 56/62] deps: update module github.com/benbjohnson/clock to v1.3.5 (#1464) Signed-off-by: Renovate Bot Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- metrics-operator/go.mod | 2 +- metrics-operator/go.sum | 4 ++-- operator/go.mod | 2 +- operator/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index a1333cc588..9744935c4c 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/DataDog/datadog-api-client-go/v2 v2.12.0 - github.com/benbjohnson/clock v1.3.4 + github.com/benbjohnson/clock v1.3.5 github.com/go-logr/logr v1.2.4 github.com/gorilla/mux v1.8.0 github.com/kelseyhightower/envconfig v1.4.0 diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index e196ee858d..0be7552008 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -59,8 +59,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.4 h1:wj3BFPrTw8yYgA1OlMqvUk95nc8OMv3cvBSF5erT2W4= -github.com/benbjohnson/clock v1.3.4/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/operator/go.mod b/operator/go.mod index 45da0c748d..7d4b6881b7 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/argoproj/argo-rollouts v1.5.0 - github.com/benbjohnson/clock v1.3.4 + github.com/benbjohnson/clock v1.3.5 github.com/go-logr/logr v1.2.4 github.com/imdario/mergo v0.3.15 github.com/kelseyhightower/envconfig v1.4.0 diff --git a/operator/go.sum b/operator/go.sum index 1a3cfaa7ca..65aea7d40f 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -43,8 +43,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/argoproj/argo-rollouts v1.5.0 h1:asKpzMuFSDGsXK5cGsbH/TGXUJD/v1mvM1D3o5Xwz34= github.com/argoproj/argo-rollouts v1.5.0/go.mod h1:OaOf+oZawsss6fy+9WEDy4IaSbwuRteBj1X2QiVfqdA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.4 h1:wj3BFPrTw8yYgA1OlMqvUk95nc8OMv3cvBSF5erT2W4= -github.com/benbjohnson/clock v1.3.4/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= From 50f7415a832f2cc4e90db5faf51f17cf471558cc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 14:03:36 +0200 Subject: [PATCH 57/62] deps: update module github.com/onsi/gomega to v1.27.7 (#1473) Signed-off-by: Renovate Bot Signed-off-by: odubajDT Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: odubajDT --- operator/go.mod | 2 +- operator/go.sum | 4 ++-- scheduler/go.mod | 2 +- scheduler/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/operator/go.mod b/operator/go.mod index 7d4b6881b7..0bf496f094 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -12,7 +12,7 @@ require ( github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230523140051-57fdcddcf73c github.com/magiconair/properties v1.8.7 github.com/onsi/ginkgo/v2 v2.9.5 - github.com/onsi/gomega v1.27.6 + github.com/onsi/gomega v1.27.7 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 github.com/stretchr/testify v1.8.3 diff --git a/operator/go.sum b/operator/go.sum index 65aea7d40f..9c8fd31910 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -236,8 +236,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/scheduler/go.mod b/scheduler/go.mod index a6cfda5da9..214198b8f3 100644 --- a/scheduler/go.mod +++ b/scheduler/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/onsi/ginkgo/v2 v2.9.5 - github.com/onsi/gomega v1.27.6 + github.com/onsi/gomega v1.27.7 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.3 go.opentelemetry.io/otel v0.20.0 diff --git a/scheduler/go.sum b/scheduler/go.sum index 5ff44c86e5..c51f9c3bce 100644 --- a/scheduler/go.sum +++ b/scheduler/go.sum @@ -272,8 +272,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU= From e65715cfe98eebfcdee599253a1e63e482773f4d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 14:04:10 +0200 Subject: [PATCH 58/62] deps: update module k8s.io/kubernetes to v1.25.10 (#1475) Signed-off-by: Renovate Bot Signed-off-by: odubajDT Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: odubajDT --- scheduler/go.mod | 6 +++--- scheduler/go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scheduler/go.mod b/scheduler/go.mod index 214198b8f3..e79c6b8eb8 100644 --- a/scheduler/go.mod +++ b/scheduler/go.mod @@ -20,7 +20,7 @@ require ( k8s.io/client-go v0.25.10 k8s.io/component-base v0.25.10 k8s.io/klog/v2 v2.100.1 - k8s.io/kubernetes v1.25.9 + k8s.io/kubernetes v1.25.10 sigs.k8s.io/controller-runtime v0.13.1 ) @@ -133,7 +133,7 @@ replace ( k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.25.10 k8s.io/code-generator => k8s.io/code-generator v0.25.10 k8s.io/component-base => k8s.io/component-base v0.25.10 - k8s.io/component-helpers => k8s.io/component-helpers v0.25.9 + k8s.io/component-helpers => k8s.io/component-helpers v0.25.10 k8s.io/controller-manager => k8s.io/controller-manager v0.25.10 k8s.io/cri-api => k8s.io/cri-api v0.25.10 k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.25.10 @@ -143,7 +143,7 @@ replace ( k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.25.10 k8s.io/kubectl => k8s.io/kubectl v0.25.10 k8s.io/kubelet => k8s.io/kubelet v0.25.10 - k8s.io/kubernetes => k8s.io/kubernetes v1.25.9 + k8s.io/kubernetes => k8s.io/kubernetes v1.25.10 k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.25.10 k8s.io/metrics => k8s.io/metrics v0.25.10 k8s.io/mount-utils => k8s.io/mount-utils v0.25.10 diff --git a/scheduler/go.sum b/scheduler/go.sum index c51f9c3bce..b6a11da39e 100644 --- a/scheduler/go.sum +++ b/scheduler/go.sum @@ -718,8 +718,8 @@ k8s.io/cloud-provider v0.25.10 h1:PKJzAjQakHS6oxkXF4ZE7cHUbx7hJu/mPEmfdXUovZU= k8s.io/cloud-provider v0.25.10/go.mod h1:sRDRQDxyQyfluYs/KxmfpyrBx7cq+x9q8olsM3+FQ2I= k8s.io/component-base v0.25.10 h1:OjsAFfzmeDqiB9rZyGOqv72KcPMNpStViwNpEg8F0/k= k8s.io/component-base v0.25.10/go.mod h1:cWsBkPnnIX6v7WDjCs72P8q3l0b00KIDgyfcjLXT/eQ= -k8s.io/component-helpers v0.25.9 h1:WfuDvS0xO+ADmIacqYuM2O8qHq9bUZCYdYCjTCB2jwM= -k8s.io/component-helpers v0.25.9/go.mod h1:o9yuVdUiyKe0ubYP78veYtYxThJx66PsGzpRandVwf0= +k8s.io/component-helpers v0.25.10 h1:VUZZS0eqNz/7f63fhhNSt2DlD+1wFsUis5aI7N5sr+4= +k8s.io/component-helpers v0.25.10/go.mod h1:fIvM88ONeNa/e4ytDIUhM+oBSL8jsgteVEmosWEnvWU= k8s.io/csi-translation-lib v0.25.10 h1:aPA/UAGffC5+4cHgeWORZe2w1ytrpXGg96TQyxLf2zU= k8s.io/csi-translation-lib v0.25.10/go.mod h1:kCLPsbj4GvdyjlYy57obi1wDjAAhuQ9wH+EqaV0qcr4= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= @@ -729,8 +729,8 @@ k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkI k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/kube-scheduler v0.25.10 h1:wpQZ8G2AE+7tC5ue3FVQox/UgpKSVDeUaE0EVxN0mQg= k8s.io/kube-scheduler v0.25.10/go.mod h1:qxG3EaVXCNzYhu0aOt20gMjgYKTCzDfdMU430XpMLLg= -k8s.io/kubernetes v1.25.9 h1:ecLjIq630FVMRgisW7jO0rr+vf2PLSbIGKgol2xlAx4= -k8s.io/kubernetes v1.25.9/go.mod h1:wP+j1DisuLPNLGK0YrsFIQdBtz04xQB7wwM7IbmrKRw= +k8s.io/kubernetes v1.25.10 h1:y2+mX7XpdBUwr3c2uGxF+sxF4c3WLR4vPLtc5s026oo= +k8s.io/kubernetes v1.25.10/go.mod h1:USGoemphFvArsdVC5SC9jPMggic3scS3nUBuXurz97w= k8s.io/mount-utils v0.25.10 h1:ZlMrV9cs6YAyiZrYCpUcLZMv6keq5cXi+50SZMXJNTo= k8s.io/mount-utils v0.25.10/go.mod h1:IM9QOFh15E1a4Nb6Rcn8FJ9Z1PbBpuyAPCty/qvKSAw= k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= From 9801e5dfe9e17fc6c30ef832d97439955964fdcc Mon Sep 17 00:00:00 2001 From: RealAnna <89971034+RealAnna@users.noreply.github.com> Date: Tue, 30 May 2023 11:47:04 +0200 Subject: [PATCH 59/62] fix(metrics-operator): improve error handling in metrics providers (#1466) Signed-off-by: realanna Signed-off-by: RealAnna <89971034+RealAnna@users.noreply.github.com> Co-authored-by: Giovanni Liva Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> --- .../common/providers/datadog/datadog.go | 10 +- .../common/providers/datadog/datadog_test.go | 125 +++++++++-------- .../common/providers/dynatrace/common.go | 7 + .../common/providers/dynatrace/dynatrace.go | 15 +- .../providers/dynatrace/dynatrace_dql.go | 9 ++ .../providers/dynatrace/dynatrace_dql_test.go | 52 ++++++- .../providers/dynatrace/dynatrace_test.go | 131 +++++++++--------- .../common/providers/provider_test.go | 6 + .../controllers/metrics/controller.go | 11 +- 9 files changed, 228 insertions(+), 138 deletions(-) diff --git a/metrics-operator/controllers/common/providers/datadog/datadog.go b/metrics-operator/controllers/common/providers/datadog/datadog.go index 54c8bd2081..19a873e45c 100644 --- a/metrics-operator/controllers/common/providers/datadog/datadog.go +++ b/metrics-operator/controllers/common/providers/datadog/datadog.go @@ -65,7 +65,13 @@ func (d *KeptnDataDogProvider) EvaluateQuery(ctx context.Context, metric metrics err = json.Unmarshal(b, &result) if err != nil { d.Log.Error(err, "Error while parsing response") - return "", nil, err + return "", b, err + } + + if result.Error != nil { + err = fmt.Errorf("%s", *result.Error) + d.Log.Error(err, "Error from DataDog provider") + return "", b, err } if len(result.Series) == 0 { @@ -76,7 +82,7 @@ func (d *KeptnDataDogProvider) EvaluateQuery(ctx context.Context, metric metrics points := (result.Series)[0].Pointlist if len(points) == 0 { d.Log.Info("No metric points in query result") - return "", nil, fmt.Errorf("no metric points in query result") + return "", b, fmt.Errorf("no metric points in query result") } r := d.getSingleValue(points) diff --git a/metrics-operator/controllers/common/providers/datadog/datadog_test.go b/metrics-operator/controllers/common/providers/datadog/datadog_test.go index 2c426bf5b4..7f4d9d9f85 100644 --- a/metrics-operator/controllers/common/providers/datadog/datadog_test.go +++ b/metrics-operator/controllers/common/providers/datadog/datadog_test.go @@ -18,14 +18,16 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" ) +const ddErrorPayload = "{\"error\":\"Token is missing required scope\"}" const ddPayload = "{\"from_date\":1677736306000,\"group_by\":[],\"message\":\"\",\"query\":\"system.cpu.idle{*}\",\"res_type\":\"time_series\",\"series\":[{\"aggr\":null,\"display_name\":\"system.cpu.idle\",\"end\":1677821999000,\"expression\":\"system.cpu.idle{*}\",\"interval\":300,\"length\":7,\"metric\":\"system.cpu.idle\",\"pointlist\":[[1677781200000,92.37997436523438],[1677781500000,91.46615447998047],[1677781800000,92.05865631103515],[1677782100000,97.49858474731445],[1677782400000,95.95263163248698],[1677821400000,69.67094268798829],[1677821700000,84.78184509277344]],\"query_index\":0,\"scope\":\"*\",\"start\":1677781200000,\"tag_set\":[],\"unit\":[{\"family\":\"percentage\",\"name\":\"percent\",\"plural\":\"percent\",\"scale_factor\":1,\"short_name\":\"%\"},{}]}],\"status\":\"ok\",\"to_date\":1677822706000}" const ddEmptyPayload = "{\"from_date\":1677736306000,\"group_by\":[],\"message\":\"\",\"query\":\"system.cpu.idle{*}\",\"res_type\":\"time_series\",\"series\":[],\"status\":\"ok\",\"to_date\":1677822706000}" -func TestEvaluateQuery_HappyPath(t *testing.T) { +func TestEvaluateQuery_APIError(t *testing.T) { svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := w.Write([]byte(ddPayload)) + _, err := w.Write([]byte(ddErrorPayload)) require.Nil(t, err) })) defer svr.Close() @@ -43,13 +45,53 @@ func TestEvaluateQuery_HappyPath(t *testing.T) { appKey: []byte(appKeyValue), }, } - fakeClient := fake.NewClient(apiToken) + kdd := setupTest(apiToken) + metric := metricsapi.KeptnMetric{ + Spec: metricsapi.KeptnMetricSpec{ + Query: "system.cpu.idle{*}", + }, + } + b := true + p := metricsapi.KeptnMetricsProvider{ + Spec: metricsapi.KeptnMetricsProviderSpec{ + SecretKeyRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: secretName, + }, + Optional: &b, + }, + TargetServer: svr.URL, + }, + } - kdd := KeptnDataDogProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, + r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p) + require.Error(t, e) + require.Contains(t, e.Error(), "Token is missing required scope") + require.Equal(t, []byte(ddErrorPayload), raw) + require.Empty(t, r) +} + +func TestEvaluateQuery_HappyPath(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(ddPayload)) + require.Nil(t, err) + })) + defer svr.Close() + + secretName := "datadogSecret" + apiKey, apiKeyValue := "DD_CLIENT_API_KEY", "fake-api-key" + appKey, appKeyValue := "DD_CLIENT_APP_KEY", "fake-app-key" + apiToken := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: "", + }, + Data: map[string][]byte{ + apiKey: []byte(apiKeyValue), + appKey: []byte(appKeyValue), + }, } + kdd := setupTest(apiToken) metric := metricsapi.KeptnMetric{ Spec: metricsapi.KeptnMetricSpec{ Query: "system.cpu.idle{*}", @@ -93,13 +135,7 @@ func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) { appKey: []byte(appKeyValue), }, } - fakeClient := fake.NewClient(apiToken) - - kdd := KeptnDataDogProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } + kdd := setupTest(apiToken) metric := metricsapi.KeptnMetric{ Spec: metricsapi.KeptnMetricSpec{ Query: "system.cpu.idle{*}", @@ -120,7 +156,7 @@ func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) { r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p) require.Equal(t, "", r) - require.Equal(t, []byte(nil), raw) + require.Equal(t, []byte("garbage"), raw) require.NotNil(t, e) } func TestEvaluateQuery_MissingSecret(t *testing.T) { @@ -130,13 +166,7 @@ func TestEvaluateQuery_MissingSecret(t *testing.T) { })) defer svr.Close() - fakeClient := fake.NewClient() - - kdd := KeptnDataDogProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } + kdd := setupTest() metric := metricsapi.KeptnMetric{ Spec: metricsapi.KeptnMetricSpec{ Query: "system.cpu.idle{*}", @@ -159,14 +189,9 @@ func TestEvaluateQuery_SecretNotFound(t *testing.T) { })) defer svr.Close() - fakeClient := fake.NewClient() secretName := "datadogSecret" - kdd := KeptnDataDogProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } + kdd := setupTest() metric := metricsapi.KeptnMetric{ Spec: metricsapi.KeptnMetricSpec{ Query: "system.cpu.idle{*}", @@ -207,13 +232,7 @@ func TestEvaluateQuery_RefNonExistingKey(t *testing.T) { apiKey: []byte(apiKeyValue), }, } - fakeClient := fake.NewClient(apiToken) - - kdd := KeptnDataDogProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } + kdd := setupTest(apiToken) metric := metricsapi.KeptnMetric{ Spec: metricsapi.KeptnMetricSpec{ Query: "system.cpu.idle{*}", @@ -256,13 +275,7 @@ func TestEvaluateQuery_EmptyPayload(t *testing.T) { appKey: []byte(appKeyValue), }, } - fakeClient := fake.NewClient(apiToken) - - kdd := KeptnDataDogProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } + kdd := setupTest(apiToken) metric := metricsapi.KeptnMetric{ Spec: metricsapi.KeptnMetricSpec{ Query: "system.cpu.idle{*}", @@ -282,30 +295,22 @@ func TestEvaluateQuery_EmptyPayload(t *testing.T) { } r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p) + t.Log(string(raw)) require.Nil(t, raw) require.Equal(t, "", r) require.True(t, strings.Contains(e.Error(), "no values in query result")) } func TestGetSingleValue_EmptyPoints(t *testing.T) { - fakeClient := fake.NewClient() - kdd := KeptnDataDogProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } + kdd := setupTest() var points [][]*float64 value := kdd.getSingleValue(points) require.Zero(t, value) } func TestGetSingleValue_HappyPath(t *testing.T) { - fakeClient := fake.NewClient() - kdd := KeptnDataDogProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } + + kdd := setupTest() result := datadogV1.MetricsQueryResponse{} _ = json.Unmarshal([]byte(ddPayload), &result) points := (result.Series)[0].Pointlist @@ -314,3 +319,15 @@ func TestGetSingleValue_HappyPath(t *testing.T) { require.NotZero(t, value) require.Equal(t, 89.11554133097331, value) } + +func setupTest(objs ...client.Object) KeptnDataDogProvider { + + fakeClient := fake.NewClient(objs...) + + kdd := KeptnDataDogProvider{ + HttpClient: http.Client{}, + Log: ctrl.Log.WithName("testytest"), + K8sClient: fakeClient, + } + return kdd +} diff --git a/metrics-operator/controllers/common/providers/dynatrace/common.go b/metrics-operator/controllers/common/providers/dynatrace/common.go index 23ba4dab36..bded16027c 100644 --- a/metrics-operator/controllers/common/providers/dynatrace/common.go +++ b/metrics-operator/controllers/common/providers/dynatrace/common.go @@ -15,6 +15,13 @@ var ErrSecretKeyRefNotDefined = errors.New("the SecretKeyRef property with the D var ErrInvalidResult = errors.New("the answer does not contain any data") var ErrDQLQueryTimeout = errors.New("timed out waiting for result of DQL query") +const ErrAPIMsg = "provider api response: %s" + +type Error struct { + Code int `json:"-"` // optional + Message string `json:"message"` +} + func getDTSecret(ctx context.Context, provider metricsapi.KeptnMetricsProvider, k8sClient client.Client) (string, error) { if !provider.HasSecretDefined() { return "", ErrSecretKeyRefNotDefined diff --git a/metrics-operator/controllers/common/providers/dynatrace/dynatrace.go b/metrics-operator/controllers/common/providers/dynatrace/dynatrace.go index 5b95c8a817..5b819577d5 100644 --- a/metrics-operator/controllers/common/providers/dynatrace/dynatrace.go +++ b/metrics-operator/controllers/common/providers/dynatrace/dynatrace.go @@ -6,6 +6,8 @@ import ( "fmt" "io" "net/http" + "net/url" + "reflect" "strings" "time" @@ -24,6 +26,7 @@ type DynatraceResponse struct { TotalCount int `json:"totalCount"` Resolution string `json:"resolution"` Result []DynatraceResult `json:"result"` + Error `json:"error"` } type DynatraceResult struct { @@ -39,7 +42,8 @@ type DynatraceData struct { // EvaluateQuery fetches the SLI values from dynatrace provider func (d *KeptnDynatraceProvider) EvaluateQuery(ctx context.Context, metric metricsapi.KeptnMetric, provider metricsapi.KeptnMetricsProvider) (string, []byte, error) { baseURL := d.normalizeAPIURL(provider.Spec.TargetServer) - qURL := baseURL + "v2/metrics/query?metricSelector=" + metric.Spec.Query + query := url.QueryEscape(metric.Spec.Query) + qURL := baseURL + "v2/metrics/query?metricSelector=" + query d.Log.Info("Running query: " + qURL) ctx, cancel := context.WithTimeout(ctx, 20*time.Second) @@ -57,6 +61,7 @@ func (d *KeptnDynatraceProvider) EvaluateQuery(ctx context.Context, metric metri req.Header.Set("Authorization", "Api-Token "+token) res, err := d.HttpClient.Do(req) + if err != nil { d.Log.Error(err, "Error while creating request") return "", nil, err @@ -74,9 +79,13 @@ func (d *KeptnDynatraceProvider) EvaluateQuery(ctx context.Context, metric metri err = json.Unmarshal(b, &result) if err != nil { d.Log.Error(err, "Error while parsing response") - return "", nil, err + return "", b, err + } + if !reflect.DeepEqual(result.Error, Error{}) { + err = fmt.Errorf(ErrAPIMsg, result.Error.Message) + d.Log.Error(err, "Error from Dynatrace provider") + return "", b, err } - r := fmt.Sprintf("%f", d.getSingleValue(result)) return r, b, nil } diff --git a/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql.go b/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql.go index b01b1c51f8..f134a7d2ba 100644 --- a/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql.go +++ b/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "reflect" "time" "github.com/benbjohnson/clock" @@ -36,6 +37,7 @@ type DynatraceDQLHandler struct { type DynatraceDQLResult struct { State string `json:"state"` Result DQLResult `json:"result,omitempty"` + Error `json:"error"` } type DQLResult struct { @@ -100,6 +102,7 @@ func (d *keptnDynatraceDQLProvider) EvaluateQuery(ctx context.Context, metric me d.log.Error(err, "Error while waiting for DQL query", "query", dqlHandler) return "", nil, err } + // parse result if len(results.Records) > 1 { d.log.Info("More than a single result, the first one will be used") @@ -195,5 +198,11 @@ func (d *keptnDynatraceDQLProvider) retrieveDQLResults(ctx context.Context, hand d.log.Error(err, "Error while parsing response") return result, err } + + if !reflect.DeepEqual(result.Error, Error{}) { + err = fmt.Errorf(ErrAPIMsg, result.Error.Message) + d.log.Error(err, "Error from Dynatrace DQL provider") + return nil, err + } return result, nil } diff --git a/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql_test.go b/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql_test.go index 864d4c17ac..7ace591c61 100644 --- a/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql_test.go +++ b/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql_test.go @@ -24,9 +24,12 @@ const dqlRequestHandler = `{"requestToken": "my-token"}` const dqlPayload = "{\"state\":\"SUCCEEDED\",\"result\":{\"records\":[{\"value\":{\"count\":1,\"sum\":36.50,\"min\":36.50,\"avg\":36.50,\"max\":36.50},\"metric.key\":\"dt.containers.cpu.usage_user_milli_cores\",\"timeframe\":{\"start\":\"2023-01-31T09:11:00.000Z\",\"end\":\"2023-01-31T09:12:00.`00Z\"},\"Container\":\"frontend\",\"host.name\":\"default-pool-349eb8c6-gccf\",\"k8s.namespace.name\":\"hipstershop\",\"k8s.pod.uid\":\"632df64d-474c-4410-968d-666f639ad358\"}],\"types\":[{\"mappings\":{\"value\":{\"type\":\"summary_stats\"},\"metric.key\":{\"type\":\"string\"},\"timeframe\":{\"type\":\"timeframe\"},\"Container\":{\"type\":\"string\"},\"host.name\":{\"type\":\"string\"},\"k8s.namespace.name\":{\"type\":\"string\"},\"k8s.pod.uid\":{\"type\":\"string\"}},\"indexRange\":[0,1]}]}}" const dqlPayloadNotFinished = "{\"state\":\"\",\"result\":{\"records\":[{\"value\":{\"count\":1,\"sum\":36.50,\"min\":36.78336878333334,\"avg\":36.50,\"max\":36.50},\"metric.key\":\"dt.containers.cpu.usage_user_milli_cores\",\"timeframe\":{\"start\":\"2023-01-31T09:11:00.000Z\",\"end\":\"2023-01-31T09:12:00.`00Z\"},\"Container\":\"frontend\",\"host.name\":\"default-pool-349eb8c6-gccf\",\"k8s.namespace.name\":\"hipstershop\",\"k8s.pod.uid\":\"632df64d-474c-4410-968d-666f639ad358\"}],\"types\":[{\"mappings\":{\"value\":{\"type\":\"summary_stats\"},\"metric.key\":{\"type\":\"string\"},\"timeframe\":{\"type\":\"timeframe\"},\"Container\":{\"type\":\"string\"},\"host.name\":{\"type\":\"string\"},\"k8s.namespace.name\":{\"type\":\"string\"},\"k8s.pod.uid\":{\"type\":\"string\"}},\"indexRange\":[0,1]}]}}" +const dqlPayloadError = "{\"error\":{\"code\":403,\"message\":\"Token is missing required scope\"}}" const dqlPayloadTooManyItems = "{\"state\":\"SUCCEEDED\",\"result\":{\"records\":[{\"value\":{\"count\":1,\"sum\":6.293549483333334,\"min\":6.293549483333334,\"avg\":6.293549483333334,\"max\":6.293549483333334},\"metric.key\":\"dt.containers.cpu.usage_user_milli_cores\",\"timeframe\":{\"start\":\"2023-01-31T09:07:00.000Z\",\"end\":\"2023-01-31T09:08:00.000Z\"},\"Container\":\"loginservice\",\"host.name\":\"default-pool-349eb8c6-gccf\",\"k8s.namespace.name\":\"easytrade\",\"k8s.pod.uid\":\"fc084e57-11a0-4a95-b8a0-76191c31d839\"},{\"value\":{\"count\":1,\"sum\":1.0421756,\"min\":1.0421756,\"avg\":1.0421756,\"max\":1.0421756},\"metric.key\":\"dt.containers.cpu.usage_user_milli_cores\",\"timeframe\":{\"start\":\"2023-01-31T09:07:00.000Z\",\"end\":\"2023-01-31T09:08:00.000Z\"},\"Container\":\"frontendreverseproxy\",\"host.name\":\"default-pool-349eb8c6-gccf\",\"k8s.namespace.name\":\"easytrade\",\"k8s.pod.uid\":\"41b5d6e0-98fc-4dce-a1b4-bb269a03d72b\"},{\"value\":{\"count\":1,\"sum\":6.3881383000000005,\"min\":6.3881383000000005,\"avg\":6.3881383000000005,\"max\":6.3881383000000005},\"metric.key\":\"dt.containers.cpu.usage_user_milli_cores\",\"timeframe\":{\"start\":\"2023-01-31T09:07:00.000Z\",\"end\":\"2023-01-31T09:08:00.000Z\"},\"Container\":\"shippingservice\",\"host.name\":\"default-pool-349eb8c6-gccf\",\"k8s.namespace.name\":\"hipstershop\",\"k8s.pod.uid\":\"96fcf9d7-748a-47f7-b1b3-ca6427e20edd\"}],\"types\":[{\"mappings\":{\"value\":{\"type\":\"summary_stats\"},\"metric.key\":{\"type\":\"string\"},\"timeframe\":{\"type\":\"timeframe\"},\"Container\":{\"type\":\"string\"},\"host.name\":{\"type\":\"string\"},\"k8s.namespace.name\":{\"type\":\"string\"},\"k8s.pod.uid\":{\"type\":\"string\"}},\"indexRange\":[0,3]}]}}" +var ErrUnexpected = errors.New("unexpected path") + //nolint:dupl func TestGetDQL(t *testing.T) { @@ -40,8 +43,7 @@ func TestGetDQL(t *testing.T) { if strings.Contains(path, "query:poll") { return []byte(dqlPayload), nil } - - return nil, errors.New("unexpected path") + return nil, ErrUnexpected } dqlProvider := NewKeptnDynatraceDQLProvider( @@ -82,7 +84,7 @@ func TestGetDQLMultipleRecords(t *testing.T) { return []byte(dqlPayloadTooManyItems), nil } - return nil, errors.New("unexpected path") + return nil, ErrUnexpected } dqlProvider := NewKeptnDynatraceDQLProvider( @@ -108,6 +110,46 @@ func TestGetDQLMultipleRecords(t *testing.T) { require.Contains(t, mockClient.DoCalls()[1].Path, "query:poll") } +func TestGetDQLAPIError(t *testing.T) { + + mockClient := &fake.DTAPIClientMock{} + + mockClient.DoFunc = func(ctx context.Context, path string, method string, payload []byte) ([]byte, error) { + if strings.Contains(path, "query:execute") { + return []byte(dqlRequestHandler), nil + } + + if strings.Contains(path, "query:poll") { + return []byte(dqlPayloadError), nil + } + + return nil, ErrUnexpected + } + + dqlProvider := NewKeptnDynatraceDQLProvider( + nil, + WithDTAPIClient(mockClient), + WithLogger(logr.New(klog.NewKlogr().GetSink())), + ) + + result, raw, err := dqlProvider.EvaluateQuery(context.TODO(), + metricsapi.KeptnMetric{ + Spec: metricsapi.KeptnMetricSpec{Query: ""}, + }, metricsapi.KeptnMetricsProvider{ + Spec: metricsapi.KeptnMetricsProviderSpec{}, + }, + ) + + require.NotNil(t, err) + require.Contains(t, err.Error(), "Token is missing required scope") + require.Empty(t, raw) + require.Empty(t, result) + + require.Len(t, mockClient.DoCalls(), 2) + require.Contains(t, mockClient.DoCalls()[0].Path, "query:execute") + require.Contains(t, mockClient.DoCalls()[1].Path, "query:poll") +} + func TestGetDQLTimeout(t *testing.T) { mockClient := &fake.DTAPIClientMock{} @@ -121,7 +163,7 @@ func TestGetDQLTimeout(t *testing.T) { return []byte(dqlPayloadNotFinished), nil } - return nil, errors.New("unexpected path") + return nil, ErrUnexpected } dqlProvider := NewKeptnDynatraceDQLProvider( @@ -172,7 +214,7 @@ func TestGetDQLCannotPostQuery(t *testing.T) { return nil, errors.New("oops") } - return nil, errors.New("unexpected path") + return nil, ErrUnexpected } dqlProvider := NewKeptnDynatraceDQLProvider( diff --git a/metrics-operator/controllers/common/providers/dynatrace/dynatrace_test.go b/metrics-operator/controllers/common/providers/dynatrace/dynatrace_test.go index e238498a3c..e32bc875de 100644 --- a/metrics-operator/controllers/common/providers/dynatrace/dynatrace_test.go +++ b/metrics-operator/controllers/common/providers/dynatrace/dynatrace_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" ) const dtpayload = "{\"totalCount\":1,\"nextPageKey\":null,\"resolution\":\"1m\",\"result\":[{\"metricId\":\"dsfm:billing.hostunit.assigned:splitBy():sort(value(auto,descending)):avg\",\"dataPointCountRatio\":6.0E-6,\"dimensionCountRatio\":1.0E-5,\"data\":[{\"dimensions\":[],\"dimensionMap\":{},\"timestamps\":[1666090140000,1666090200000,1666090260000,1666090320000,1666090380000,1666090440000,1666090500000,1666090560000,1666090620000,1666090680000,1666090740000,1666090800000,1666090860000,1666090920000,1666090980000,1666091040000,1666091100000,1666091160000,1666091220000,1666091280000,1666091340000,1666091400000,1666091460000,1666091520000,1666091580000,1666091640000,1666091700000,1666091760000,1666091820000,1666091880000,1666091940000,1666092000000,1666092060000,1666092120000,1666092180000,1666092240000,1666092300000,1666092360000,1666092420000,1666092480000,1666092540000,1666092600000,1666092660000,1666092720000,1666092780000,1666092840000,1666092900000,1666092960000,1666093020000,1666093080000,1666093140000,1666093200000,1666093260000,1666093320000,1666093380000,1666093440000,1666093500000,1666093560000,1666093620000,1666093680000,1666093740000,1666093800000,1666093860000,1666093920000,1666093980000,1666094040000,1666094100000,1666094160000,1666094220000,1666094280000,1666094340000,1666094400000,1666094460000,1666094520000,1666094580000,1666094640000,1666094700000,1666094760000,1666094820000,1666094880000,1666094940000,1666095000000,1666095060000,1666095120000,1666095180000,1666095240000,1666095300000,1666095360000,1666095420000,1666095480000,1666095540000,1666095600000,1666095660000,1666095720000,1666095780000,1666095840000,1666095900000,1666095960000,1666096020000,1666096080000,1666096140000,1666096200000,1666096260000,1666096320000,1666096380000,1666096440000,1666096500000,1666096560000,1666096620000,1666096680000,1666096740000,1666096800000,1666096860000,1666096920000,1666096980000,1666097040000,1666097100000,1666097160000,1666097220000,1666097280000,1666097340000],\"values\":[null,null,null,null,null,null,50,null,null,null,null,null,null,null,null,null,null,null,null,null,null,50,null,null,null,null,null,null,null,null,null,null,null,null,null,null,50,null,null,null,null,null,null,null,null,null,null,null,null,null,null,50,null,null,null,null,null,null,null,null,null,null,null,null,null,null,50,null,null,null,null,null,null,null,null,null,null,null,null,null,null,50,null,null,null,null,null,null,null,null,null,null,null,null,null,null,50,null,null,null,null,null,null,null,null,null,null,null,null,null,null,50,null,null,null,null,null,null,null,null,null]}]}]}" @@ -151,18 +152,7 @@ func TestEvaluateQuery_CorrectHTTP(t *testing.T) { require.Equal(t, 1, len(r.Header["Authorization"])) })) defer svr.Close() - fakeClient := fake.NewClient() - - kdp := KeptnDynatraceProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } - obj := metricsapi.KeptnMetric{ - Spec: metricsapi.KeptnMetricSpec{ - Query: query, - }, - } + kdp, obj := setupTest() p := metricsapi.KeptnMetricsProvider{ Spec: metricsapi.KeptnMetricsProviderSpec{ SecretKeyRef: v1.SecretKeySelector{ @@ -181,9 +171,10 @@ func TestEvaluateQuery_CorrectHTTP(t *testing.T) { } -func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) { +func TestEvaluateQuery_APIError(t *testing.T) { + errorResponse := []byte("{\"error\":{\"code\":403,\"message\":\"Token is missing required scope. Use one of: metrics.read (Read metrics)\"}}") svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := w.Write([]byte("garbage")) + _, err := w.Write(errorResponse) require.Nil(t, err) })) defer svr.Close() @@ -197,18 +188,44 @@ func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) { secretKey: []byte(secretValue), }, } - fakeClient := fake.NewClient(apiToken) - - kdp := KeptnDynatraceProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, + kdp, obj := setupTest(apiToken) + p := metricsapi.KeptnMetricsProvider{ + Spec: metricsapi.KeptnMetricsProviderSpec{ + SecretKeyRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: secretName, + }, + Key: secretKey, + }, + TargetServer: svr.URL, + }, } - obj := metricsapi.KeptnMetric{ - Spec: metricsapi.KeptnMetricSpec{ - Query: "my-query", + r, raw, e := kdp.EvaluateQuery(context.TODO(), obj, p) + require.Equal(t, "", r) + t.Log(string(raw)) + require.Equal(t, errorResponse, raw) //we still return the raw answer to help user debug + require.NotNil(t, e) + require.Contains(t, e.Error(), "Token is missing required scope.") +} + +func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("garbage")) + require.Nil(t, err) + })) + defer svr.Close() + secretName, secretKey, secretValue := "secretName", "secretKey", "secretValue" + apiToken := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: "", + }, + Data: map[string][]byte{ + secretKey: []byte(secretValue), }, } + + kdp, obj := setupTest(apiToken) p := metricsapi.KeptnMetricsProvider{ Spec: metricsapi.KeptnMetricsProviderSpec{ SecretKeyRef: v1.SecretKeySelector{ @@ -222,7 +239,8 @@ func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) { } r, raw, e := kdp.EvaluateQuery(context.TODO(), obj, p) require.Equal(t, "", r) - require.Equal(t, []byte(nil), raw) + t.Log(string(raw), e) + require.Equal(t, []byte("garbage"), raw) //we still return the raw answer to help user debug require.NotNil(t, e) } @@ -232,18 +250,8 @@ func TestEvaluateQuery_MissingSecret(t *testing.T) { require.Nil(t, err) })) defer svr.Close() - fakeClient := fake.NewClient() + kdp, obj := setupTest() - kdp := KeptnDynatraceProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } - obj := metricsapi.KeptnMetric{ - Spec: metricsapi.KeptnMetricSpec{ - Query: "my-query", - }, - } p := metricsapi.KeptnMetricsProvider{ Spec: metricsapi.KeptnMetricsProviderSpec{ TargetServer: svr.URL, @@ -260,18 +268,8 @@ func TestEvaluateQuery_SecretNotFound(t *testing.T) { require.Nil(t, err) })) defer svr.Close() - fakeClient := fake.NewClient() + kdp, obj := setupTest() - kdp := KeptnDynatraceProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } - obj := metricsapi.KeptnMetric{ - Spec: metricsapi.KeptnMetricSpec{ - Query: "my-query", - }, - } p := metricsapi.KeptnMetricsProvider{ Spec: metricsapi.KeptnMetricsProviderSpec{ SecretKeyRef: v1.SecretKeySelector{ @@ -304,18 +302,8 @@ func TestEvaluateQuery_RefNotExistingKey(t *testing.T) { secretKey: []byte(secretValue), }, } - fakeClient := fake.NewClient(apiToken) + kdp, obj := setupTest(apiToken) - kdp := KeptnDynatraceProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } - obj := metricsapi.KeptnMetric{ - Spec: metricsapi.KeptnMetricSpec{ - Query: "my-query", - }, - } missingKey := "key_not_found" p := metricsapi.KeptnMetricsProvider{ Spec: metricsapi.KeptnMetricsProviderSpec{ @@ -350,18 +338,8 @@ func TestEvaluateQuery_HappyPath(t *testing.T) { secretKey: []byte(secretValue), }, } - fakeClient := fake.NewClient(apiToken) + kdp, obj := setupTest(apiToken) - kdp := KeptnDynatraceProvider{ - HttpClient: http.Client{}, - Log: ctrl.Log.WithName("testytest"), - K8sClient: fakeClient, - } - obj := metricsapi.KeptnMetric{ - Spec: metricsapi.KeptnMetricSpec{ - Query: "my-query", - }, - } p := metricsapi.KeptnMetricsProvider{ Spec: metricsapi.KeptnMetricsProviderSpec{ SecretKeyRef: v1.SecretKeySelector{ @@ -378,3 +356,20 @@ func TestEvaluateQuery_HappyPath(t *testing.T) { require.Equal(t, []byte(dtpayload), raw) require.Equal(t, fmt.Sprintf("%f", 50.0), r) } + +func setupTest(objs ...client.Object) (KeptnDynatraceProvider, metricsapi.KeptnMetric) { + + fakeClient := fake.NewClient(objs...) + + kdp := KeptnDynatraceProvider{ + HttpClient: http.Client{}, + Log: ctrl.Log.WithName("testytest"), + K8sClient: fakeClient, + } + obj := metricsapi.KeptnMetric{ + Spec: metricsapi.KeptnMetricSpec{ + Query: "my-query", + }, + } + return kdp, obj +} diff --git a/metrics-operator/controllers/common/providers/provider_test.go b/metrics-operator/controllers/common/providers/provider_test.go index ad150fa88a..f3869d424a 100644 --- a/metrics-operator/controllers/common/providers/provider_test.go +++ b/metrics-operator/controllers/common/providers/provider_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/go-logr/logr" + "github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/fake" "github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/providers/datadog" "github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/providers/dynatrace" "github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/providers/prometheus" @@ -26,6 +27,11 @@ func TestFactory(t *testing.T) { provider: &dynatrace.KeptnDynatraceProvider{}, err: false, }, + { + providerType: DynatraceDQLProviderType, + provider: dynatrace.NewKeptnDynatraceDQLProvider(fake.NewClient()), + err: false, + }, { providerType: DataDogProviderType, provider: &datadog.KeptnDataDogProvider{}, diff --git a/metrics-operator/controllers/metrics/controller.go b/metrics-operator/controllers/metrics/controller.go index 83982d1662..2d38a4c580 100644 --- a/metrics-operator/controllers/metrics/controller.go +++ b/metrics-operator/controllers/metrics/controller.go @@ -55,10 +55,6 @@ type KeptnMetricReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the KeptnMetric object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile @@ -102,10 +98,10 @@ func (r *KeptnMetricReconciler) Reconcile(ctx context.Context, req ctrl.Request) reconcile := ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second} value, rawValue, err := provider.EvaluateQuery(ctx, *metric, *metricProvider) if err != nil { - r.Log.Error(err, "Failed to evaluate the query") + r.Log.Error(err, "Failed to evaluate the query", "Response from provider was:", (string)(rawValue)) metric.Status.ErrMsg = err.Error() metric.Status.Value = "" - metric.Status.RawValue = []byte{} + metric.Status.RawValue = cupSize(rawValue) metric.Status.LastUpdated = metav1.Time{Time: time.Now()} reconcile = ctrl.Result{Requeue: false} } else { @@ -123,6 +119,9 @@ func (r *KeptnMetricReconciler) Reconcile(ctx context.Context, req ctrl.Request) } func cupSize(value []byte) []byte { + if len(value) == 0 { + return []byte{} + } if len(value) > MB { return value[:MB] } From 76a4bd92607d05c16c63ccc4c1dd91e35cb4d6b0 Mon Sep 17 00:00:00 2001 From: RealAnna <89971034+RealAnna@users.noreply.github.com> Date: Wed, 31 May 2023 14:05:40 +0200 Subject: [PATCH 60/62] feat: add python-runtime (#1496) Signed-off-by: realanna --- python-runtime/Dockerfile | 18 ++++++++ python-runtime/README.md | 68 +++++++++++++++++++++++++++++++ python-runtime/entrypoint.sh | 12 ++++++ python-runtime/samples/args.py | 12 ++++++ python-runtime/samples/hellopy.py | 4 ++ 5 files changed, 114 insertions(+) create mode 100644 python-runtime/Dockerfile create mode 100644 python-runtime/README.md create mode 100755 python-runtime/entrypoint.sh create mode 100644 python-runtime/samples/args.py create mode 100644 python-runtime/samples/hellopy.py diff --git a/python-runtime/Dockerfile b/python-runtime/Dockerfile new file mode 100644 index 0000000000..062f7301a4 --- /dev/null +++ b/python-runtime/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.9 AS production + +LABEL org.opencontainers.image.source="https://github.com/keptn/lifecycle-toolkit" \ + org.opencontainers.image.url="https://keptn.sh" \ + org.opencontainers.image.title="Keptn Python Runtime" \ + org.opencontainers.image.vendor="Keptn" \ + org.opencontainers.image.licenses="Apache-2.0" + +RUN pip install -q --disable-pip-version-check pyyaml GitPython requests + +COPY entrypoint.sh /entrypoint.sh + +USER 1000:1000 + +ENV CMD_ARGS="" +ENV SCRIPT="" + +ENTRYPOINT /entrypoint.sh diff --git a/python-runtime/README.md b/python-runtime/README.md new file mode 100644 index 0000000000..506be18caa --- /dev/null +++ b/python-runtime/README.md @@ -0,0 +1,68 @@ +# Keptn Lifecycle Controller - Function Runtime + +## Build + +```shell +docker build -t lifecycle-toolkit/python-runtime:${VERSION} . +``` + +## Usage + +The Keptn python runtime uses python3, and enables the follwing packages: requests, json, git, yaml + +The Keptn Lifecycle Toolkit uses this runtime to run [KeptnTask](https://lifecycle.keptn.sh/docs/tasks/write-tasks/) +for pre- and post-checks. + +`KeptnTask`s can be tested locally with the runtime using the following commands. +Replace `${VERSION}` with the KLT version of your choice. +`SCRIPT` should refer to either a python file mounted locally in the container or to a url containing the file. + +### mounting a python file + +```shell +docker run -v $(pwd)/samples/hellopy.py:/hellopy.py -e "SCRIPT=hellopy.py" -it lifecycle-toolkit/python-runtime:${VERSION} +``` + +Where the file in sample/hellopy.py contains python3 code: + +```python3 +import os + +print("Hello, World!") +print(os.environ) +``` + +This should print in your shell, something like: + +```shell +Hello, World! +environ({'HOSTNAME': 'myhost', 'PYTHON_VERSION': '3.9.16', 'PWD': '/', 'CMD_ARGS': '','SCRIPT': 'hellopy.py', ...}) +``` + +### Pass command line arguments to the python command + +You can pass python command line arguments by specifying `CMD_ARGS`. +The following example will print the help of python3: + +```shell +docker run -e "CMD_ARGS= -help" -it lifecycle-toolkit/python-runtime:${VERSION} +``` + +### Pass arguments to your python script + +In this example we pass one argument (-i test.txt) to the script + +```shell +docker run -v $(pwd)/samples/args.py:/args.py -e "SCRIPT=args.py -i test.txt" -it lifecycle-toolkit/python-runtime:${VERSION} +``` + +### Use a script from url + +We can call the hellopy.py script downloading it directly from github + +```shell +docker run -e "SCRIPT=https://raw.githubusercontent.com/keptn/lifecycle-toolkit/main/python-runtime/samples/hellopy.py" -it lifecycle-toolkit/python-runtime:${VERSION} +``` + + + diff --git a/python-runtime/entrypoint.sh b/python-runtime/entrypoint.sh new file mode 100755 index 0000000000..d1628f229e --- /dev/null +++ b/python-runtime/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -eu + +regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]' + +if [[ $SCRIPT =~ $regex ]] +then + curl -s $SCRIPT | python3 $CMD_ARGS - +else + python3 $CMD_ARGS $SCRIPT +fi diff --git a/python-runtime/samples/args.py b/python-runtime/samples/args.py new file mode 100644 index 0000000000..0dc3aba098 --- /dev/null +++ b/python-runtime/samples/args.py @@ -0,0 +1,12 @@ +import sys, getopt + +def main(argv): + inputfile = '' + opts, _ = getopt.getopt(argv,"i:",["ifile="]) + for opt, arg in opts: + if opt in ("-i", "--ifile"): + inputfile = arg + print ('Input file is ', inputfile) + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/python-runtime/samples/hellopy.py b/python-runtime/samples/hellopy.py new file mode 100644 index 0000000000..0d973a7557 --- /dev/null +++ b/python-runtime/samples/hellopy.py @@ -0,0 +1,4 @@ +import os + +print("Hello, World!") +print(os.environ) From 0f28b8c2b5854944d9b0e72fee97a6bc91f39bea Mon Sep 17 00:00:00 2001 From: Florian Bacher Date: Wed, 31 May 2023 14:30:06 +0200 Subject: [PATCH 61/62] fix(cert-manager): avoid index-out-of-bounds error when updating webhook configs (#1497) Signed-off-by: Florian Bacher --- .../keptnwebhookcertificate_controller.go | 2 +- .../keptnwebhookcontroller/webhook_cert_controller_test.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/klt-cert-manager/controllers/keptnwebhookcontroller/keptnwebhookcertificate_controller.go b/klt-cert-manager/controllers/keptnwebhookcontroller/keptnwebhookcertificate_controller.go index a085394d88..c1414121f4 100644 --- a/klt-cert-manager/controllers/keptnwebhookcontroller/keptnwebhookcertificate_controller.go +++ b/klt-cert-manager/controllers/keptnwebhookcontroller/keptnwebhookcertificate_controller.go @@ -180,7 +180,7 @@ func (r *KeptnWebhookCertificateReconciler) updateConfigurations(ctx context.Con } for i := range validatingWebhookConfigurationList.Items { - r.Log.Info("injecting certificate into validating webhook config", "vwc", mutatingWebhookConfigurationList.Items[i].Name) + r.Log.Info("injecting certificate into validating webhook config", "vwc", validatingWebhookConfigurationList.Items[i].Name) if err := r.updateClientConfigurations(ctx, bundle, validatingWebhookConfigs, &validatingWebhookConfigurationList.Items[i]); err != nil { return err } diff --git a/klt-cert-manager/controllers/keptnwebhookcontroller/webhook_cert_controller_test.go b/klt-cert-manager/controllers/keptnwebhookcontroller/webhook_cert_controller_test.go index bcec3a5e66..3e148b11db 100644 --- a/klt-cert-manager/controllers/keptnwebhookcontroller/webhook_cert_controller_test.go +++ b/klt-cert-manager/controllers/keptnwebhookcontroller/webhook_cert_controller_test.go @@ -146,7 +146,8 @@ func TestReconcile(t *testing.T) { t.Run(`reconcile successfully with mutatingwebhookconfiguration`, func(t *testing.T) { fakeClient := fake.NewClient(crd1, crd2, crd3, &admissionregistrationv1.MutatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{ - Name: "my-mutating-webhook-config", + Name: "my-mutating-webhook-config", + Labels: getMatchLabel(), }, Webhooks: []admissionregistrationv1.MutatingWebhook{ { @@ -167,7 +168,8 @@ func TestReconcile(t *testing.T) { t.Run(`reconcile successfully with validatingwebhookconfiguration`, func(t *testing.T) { fakeClient := fake.NewClient(crd1, crd2, crd3, &admissionregistrationv1.ValidatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{ - Name: "my-validating-webhook-config", + Name: "my-validating-webhook-config", + Labels: getMatchLabel(), }, Webhooks: []admissionregistrationv1.ValidatingWebhook{ { From 02ce86023b3db175481b859f379cb4298d03566a Mon Sep 17 00:00:00 2001 From: odubajDT <93584209+odubajDT@users.noreply.github.com> Date: Thu, 1 Jun 2023 10:05:16 +0200 Subject: [PATCH 62/62] feat(operator): introduce container-runtime runner (#1493) Signed-off-by: odubajDT Signed-off-by: odubajDT <93584209+odubajDT@users.noreply.github.com> Co-authored-by: Giovanni Liva --- examples/sample-app/base/container-task.yaml | 12 + examples/sample-app/version-1/app.yaml | 2 + examples/sample-app/version-2/app.yaml | 2 + examples/sample-app/version-3/app.yaml | 2 + .../v1alpha3/keptntaskdefinition_types.go | 26 +- .../keptntaskdefinition_types_test.go | 167 +++ .../v1alpha3/zz_generated.deepcopy.go | 17 +- ...fecycle.keptn.sh_keptntaskdefinitions.yaml | 1231 +++++++++++++++++ ...ifecycle_v1alpha3_keptntaskdefinition.yaml | 6 - ...1alpha3_keptntaskdefinition_container.yaml | 19 + ...cycle_v1alpha3_keptntaskdefinition_js.yaml | 10 + .../lifecycle/keptntask/container_builder.go | 65 +- .../keptntask/container_builder_test.go | 244 ++++ .../lifecycle/keptntask/job_runner_builder.go | 37 + .../keptntask/job_runner_builder_test.go | 62 + .../lifecycle/keptntask/job_utils.go | 7 +- .../lifecycle/keptntask/job_utils_test.go | 2 +- .../lifecycle/keptntask/js_builder.go | 4 +- .../lifecycle/keptntask/js_builder_test.go | 8 +- .../keptntaskdefinition/reconcile_function.go | 3 + operator/test/component/task/task_test.go | 2 +- .../taskdefinition/taskdefinition_test.go | 6 +- .../container-runtime/00-assert.yaml | 64 + .../container-runtime/00-install.yaml | 40 + .../container-runtime/00-teststep.yaml | 4 + 25 files changed, 1990 insertions(+), 52 deletions(-) create mode 100644 examples/sample-app/base/container-task.yaml create mode 100644 operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types_test.go delete mode 100644 operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition.yaml create mode 100644 operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition_container.yaml create mode 100644 operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition_js.yaml create mode 100644 operator/controllers/lifecycle/keptntask/container_builder_test.go create mode 100644 operator/controllers/lifecycle/keptntask/job_runner_builder.go create mode 100644 operator/controllers/lifecycle/keptntask/job_runner_builder_test.go create mode 100644 test/integration/container-runtime/00-assert.yaml create mode 100644 test/integration/container-runtime/00-install.yaml create mode 100644 test/integration/container-runtime/00-teststep.yaml diff --git a/examples/sample-app/base/container-task.yaml b/examples/sample-app/base/container-task.yaml new file mode 100644 index 0000000000..b6001490da --- /dev/null +++ b/examples/sample-app/base/container-task.yaml @@ -0,0 +1,12 @@ +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: container-sleep +spec: + container: + name: testy-test + image: busybox:1.36.0 + command: + - 'sh' + - '-c' + - 'sleep 30' diff --git a/examples/sample-app/version-1/app.yaml b/examples/sample-app/version-1/app.yaml index 7358f7fbe4..ded6ef9192 100644 --- a/examples/sample-app/version-1/app.yaml +++ b/examples/sample-app/version-1/app.yaml @@ -16,3 +16,5 @@ spec: version: 0.1.1 preDeploymentEvaluations: - app-pre-deploy-eval-1 + preDeploymentTasks: + - container-sleep diff --git a/examples/sample-app/version-2/app.yaml b/examples/sample-app/version-2/app.yaml index d43ed4a004..1801433737 100644 --- a/examples/sample-app/version-2/app.yaml +++ b/examples/sample-app/version-2/app.yaml @@ -16,3 +16,5 @@ spec: version: 0.1.1 preDeploymentEvaluations: - app-pre-deploy-eval-2 + preDeploymentTasks: + - container-sleep diff --git a/examples/sample-app/version-3/app.yaml b/examples/sample-app/version-3/app.yaml index 3c33d0f0ca..3bb50a1dc5 100644 --- a/examples/sample-app/version-3/app.yaml +++ b/examples/sample-app/version-3/app.yaml @@ -14,3 +14,5 @@ spec: version: 0.1.1 - name: podtato-head-hat version: 0.1.2 + preDeploymentTasks: + - container-sleep diff --git a/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types.go b/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types.go index 95df2aef36..93b45fe139 100644 --- a/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types.go +++ b/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha3 import ( + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -27,7 +28,12 @@ import ( type KeptnTaskDefinitionSpec struct { // Function contains the definition for the function that is to be executed in KeptnTasks based on // the KeptnTaskDefinitions. - Function FunctionSpec `json:"function,omitempty"` + // +optional + Function *FunctionSpec `json:"function,omitempty"` + // Container contains the definition for the container that is to be used in Job based on + // the KeptnTaskDefinitions. + // +optional + Container *ContainerSpec `json:"container,omitempty"` // Retries specifies how many times a job executing the KeptnTaskDefinition should be restarted in the case // of an unsuccessful attempt. // +kubebuilder:default:=10 @@ -39,7 +45,6 @@ type KeptnTaskDefinitionSpec struct { // +kubebuilder:default:="5m" // +kubebuilder:validation:Pattern="^0|([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" // +kubebuilder:validation:Type:=string - // +optional Timeout metav1.Duration `json:"timeout,omitempty"` } @@ -85,6 +90,7 @@ type HttpReference struct { } type ContainerSpec struct { + *v1.Container `json:",inline"` } // KeptnTaskDefinitionStatus defines the observed state of KeptnTaskDefinition @@ -125,3 +131,19 @@ type KeptnTaskDefinitionList struct { func init() { SchemeBuilder.Register(&KeptnTaskDefinition{}, &KeptnTaskDefinitionList{}) } + +func (d KeptnTaskDefinition) SpecExists() bool { + return d.IsJSSpecDefined() || d.IsContainerSpecDefined() +} + +func (d KeptnTaskDefinition) IsJSSpecDefined() bool { + return d.Spec.Function != nil +} + +func (d KeptnTaskDefinition) IsContainerSpecDefined() bool { + return d.Spec.Container != nil +} + +func (d KeptnTaskDefinition) IsVolumeMountPresent() bool { + return d.IsContainerSpecDefined() && d.Spec.Container.VolumeMounts != nil && len(d.Spec.Container.VolumeMounts) > 0 +} diff --git a/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types_test.go b/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types_test.go new file mode 100644 index 0000000000..3f141d7c2f --- /dev/null +++ b/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_types_test.go @@ -0,0 +1,167 @@ +package v1alpha3 + +import ( + "testing" + + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" +) + +var jsTaskDef = &KeptnTaskDefinition{ + Spec: KeptnTaskDefinitionSpec{ + Function: &FunctionSpec{ + Inline: Inline{ + Code: "some code", + }, + }, + }, +} + +var containerTaskDef = &KeptnTaskDefinition{ + Spec: KeptnTaskDefinitionSpec{ + Container: &ContainerSpec{ + Container: &v1.Container{ + Image: "image", + }, + }, + }, +} + +func Test_SpecExists(t *testing.T) { + tests := []struct { + name string + taskDef *KeptnTaskDefinition + want bool + }{ + { + name: "js builder", + taskDef: jsTaskDef, + want: true, + }, + { + name: "container builder", + taskDef: containerTaskDef, + want: true, + }, + { + name: "empty builder", + taskDef: &KeptnTaskDefinition{ + Spec: KeptnTaskDefinitionSpec{}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.taskDef.SpecExists()) + }) + } +} + +func Test_IsJSSpecDefined(t *testing.T) { + tests := []struct { + name string + taskDef *KeptnTaskDefinition + want bool + }{ + { + name: "defined", + taskDef: jsTaskDef, + want: true, + }, + { + name: "empty", + taskDef: &KeptnTaskDefinition{}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.taskDef.IsJSSpecDefined()) + }) + } +} + +func Test_IsVolumeMountPresent(t *testing.T) { + tests := []struct { + name string + taskDef *KeptnTaskDefinition + want bool + }{ + { + name: "defined", + taskDef: &KeptnTaskDefinition{ + Spec: KeptnTaskDefinitionSpec{ + Container: &ContainerSpec{ + Container: &v1.Container{ + Image: "image", + VolumeMounts: []v1.VolumeMount{ + { + Name: "name", + MountPath: "path", + }, + }, + }, + }, + }, + }, + want: true, + }, + { + name: "empty", + taskDef: &KeptnTaskDefinition{ + Spec: KeptnTaskDefinitionSpec{ + Container: &ContainerSpec{ + Container: &v1.Container{ + Image: "image", + VolumeMounts: []v1.VolumeMount{}, + }, + }, + }, + }, + want: false, + }, + { + name: "nil", + taskDef: &KeptnTaskDefinition{ + Spec: KeptnTaskDefinitionSpec{ + Container: &ContainerSpec{ + Container: &v1.Container{ + Image: "image", + }, + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.taskDef.IsVolumeMountPresent()) + }) + } +} + +func Test_IsContainerSpecDefined(t *testing.T) { + tests := []struct { + name string + taskDef *KeptnTaskDefinition + want bool + }{ + { + name: "defined", + taskDef: containerTaskDef, + want: true, + }, + { + name: "empty", + taskDef: &KeptnTaskDefinition{}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.taskDef.IsContainerSpecDefined()) + }) + } +} diff --git a/operator/apis/lifecycle/v1alpha3/zz_generated.deepcopy.go b/operator/apis/lifecycle/v1alpha3/zz_generated.deepcopy.go index 7ee7cd41e2..cc68f44e91 100644 --- a/operator/apis/lifecycle/v1alpha3/zz_generated.deepcopy.go +++ b/operator/apis/lifecycle/v1alpha3/zz_generated.deepcopy.go @@ -24,6 +24,7 @@ package v1alpha3 import ( "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" "go.opentelemetry.io/otel/propagation" + "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -45,6 +46,11 @@ func (in *ConfigMapReference) DeepCopy() *ConfigMapReference { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ContainerSpec) DeepCopyInto(out *ContainerSpec) { *out = *in + if in.Container != nil { + in, out := &in.Container, &out.Container + *out = new(v1.Container) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerSpec. @@ -909,7 +915,16 @@ func (in *KeptnTaskDefinitionList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KeptnTaskDefinitionSpec) DeepCopyInto(out *KeptnTaskDefinitionSpec) { *out = *in - in.Function.DeepCopyInto(&out.Function) + if in.Function != nil { + in, out := &in.Function, &out.Function + *out = new(FunctionSpec) + (*in).DeepCopyInto(*out) + } + if in.Container != nil { + in, out := &in.Container, &out.Container + *out = new(ContainerSpec) + (*in).DeepCopyInto(*out) + } if in.Retries != nil { in, out := &in.Retries, &out.Retries *out = new(int32) diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml index f6be0e8b34..d88447aeef 100644 --- a/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptntaskdefinitions.yaml @@ -189,6 +189,1237 @@ spec: spec: description: Spec describes the desired state of the KeptnTaskDefinition. properties: + container: + description: Container contains the definition for the container that + is to be used in Job based on the KeptnTaskDefinitions. + properties: + args: + description: 'Arguments to the entrypoint. The container image''s + CMD is used if this is not provided. Variable references $(VAR_NAME) + are expanded using the container''s environment. If a variable + cannot be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references + will never be expanded, regardless of whether the variable exists + or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. The + container image''s ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container''s + environment. If a variable cannot be resolved, the reference + in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: + i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the container. + Cannot be updated. + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must be a + C_IDENTIFIER. All invalid keys will be reported as an event + when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take + precedence. Values defined by an Env with a duplicate key will + take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a set of + ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend to each key + in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + image: + description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take in + response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after a container + is created. If the handler fails, the container is terminated + and restarted according to its restart policy. Other management + of the container blocks until the hook completes. More info: + https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's filesystem. + The command is simply exec'd, it is not run inside + a shell, so traditional shell instructions ('|', + etc) won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is treated + as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in httpHeaders + instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name. This will + be canonicalized upon output, so case-variant + names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range 1 + to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported as + a LifecycleHandler and kept for the backward compatibility. + There are no validation of this field and lifecycle + hooks will fail in runtime when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range 1 + to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a container + is terminated due to an API request or management event + such as liveness/startup probe failure, preemption, resource + contention, etc. The handler is not called if the container + crashes or exits. The Pod''s termination grace period countdown + begins before the PreStop hook is executed. Regardless of + the outcome of the handler, the container will eventually + terminate within the Pod''s termination grace period (unless + delayed by finalizers). Other management of the container + blocks until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's filesystem. + The command is simply exec'd, it is not run inside + a shell, so traditional shell instructions ('|', + etc) won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is treated + as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in httpHeaders + instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name. This will + be canonicalized upon output, so case-variant + names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range 1 + to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported as + a LifecycleHandler and kept for the backward compatibility. + There are no validation of this field and lifecycle + hooks will fail in runtime when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range 1 + to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. More + info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe to + be considered failed after having succeeded. Defaults to + 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC port. + This is a beta field and requires enabling GRPCContainerProbe + feature gate. + properties: + port: + description: Port number of the gRPC service. Number must + be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service to place + in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior is + defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to the + pod IP. You probably want to set "Host" in httpHeaders + instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP + allows repeated headers. + items: + description: HTTPHeader describes a custom header to + be used in HTTP probes + properties: + name: + description: The header field name. This will be + canonicalized upon output, so case-variant names + will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container has started + before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe to + be considered successful after having failed. Defaults to + 1. Must be 1 for liveness and startup. Minimum value is + 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving a TCP + port. + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to + terminate gracefully upon probe failure. The grace period + is the duration in seconds after the processes running in + the pod are sent a termination signal and the time when + the processes are forcibly halted with a kill signal. Set + this value longer than the expected cleanup time for your + process. If this value is nil, the pod's terminationGracePeriodSeconds + will be used. Otherwise, this value overrides the value + provided by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the kill signal + (no opportunity to shut down). This is a beta field and + requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is + used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe times + out. Defaults to 1 second. Minimum value is 1. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. Each + container in a pod must have a unique name (DNS_LABEL). Cannot + be updated. + type: string + ports: + description: List of ports to expose from the container. Not specifying + a port here DOES NOT prevent that port from being exposed. Any + port which is listening on the default "0.0.0.0" address inside + a container will be accessible from the network. Modifying this + array with strategic merge patch may corrupt the data. For more + information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + items: + description: ContainerPort represents a network port in a single + container. + properties: + containerPort: + description: Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port to. + type: string + hostPort: + description: Number of port to expose on the host. If specified, + this must be a valid port number, 0 < x < 65536. If HostNetwork + is specified, this must match ContainerPort. Most containers + do not need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a pod must + have a unique name. Name for the port that can be referred + to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. Container + will be removed from service endpoints if the probe fails. Cannot + be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe to + be considered failed after having succeeded. Defaults to + 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC port. + This is a beta field and requires enabling GRPCContainerProbe + feature gate. + properties: + port: + description: Port number of the gRPC service. Number must + be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service to place + in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior is + defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to the + pod IP. You probably want to set "Host" in httpHeaders + instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP + allows repeated headers. + items: + description: HTTPHeader describes a custom header to + be used in HTTP probes + properties: + name: + description: The header field name. This will be + canonicalized upon output, so case-variant names + will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container has started + before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe to + be considered successful after having failed. Defaults to + 1. Must be 1 for liveness and startup. Minimum value is + 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving a TCP + port. + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to + terminate gracefully upon probe failure. The grace period + is the duration in seconds after the processes running in + the pod are sent a termination signal and the time when + the processes are forcibly halted with a kill signal. Set + this value longer than the expected cleanup time for your + process. If this value is nil, the pod's terminationGracePeriodSeconds + will be used. Otherwise, this value overrides the value + provided by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the kill signal + (no opportunity to shut down). This is a beta field and + requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is + used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe times + out. Defaults to 1 second. Minimum value is 1. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. Cannot + be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable. It can only be + set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in + pod.spec.resourceClaims of the Pod where this field + is used. It makes that resource available inside a + container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options the + container should be run with. If set, the fields of SecurityContext + override the equivalent fields of PodSecurityContext. More info: + https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed until + this completes successfully. If this probe fails, the Pod will + be restarted, just as if the livenessProbe failed. This can + be used to provide different probe parameters at the beginning + of a Pod''s lifecycle, when it might take a long time to load + data or warm a cache, than during steady-state operation. This + cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe to + be considered failed after having succeeded. Defaults to + 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC port. + This is a beta field and requires enabling GRPCContainerProbe + feature gate. + properties: + port: + description: Port number of the gRPC service. Number must + be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service to place + in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior is + defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to the + pod IP. You probably want to set "Host" in httpHeaders + instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP + allows repeated headers. + items: + description: HTTPHeader describes a custom header to + be used in HTTP probes + properties: + name: + description: The header field name. This will be + canonicalized upon output, so case-variant names + will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container has started + before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe to + be considered successful after having failed. Defaults to + 1. Must be 1 for liveness and startup. Minimum value is + 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving a TCP + port. + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access on the + container. Number must be in the range 1 to 65535. Name + must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to + terminate gracefully upon probe failure. The grace period + is the duration in seconds after the processes running in + the pod are sent a termination signal and the time when + the processes are forcibly halted with a kill signal. Set + this value longer than the expected cleanup time for your + process. If this value is nil, the pod's terminationGracePeriodSeconds + will be used. Otherwise, this value overrides the value + provided by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the kill signal + (no opportunity to shut down). This is a beta field and + requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is + used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe times + out. Defaults to 1 second. Minimum value is 1. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer for + stdin in the container runtime. If this is not set, reads from + stdin in the container will always result in EOF. Default is + false. + type: boolean + stdinOnce: + description: Whether the container runtime should close the stdin + channel after it has been opened by a single attach. When stdin + is true the stdin stream will remain open across multiple attach + sessions. If stdinOnce is set to true, stdin is opened on container + start, is empty until the first client attaches to stdin, and + then remains open and accepts data until the client disconnects, + at which time stdin is closed and remains closed until the container + is restarted. If this flag is false, a container processes that + reads from stdin will never receive an EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which the container''s + termination message will be written is mounted into the container''s + filesystem. Message written is intended to be brief final status, + such as an assertion failure message. Will be truncated by the + node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should be populated. + File will use the contents of terminationMessagePath to populate + the container status message on both success and failure. FallbackToLogsOnError + will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY for + itself, also requires 'stdin' to be true. Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices to be + used by the container. + items: + description: volumeDevice describes a mapping of a raw block + device within a container. + properties: + devicePath: + description: devicePath is the path inside of the container + that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: Path within the container at which the volume + should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are + propagated from the host to container and the other way + around. When not set, MountPropagationNone is used. This + field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise + (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's + volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which + the container's volume should be mounted. Behaves similarly + to SubPath but environment variable references $(VAR_NAME) + are expanded using the container's environment. Defaults + to "" (volume's root). SubPathExpr and SubPath are mutually + exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might be + configured in the container image. Cannot be updated. + type: string + required: + - name + type: object function: description: Function contains the definition for the function that is to be executed in KeptnTasks based on the KeptnTaskDefinitions. diff --git a/operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition.yaml b/operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition.yaml deleted file mode 100644 index 5d10c28fae..0000000000 --- a/operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: lifecycle.keptn.sh/v1alpha3 -kind: KeptnTaskDefinition -metadata: - name: keptntaskdefinition-sample -spec: -# TODO(user): Add fields here diff --git a/operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition_container.yaml b/operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition_container.yaml new file mode 100644 index 0000000000..a0cbfcffc0 --- /dev/null +++ b/operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition_container.yaml @@ -0,0 +1,19 @@ +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: keptntaskdefinition-sample-container +spec: + container: + name: testy-test + image: busybox:1.36.0 + resources: + limits: + memory: "200Mi" + command: + - 'echo' + - 'Hello World!' + - '>' + - '/cache/log.txt' + volumeMounts: + - mountPath: /cache + name: logger diff --git a/operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition_js.yaml b/operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition_js.yaml new file mode 100644 index 0000000000..83a33dfc3c --- /dev/null +++ b/operator/config/samples/lifecycle_v1alpha3_keptntaskdefinition_js.yaml @@ -0,0 +1,10 @@ +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: keptntaskdefinition-sample-js +spec: + function: + inline: + code: | + console.log('hello'); + retries: 2 diff --git a/operator/controllers/lifecycle/keptntask/container_builder.go b/operator/controllers/lifecycle/keptntask/container_builder.go index e550b9dfac..098db9bb61 100644 --- a/operator/controllers/lifecycle/keptntask/container_builder.go +++ b/operator/controllers/lifecycle/keptntask/container_builder.go @@ -1,46 +1,53 @@ package keptntask import ( - "reflect" - - "github.com/go-logr/logr" klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" "golang.org/x/net/context" corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/tools/record" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/apimachinery/pkg/api/resource" ) -// IContainerBuilder is the interface that describes the operations needed to help build job specs of a task -type IContainerBuilder interface { - // CreateContainerWithVolumes returns a job container and volumes based on the task definition spec - CreateContainerWithVolumes(ctx context.Context) (*corev1.Container, []corev1.Volume, error) +// ContainerBuilder implements container builder interface for python +type ContainerBuilder struct { + taskDef *klcv1alpha3.KeptnTaskDefinition } -// BuilderOptions contains everything needed to build the current job -type BuilderOptions struct { - client.Client - recorder record.EventRecorder - req ctrl.Request - Log logr.Logger - task *klcv1alpha3.KeptnTask - taskDef *klcv1alpha3.KeptnTaskDefinition +func NewContainerBuilder(taskDef *klcv1alpha3.KeptnTaskDefinition) *ContainerBuilder { + return &ContainerBuilder{ + taskDef: taskDef, + } } -func getContainerBuilder(options BuilderOptions) IContainerBuilder { - if isJSSpecDefined(&options.taskDef.Spec) { - builder := newJSBuilder(options) - return &builder - } - return nil +func (c *ContainerBuilder) CreateContainerWithVolumes(ctx context.Context) (*corev1.Container, []corev1.Volume, error) { + return c.taskDef.Spec.Container.Container, c.generateVolumes(), nil } -func specExists(definition *klcv1alpha3.KeptnTaskDefinition) bool { - //TODO when adding new builders add more logic here - return isJSSpecDefined(&definition.Spec) +func (c *ContainerBuilder) getVolumeSource() *corev1.EmptyDirVolumeSource { + quantity, ok := c.taskDef.Spec.Container.Resources.Limits["memory"] + if ok { + return &corev1.EmptyDirVolumeSource{ + SizeLimit: &quantity, + Medium: corev1.StorageMedium("Memory"), + } + } + + return &corev1.EmptyDirVolumeSource{ + // Default 50% of the memory of the node, max 1Gi + SizeLimit: resource.NewQuantity(1, resource.Format("Gi")), + Medium: corev1.StorageMedium("Memory"), + } } -func isJSSpecDefined(spec *klcv1alpha3.KeptnTaskDefinitionSpec) bool { - return !reflect.DeepEqual(spec.Function, klcv1alpha3.FunctionSpec{}) +func (c *ContainerBuilder) generateVolumes() []corev1.Volume { + if !c.taskDef.IsVolumeMountPresent() { + return []corev1.Volume{} + } + return []corev1.Volume{ + { + Name: c.taskDef.Spec.Container.VolumeMounts[0].Name, + VolumeSource: corev1.VolumeSource{ + EmptyDir: c.getVolumeSource(), + }, + }, + } } diff --git a/operator/controllers/lifecycle/keptntask/container_builder_test.go b/operator/controllers/lifecycle/keptntask/container_builder_test.go new file mode 100644 index 0000000000..bcdaee78c1 --- /dev/null +++ b/operator/controllers/lifecycle/keptntask/container_builder_test.go @@ -0,0 +1,244 @@ +package keptntask + +import ( + "context" + "testing" + + "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) + +func TestContainerBuilder_CreateContainerWithVolumes(t *testing.T) { + tests := []struct { + name string + builder ContainerBuilder + wantContainer *v1.Container + wantVolumes []v1.Volume + }{ + { + name: "defined without volumes", + builder: ContainerBuilder{ + taskDef: &v1alpha3.KeptnTaskDefinition{ + Spec: v1alpha3.KeptnTaskDefinitionSpec{ + Container: &v1alpha3.ContainerSpec{ + Container: &v1.Container{ + Image: "image", + }, + }, + }, + }, + }, + wantContainer: &v1.Container{ + Image: "image", + }, + wantVolumes: []v1.Volume{}, + }, + { + name: "defined with volume", + builder: ContainerBuilder{ + taskDef: &v1alpha3.KeptnTaskDefinition{ + Spec: v1alpha3.KeptnTaskDefinitionSpec{ + Container: &v1alpha3.ContainerSpec{ + Container: &v1.Container{ + Image: "image", + VolumeMounts: []v1.VolumeMount{ + { + Name: "test-volume", + MountPath: "path", + }, + }, + }, + }, + }, + }, + }, + wantContainer: &v1.Container{ + Image: "image", + VolumeMounts: []v1.VolumeMount{ + { + Name: "test-volume", + MountPath: "path", + }, + }, + }, + wantVolumes: []v1.Volume{ + { + Name: "test-volume", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{ + SizeLimit: resource.NewQuantity(1, resource.Format("Gi")), + Medium: v1.StorageMedium("Memory"), + }, + }, + }, + }, + }, + { + name: "defined with volume and limits", + builder: ContainerBuilder{ + taskDef: &v1alpha3.KeptnTaskDefinition{ + Spec: v1alpha3.KeptnTaskDefinitionSpec{ + Container: &v1alpha3.ContainerSpec{ + Container: &v1.Container{ + Image: "image", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "memory": *resource.NewQuantity(100, resource.Format("Mi")), + }, + }, + VolumeMounts: []v1.VolumeMount{ + { + Name: "test-volume", + MountPath: "path", + }, + }, + }, + }, + }, + }, + }, + wantContainer: &v1.Container{ + Image: "image", + VolumeMounts: []v1.VolumeMount{ + { + Name: "test-volume", + MountPath: "path", + }, + }, + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "memory": *resource.NewQuantity(100, resource.Format("Mi")), + }, + }, + }, + wantVolumes: []v1.Volume{ + { + Name: "test-volume", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{ + SizeLimit: resource.NewQuantity(100, resource.Format("Mi")), + Medium: v1.StorageMedium("Memory"), + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + container, volumes, _ := tt.builder.CreateContainerWithVolumes(context.TODO()) + require.Equal(t, tt.wantContainer, container) + require.Equal(t, tt.wantVolumes, volumes) + }) + } +} + +func Test_GenerateVolumes(t *testing.T) { + tests := []struct { + name string + taskDef *v1alpha3.KeptnTaskDefinition + want []v1.Volume + }{ + { + name: "defined", + taskDef: &v1alpha3.KeptnTaskDefinition{ + Spec: v1alpha3.KeptnTaskDefinitionSpec{ + Container: &v1alpha3.ContainerSpec{ + Container: &v1.Container{ + Image: "image", + VolumeMounts: []v1.VolumeMount{ + { + Name: "name", + MountPath: "path", + }, + }, + }, + }, + }, + }, + want: []v1.Volume{ + { + Name: "name", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{ + SizeLimit: resource.NewQuantity(1, resource.Format("Gi")), + Medium: v1.StorageMedium("Memory"), + }, + }, + }, + }, + }, + { + name: "empty", + taskDef: &v1alpha3.KeptnTaskDefinition{}, + want: []v1.Volume{}, + }, + } + for _, tt := range tests { + builder := ContainerBuilder{ + taskDef: tt.taskDef, + } + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, builder.generateVolumes()) + }) + } +} + +func Test_GetVolumeSource(t *testing.T) { + tests := []struct { + name string + taskDef *v1alpha3.KeptnTaskDefinition + want *v1.EmptyDirVolumeSource + }{ + { + name: "not set limits", + taskDef: &v1alpha3.KeptnTaskDefinition{ + Spec: v1alpha3.KeptnTaskDefinitionSpec{ + Container: &v1alpha3.ContainerSpec{ + Container: &v1.Container{ + Image: "image", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{}, + }, + }, + }, + }, + }, + want: &v1.EmptyDirVolumeSource{ + SizeLimit: resource.NewQuantity(1, resource.Format("Gi")), + Medium: v1.StorageMedium("Memory"), + }, + }, + { + name: "set limits", + taskDef: &v1alpha3.KeptnTaskDefinition{ + Spec: v1alpha3.KeptnTaskDefinitionSpec{ + Container: &v1alpha3.ContainerSpec{ + Container: &v1.Container{ + Image: "image", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "memory": *resource.NewQuantity(100, resource.Format("Mi")), + }, + }, + }, + }, + }, + }, + want: &v1.EmptyDirVolumeSource{ + SizeLimit: resource.NewQuantity(100, resource.Format("Mi")), + Medium: v1.StorageMedium("Memory"), + }, + }, + } + for _, tt := range tests { + builder := ContainerBuilder{ + taskDef: tt.taskDef, + } + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, builder.getVolumeSource()) + }) + } +} diff --git a/operator/controllers/lifecycle/keptntask/job_runner_builder.go b/operator/controllers/lifecycle/keptntask/job_runner_builder.go new file mode 100644 index 0000000000..d177c0cae6 --- /dev/null +++ b/operator/controllers/lifecycle/keptntask/job_runner_builder.go @@ -0,0 +1,37 @@ +package keptntask + +import ( + "github.com/go-logr/logr" + klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" + "golang.org/x/net/context" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// JobRunnerBuilder is the interface that describes the operations needed to help build job specs of a task +type JobRunnerBuilder interface { + // CreateContainerWithVolumes returns a job container and volumes based on the task definition spec + CreateContainerWithVolumes(ctx context.Context) (*corev1.Container, []corev1.Volume, error) +} + +// BuilderOptions contains everything needed to build the current job +type BuilderOptions struct { + client.Client + recorder record.EventRecorder + req ctrl.Request + Log logr.Logger + task *klcv1alpha3.KeptnTask + taskDef *klcv1alpha3.KeptnTaskDefinition +} + +func getJobRunnerBuilder(options BuilderOptions) JobRunnerBuilder { + if options.taskDef.IsJSSpecDefined() { + return NewJSBuilder(options) + } + if options.taskDef.IsContainerSpecDefined() { + return NewContainerBuilder(options.taskDef) + } + return nil +} diff --git a/operator/controllers/lifecycle/keptntask/job_runner_builder_test.go b/operator/controllers/lifecycle/keptntask/job_runner_builder_test.go new file mode 100644 index 0000000000..36f23442e1 --- /dev/null +++ b/operator/controllers/lifecycle/keptntask/job_runner_builder_test.go @@ -0,0 +1,62 @@ +package keptntask + +import ( + "testing" + + "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" +) + +func Test_getJobRunnerBuilder(t *testing.T) { + jsBuilderOptions := BuilderOptions{ + taskDef: &v1alpha3.KeptnTaskDefinition{ + Spec: v1alpha3.KeptnTaskDefinitionSpec{ + Function: &v1alpha3.FunctionSpec{ + Inline: v1alpha3.Inline{ + Code: "some code", + }, + }, + }, + }, + } + containerBuilderOptions := BuilderOptions{ + taskDef: &v1alpha3.KeptnTaskDefinition{ + Spec: v1alpha3.KeptnTaskDefinitionSpec{ + Container: &v1alpha3.ContainerSpec{ + Container: &v1.Container{ + Image: "image", + }, + }, + }, + }, + } + tests := []struct { + name string + options BuilderOptions + want JobRunnerBuilder + }{ + { + name: "js builder", + options: jsBuilderOptions, + want: NewJSBuilder(jsBuilderOptions), + }, + { + name: "container builder", + options: containerBuilderOptions, + want: NewContainerBuilder(containerBuilderOptions.taskDef), + }, + { + name: "invalid builder", + options: BuilderOptions{ + taskDef: &v1alpha3.KeptnTaskDefinition{}, + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, getJobRunnerBuilder(tt.options)) + }) + } +} diff --git a/operator/controllers/lifecycle/keptntask/job_utils.go b/operator/controllers/lifecycle/keptntask/job_utils.go index 61eadf5238..8d58bb96dd 100644 --- a/operator/controllers/lifecycle/keptntask/job_utils.go +++ b/operator/controllers/lifecycle/keptntask/job_utils.go @@ -24,7 +24,7 @@ func (r *KeptnTaskReconciler) createJob(ctx context.Context, req ctrl.Request, t return err } - if specExists(definition) { + if definition.SpecExists() { jobName, err = r.createFunctionJob(ctx, req, task, definition) if err != nil { return err @@ -141,12 +141,13 @@ func (r *KeptnTaskReconciler) generateJob(ctx context.Context, task *klcv1alpha3 taskDef: definition, recorder: r.Recorder, } - builder := getContainerBuilder(builderOpt) + + builder := getJobRunnerBuilder(builderOpt) if builder == nil { return nil, controllererrors.ErrNoTaskDefinitionSpec } - container, volumes, err := builder.CreateContainerWithVolumes(ctx) + container, volumes, err := builder.CreateContainerWithVolumes(ctx) if err != nil { return nil, controllererrors.ErrCannotMarshalParams } diff --git a/operator/controllers/lifecycle/keptntask/job_utils_test.go b/operator/controllers/lifecycle/keptntask/job_utils_test.go index ec242e07e1..5d18417361 100644 --- a/operator/controllers/lifecycle/keptntask/job_utils_test.go +++ b/operator/controllers/lifecycle/keptntask/job_utils_test.go @@ -272,7 +272,7 @@ func makeTaskDefinitionWithConfigmapRef(name, namespace, configMapName string) * }, }, Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ - Function: klcv1alpha3.FunctionSpec{ + Function: &klcv1alpha3.FunctionSpec{ ConfigMapReference: klcv1alpha3.ConfigMapReference{ Name: configMapName, }, diff --git a/operator/controllers/lifecycle/keptntask/js_builder.go b/operator/controllers/lifecycle/keptntask/js_builder.go index 5a990f4b3c..b6fb1083e8 100644 --- a/operator/controllers/lifecycle/keptntask/js_builder.go +++ b/operator/controllers/lifecycle/keptntask/js_builder.go @@ -19,8 +19,8 @@ type JSBuilder struct { options BuilderOptions } -func newJSBuilder(options BuilderOptions) JSBuilder { - return JSBuilder{ +func NewJSBuilder(options BuilderOptions) *JSBuilder { + return &JSBuilder{ options: options, } } diff --git a/operator/controllers/lifecycle/keptntask/js_builder_test.go b/operator/controllers/lifecycle/keptntask/js_builder_test.go index b935be9157..067f235aa1 100644 --- a/operator/controllers/lifecycle/keptntask/js_builder_test.go +++ b/operator/controllers/lifecycle/keptntask/js_builder_test.go @@ -22,7 +22,7 @@ func TestJSBuilder_handleParent(t *testing.T) { Namespace: "default", }, Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ - Function: klcv1alpha3.FunctionSpec{ + Function: &klcv1alpha3.FunctionSpec{ FunctionReference: klcv1alpha3.FunctionReference{ Name: "mytaskdef", }}}, @@ -33,7 +33,7 @@ func TestJSBuilder_handleParent(t *testing.T) { Namespace: "default", }, Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ - Function: klcv1alpha3.FunctionSpec{ + Function: &klcv1alpha3.FunctionSpec{ FunctionReference: klcv1alpha3.FunctionReference{ Name: "mytd"}, Parameters: klcv1alpha3.TaskParameters{ @@ -124,7 +124,7 @@ func TestJSBuilder_hasParams(t *testing.T) { Namespace: "default", }, Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ - Function: klcv1alpha3.FunctionSpec{ + Function: &klcv1alpha3.FunctionSpec{ HttpReference: klcv1alpha3.HttpReference{Url: "donothing"}, Parameters: klcv1alpha3.TaskParameters{ Inline: map[string]string{"DATA2": "mydata2"}, @@ -140,7 +140,7 @@ func TestJSBuilder_hasParams(t *testing.T) { Namespace: "default", }, Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ - Function: klcv1alpha3.FunctionSpec{ + Function: &klcv1alpha3.FunctionSpec{ HttpReference: klcv1alpha3.HttpReference{Url: "something"}, FunctionReference: klcv1alpha3.FunctionReference{ Name: "mytaskdef"}, diff --git a/operator/controllers/lifecycle/keptntaskdefinition/reconcile_function.go b/operator/controllers/lifecycle/keptntaskdefinition/reconcile_function.go index 321dda631f..6f1f612e82 100644 --- a/operator/controllers/lifecycle/keptntaskdefinition/reconcile_function.go +++ b/operator/controllers/lifecycle/keptntaskdefinition/reconcile_function.go @@ -18,6 +18,9 @@ import ( ) func (r *KeptnTaskDefinitionReconciler) reconcileFunction(ctx context.Context, req ctrl.Request, definition *klcv1alpha3.KeptnTaskDefinition) error { + if !definition.IsJSSpecDefined() { + return nil + } if definition.Spec.Function.Inline != (klcv1alpha3.Inline{}) { err := r.reconcileFunctionInline(ctx, req, definition) if err != nil { diff --git a/operator/test/component/task/task_test.go b/operator/test/component/task/task_test.go index b86997c559..883709320d 100644 --- a/operator/test/component/task/task_test.go +++ b/operator/test/component/task/task_test.go @@ -237,7 +237,7 @@ func makeTaskDefinition(taskDefinitionName, namespace string) *klcv1alpha3.Keptn Namespace: namespace, }, Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ - Function: klcv1alpha3.FunctionSpec{ + Function: &klcv1alpha3.FunctionSpec{ ConfigMapReference: klcv1alpha3.ConfigMapReference{ Name: cmName, }, diff --git a/operator/test/component/taskdefinition/taskdefinition_test.go b/operator/test/component/taskdefinition/taskdefinition_test.go index adc55b5f84..da33ac77a6 100644 --- a/operator/test/component/taskdefinition/taskdefinition_test.go +++ b/operator/test/component/taskdefinition/taskdefinition_test.go @@ -41,7 +41,7 @@ var _ = Describe("Taskdefinition", Ordered, func() { Namespace: namespace, }, Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ - Function: klcv1alpha3.FunctionSpec{ + Function: &klcv1alpha3.FunctionSpec{ Inline: klcv1alpha3.Inline{ Code: "console.log(Hello);", }, @@ -90,7 +90,7 @@ var _ = Describe("Taskdefinition", Ordered, func() { Namespace: namespace, }, Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ - Function: klcv1alpha3.FunctionSpec{ + Function: &klcv1alpha3.FunctionSpec{ ConfigMapReference: klcv1alpha3.ConfigMapReference{ Name: "my-configmap", }, @@ -141,7 +141,7 @@ var _ = Describe("Taskdefinition", Ordered, func() { Namespace: namespace, }, Spec: klcv1alpha3.KeptnTaskDefinitionSpec{ - Function: klcv1alpha3.FunctionSpec{ + Function: &klcv1alpha3.FunctionSpec{ ConfigMapReference: klcv1alpha3.ConfigMapReference{ Name: "my-configmap-non-existing", }, diff --git a/test/integration/container-runtime/00-assert.yaml b/test/integration/container-runtime/00-assert.yaml new file mode 100644 index 0000000000..109db6ff9f --- /dev/null +++ b/test/integration/container-runtime/00-assert.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test + name: test +status: + readyReplicas: 1 +--- +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnWorkload +metadata: + name: waiter-waiter +--- +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnWorkloadInstance +metadata: + name: waiter-waiter-0.4 +status: + currentPhase: Completed + deploymentStatus: Succeeded + postDeploymentEvaluationStatus: Succeeded + postDeploymentStatus: Succeeded + preDeploymentEvaluationStatus: Succeeded + preDeploymentStatus: Succeeded + preDeploymentTaskStatus: + - definitionName: pre-deployment-sleep + status: Succeeded + status: Succeeded +--- +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnApp +metadata: + name: waiter +--- +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnAppVersion +metadata: + name: waiter-1b899b6ce1-6b86b273 +status: + currentPhase: Completed + status: Succeeded +--- +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTask +metadata: + annotations: + container: test +status: + status: Succeeded +--- +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + container: test + keptn.sh/app: waiter + keptn.sh/version: '0.4' + keptn.sh/workload: waiter-waiter +status: + conditions: + - type: Complete + status: 'True' + succeeded: 1 diff --git a/test/integration/container-runtime/00-install.yaml b/test/integration/container-runtime/00-install.yaml new file mode 100644 index 0000000000..356aaa942f --- /dev/null +++ b/test/integration/container-runtime/00-install.yaml @@ -0,0 +1,40 @@ +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: pre-deployment-sleep + annotations: + container: test +spec: + container: + name: testy-test + image: busybox:1.36.0 + command: + - 'sh' + - '-c' + - 'sleep 30' +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test + name: test +spec: + replicas: 1 + selector: + matchLabels: + app: test + strategy: {} + template: + metadata: + labels: + app: test + annotations: + keptn.sh/workload: waiter + keptn.sh/version: "0.4" + keptn.sh/pre-deployment-tasks: pre-deployment-sleep + spec: + containers: + - image: busybox + name: busybox + command: ['sh', '-c', 'echo The app is running! && sleep infinity'] diff --git a/test/integration/container-runtime/00-teststep.yaml b/test/integration/container-runtime/00-teststep.yaml new file mode 100644 index 0000000000..ad4f1d95d5 --- /dev/null +++ b/test/integration/container-runtime/00-teststep.yaml @@ -0,0 +1,4 @@ +apiVersion: kuttl.dev/v1 +kind: TestStep +commands: + - script: kubectl annotate ns $NAMESPACE keptn.sh/lifecycle-toolkit='enabled'