From 596b0829d9494dbafa64eafdf48d212a57f0d9b7 Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Fri, 20 Dec 2024 15:59:41 +0300 Subject: [PATCH 01/11] feat: add webhook config Signed-off-by: Vladislav Sukhin --- api/v1/testkube.yaml | 58 +++++++++++++++++++ pkg/api/v1/testkube/model_webhook.go | 4 +- .../v1/testkube/model_webhook_config_value.go | 17 ++++++ .../testkube/model_webhook_create_request.go | 4 +- .../model_webhook_parameter_schema.go | 22 +++++++ .../v1/testkube/model_webhook_template_ref.go | 16 +++++ .../testkube/model_webhook_update_request.go | 4 +- 7 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 pkg/api/v1/testkube/model_webhook_config_value.go create mode 100644 pkg/api/v1/testkube/model_webhook_parameter_schema.go create mode 100644 pkg/api/v1/testkube/model_webhook_template_ref.go diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index 383e526afb..04b94bf934 100644 --- a/api/v1/testkube.yaml +++ b/api/v1/testkube.yaml @@ -7346,6 +7346,10 @@ components: example: - true - false + webhookConfig: + $ref: "#/components/schemas/WebhookConfigSchema" + webhookTemplateRef: + $ref: "#/components/schemas/WebhookTemplateRef" Event: description: Event data @@ -10687,6 +10691,60 @@ components: description: Namespace is the namespace of resource being referenced $ref: "#/components/schemas/BoxedString" + WebhookParameterSchema: + type: object + properties: + description: + description: description for the property + type: string + required: + description: whether parameter is required + type: boolean + default: false + example: + description: example value for the parameter + type: string + default: + $ref: "#/components/schemas/BoxedString" + pattern: + type: string + description: "regular expression to match" + + WebhookConfigSchema: + type: object + description: configuration definition + additionalProperties: + $ref: "#/components/schemas/WebhookParameterSchema" + + WebhookConfigValue: + type: object + description: configuration value + properties: + public: + description: public value to use in webhook template + type: string + private: + description: private value stored in secret to use in webhook template + $ref: "#/components/schemas/SecretRef" + + WebhookConfig: + type: object + description: configuration values to pass to the webhook template + additionalProperties: + $ref: "#/components/schemas/WebhookConfigValue" + + WebhookTemplateRef: + type: object + properties: + name: + description: webhook template name to include + type: string + config: + description: webhook config to use in webhook template + $ref: "#/components/schemas/WebhookConfig" + required: + - name + # # Errors # diff --git a/pkg/api/v1/testkube/model_webhook.go b/pkg/api/v1/testkube/model_webhook.go index 9ad77b1ca2..cee74e8f44 100644 --- a/pkg/api/v1/testkube/model_webhook.go +++ b/pkg/api/v1/testkube/model_webhook.go @@ -30,5 +30,7 @@ type Webhook struct { // webhook annotations Annotations map[string]string `json:"annotations,omitempty"` // whether webhook is disabled - Disabled bool `json:"disabled,omitempty"` + Disabled bool `json:"disabled,omitempty"` + WebhookConfig map[string]WebhookParameterSchema `json:"webhookConfig,omitempty"` + WebhookTemplateRef *WebhookTemplateRef `json:"webhookTemplateRef,omitempty"` } diff --git a/pkg/api/v1/testkube/model_webhook_config_value.go b/pkg/api/v1/testkube/model_webhook_config_value.go new file mode 100644 index 0000000000..ed8312d776 --- /dev/null +++ b/pkg/api/v1/testkube/model_webhook_config_value.go @@ -0,0 +1,17 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +// configuration value +type WebhookConfigValue struct { + // public value to use in webhook template + Public string `json:"public,omitempty"` + Private *SecretRef `json:"private,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_webhook_create_request.go b/pkg/api/v1/testkube/model_webhook_create_request.go index c4fe0ef2fc..07753df9f4 100644 --- a/pkg/api/v1/testkube/model_webhook_create_request.go +++ b/pkg/api/v1/testkube/model_webhook_create_request.go @@ -30,5 +30,7 @@ type WebhookCreateRequest struct { // webhook annotations Annotations map[string]string `json:"annotations,omitempty"` // whether webhook is disabled - Disabled bool `json:"disabled,omitempty"` + Disabled bool `json:"disabled,omitempty"` + WebhookConfig map[string]WebhookParameterSchema `json:"webhookConfig,omitempty"` + WebhookTemplateRef *WebhookTemplateRef `json:"webhookTemplateRef,omitempty"` } diff --git a/pkg/api/v1/testkube/model_webhook_parameter_schema.go b/pkg/api/v1/testkube/model_webhook_parameter_schema.go new file mode 100644 index 0000000000..a45272fc7b --- /dev/null +++ b/pkg/api/v1/testkube/model_webhook_parameter_schema.go @@ -0,0 +1,22 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +type WebhookParameterSchema struct { + // description for the property + Description string `json:"description,omitempty"` + // whether parameter is required + Required bool `json:"required,omitempty"` + // example value for the parameter + Example string `json:"example,omitempty"` + Default_ *BoxedString `json:"default,omitempty"` + // regular expression to match + Pattern string `json:"pattern,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_webhook_template_ref.go b/pkg/api/v1/testkube/model_webhook_template_ref.go new file mode 100644 index 0000000000..fe6fce7965 --- /dev/null +++ b/pkg/api/v1/testkube/model_webhook_template_ref.go @@ -0,0 +1,16 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +type WebhookTemplateRef struct { + // webhook template name to include + Name string `json:"name"` + Config map[string]WebhookConfigValue `json:"config,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_webhook_update_request.go b/pkg/api/v1/testkube/model_webhook_update_request.go index 46307f9d2e..e7daf92a31 100644 --- a/pkg/api/v1/testkube/model_webhook_update_request.go +++ b/pkg/api/v1/testkube/model_webhook_update_request.go @@ -30,5 +30,7 @@ type WebhookUpdateRequest struct { // webhook annotations Annotations *map[string]string `json:"annotations,omitempty"` // whether webhook is disabled - Disabled *bool `json:"disabled,omitempty"` + Disabled *bool `json:"disabled,omitempty"` + WebhookConfig *map[string]WebhookParameterSchema `json:"webhookConfig,omitempty"` + WebhookTemplateRef *WebhookTemplateRef `json:"webhookTemplateRef,omitempty"` } From 97332964704801283ec4a44225947f18914d64a6 Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Fri, 20 Dec 2024 16:12:43 +0300 Subject: [PATCH 02/11] fix: use boxed value Signed-off-by: Vladislav Sukhin --- api/v1/testkube.yaml | 2 +- pkg/api/v1/testkube/model_webhook_config_value.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index 04b94bf934..ba4fe7e3eb 100644 --- a/api/v1/testkube.yaml +++ b/api/v1/testkube.yaml @@ -10722,7 +10722,7 @@ components: properties: public: description: public value to use in webhook template - type: string + $ref: "#/components/schemas/BoxedString" private: description: private value stored in secret to use in webhook template $ref: "#/components/schemas/SecretRef" diff --git a/pkg/api/v1/testkube/model_webhook_config_value.go b/pkg/api/v1/testkube/model_webhook_config_value.go index ed8312d776..29759168c8 100644 --- a/pkg/api/v1/testkube/model_webhook_config_value.go +++ b/pkg/api/v1/testkube/model_webhook_config_value.go @@ -11,7 +11,6 @@ package testkube // configuration value type WebhookConfigValue struct { - // public value to use in webhook template - Public string `json:"public,omitempty"` - Private *SecretRef `json:"private,omitempty"` + Public *BoxedString `json:"public,omitempty"` + Private *SecretRef `json:"private,omitempty"` } From 05949049334b77c1ae9ce985b04a3847cb5fe96d Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Fri, 20 Dec 2024 16:53:06 +0300 Subject: [PATCH 03/11] fix: change description Signed-off-by: Vladislav Sukhin --- api/v1/testkube.yaml | 2 +- pkg/api/v1/testkube/model_webhook_template_ref.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index ba4fe7e3eb..06e49eed71 100644 --- a/api/v1/testkube.yaml +++ b/api/v1/testkube.yaml @@ -10737,7 +10737,7 @@ components: type: object properties: name: - description: webhook template name to include + description: webhook template name to use type: string config: description: webhook config to use in webhook template diff --git a/pkg/api/v1/testkube/model_webhook_template_ref.go b/pkg/api/v1/testkube/model_webhook_template_ref.go index fe6fce7965..752eb69fe9 100644 --- a/pkg/api/v1/testkube/model_webhook_template_ref.go +++ b/pkg/api/v1/testkube/model_webhook_template_ref.go @@ -10,7 +10,7 @@ package testkube type WebhookTemplateRef struct { - // webhook template name to include + // webhook template name to use Name string `json:"name"` Config map[string]WebhookConfigValue `json:"config,omitempty"` } From 0a72a49303587c7ddc819f715177b5ad6e0bf81a Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Fri, 20 Dec 2024 20:16:02 +0300 Subject: [PATCH 04/11] fix: tune confg model Signed-off-by: Vladislav Sukhin --- api/v1/testkube.yaml | 14 +++++++------- pkg/api/v1/testkube/model_webhook.go | 3 ++- .../v1/testkube/model_webhook_create_request.go | 3 ++- .../v1/testkube/model_webhook_parameter_schema.go | 1 + pkg/api/v1/testkube/model_webhook_template_ref.go | 3 +-- .../v1/testkube/model_webhook_update_request.go | 3 ++- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index 06e49eed71..ee7412428a 100644 --- a/api/v1/testkube.yaml +++ b/api/v1/testkube.yaml @@ -7346,8 +7346,10 @@ components: example: - true - false - webhookConfig: - $ref: "#/components/schemas/WebhookConfigSchema" + config: + $ref: "#/components/schemas/WebhookConfig" + parameters: + $ref: "#/components/schemas/WebhookSchema" webhookTemplateRef: $ref: "#/components/schemas/WebhookTemplateRef" @@ -10693,6 +10695,7 @@ components: WebhookParameterSchema: type: object + description: parameter definition properties: description: description: description for the property @@ -10710,7 +10713,7 @@ components: type: string description: "regular expression to match" - WebhookConfigSchema: + WebhookSchema: type: object description: configuration definition additionalProperties: @@ -10729,7 +10732,7 @@ components: WebhookConfig: type: object - description: configuration values to pass to the webhook template + description: configuration values additionalProperties: $ref: "#/components/schemas/WebhookConfigValue" @@ -10739,9 +10742,6 @@ components: name: description: webhook template name to use type: string - config: - description: webhook config to use in webhook template - $ref: "#/components/schemas/WebhookConfig" required: - name diff --git a/pkg/api/v1/testkube/model_webhook.go b/pkg/api/v1/testkube/model_webhook.go index cee74e8f44..32905b4011 100644 --- a/pkg/api/v1/testkube/model_webhook.go +++ b/pkg/api/v1/testkube/model_webhook.go @@ -31,6 +31,7 @@ type Webhook struct { Annotations map[string]string `json:"annotations,omitempty"` // whether webhook is disabled Disabled bool `json:"disabled,omitempty"` - WebhookConfig map[string]WebhookParameterSchema `json:"webhookConfig,omitempty"` + Config map[string]WebhookConfigValue `json:"config,omitempty"` + Parameters map[string]WebhookParameterSchema `json:"parameters,omitempty"` WebhookTemplateRef *WebhookTemplateRef `json:"webhookTemplateRef,omitempty"` } diff --git a/pkg/api/v1/testkube/model_webhook_create_request.go b/pkg/api/v1/testkube/model_webhook_create_request.go index 07753df9f4..3b0d6df238 100644 --- a/pkg/api/v1/testkube/model_webhook_create_request.go +++ b/pkg/api/v1/testkube/model_webhook_create_request.go @@ -31,6 +31,7 @@ type WebhookCreateRequest struct { Annotations map[string]string `json:"annotations,omitempty"` // whether webhook is disabled Disabled bool `json:"disabled,omitempty"` - WebhookConfig map[string]WebhookParameterSchema `json:"webhookConfig,omitempty"` + Config map[string]WebhookConfigValue `json:"config,omitempty"` + Parameters map[string]WebhookParameterSchema `json:"parameters,omitempty"` WebhookTemplateRef *WebhookTemplateRef `json:"webhookTemplateRef,omitempty"` } diff --git a/pkg/api/v1/testkube/model_webhook_parameter_schema.go b/pkg/api/v1/testkube/model_webhook_parameter_schema.go index a45272fc7b..194908475a 100644 --- a/pkg/api/v1/testkube/model_webhook_parameter_schema.go +++ b/pkg/api/v1/testkube/model_webhook_parameter_schema.go @@ -9,6 +9,7 @@ */ package testkube +// parameter definition type WebhookParameterSchema struct { // description for the property Description string `json:"description,omitempty"` diff --git a/pkg/api/v1/testkube/model_webhook_template_ref.go b/pkg/api/v1/testkube/model_webhook_template_ref.go index 752eb69fe9..943a05a1fe 100644 --- a/pkg/api/v1/testkube/model_webhook_template_ref.go +++ b/pkg/api/v1/testkube/model_webhook_template_ref.go @@ -11,6 +11,5 @@ package testkube type WebhookTemplateRef struct { // webhook template name to use - Name string `json:"name"` - Config map[string]WebhookConfigValue `json:"config,omitempty"` + Name string `json:"name"` } diff --git a/pkg/api/v1/testkube/model_webhook_update_request.go b/pkg/api/v1/testkube/model_webhook_update_request.go index e7daf92a31..64e5060b33 100644 --- a/pkg/api/v1/testkube/model_webhook_update_request.go +++ b/pkg/api/v1/testkube/model_webhook_update_request.go @@ -31,6 +31,7 @@ type WebhookUpdateRequest struct { Annotations *map[string]string `json:"annotations,omitempty"` // whether webhook is disabled Disabled *bool `json:"disabled,omitempty"` - WebhookConfig *map[string]WebhookParameterSchema `json:"webhookConfig,omitempty"` + Config *map[string]WebhookConfigValue `json:"config,omitempty"` + Parameters *map[string]WebhookParameterSchema `json:"parameters,omitempty"` WebhookTemplateRef *WebhookTemplateRef `json:"webhookTemplateRef,omitempty"` } From 32d8fd563e93466675704b7d2e1e8d2a18dda583 Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Mon, 23 Dec 2024 17:29:16 +0300 Subject: [PATCH 05/11] fix: mapping new webhook fields Signed-off-by: Vladislav Sukhin --- Makefile | 1 + go.mod | 2 +- go.sum | 4 +- .../testkube/model_webhook_update_request.go | 2 +- pkg/crd/crd_test.go | 61 +++++++++- pkg/crd/templates/webhook.tmpl | 49 ++++++++ pkg/mapper/webhooks/mapper.go | 108 ++++++++++++++++++ 7 files changed, 221 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index d4594e1222..9e79bffce8 100644 --- a/Makefile +++ b/Makefile @@ -138,6 +138,7 @@ openapi-generate-model-testkube: find ./pkg/api/v1/testkube -name "*update*.go" -type f -exec sed -i '' -e "s/ \*PodRequest/ \*\*PodUpdateRequest/g" {} \; find ./pkg/api/v1/testkube -name "*update*.go" -type f -exec sed -i '' -e "s/ \*PodResourcesRequest/ \*\*PodResourcesUpdateRequest/g" {} \; find ./pkg/api/v1/testkube -name "*update*.go" -type f -exec sed -i '' -e "s/ \*ResourceRequest/ \*ResourceUpdateRequest/g" {} \; + find ./pkg/api/v1/testkube -name "*update*.go" -type f -exec sed -i '' -e "s/ \*WebhookTemplateRef/ \*\*WebhookTemplateRef/g" {} \; find ./pkg/api/v1/testkube -type f -exec sed -i '' -e "s/ Deprecated/ \\n\/\/ Deprecated/g" {} \; go fmt pkg/api/v1/testkube/*.go diff --git a/go.mod b/go.mod index 3e91f2b9bc..fd58f902d1 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/keygen-sh/jsonapi-go v1.2.1 github.com/keygen-sh/keygen-go/v3 v3.2.0 github.com/kubepug/kubepug v1.7.1 - github.com/kubeshop/testkube-operator v1.17.55-0.20241219143413-6a33aef7130f + github.com/kubeshop/testkube-operator v1.17.55-0.20241220172614-fb95745c73c1 github.com/minio/minio-go/v7 v7.0.66 github.com/montanaflynn/stats v0.7.1 github.com/moogar0880/problems v0.1.1 diff --git a/go.sum b/go.sum index 0419aff4e3..56baeafa04 100644 --- a/go.sum +++ b/go.sum @@ -336,8 +336,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kubepug/kubepug v1.7.1 h1:LKhfSxS8Y5mXs50v+3Lpyec+cogErDLcV7CMUuiaisw= github.com/kubepug/kubepug v1.7.1/go.mod h1:lv+HxD0oTFL7ZWjj0u6HKhMbbTIId3eG7aWIW0gyF8g= -github.com/kubeshop/testkube-operator v1.17.55-0.20241219143413-6a33aef7130f h1:7wDuGy4MohoFcgz2QPH/02JWCsgBqkWdGgefIH2YSEE= -github.com/kubeshop/testkube-operator v1.17.55-0.20241219143413-6a33aef7130f/go.mod h1:P47tw1nKQFufdsZndyq2HG2MSa0zK/lU0XpRfZtEmIk= +github.com/kubeshop/testkube-operator v1.17.55-0.20241220172614-fb95745c73c1 h1:C1TIQh5zlZG25KlI8RuyaeoehcQE4FKTuw6k5oATbp0= +github.com/kubeshop/testkube-operator v1.17.55-0.20241220172614-fb95745c73c1/go.mod h1:P47tw1nKQFufdsZndyq2HG2MSa0zK/lU0XpRfZtEmIk= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= diff --git a/pkg/api/v1/testkube/model_webhook_update_request.go b/pkg/api/v1/testkube/model_webhook_update_request.go index 64e5060b33..a779a3ac96 100644 --- a/pkg/api/v1/testkube/model_webhook_update_request.go +++ b/pkg/api/v1/testkube/model_webhook_update_request.go @@ -33,5 +33,5 @@ type WebhookUpdateRequest struct { Disabled *bool `json:"disabled,omitempty"` Config *map[string]WebhookConfigValue `json:"config,omitempty"` Parameters *map[string]WebhookParameterSchema `json:"parameters,omitempty"` - WebhookTemplateRef *WebhookTemplateRef `json:"webhookTemplateRef,omitempty"` + WebhookTemplateRef **WebhookTemplateRef `json:"webhookTemplateRef,omitempty"` } diff --git a/pkg/crd/crd_test.go b/pkg/crd/crd_test.go index 5db775ba33..a540e80de3 100644 --- a/pkg/crd/crd_test.go +++ b/pkg/crd/crd_test.go @@ -12,7 +12,7 @@ func TestGenerateYAML(t *testing.T) { t.Run("generate single CRD yaml", func(t *testing.T) { // given - expected := "apiVersion: executor.testkube.io/v1\nkind: Webhook\nmetadata:\n name: name1\n namespace: namespace1\n labels:\n key1: value1\nspec:\n events:\n - start-test\n uri: http://localhost\n selector: app=backend\n payloadObjectField: text\n payloadTemplate: {{ .Id }}\n payloadTemplateReference: ref\n headers:\n Content-Type: appication/xml\n" + expected := "apiVersion: executor.testkube.io/v1\nkind: Webhook\nmetadata:\n name: name1\n namespace: namespace1\n labels:\n key1: value1\n annotations:\n key2: value2 \nspec:\n events:\n - start-test\n uri: http://localhost\n selector: app=backend\n payloadObjectField: text\n payloadTemplate: {{ .Id }}\n payloadTemplateReference: ref\n headers:\n Content-Type: appication/xml\n disabled: true\n config:\n var1:\n public: pb\n var2:\n private:\n namespace: ns\n name: secret\n key: pr\n parameters:\n var3:\n description: descr\n required: true\n example: 12345\n default: 0\n pattern: [0-9]*\n webhookTemplateRef:\n name: tmpl\n" webhooks := []testkube.Webhook{ { Name: "name1", @@ -21,10 +21,29 @@ func TestGenerateYAML(t *testing.T) { Events: []testkube.EventType{*testkube.EventStartTest}, Selector: "app=backend", Labels: map[string]string{"key1": "value1"}, + Annotations: map[string]string{"key2": "value2"}, PayloadObjectField: "text", PayloadTemplate: "{{ .Id }}", Headers: map[string]string{"Content-Type": "appication/xml"}, PayloadTemplateReference: "ref", + Disabled: true, + Config: map[string]testkube.WebhookConfigValue{ + "var1": {Public: &testkube.BoxedString{Value: "pb"}}, + "var2": {Private: &testkube.SecretRef{Namespace: "ns", Name: "secret", Key: "pr"}}}, + Parameters: map[string]testkube.WebhookParameterSchema{ + "var3": { + Description: "descr", + Required: true, + Example: "12345", + Default_: &testkube.BoxedString{ + Value: "0", + }, + Pattern: "[0-9]*", + }, + }, + WebhookTemplateRef: &testkube.WebhookTemplateRef{ + Name: "tmpl", + }, }, } @@ -38,7 +57,7 @@ func TestGenerateYAML(t *testing.T) { t.Run("generate multiple CRDs yaml", func(t *testing.T) { // given - expected := "apiVersion: executor.testkube.io/v1\nkind: Webhook\nmetadata:\n name: name1\n namespace: namespace1\n labels:\n key1: value1\nspec:\n events:\n - start-test\n uri: http://localhost\n selector: app=backend\n payloadObjectField: text\n payloadTemplate: {{ .Id }}\n payloadTemplateReference: ref\n headers:\n Content-Type: appication/xml\n\n---\napiVersion: executor.testkube.io/v1\nkind: Webhook\nmetadata:\n name: name2\n namespace: namespace2\n labels:\n key2: value2\nspec:\n events:\n - end-test-success\n uri: http://localhost\n selector: app=backend\n payloadObjectField: text\n payloadTemplate: {{ .Id }}\n payloadTemplateReference: ref\n headers:\n Content-Type: appication/xml\n" + expected := "apiVersion: executor.testkube.io/v1\nkind: Webhook\nmetadata:\n name: name1\n namespace: namespace1\n labels:\n key1: value1\n annotations:\n key3: value3 \nspec:\n events:\n - start-test\n uri: http://localhost\n selector: app=backend\n payloadObjectField: text\n payloadTemplate: {{ .Id }}\n payloadTemplateReference: ref\n headers:\n Content-Type: appication/xml\n disabled: true\n config:\n var1:\n public: pb\n var2:\n private:\n namespace: ns\n name: secret\n key: pr\n parameters:\n var3:\n description: descr\n required: true\n example: 12345\n default: 0\n pattern: [0-9]*\n webhookTemplateRef:\n name: tmpl\n\n---\napiVersion: executor.testkube.io/v1\nkind: Webhook\nmetadata:\n name: name2\n namespace: namespace2\n labels:\n key2: value2\n annotations:\n key4: value4 \nspec:\n events:\n - end-test-success\n uri: http://localhost\n selector: app=backend\n payloadObjectField: text\n payloadTemplate: {{ .Id }}\n payloadTemplateReference: ref\n headers:\n Content-Type: appication/xml\n disabled: true\n config:\n var1:\n public: pb\n var2:\n private:\n namespace: ns\n name: secret\n key: pr\n parameters:\n var3:\n description: descr\n required: true\n example: 12345\n default: 0\n pattern: [0-9]*\n webhookTemplateRef:\n name: tmpl\n" webhooks := []testkube.Webhook{ { Name: "name1", @@ -47,10 +66,29 @@ func TestGenerateYAML(t *testing.T) { Events: []testkube.EventType{*testkube.EventStartTest}, Selector: "app=backend", Labels: map[string]string{"key1": "value1"}, + Annotations: map[string]string{"key3": "value3"}, PayloadObjectField: "text", PayloadTemplate: "{{ .Id }}", Headers: map[string]string{"Content-Type": "appication/xml"}, PayloadTemplateReference: "ref", + Disabled: true, + Config: map[string]testkube.WebhookConfigValue{ + "var1": {Public: &testkube.BoxedString{Value: "pb"}}, + "var2": {Private: &testkube.SecretRef{Namespace: "ns", Name: "secret", Key: "pr"}}}, + Parameters: map[string]testkube.WebhookParameterSchema{ + "var3": { + Description: "descr", + Required: true, + Example: "12345", + Default_: &testkube.BoxedString{ + Value: "0", + }, + Pattern: "[0-9]*", + }, + }, + WebhookTemplateRef: &testkube.WebhookTemplateRef{ + Name: "tmpl", + }, }, { Name: "name2", @@ -59,10 +97,29 @@ func TestGenerateYAML(t *testing.T) { Events: []testkube.EventType{*testkube.EventEndTestSuccess}, Selector: "app=backend", Labels: map[string]string{"key2": "value2"}, + Annotations: map[string]string{"key4": "value4"}, PayloadObjectField: "text", PayloadTemplate: "{{ .Id }}", Headers: map[string]string{"Content-Type": "appication/xml"}, PayloadTemplateReference: "ref", + Disabled: true, + Config: map[string]testkube.WebhookConfigValue{ + "var1": {Public: &testkube.BoxedString{Value: "pb"}}, + "var2": {Private: &testkube.SecretRef{Namespace: "ns", Name: "secret", Key: "pr"}}}, + Parameters: map[string]testkube.WebhookParameterSchema{ + "var3": { + Description: "descr", + Required: true, + Example: "12345", + Default_: &testkube.BoxedString{ + Value: "0", + }, + Pattern: "[0-9]*", + }, + }, + WebhookTemplateRef: &testkube.WebhookTemplateRef{ + Name: "tmpl", + }, }, } diff --git a/pkg/crd/templates/webhook.tmpl b/pkg/crd/templates/webhook.tmpl index dae15d9bb7..68f0ecb5a4 100644 --- a/pkg/crd/templates/webhook.tmpl +++ b/pkg/crd/templates/webhook.tmpl @@ -9,6 +9,12 @@ metadata: {{ $key }}: {{ $value }} {{- end }} {{- end }} + {{- if ne (len .Annotations) 0 }} + annotations: + {{- range $key, $value := .Annotations }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} spec: {{- if ne (len .Events) 0 }} events: @@ -36,4 +42,47 @@ spec: {{- range $key, $value := .Headers }} {{ $key }}: {{ $value }} {{- end }} + {{- if .Disabled }} + disabled: {{ .Disabled }} + {{- end }} + {{- if ne (len .Config) 0 }} + config: + {{- range $key, $value := .Config }} + {{ $key }}: + {{- if $value.Public }} + public: {{ $value.Public.Value }} + {{- end }} + {{- if $value.Private }} + private: + namespace: {{ $value.Private.Namespace }} + name: {{ $value.Private.Name }} + key: {{ $value.Private.Key }} + {{- end }} + {{- end }} + {{- end }} + {{- if ne (len .Parameters) 0 }} + parameters: + {{- range $key, $value := .Parameters }} + {{ $key }}: + {{- if $value.Description }} + description: {{ $value.Description }} + {{- end }} + {{- if $value.Required }} + required: {{ $value.Required }} + {{- end }} + {{- if $value.Example }} + example: {{ $value.Example }} + {{- end }} + {{- if $value.Default_ }} + default: {{ $value.Default_.Value }} + {{- end }} + {{- if $value.Pattern }} + pattern: {{ $value.Pattern }} + {{- end }} + {{- end }} + {{- end }} + {{- if .WebhookTemplateRef }} + webhookTemplateRef: + name: {{ .WebhookTemplateRef.Name }} + {{- end }} {{- end }} diff --git a/pkg/mapper/webhooks/mapper.go b/pkg/mapper/webhooks/mapper.go index 21f00d88cc..3e961dd074 100644 --- a/pkg/mapper/webhooks/mapper.go +++ b/pkg/mapper/webhooks/mapper.go @@ -4,6 +4,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" executorv1 "github.com/kubeshop/testkube-operator/api/executor/v1" + "github.com/kubeshop/testkube/internal/common" "github.com/kubeshop/testkube/pkg/api/v1/testkube" ) @@ -21,6 +22,52 @@ func MapCRDToAPI(item executorv1.Webhook) testkube.Webhook { PayloadTemplateReference: item.Spec.PayloadTemplateReference, Headers: item.Spec.Headers, Disabled: item.Spec.Disabled, + Config: common.MapMap(item.Spec.Config, MapConfigValueCRDToAPI), + Parameters: common.MapMap(item.Spec.Parameters, MapParameterSchemaCRDToAPI), + WebhookTemplateRef: common.MapPtr(item.Spec.WebhookTemplateRef, MapTemplateRefCRDToAPI), + } +} + +// MapStringToBoxedString maps string to boxed string +func MapStringToBoxedString(v *string) *testkube.BoxedString { + if v == nil { + return nil + } + return &testkube.BoxedString{Value: *v} +} + +// MapSecretRefCRDToAPI maps secret ref to OpenAPI spec +func MapSecretRefCRDToAPI(v executorv1.SecretRef) testkube.SecretRef { + return testkube.SecretRef{ + Namespace: v.Namespace, + Name: v.Name, + Key: v.Key, + } +} + +// MapConigValueCRDToAPI maps config value to OpenAPI spec +func MapConfigValueCRDToAPI(v executorv1.WebhookConfigValue) testkube.WebhookConfigValue { + return testkube.WebhookConfigValue{ + Public: MapStringToBoxedString(v.Public), + Private: common.MapPtr(v.Private, MapSecretRefCRDToAPI), + } +} + +// MapParameterSchemaCRDToAPI maps parameter schema to OpenAPI spec +func MapParameterSchemaCRDToAPI(v executorv1.WebhookParameterSchema) testkube.WebhookParameterSchema { + return testkube.WebhookParameterSchema{ + Description: v.Description, + Required: v.Required, + Example: v.Example, + Default_: MapStringToBoxedString(v.Default_), + Pattern: v.Pattern, + } +} + +// MapTemplateRefCRDToAPI maps template ref to OpenAPI spec +func MapTemplateRefCRDToAPI(v executorv1.WebhookTemplateRef) testkube.WebhookTemplateRef { + return testkube.WebhookTemplateRef{ + Name: v.Name, } } @@ -57,10 +104,56 @@ func MapAPIToCRD(request testkube.WebhookCreateRequest) executorv1.Webhook { PayloadTemplateReference: request.PayloadTemplateReference, Headers: request.Headers, Disabled: request.Disabled, + Config: common.MapMap(request.Config, MapConfigValueAPIToCRD), + Parameters: common.MapMap(request.Parameters, MapParameterSchemaAPIToCRD), + WebhookTemplateRef: common.MapPtr(request.WebhookTemplateRef, MapTemplateRefAPIToCRD), }, } } +// MapBoxedStringToString maps boxed string to string +func MapBoxedStringToString(v *testkube.BoxedString) *string { + if v == nil { + return nil + } + return &v.Value +} + +// MapSecretRefAPIToCRD maps secret ref to CRD spec +func MapSecretRefAPIToCRD(v testkube.SecretRef) executorv1.SecretRef { + return executorv1.SecretRef{ + Namespace: v.Namespace, + Name: v.Name, + Key: v.Key, + } +} + +// MapConigValueAPIToCRD maps config value to CRD spec +func MapConfigValueAPIToCRD(v testkube.WebhookConfigValue) executorv1.WebhookConfigValue { + return executorv1.WebhookConfigValue{ + Public: MapBoxedStringToString(v.Public), + Private: common.MapPtr(v.Private, MapSecretRefAPIToCRD), + } +} + +// MapParameterSchemaAPIToCRD maps parameter schema to CRD spec +func MapParameterSchemaAPIToCRD(v testkube.WebhookParameterSchema) executorv1.WebhookParameterSchema { + return executorv1.WebhookParameterSchema{ + Description: v.Description, + Required: v.Required, + Example: v.Example, + Default_: MapBoxedStringToString(v.Default_), + Pattern: v.Pattern, + } +} + +// MapTemplateRefAPIToCRD maps template ref to CRD spec +func MapTemplateRefAPIToCRD(v testkube.WebhookTemplateRef) executorv1.WebhookTemplateRef { + return executorv1.WebhookTemplateRef{ + Name: v.Name, + } +} + // MapEventTypesToStringArray maps OpenAPI spec list of EventType to string array func MapEventTypesToStringArray(eventTypes []testkube.EventType) (arr []executorv1.EventType) { for _, et := range eventTypes { @@ -131,6 +224,18 @@ func MapUpdateToSpec(request testkube.WebhookUpdateRequest, webhook *executorv1. webhook.Spec.Disabled = *request.Disabled } + if request.Config != nil { + webhook.Spec.Config = common.MapMap(*request.Config, MapConfigValueAPIToCRD) + } + + if request.Parameters != nil { + webhook.Spec.Parameters = common.MapMap(*request.Parameters, MapParameterSchemaAPIToCRD) + } + + if request.WebhookTemplateRef != nil { + webhook.Spec.WebhookTemplateRef = common.MapPtr(*request.WebhookTemplateRef, MapTemplateRefAPIToCRD) + } + return webhook } @@ -181,6 +286,9 @@ func MapSpecToUpdate(webhook *executorv1.Webhook) (request testkube.WebhookUpdat request.Annotations = &webhook.Annotations request.Headers = &webhook.Spec.Headers request.Disabled = &webhook.Spec.Disabled + request.Config = common.Ptr(common.MapMap(webhook.Spec.Config, MapConfigValueCRDToAPI)) + request.Parameters = common.Ptr(common.MapMap(webhook.Spec.Parameters, MapParameterSchemaCRDToAPI)) + request.WebhookTemplateRef = common.Ptr(common.MapPtr(webhook.Spec.WebhookTemplateRef, MapTemplateRefCRDToAPI)) return request } From 6d9d798bab839d5bcd9111af48d86473c71ea63a Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Tue, 24 Dec 2024 15:33:32 +0300 Subject: [PATCH 06/11] fix: update webhook cli Signed-off-by: Vladislav Sukhin --- .../commands/webhooks/common.go | 157 ++++++++++++++++++ .../commands/webhooks/create.go | 6 + cmd/kubectl-testkube/commands/webhooks/get.go | 11 +- .../commands/webhooks/update.go | 6 + internal/app/api/v1/webhook.go | 14 +- .../model_webhook_create_request_extended.go | 36 ++++ pkg/api/v1/testkube/model_webhook_extended.go | 33 ++++ 7 files changed, 243 insertions(+), 20 deletions(-) create mode 100644 pkg/api/v1/testkube/model_webhook_create_request_extended.go diff --git a/cmd/kubectl-testkube/commands/webhooks/common.go b/cmd/kubectl-testkube/commands/webhooks/common.go index bc3cac9269..da40ef86a1 100644 --- a/cmd/kubectl-testkube/commands/webhooks/common.go +++ b/cmd/kubectl-testkube/commands/webhooks/common.go @@ -1,11 +1,16 @@ package webhooks import ( + "encoding/csv" + "errors" "fmt" "os" + "strconv" + "strings" "github.com/spf13/cobra" + "github.com/kubeshop/testkube/internal/common" apiv1 "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" webhooksmapper "github.com/kubeshop/testkube/pkg/mapper/webhooks" @@ -48,6 +53,39 @@ func NewCreateWebhookOptionsFromFlags(cmd *cobra.Command) (options apiv1.CreateW } payloadTemplateReference := cmd.Flag("payload-template-reference").Value.String() + var config map[string]testkube.WebhookConfigValue + configs, err := cmd.Flags().GetStringToString("config") + if err != nil { + return options, err + } + + if len(configs) != 0 { + config, err = getWebhookConfig(configs) + if err != nil { + return options, err + } + } + + var parameter map[string]testkube.WebhookParameterSchema + parameters, err := cmd.Flags().GetStringToString("parameter") + if err != nil { + return options, err + } + + if len(parameters) != 0 { + parameter, err = getWebhookParameters(parameters) + if err != nil { + return options, err + } + } + + var webhookTemplateReference *testkube.WebhookTemplateRef + if cmd.Flag("webhook-template-reference").Changed { + webhookTemplateReference = &testkube.WebhookTemplateRef{ + Name: cmd.Flag("webhook-template-reference").Value.String(), + } + } + options = apiv1.CreateWebhookOptions{ Name: name, Namespace: namespace, @@ -60,6 +98,9 @@ func NewCreateWebhookOptionsFromFlags(cmd *cobra.Command) (options apiv1.CreateW Headers: headers, PayloadTemplateReference: payloadTemplateReference, Disabled: disabled, + Config: config, + Parameters: parameter, + WebhookTemplateRef: webhookTemplateReference, } return options, nil @@ -151,5 +192,121 @@ func NewUpdateWebhookOptionsFromFlags(cmd *cobra.Command) (options apiv1.UpdateW options.Disabled = &disabled } + if cmd.Flag("config").Changed { + configs, err := cmd.Flags().GetStringToString("config") + if err != nil { + return options, err + } + + values, err := getWebhookConfig(configs) + if err != nil { + return options, err + } + options.Config = &values + } + + if cmd.Flag("parameter").Changed { + parameters, err := cmd.Flags().GetStringToString("parameter") + if err != nil { + return options, err + } + + values, err := getWebhookParameters(parameters) + if err != nil { + return options, err + } + options.Parameters = &values + } + + if cmd.Flag("webhook-template-reference").Changed { + options.WebhookTemplateRef = common.Ptr(&testkube.WebhookTemplateRef{ + Name: cmd.Flag("webhook-template-reference").Value.String(), + }) + } + return options, nil } + +func getWebhookConfig(configs map[string]string) (map[string]testkube.WebhookConfigValue, error) { + config := map[string]testkube.WebhookConfigValue{} + for key, value := range configs { + switch { + case strings.HasPrefix(value, "public="): + config[key] = testkube.WebhookConfigValue{ + Public: &testkube.BoxedString{Value: strings.TrimPrefix(value, "public=")}, + } + case strings.HasPrefix(value, "private="): + data := strings.TrimPrefix(value, "private=") + r := csv.NewReader(strings.NewReader(data)) + r.Comma = ',' + r.LazyQuotes = true + r.TrimLeadingSpace = true + + records, err := r.ReadAll() + if err != nil { + return nil, err + } + + if len(records) != 1 { + return nil, errors.New("single string expected") + } + + if len(records[0]) != 3 { + return nil, errors.New("3 fields expected") + } + + config[key] = testkube.WebhookConfigValue{ + Private: &testkube.SecretRef{ + Namespace: records[0][0], + Name: records[0][1], + Key: records[0][2], + }, + } + default: + continue + } + } + + return config, nil +} + +func getWebhookParameters(parameters map[string]string) (map[string]testkube.WebhookParameterSchema, error) { + parameter := map[string]testkube.WebhookParameterSchema{} + for key, value := range parameters { + r := csv.NewReader(strings.NewReader(value)) + r.Comma = ',' + r.LazyQuotes = true + r.TrimLeadingSpace = true + + records, err := r.ReadAll() + if err != nil { + return nil, err + } + + if len(records) != 1 { + return nil, errors.New("single string expected") + } + + if len(records[0]) != 5 { + return nil, errors.New("5 fields expected") + } + + var required bool + required, err = strconv.ParseBool(records[0][1]) + if err != nil { + return nil, err + } + + parameter[key] = testkube.WebhookParameterSchema{ + Description: records[0][0], + Required: required, + Example: records[0][2], + Default_: &testkube.BoxedString{ + Value: records[0][3], + }, + Pattern: records[0][4], + } + } + + return parameter, nil +} diff --git a/cmd/kubectl-testkube/commands/webhooks/create.go b/cmd/kubectl-testkube/commands/webhooks/create.go index c7f3b80478..e46b264de5 100644 --- a/cmd/kubectl-testkube/commands/webhooks/create.go +++ b/cmd/kubectl-testkube/commands/webhooks/create.go @@ -24,6 +24,9 @@ func NewCreateWebhookCmd() *cobra.Command { payloadTemplateReference string update bool disable bool + config map[string]string + parameters map[string]string + webhookTemplateReference string ) cmd := &cobra.Command{ @@ -99,6 +102,9 @@ func NewCreateWebhookCmd() *cobra.Command { cmd.Flags().StringVarP(&payloadTemplate, "payload-template", "", "", "if webhook needs to send a custom notification, then a path to template file should be provided") cmd.Flags().StringToStringVarP(&headers, "header", "", nil, "webhook header value pair (golang template supported): --header Content-Type=application/xml") cmd.Flags().StringVar(&payloadTemplateReference, "payload-template-reference", "", "reference to payload template to use for the webhook") + cmd.Flags().StringToStringVarP(&config, "config", "", nil, "webhook config variable with csv coluums (public=value or private=namespace,secret,key): --config var1=public=value1 or --config var2=private=ns1,secret1,key1") + cmd.Flags().StringToStringVarP(¶meters, "parameter", "", nil, "webhook parameter variable with csv coluums (description,required,example,default,pattern): --parameter var3=descr,true,12345,0,[0-9]*") + cmd.Flags().StringVar(&webhookTemplateReference, "webhook-template-reference", "", "reference to webhook to use as template for the webhook") cmd.Flags().BoolVar(&update, "update", false, "update, if webhook already exists") cmd.Flags().BoolVar(&disable, "disable", false, "disable webhook") cmd.Flags().MarkDeprecated("enable", "enable webhook is deprecated") diff --git a/cmd/kubectl-testkube/commands/webhooks/get.go b/cmd/kubectl-testkube/commands/webhooks/get.go index 1ea05b02f5..13e6df63f5 100644 --- a/cmd/kubectl-testkube/commands/webhooks/get.go +++ b/cmd/kubectl-testkube/commands/webhooks/get.go @@ -1,7 +1,6 @@ package webhooks import ( - "fmt" "os" "strings" @@ -34,10 +33,7 @@ func NewGetWebhookCmd() *cobra.Command { ui.ExitOnError("getting webhook: "+name, err) if crdOnly { - if webhook.PayloadTemplate != "" { - webhook.PayloadTemplate = fmt.Sprintf("%q", webhook.PayloadTemplate) - } - + webhook.QuoteTextFields() common.UIPrintCRD(crd.TemplateWebhook, webhook, &firstEntry) return } @@ -50,10 +46,7 @@ func NewGetWebhookCmd() *cobra.Command { if crdOnly { for _, webhook := range webhooks { - if webhook.PayloadTemplate != "" { - webhook.PayloadTemplate = fmt.Sprintf("%q", webhook.PayloadTemplate) - } - + webhook.QuoteTextFields() common.UIPrintCRD(crd.TemplateWebhook, webhook, &firstEntry) } diff --git a/cmd/kubectl-testkube/commands/webhooks/update.go b/cmd/kubectl-testkube/commands/webhooks/update.go index cbc93070d7..8334ab104e 100644 --- a/cmd/kubectl-testkube/commands/webhooks/update.go +++ b/cmd/kubectl-testkube/commands/webhooks/update.go @@ -18,6 +18,9 @@ func UpdateWebhookCmd() *cobra.Command { headers map[string]string payloadTemplateReference string disable bool + config map[string]string + parameters map[string]string + webhookTemplateReference string ) cmd := &cobra.Command{ @@ -57,6 +60,9 @@ func UpdateWebhookCmd() *cobra.Command { cmd.Flags().StringVarP(&payloadTemplate, "payload-template", "", "", "if webhook needs to send a custom notification, then a path to template file should be provided") cmd.Flags().StringToStringVarP(&headers, "header", "", nil, "webhook header value pair (golang template supported): --header Content-Type=application/xml") cmd.Flags().StringVar(&payloadTemplateReference, "payload-template-reference", "", "reference to payload template to use for the webhook") + cmd.Flags().StringToStringVarP(&config, "config", "", nil, "webhook config variable with csv coluums (public=value or private=namespace,secret,key): --config var1=public=value1 or --config var2=private=ns1,secret1,key1") + cmd.Flags().StringToStringVarP(¶meters, "parameter", "", nil, "webhook parameter variable with csv coluums (description,required,example,default,pattern): --parameter var3=descr,true,12345,0,[0-9]*") + cmd.Flags().StringVar(&webhookTemplateReference, "webhook-template-reference", "", "reference to webhook to use as template for the webhook") cmd.Flags().BoolVar(&disable, "disable", false, "disable webhook") cmd.Flags().MarkDeprecated("enable", "enable webhook is depecated") diff --git a/internal/app/api/v1/webhook.go b/internal/app/api/v1/webhook.go index 8bcdec7281..569000a931 100644 --- a/internal/app/api/v1/webhook.go +++ b/internal/app/api/v1/webhook.go @@ -34,10 +34,7 @@ func (s *TestkubeAPI) CreateWebhookHandler() fiber.Handler { } if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { - if request.PayloadTemplate != "" { - request.PayloadTemplate = fmt.Sprintf("%q", request.PayloadTemplate) - } - + request.QuoteTextFields() data, err := crd.GenerateYAML(crd.TemplateWebhook, []testkube.WebhookCreateRequest{request}) return apiutils.SendLegacyCRDs(c, data, err) } @@ -120,9 +117,7 @@ func (s *TestkubeAPI) ListWebhooksHandler() fiber.Handler { if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { for i := range results { - if results[i].PayloadTemplate != "" { - results[i].PayloadTemplate = fmt.Sprintf("%q", results[i].PayloadTemplate) - } + results[i].QuoteTextFields() } data, err := crd.GenerateYAML(crd.TemplateWebhook, results) @@ -148,10 +143,7 @@ func (s *TestkubeAPI) GetWebhookHandler() fiber.Handler { result := webhooksmapper.MapCRDToAPI(*item) if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { - if result.PayloadTemplate != "" { - result.PayloadTemplate = fmt.Sprintf("%q", result.PayloadTemplate) - } - + result.QuoteTextFields() data, err := crd.GenerateYAML(crd.TemplateWebhook, []testkube.Webhook{result}) return apiutils.SendLegacyCRDs(c, data, err) } diff --git a/pkg/api/v1/testkube/model_webhook_create_request_extended.go b/pkg/api/v1/testkube/model_webhook_create_request_extended.go new file mode 100644 index 0000000000..957a908f6d --- /dev/null +++ b/pkg/api/v1/testkube/model_webhook_create_request_extended.go @@ -0,0 +1,36 @@ +package testkube + +import "fmt" + +func (w *WebhookCreateRequest) QuoteTextFields() { + if w.PayloadTemplate != "" { + w.PayloadTemplate = fmt.Sprintf("%q", w.PayloadTemplate) + } + + for key, value := range w.Config { + if value.Public != nil && value.Public.Value != "" { + value.Public.Value = fmt.Sprintf("%q", value.Public.Value) + } + w.Config[key] = value + } + + for key, value := range w.Parameters { + if value.Description != "" { + value.Description = fmt.Sprintf("%q", value.Description) + } + + if value.Example != "" { + value.Example = fmt.Sprintf("%q", value.Example) + } + + if value.Default_ != nil && value.Default_.Value != "" { + value.Pattern = fmt.Sprintf("%q", value.Pattern) + } + + if value.Pattern != "" { + value.Pattern = fmt.Sprintf("%q", value.Pattern) + } + + w.Parameters[key] = value + } +} diff --git a/pkg/api/v1/testkube/model_webhook_extended.go b/pkg/api/v1/testkube/model_webhook_extended.go index 11a118bf10..a78a593be4 100644 --- a/pkg/api/v1/testkube/model_webhook_extended.go +++ b/pkg/api/v1/testkube/model_webhook_extended.go @@ -45,3 +45,36 @@ func (w Webhook) GetLabels() map[string]string { func (w Webhook) GetAnnotations() map[string]string { return w.Annotations } + +func (w *Webhook) QuoteTextFields() { + if w.PayloadTemplate != "" { + w.PayloadTemplate = fmt.Sprintf("%q", w.PayloadTemplate) + } + + for key, value := range w.Config { + if value.Public != nil && value.Public.Value != "" { + value.Public.Value = fmt.Sprintf("%q", value.Public.Value) + } + w.Config[key] = value + } + + for key, value := range w.Parameters { + if value.Description != "" { + value.Description = fmt.Sprintf("%q", value.Description) + } + + if value.Example != "" { + value.Example = fmt.Sprintf("%q", value.Example) + } + + if value.Default_ != nil && value.Default_.Value != "" { + value.Pattern = fmt.Sprintf("%q", value.Pattern) + } + + if value.Pattern != "" { + value.Pattern = fmt.Sprintf("%q", value.Pattern) + } + + w.Parameters[key] = value + } +} From 12f50572ef043fe6ce9df40602bbd19dc32a1469 Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Tue, 24 Dec 2024 17:23:39 +0300 Subject: [PATCH 07/11] fix: merge web hooks Signed-off-by: Vladislav Sukhin --- pkg/event/kind/webhook/loader.go | 109 +++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/pkg/event/kind/webhook/loader.go b/pkg/event/kind/webhook/loader.go index 0804ad3d77..42af64984e 100644 --- a/pkg/event/kind/webhook/loader.go +++ b/pkg/event/kind/webhook/loader.go @@ -5,6 +5,7 @@ import ( "go.uber.org/zap" + executorv1 "github.com/kubeshop/testkube-operator/api/executor/v1" executorsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/executors/v1" "github.com/kubeshop/testkube/cmd/api-server/commons" v1 "github.com/kubeshop/testkube/internal/app/api/metrics" @@ -57,6 +58,16 @@ func (r WebhooksLoader) Load() (listeners common.Listeners, err error) { // and create listeners for each webhook spec for _, webhook := range webhookList.Items { + if webhook.Spec.WebhookTemplateRef != nil && webhook.Spec.WebhookTemplateRef.Name != "" { + webhookTemplate, err := r.WebhooksClient.Get(webhook.Spec.WebhookTemplateRef.Name) + if err != nil { + r.log.Errorw("error webhook template loading", "error", err, "name", webhook.Name, "template", webhook.Spec.WebhookTemplateRef.Name) + continue + } + + webhook = mergeWebhooks(webhook, *webhookTemplate) + } + payloadTemplate := "" if webhook.Spec.PayloadTemplateReference != "" { if r.deprecatedClients == nil { @@ -94,3 +105,101 @@ func (r WebhooksLoader) Load() (listeners common.Listeners, err error) { return listeners, nil } + +func mergeWebhooks(dst, src executorv1.Webhook) executorv1.Webhook { + var maps = []struct { + d *map[string]string + s *map[string]string + }{ + { + &dst.ObjectMeta.Labels, + &src.ObjectMeta.Labels, + }, + { + &dst.ObjectMeta.Annotations, + &src.ObjectMeta.Annotations, + }, + { + &dst.Spec.Headers, + &src.Spec.Headers, + }, + } + + for _, m := range maps { + if *m.s != nil { + if *m.d == nil { + *m.d = map[string]string{} + } + + for key, value := range *m.s { + if _, ok := (*m.d)[key]; !ok { + (*m.d)[key] = value + } + } + } + } + + var items = []struct { + d *string + s *string + }{ + { + &dst.Spec.Uri, + &src.Spec.Uri, + }, + { + &dst.Spec.Selector, + &src.Spec.Selector, + }, + { + &dst.Spec.PayloadObjectField, + &src.Spec.PayloadObjectField, + }, + { + &dst.Spec.PayloadTemplate, + &src.Spec.PayloadTemplate, + }, + { + &dst.Spec.PayloadTemplateReference, + &src.Spec.PayloadTemplateReference, + }, + } + + for _, item := range items { + if *item.d == "" && *item.s != "" { + *item.d = *item.s + } + } + + // events + + if !dst.Spec.Disabled && src.Spec.Disabled { + dst.Spec.Disabled = src.Spec.Disabled + } + + if src.Spec.Config != nil { + if dst.Spec.Config == nil { + dst.Spec.Config = map[string]executorv1.WebhookConfigValue{} + } + + for key, value := range src.Spec.Config { + if _, ok := (dst.Spec.Config)[key]; !ok { + dst.Spec.Config[key] = value + } + } + } + + if src.Spec.Parameters != nil { + if dst.Spec.Parameters == nil { + dst.Spec.Parameters = map[string]executorv1.WebhookParameterSchema{} + } + + for key, value := range src.Spec.Parameters { + if _, ok := (dst.Spec.Parameters)[key]; !ok { + dst.Spec.Parameters[key] = value + } + } + } + + return dst +} From f5933aa7ff559b1642cb67c6d2e8f6d507d932f3 Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Tue, 24 Dec 2024 18:30:18 +0300 Subject: [PATCH 08/11] fix: pass config to template Signed-off-by: Vladislav Sukhin --- pkg/event/kind/webhook/listener.go | 5 ++++- pkg/event/kind/webhook/listener_test.go | 12 ++++++------ pkg/event/kind/webhook/loader.go | 2 +- pkg/event/kind/webhook/templatevars.go | 6 ++++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/pkg/event/kind/webhook/listener.go b/pkg/event/kind/webhook/listener.go index 41615aa62f..ea628ab78d 100644 --- a/pkg/event/kind/webhook/listener.go +++ b/pkg/event/kind/webhook/listener.go @@ -33,6 +33,7 @@ func NewWebhookListener(name, uri, selector string, events []testkube.EventType, metrics v1.Metrics, proContext *config.ProContext, envs map[string]string, + config map[string]string, ) *WebhookListener { return &WebhookListener{ name: name, @@ -50,6 +51,7 @@ func NewWebhookListener(name, uri, selector string, events []testkube.EventType, metrics: metrics, proContext: proContext, envs: envs, + config: config, } } @@ -69,6 +71,7 @@ type WebhookListener struct { metrics v1.Metrics proContext *config.ProContext envs map[string]string + config map[string]string } func (l *WebhookListener) Name() string { @@ -267,7 +270,7 @@ func (l *WebhookListener) processTemplate(field, body string, event testkube.Eve } var buffer bytes.Buffer - if err = tmpl.ExecuteTemplate(&buffer, field, NewTemplateVars(event, l.proContext)); err != nil { + if err = tmpl.ExecuteTemplate(&buffer, field, NewTemplateVars(event, l.proContext, l.config)); err != nil { log.Errorw(fmt.Sprintf("executing webhook %s error", field), "error", err) return nil, err } diff --git a/pkg/event/kind/webhook/listener_test.go b/pkg/event/kind/webhook/listener_test.go index 386424cb74..c60d73d641 100644 --- a/pkg/event/kind/webhook/listener_test.go +++ b/pkg/event/kind/webhook/listener_test.go @@ -34,7 +34,7 @@ func TestWebhookListener_Notify(t *testing.T) { svr := httptest.NewServer(testHandler) defer svr.Close() - l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "", nil, false, nil, nil, v1.NewMetrics(), nil, nil) + l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "", nil, false, nil, nil, v1.NewMetrics(), nil, nil, nil) // when r := l.Notify(testkube.Event{ @@ -56,7 +56,7 @@ func TestWebhookListener_Notify(t *testing.T) { svr := httptest.NewServer(testHandler) defer svr.Close() - l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "", nil, false, nil, nil, v1.NewMetrics(), nil, nil) + l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "", nil, false, nil, nil, v1.NewMetrics(), nil, nil, nil) // when r := l.Notify(testkube.Event{ @@ -73,7 +73,7 @@ func TestWebhookListener_Notify(t *testing.T) { t.Parallel() // given - s := NewWebhookListener("l1", "http://baduri.badbadbad", "", testEventTypes, "", "", nil, false, nil, nil, v1.NewMetrics(), nil, nil) + s := NewWebhookListener("l1", "http://baduri.badbadbad", "", testEventTypes, "", "", nil, false, nil, nil, v1.NewMetrics(), nil, nil, nil) // when r := s.Notify(testkube.Event{ @@ -106,7 +106,7 @@ func TestWebhookListener_Notify(t *testing.T) { svr := httptest.NewServer(testHandler) defer svr.Close() - l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "field", "", nil, false, nil, nil, v1.NewMetrics(), nil, nil) + l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "field", "", nil, false, nil, nil, v1.NewMetrics(), nil, nil, nil) // when r := l.Notify(testkube.Event{ @@ -133,7 +133,7 @@ func TestWebhookListener_Notify(t *testing.T) { defer svr.Close() l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "{\"id\": \"{{ .Id }}\"}", - map[string]string{"Content-Type": "application/json"}, false, nil, nil, v1.NewMetrics(), nil, nil) + map[string]string{"Content-Type": "application/json"}, false, nil, nil, v1.NewMetrics(), nil, nil, nil) // when r := l.Notify(testkube.Event{ @@ -150,7 +150,7 @@ func TestWebhookListener_Notify(t *testing.T) { t.Parallel() // given - s := NewWebhookListener("l1", "http://baduri.badbadbad", "", testEventTypes, "", "", nil, true, nil, nil, v1.NewMetrics(), nil, nil) + s := NewWebhookListener("l1", "http://baduri.badbadbad", "", testEventTypes, "", "", nil, true, nil, nil, v1.NewMetrics(), nil, nil, nil) // when r := s.Notify(testkube.Event{ diff --git a/pkg/event/kind/webhook/loader.go b/pkg/event/kind/webhook/loader.go index 42af64984e..1efc0f3d0d 100644 --- a/pkg/event/kind/webhook/loader.go +++ b/pkg/event/kind/webhook/loader.go @@ -98,7 +98,7 @@ func (r WebhooksLoader) Load() (listeners common.Listeners, err error) { name, webhook.Spec.Uri, webhook.Spec.Selector, types, webhook.Spec.PayloadObjectField, payloadTemplate, webhook.Spec.Headers, webhook.Spec.Disabled, r.deprecatedRepositories, r.testWorkflowExecutionResults, - r.metrics, r.proContext, r.envs, + r.metrics, r.proContext, r.envs, nil, ), ) } diff --git a/pkg/event/kind/webhook/templatevars.go b/pkg/event/kind/webhook/templatevars.go index 391244f62a..66c61d9032 100644 --- a/pkg/event/kind/webhook/templatevars.go +++ b/pkg/event/kind/webhook/templatevars.go @@ -15,11 +15,13 @@ type TemplateVars struct { ArtifactCommand string LogsURL string LogsCommand string + Config map[string]string } -func NewTemplateVars(event testkube.Event, proContext *config.ProContext) TemplateVars { +func NewTemplateVars(event testkube.Event, proContext *config.ProContext, config map[string]string) TemplateVars { vars := TemplateVars{ - Event: event, + Event: event, + Config: config, } switch { From a4b76bb8623c1fe449804643d0d06eb38068eee5 Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Tue, 24 Dec 2024 18:53:24 +0300 Subject: [PATCH 09/11] fix: merge events Signed-off-by: Vladislav Sukhin --- pkg/event/kind/webhook/loader.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/event/kind/webhook/loader.go b/pkg/event/kind/webhook/loader.go index 1efc0f3d0d..c3d176342e 100644 --- a/pkg/event/kind/webhook/loader.go +++ b/pkg/event/kind/webhook/loader.go @@ -171,7 +171,21 @@ func mergeWebhooks(dst, src executorv1.Webhook) executorv1.Webhook { } } - // events + srcEventTypes := make(map[executorv1.EventType]struct{}) + for _, eventType := range src.Spec.Events { + srcEventTypes[eventType] = struct{}{} + } + + dstEventTypes := make(map[executorv1.EventType]struct{}) + for _, eventType := range dst.Spec.Events { + dstEventTypes[eventType] = struct{}{} + } + + for evenType := range srcEventTypes { + if _, ok := dstEventTypes[evenType]; !ok { + dst.Spec.Events = append(dst.Spec.Events, evenType) + } + } if !dst.Spec.Disabled && src.Spec.Disabled { dst.Spec.Disabled = src.Spec.Disabled From 5f0ade88576ea5896fe7786925bde4ef539f39cc Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Tue, 24 Dec 2024 19:26:42 +0300 Subject: [PATCH 10/11] fix: prepare config data Signed-off-by: Vladislav Sukhin --- cmd/api-server/main.go | 3 +- pkg/event/kind/webhook/loader.go | 43 +++++++++++++++++++++++++-- pkg/event/kind/webhook/loader_test.go | 2 +- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go index de8c8bd41b..3302b1525a 100644 --- a/cmd/api-server/main.go +++ b/cmd/api-server/main.go @@ -247,7 +247,8 @@ func main() { // Initialize event handlers websocketLoader := ws.NewWebsocketLoader() if !cfg.DisableWebhooks { - eventsEmitter.Loader.Register(webhook.NewWebhookLoader(log.DefaultLogger, webhooksClient, deprecatedClients, deprecatedRepositories, testWorkflowResultsRepository, metrics, &proContext, envs)) + eventsEmitter.Loader.Register(webhook.NewWebhookLoader(log.DefaultLogger, webhooksClient, deprecatedClients, deprecatedRepositories, + testWorkflowResultsRepository, secretClient, metrics, &proContext, envs)) } eventsEmitter.Loader.Register(websocketLoader) eventsEmitter.Loader.Register(commons.MustCreateSlackLoader(cfg, envs)) diff --git a/pkg/event/kind/webhook/loader.go b/pkg/event/kind/webhook/loader.go index c3d176342e..c1714087aa 100644 --- a/pkg/event/kind/webhook/loader.go +++ b/pkg/event/kind/webhook/loader.go @@ -14,13 +14,14 @@ import ( "github.com/kubeshop/testkube/pkg/event/kind/common" "github.com/kubeshop/testkube/pkg/mapper/webhooks" "github.com/kubeshop/testkube/pkg/repository/testworkflow" + "github.com/kubeshop/testkube/pkg/secret" ) var _ common.ListenerLoader = (*WebhooksLoader)(nil) func NewWebhookLoader(log *zap.SugaredLogger, webhooksClient executorsclientv1.WebhooksInterface, deprecatedClients commons.DeprecatedClients, deprecatedRepositories commons.DeprecatedRepositories, testWorkflowExecutionResults testworkflow.Repository, - metrics v1.Metrics, proContext *config.ProContext, envs map[string]string, + secretClient secret.Interface, metrics v1.Metrics, proContext *config.ProContext, envs map[string]string, ) *WebhooksLoader { return &WebhooksLoader{ log: log, @@ -28,6 +29,7 @@ func NewWebhookLoader(log *zap.SugaredLogger, webhooksClient executorsclientv1.W deprecatedClients: deprecatedClients, deprecatedRepositories: deprecatedRepositories, testWorkflowExecutionResults: testWorkflowExecutionResults, + secretClient: secretClient, metrics: metrics, proContext: proContext, envs: envs, @@ -40,6 +42,7 @@ type WebhooksLoader struct { deprecatedClients commons.DeprecatedClients deprecatedRepositories commons.DeprecatedRepositories testWorkflowExecutionResults testworkflow.Repository + secretClient secret.Interface metrics v1.Metrics proContext *config.ProContext envs map[string]string @@ -92,13 +95,49 @@ func (r WebhooksLoader) Load() (listeners common.Listeners, err error) { types := webhooks.MapEventArrayToCRDEvents(webhook.Spec.Events) name := fmt.Sprintf("%s.%s", webhook.ObjectMeta.Namespace, webhook.ObjectMeta.Name) + vars := make(map[string]string) + for key, value := range webhook.Spec.Config { + data := "" + if value.Public != nil { + data = *value.Public + } + + if value.Private != nil { + var ns []string + if value.Private.Namespace != "" { + ns = append(ns, value.Private.Namespace) + } + + elements, err := r.secretClient.Get(value.Private.Name, ns...) + if err != nil { + r.log.Errorw("error secret loading", "error", err, "name", value.Private.Name) + continue + } + + if element, ok := elements[value.Private.Key]; ok { + data = element + } else { + r.log.Errorw("error secret key finding loading", "name", value.Private.Name, "key", value.Private.Key) + continue + } + } + + vars[key] = data + } + + for key, value := range webhook.Spec.Parameters { + if _, ok := vars[key]; !ok && value.Default_ != nil { + vars[key] = *value.Default_ + } + } + listeners = append( listeners, NewWebhookListener( name, webhook.Spec.Uri, webhook.Spec.Selector, types, webhook.Spec.PayloadObjectField, payloadTemplate, webhook.Spec.Headers, webhook.Spec.Disabled, r.deprecatedRepositories, r.testWorkflowExecutionResults, - r.metrics, r.proContext, r.envs, nil, + r.metrics, r.proContext, r.envs, vars, ), ) } diff --git a/pkg/event/kind/webhook/loader_test.go b/pkg/event/kind/webhook/loader_test.go index 532d27aae8..707da70b46 100644 --- a/pkg/event/kind/webhook/loader_test.go +++ b/pkg/event/kind/webhook/loader_test.go @@ -30,7 +30,7 @@ func TestWebhookLoader(t *testing.T) { mockDeprecatedClients := commons.NewMockDeprecatedClients(mockCtrl) mockDeprecatedClients.EXPECT().Templates().Return(mockTemplatesClient).AnyTimes() - webhooksLoader := NewWebhookLoader(zap.NewNop().Sugar(), mockWebhooksClient, mockDeprecatedClients, nil, nil, v1.NewMetrics(), nil, nil) + webhooksLoader := NewWebhookLoader(zap.NewNop().Sugar(), mockWebhooksClient, mockDeprecatedClients, nil, nil, nil, v1.NewMetrics(), nil, nil) listeners, err := webhooksLoader.Load() assert.Equal(t, 1, len(listeners)) From a87a9f42923196e13261a10bb17250499ca705f5 Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Tue, 24 Dec 2024 21:27:37 +0300 Subject: [PATCH 11/11] fix: check var Signed-off-by: Vladislav Sukhin --- pkg/event/kind/webhook/loader.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pkg/event/kind/webhook/loader.go b/pkg/event/kind/webhook/loader.go index c1714087aa..cd68d8dfc0 100644 --- a/pkg/event/kind/webhook/loader.go +++ b/pkg/event/kind/webhook/loader.go @@ -2,6 +2,7 @@ package webhook import ( "fmt" + "regexp" "go.uber.org/zap" @@ -60,6 +61,7 @@ func (r WebhooksLoader) Load() (listeners common.Listeners, err error) { } // and create listeners for each webhook spec +OuterLoop: for _, webhook := range webhookList.Items { if webhook.Spec.WebhookTemplateRef != nil && webhook.Spec.WebhookTemplateRef.Name != "" { webhookTemplate, err := r.WebhooksClient.Get(webhook.Spec.WebhookTemplateRef.Name) @@ -126,8 +128,24 @@ func (r WebhooksLoader) Load() (listeners common.Listeners, err error) { } for key, value := range webhook.Spec.Parameters { - if _, ok := vars[key]; !ok && value.Default_ != nil { - vars[key] = *value.Default_ + if data, ok := vars[key]; !ok { + if value.Default_ != nil { + vars[key] = *value.Default_ + } else if value.Required { + r.log.Errorw("error missing required parameter", "name", key) + continue OuterLoop + } + } else if value.Pattern != "" { + re, err := regexp.Compile(value.Pattern) + if err != nil { + r.log.Errorw("error compiling pattern", "error", err, "name", key, "pattern", value.Pattern) + continue OuterLoop + } + + if !re.MatchString(data) { + r.log.Errorw("error matching pattern", "error", err, "name", key, "pattern", value.Pattern) + continue OuterLoop + } } }