diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 00000000000..ec9953b5f24 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,72 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '35 11 * * 1' + push: + branches: [ "develop", "main" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4 + with: + sarif_file: results.sarif diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index c78b91862f5..a461e3d4d73 100644 --- a/api/v1/testkube.yaml +++ b/api/v1/testkube.yaml @@ -2500,6 +2500,243 @@ paths: items: $ref: "#/components/schemas/Problem" + /templates: + get: + tags: + - templates + - api + summary: "List templates" + description: "List templates available in cluster" + operationId: listTemplates + parameters: + - $ref: "#/components/parameters/Selector" + responses: + 200: + description: "successful operation" + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Template" + text/yaml: + schema: + type: string + 400: + description: "problem with input for CRD generation" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + 502: + description: "problem with read information from kubernetes cluster" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + post: + tags: + - template + - api + summary: "Create new template" + description: "Create new template based on variables passed in request" + operationId: createTemplate + requestBody: + description: template request body data + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/TemplateCreateRequest" + text/yaml: + schema: + type: string + responses: + 200: + description: "successful operation" + content: + text/yaml: + schema: + type: string + 201: + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Template" + 400: + description: "problem with template definition - probably some bad input occurs (invalid JSON body or similar)" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + 502: + description: "problem with communicating with kubernetes cluster" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + delete: + tags: + - template + - api + summary: "Delete templates" + description: "Deletes labeled templates" + operationId: deleteTemplates + parameters: + - $ref: "#/components/parameters/Selector" + responses: + 204: + description: "no content" + 502: + description: "problem with read information from kubernetes cluster" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + + /templates/{id}: + delete: + parameters: + - $ref: "#/components/parameters/ID" + tags: + - api + - template + summary: "Delete template" + description: "Deletes template by its name" + operationId: deleteTemplate + responses: + 204: + description: template deleted successfuly + 404: + description: "template not found" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + 502: + description: "problem with communicating with kubernetes cluster" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + + get: + parameters: + - $ref: "#/components/parameters/ID" + tags: + - api + - template + summary: "Get template details" + description: "Returns template" + operationId: getTemplate + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Template" + text/yaml: + schema: + type: string + 400: + description: "problem with input for CRD generation" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + 404: + description: "template not found" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + 500: + description: "problem with getting template data" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + 502: + description: "problem with communicating with kubernetes cluster" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + patch: + parameters: + - $ref: "#/components/parameters/ID" + tags: + - template + - api + summary: "Update new template" + description: "Update new template based on variables passed in request" + operationId: updateTemplate + requestBody: + description: template request body data + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/TemplateUpdateRequest" + text/yaml: + schema: + type: string + responses: + 200: + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Template" + 400: + description: "problem with template definition - probably some bad input occurs (invalid JSON body or similar)" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + 404: + description: "template not found" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + 502: + description: "problem with communicating with kubernetes cluster" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + /config: patch: tags: @@ -2951,6 +3188,32 @@ paths: items: $ref: "#/components/schemas/Problem" + /secrets: + get: + tags: + - secrets + - api + summary: "List secrets" + description: "List secrets available in cluster" + operationId: listSecrets + responses: + 200: + description: "successful operation" + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Secret" + 502: + description: "problem with communicating with kubernetes cluster or git server" + content: + application/problem+json: + schema: + type: array + items: + $ref: "#/components/schemas/Problem" + components: schemas: ExecutionsMetrics: @@ -4470,9 +4733,15 @@ components: jobTemplate: type: string description: job template extensions + jobTemplateReference: + type: string + description: name of the template resource cronJobTemplate: type: string description: cron job template extensions + cronJobTemplateReference: + type: string + description: name of the template resource contentRequest: $ref: "#/components/schemas/TestContentRequest" description: adjusting parameters for test content @@ -4487,6 +4756,15 @@ components: scraperTemplate: type: string description: scraper template extensions + scraperTemplateReference: + type: string + description: name of the template resource + pvcTemplate: + type: string + description: pvc template extensions + pvcTemplateReference: + type: string + description: name of the template resource envConfigMaps: type: array description: "config map references" @@ -4572,9 +4850,30 @@ components: runningContext: $ref: "#/components/schemas/RunningContext" description: running context for the test suite execution + jobTemplate: + type: string + description: job template extensions + jobTemplateReference: + type: string + description: name of the template resource cronJobTemplate: type: string description: cron job template extensions + cronJobTemplateReference: + type: string + description: name of the template resource + scraperTemplate: + type: string + description: scraper template extensions + scraperTemplateReference: + type: string + description: name of the template resource + pvcTemplate: + type: string + description: pvc template extensions + pvcTemplateReference: + type: string + description: name of the template resource concurrencyLevel: type: integer format: int32 @@ -4739,6 +5038,9 @@ components: jobTemplate: description: Job template to launch executor type: string + jobTemplateReference: + type: string + description: name of the template resource labels: type: object description: "executor labels" @@ -4871,9 +5173,12 @@ components: payloadTemplate: type: string description: golang based template for notification payload + payloadTemplateReference: + type: string + description: name of the template resource headers: type: object - description: "webhook headers" + description: "webhook headers (golang template supported)" additionalProperties: type: string example: @@ -5356,6 +5661,78 @@ components: description: deleted test sources example: ["name7", "name8", "name9"] + Template: + description: Golang based template + type: object + required: + - name + - type + - body + properties: + name: + type: string + description: template name for reference + example: "webhook-template" + namespace: + type: string + description: template namespace + example: "testkube" + type: + $ref: "#/components/schemas/TemplateType" + body: + type: string + description: template body to use + example: "{\"id\": \"{{ .Id }}\"}" + labels: + type: object + description: "template labels" + additionalProperties: + type: string + example: + env: "prod" + app: "backend" + + TemplateType: + description: template type by purpose + type: string + enum: + - job + - container + - cronjob + - scraper + - pvc + - webhook + + TemplateCreateRequest: + description: template create request body + type: object + allOf: + - $ref: "#/components/schemas/Template" + + TemplateUpdateRequest: + description: template update request body + type: object + nullable: true + allOf: + - $ref: "#/components/schemas/Template" + + Secret: + description: Secret with keys + type: object + required: + - name + properties: + name: + type: string + description: secret name + example: "git-secret" + keys: + type: array + description: secret keys + items: + type: string + example: ["key1", "key2", "key3"] + # # Errors # diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go index 78ad866db86..f1a8b7efdab 100644 --- a/cmd/api-server/main.go +++ b/cmd/api-server/main.go @@ -43,6 +43,7 @@ import ( "github.com/pkg/errors" + "github.com/kubeshop/testkube/internal/app/api/debug" "github.com/kubeshop/testkube/internal/app/api/metrics" "github.com/kubeshop/testkube/pkg/agent" kubeexecutor "github.com/kubeshop/testkube/pkg/executor" @@ -60,6 +61,7 @@ import ( kubeclient "github.com/kubeshop/testkube-operator/client" executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors/v1" scriptsclient "github.com/kubeshop/testkube-operator/client/scripts/v2" + templatesclientv1 "github.com/kubeshop/testkube-operator/client/templates/v1" testexecutionsclientv1 "github.com/kubeshop/testkube-operator/client/testexecutions/v1" testsclientv1 "github.com/kubeshop/testkube-operator/client/tests" testsclientv3 "github.com/kubeshop/testkube-operator/client/tests/v3" @@ -153,6 +155,15 @@ func main() { grpcClient = cloud.NewTestKubeCloudAPIClient(grpcConn) } + if cfg.EnableDebugServer { + debugSrv := debug.NewDebugServer(cfg.DebugListenAddr) + + g.Go(func() error { + log.DefaultLogger.Infof("starting debug pprof server") + return debugSrv.ListenAndServe() + }) + } + // k8s scriptsClient := scriptsclient.NewClient(kubeClient, cfg.TestkubeNamespace) testsClientV1 := testsclientv1.NewClient(kubeClient, cfg.TestkubeNamespace) @@ -164,6 +175,7 @@ func main() { testsourcesClient := testsourcesclientv1.NewClient(kubeClient, cfg.TestkubeNamespace) testExecutionsClient := testexecutionsclientv1.NewClient(kubeClient, cfg.TestkubeNamespace) testsuiteExecutionsClient := testsuiteexecutionsclientv1.NewClient(kubeClient, cfg.TestkubeNamespace) + templatesClient := templatesclientv1.NewClient(kubeClient, cfg.TestkubeNamespace) clientset, err := k8sclient.ConnectToK8s() if err != nil { @@ -332,6 +344,7 @@ func main() { testsClientV3, clientset, testExecutionsClient, + templatesClient, cfg.TestkubeRegistry, cfg.TestkubePodStartTimeout, clusterId, @@ -357,6 +370,7 @@ func main() { executorsClient, testsClientV3, testExecutionsClient, + templatesClient, cfg.TestkubeRegistry, cfg.TestkubePodStartTimeout, clusterId, @@ -381,6 +395,7 @@ func main() { configMapConfig, configMapClient, testsuiteExecutionsClient, + eventBus, ) slackLoader, err := newSlackLoader(cfg, envs) @@ -412,10 +427,12 @@ func main() { storageClient, cfg.GraphqlPort, artifactStorage, + templatesClient, cfg.CDEventsTarget, cfg.TestkubeDashboardURI, cfg.TestkubeHelmchartVersion, mode, + eventBus, ) if mode == common.ModeAgent { diff --git a/cmd/kubectl-testkube/commands/common/cloudcontext.go b/cmd/kubectl-testkube/commands/common/cloudcontext.go index fb989c4cb16..178a2813616 100644 --- a/cmd/kubectl-testkube/commands/common/cloudcontext.go +++ b/cmd/kubectl-testkube/commands/common/cloudcontext.go @@ -75,7 +75,7 @@ func UiContextHeader(cmd *cobra.Command, cfg config.Data) { header += ui.DarkGray("Env: ") + ui.White(envName) } else { header += ui.DarkGray("Context: ") + ui.White(cfg.ContextType) + ui.DarkGray(" ("+Version+")") + separator - header += ui.DarkGray("Namespace: ") + ui.White(cfg.Namespace) + header += ui.DarkGray("Namespace: ") + ui.White(cmd.Flag("namespace").Value.String()) } fmt.Println(header) diff --git a/cmd/kubectl-testkube/commands/create.go b/cmd/kubectl-testkube/commands/create.go index 4e5e344afa0..46de9b05cc9 100644 --- a/cmd/kubectl-testkube/commands/create.go +++ b/cmd/kubectl-testkube/commands/create.go @@ -6,6 +6,7 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/validator" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/executors" + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/templates" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/tests" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/testsources" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/testsuites" @@ -41,6 +42,7 @@ func NewCreateCmd() *cobra.Command { cmd.AddCommand(webhooks.NewCreateWebhookCmd()) cmd.AddCommand(executors.NewCreateExecutorCmd()) cmd.AddCommand(testsources.NewCreateTestSourceCmd()) + cmd.AddCommand(templates.NewCreateTemplateCmd()) cmd.PersistentFlags().BoolVar(&crdOnly, "crd-only", false, "generate only crd") diff --git a/cmd/kubectl-testkube/commands/delete.go b/cmd/kubectl-testkube/commands/delete.go index cb6385dec90..8f8cfa87727 100644 --- a/cmd/kubectl-testkube/commands/delete.go +++ b/cmd/kubectl-testkube/commands/delete.go @@ -6,6 +6,7 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/validator" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/executors" + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/templates" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/tests" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/testsources" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/testsuites" @@ -40,6 +41,7 @@ func NewDeleteCmd() *cobra.Command { cmd.AddCommand(webhooks.NewDeleteWebhookCmd()) cmd.AddCommand(executors.NewDeleteExecutorCmd()) cmd.AddCommand(testsources.NewDeleteTestSourceCmd()) + cmd.AddCommand(templates.NewDeleteTemplateCmd()) return cmd } diff --git a/cmd/kubectl-testkube/commands/executors/common.go b/cmd/kubectl-testkube/commands/executors/common.go index 37d084097fd..7cdf14b2d10 100644 --- a/cmd/kubectl-testkube/commands/executors/common.go +++ b/cmd/kubectl-testkube/commands/executors/common.go @@ -32,6 +32,7 @@ func NewUpsertExecutorOptionsFromFlags(cmd *cobra.Command) (options apiClient.Up return options, err } + jobTemplateReference := cmd.Flag("job-template-reference").Value.String() jobTemplate := cmd.Flag("job-template").Value.String() jobTemplateContent := "" if jobTemplate != "" { @@ -83,19 +84,20 @@ func NewUpsertExecutorOptionsFromFlags(cmd *cobra.Command) (options apiClient.Up } options = apiClient.UpsertExecutorOptions{ - Name: name, - Types: types, - ExecutorType: executorType, - Image: image, - ImagePullSecrets: imageSecrets, - Command: command, - Args: executorArgs, - Uri: uri, - ContentTypes: contentTypes, - JobTemplate: jobTemplateContent, - Features: features, - Labels: labels, - Meta: meta, + Name: name, + Types: types, + ExecutorType: executorType, + Image: image, + ImagePullSecrets: imageSecrets, + Command: command, + Args: executorArgs, + Uri: uri, + ContentTypes: contentTypes, + JobTemplate: jobTemplateContent, + JobTemplateReference: jobTemplateReference, + Features: features, + Labels: labels, + Meta: meta, } return options, nil @@ -123,6 +125,10 @@ func NewUpdateExecutorOptionsFromFlags(cmd *cobra.Command) (options apiClient.Up "image", &options.Image, }, + { + "job-template-reference", + &options.JobTemplateReference, + }, } for _, field := range fields { diff --git a/cmd/kubectl-testkube/commands/executors/create.go b/cmd/kubectl-testkube/commands/executors/create.go index 1dbf04372cd..6b552871d8d 100644 --- a/cmd/kubectl-testkube/commands/executors/create.go +++ b/cmd/kubectl-testkube/commands/executors/create.go @@ -14,9 +14,9 @@ import ( func NewCreateExecutorCmd() *cobra.Command { var ( - types, command, executorArgs, imagePullSecretNames, features, contentTypes []string - name, executorType, image, uri, jobTemplate, iconURI, docsURI string - labels, tooltips map[string]string + types, command, executorArgs, imagePullSecretNames, features, contentTypes []string + name, executorType, image, uri, jobTemplate, iconURI, docsURI, jobTemplateReference string + labels, tooltips map[string]string ) cmd := &cobra.Command{ @@ -72,6 +72,7 @@ func NewCreateExecutorCmd() *cobra.Command { cmd.Flags().StringArrayVar(&command, "command", []string{}, "command passed to image in executor") cmd.Flags().StringArrayVar(&executorArgs, "args", []string{}, "args passed to image in executor") cmd.Flags().StringVarP(&jobTemplate, "job-template", "j", "", "if executor needs to be launched using custom job specification, then a path to template file should be provided") + cmd.Flags().StringVarP(&jobTemplateReference, "job-template-reference", "", "", "reference to job template for using with executor") cmd.Flags().StringToStringVarP(&labels, "label", "l", nil, "label key value pair: --label key1=value1") cmd.Flags().StringArrayVar(&features, "feature", []string{}, "feature provided by executor") cmd.Flags().StringVarP(&iconURI, "icon-uri", "", "", "URI to executor icon") diff --git a/cmd/kubectl-testkube/commands/executors/get.go b/cmd/kubectl-testkube/commands/executors/get.go index 47f3f2fd9a0..033561ded0d 100644 --- a/cmd/kubectl-testkube/commands/executors/get.go +++ b/cmd/kubectl-testkube/commands/executors/get.go @@ -86,6 +86,7 @@ func mapExecutorDetailsToCreateExecutorOptions(namespace string, executor *testk options.Args = executor.Executor.Args options.Uri = executor.Executor.Uri options.Labels = executor.Executor.Labels + options.JobTemplateReference = executor.Executor.JobTemplateReference if executor.Executor.JobTemplate != "" { options.JobTemplate = fmt.Sprintf("%q", executor.Executor.JobTemplate) } diff --git a/cmd/kubectl-testkube/commands/executors/update.go b/cmd/kubectl-testkube/commands/executors/update.go index bc8f08ab1c2..f87c1700791 100644 --- a/cmd/kubectl-testkube/commands/executors/update.go +++ b/cmd/kubectl-testkube/commands/executors/update.go @@ -9,9 +9,9 @@ import ( func UpdateExecutorCmd() *cobra.Command { var ( - types, command, executorArgs, imagePullSecretNames, features, contentTypes []string - name, executorType, image, uri, jobTemplate, iconURI, docsURI string - labels, tooltips map[string]string + types, command, executorArgs, imagePullSecretNames, features, contentTypes []string + name, executorType, image, uri, jobTemplate, iconURI, docsURI, jobTemplateReference string + labels, tooltips map[string]string ) cmd := &cobra.Command{ @@ -52,6 +52,7 @@ func UpdateExecutorCmd() *cobra.Command { cmd.Flags().StringArrayVar(&command, "command", []string{}, "command passed to image in executor") cmd.Flags().StringArrayVar(&executorArgs, "args", []string{}, "args passed to image in executor") cmd.Flags().StringVarP(&jobTemplate, "job-template", "j", "", "if executor needs to be launched using custom job specification, then a path to template file should be provided") + cmd.Flags().StringVarP(&jobTemplateReference, "job-template-reference", "", "", "reference to job template for using with executor") cmd.Flags().StringToStringVarP(&labels, "label", "l", nil, "label key value pair: --label key1=value1") cmd.Flags().StringArrayVar(&features, "feature", []string{}, "feature provided by executor") cmd.Flags().StringVarP(&iconURI, "icon-uri", "", "", "URI to executor icon") diff --git a/cmd/kubectl-testkube/commands/get.go b/cmd/kubectl-testkube/commands/get.go index c7cfb17dfad..b0355e79623 100644 --- a/cmd/kubectl-testkube/commands/get.go +++ b/cmd/kubectl-testkube/commands/get.go @@ -8,6 +8,7 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/validator" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/context" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/executors" + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/templates" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/tests" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/testsources" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/testsuites" @@ -46,6 +47,7 @@ func NewGetCmd() *cobra.Command { cmd.AddCommand(testsuites.NewTestSuiteExecutionCmd()) cmd.AddCommand(testsources.NewGetTestSourceCmd()) cmd.AddCommand(context.NewGetContextCmd()) + cmd.AddCommand(templates.NewGetTemplateCmd()) cmd.PersistentFlags().StringP("output", "o", "pretty", "output type can be one of json|yaml|pretty|go-template") cmd.PersistentFlags().StringP("go-template", "", "{{.}}", "go template to render") diff --git a/cmd/kubectl-testkube/commands/templates/common.go b/cmd/kubectl-testkube/commands/templates/common.go new file mode 100644 index 00000000000..e2ca1f283c2 --- /dev/null +++ b/cmd/kubectl-testkube/commands/templates/common.go @@ -0,0 +1,104 @@ +package templates + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + apiv1 "github.com/kubeshop/testkube/pkg/api/v1/client" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/ui" +) + +// NewCreateTemplateOptionsFromFlags creates create template options from command flags +func NewCreateTemplateOptionsFromFlags(cmd *cobra.Command) (options apiv1.CreateTemplateOptions, err error) { + name := cmd.Flag("name").Value.String() + namespace := cmd.Flag("namespace").Value.String() + if err != nil { + return options, err + } + + templateType := testkube.TemplateType(cmd.Flag("template-type").Value.String()) + + if templateType != testkube.JOB_TemplateType && templateType != testkube.CRONJOB_TemplateType && + templateType != testkube.SCRAPER_TemplateType && templateType != testkube.PVC_TemplateType && + templateType != testkube.WEBHOOK_TemplateType { + ui.Failf("invalid template type: %s. use one of job|container|cronjob|scraper|pvc|webhook", templateType) + } + + body := cmd.Flag("body").Value.String() + bodyContent := "" + if body != "" { + b, err := os.ReadFile(body) + ui.ExitOnError("reading template body", err) + bodyContent = string(b) + } + + labels, err := cmd.Flags().GetStringToString("label") + if err != nil { + return options, err + } + + options = apiv1.CreateTemplateOptions{ + Name: name, + Namespace: namespace, + Type_: &templateType, + Labels: labels, + Body: bodyContent, + } + + return options, nil +} + +// NewUpdateTemplateOptionsFromFlags creates update template options from command flags +func NewUpdateTemplateOptionsFromFlags(cmd *cobra.Command) (options apiv1.UpdateTemplateOptions, err error) { + var fields = []struct { + name string + destination **string + }{ + { + "name", + &options.Name, + }, + } + + for _, field := range fields { + if cmd.Flag(field.name).Changed { + value := cmd.Flag(field.name).Value.String() + *field.destination = &value + } + } + + if cmd.Flag("template-type").Changed { + templateType := testkube.TemplateType(cmd.Flag("template-type").Value.String()) + if templateType != testkube.JOB_TemplateType && templateType != testkube.CRONJOB_TemplateType && + templateType != testkube.SCRAPER_TemplateType && templateType != testkube.PVC_TemplateType && + templateType != testkube.WEBHOOK_TemplateType { + ui.Failf("invalid template type: %s. use one of job|container|cronjob|scraper|pvc|webhook", templateType) + } + options.Type_ = &templateType + } + + if cmd.Flag("body").Changed { + body := cmd.Flag("body").Value.String() + b, err := os.ReadFile(body) + if err != nil { + return options, fmt.Errorf("reading template body %w", err) + } + + value := string(b) + options.Body = &value + } + + if cmd.Flag("label").Changed { + labels, err := cmd.Flags().GetStringToString("label") + if err != nil { + return options, err + } + + options.Labels = &labels + } + + return options, nil +} diff --git a/cmd/kubectl-testkube/commands/templates/create.go b/cmd/kubectl-testkube/commands/templates/create.go new file mode 100644 index 00000000000..7ce82b90dae --- /dev/null +++ b/cmd/kubectl-testkube/commands/templates/create.go @@ -0,0 +1,75 @@ +package templates + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" + apiv1 "github.com/kubeshop/testkube/pkg/api/v1/client" + "github.com/kubeshop/testkube/pkg/crd" + "github.com/kubeshop/testkube/pkg/ui" +) + +func NewCreateTemplateCmd() *cobra.Command { + var ( + name string + templateType string + labels map[string]string + body string + ) + + cmd := &cobra.Command{ + Use: "template", + Aliases: []string{"tp"}, + Short: "Create a new Template.", + Long: `Create a new Template Custom Resource.`, + Run: func(cmd *cobra.Command, args []string) { + crdOnly, err := strconv.ParseBool(cmd.Flag("crd-only").Value.String()) + ui.ExitOnError("parsing flag value", err) + + if name == "" { + ui.Failf("pass valid name (in '--name' flag)") + } + + namespace := cmd.Flag("namespace").Value.String() + var client apiv1.Client + if !crdOnly { + client, namespace, err = common.GetClient(cmd) + ui.ExitOnError("getting client", err) + + template, _ := client.GetTemplate(name) + if name == template.Name { + ui.Failf("Template with name '%s' already exists in namespace %s", name, namespace) + } + } + + options, err := NewCreateTemplateOptionsFromFlags(cmd) + ui.ExitOnError("getting template options", err) + + if !crdOnly { + _, err := client.CreateTemplate(options) + ui.ExitOnError("creating template "+name+" in namespace "+namespace, err) + + ui.Success("Template created", name) + } else { + if options.Body != "" { + options.Body = fmt.Sprintf("%q", options.Body) + } + + data, err := crd.ExecuteTemplate(crd.TemplateTemplate, options) + ui.ExitOnError("executing crd template", err) + + ui.Info(data) + } + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "unique template name - mandatory") + cmd.Flags().StringVarP(&templateType, "template-type", "", "", "template type one of job|container|cronjob|scraper|pvc|webhook") + cmd.Flags().StringToStringVarP(&labels, "label", "l", nil, "label key value pair: --label key1=value1") + cmd.Flags().StringVarP(&body, "body", "", "", "a path to template file to use as template body") + + return cmd +} diff --git a/cmd/kubectl-testkube/commands/templates/delete.go b/cmd/kubectl-testkube/commands/templates/delete.go new file mode 100644 index 00000000000..f6caef2981c --- /dev/null +++ b/cmd/kubectl-testkube/commands/templates/delete.go @@ -0,0 +1,48 @@ +package templates + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" + "github.com/kubeshop/testkube/pkg/ui" +) + +func NewDeleteTemplateCmd() *cobra.Command { + var name string + var selectors []string + + cmd := &cobra.Command{ + + Use: "template ", + Aliases: []string{"tp"}, + Short: "Delete a template.", + Long: `Delete a template and pass the template name to be deleted.`, + Run: func(cmd *cobra.Command, args []string) { + client, _, err := common.GetClient(cmd) + ui.ExitOnError("getting client", err) + + if len(args) > 0 { + name = args[0] + err := client.DeleteTemplate(name) + ui.ExitOnError("deleting template: "+name, err) + ui.SuccessAndExit("Succesfully deleted template", name) + } + + if len(selectors) != 0 { + selector := strings.Join(selectors, ",") + err := client.DeleteTemplates(selector) + ui.ExitOnError("deleting templates by labels: "+selector, err) + ui.SuccessAndExit("Succesfully deleted templates by labels", selector) + } + + ui.Failf("Pass Template name or labels to delete by labels") + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "unique template name, you can also pass it as first argument") + cmd.Flags().StringSliceVarP(&selectors, "label", "l", nil, "label key value pair: --label key1=value1") + + return cmd +} diff --git a/cmd/kubectl-testkube/commands/templates/get.go b/cmd/kubectl-testkube/commands/templates/get.go new file mode 100644 index 00000000000..cd5e3c60155 --- /dev/null +++ b/cmd/kubectl-testkube/commands/templates/get.go @@ -0,0 +1,74 @@ +package templates + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/render" + "github.com/kubeshop/testkube/pkg/crd" + "github.com/kubeshop/testkube/pkg/ui" +) + +func NewGetTemplateCmd() *cobra.Command { + var name string + var selectors []string + var crdOnly bool + + cmd := &cobra.Command{ + Use: "template ", + Aliases: []string{"templates", "tp"}, + Short: "Get template details.", + Long: `Get template allows you to change the output format. To get single details, pass the template name as the first argument.`, + Run: func(cmd *cobra.Command, args []string) { + client, _, err := common.GetClient(cmd) + ui.ExitOnError("getting client", err) + + firstEntry := true + if len(args) > 0 { + name := args[0] + template, err := client.GetTemplate(name) + ui.ExitOnError("getting template: "+name, err) + + if crdOnly { + if template.Body != "" { + template.Body = fmt.Sprintf("%q", template.Body) + } + + common.UIPrintCRD(crd.TemplateTemplate, template, &firstEntry) + return + } + + err = render.Obj(cmd, template, os.Stdout) + ui.ExitOnError("rendering obj", err) + } else { + templates, err := client.ListTemplates(strings.Join(selectors, ",")) + ui.ExitOnError("getting templates", err) + + if crdOnly { + for _, template := range templates { + if template.Body != "" { + template.Body = fmt.Sprintf("%q", template.Body) + } + + common.UIPrintCRD(crd.TemplateTemplate, template, &firstEntry) + } + + return + } + + err = render.List(cmd, templates, os.Stdout) + ui.ExitOnError("rendering list", err) + } + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "unique template name, you can also pass it as argument") + cmd.Flags().StringSliceVarP(&selectors, "label", "l", nil, "label key value pair: --label key1=value1") + cmd.Flags().BoolVar(&crdOnly, "crd-only", false, "show only test crd") + + return cmd +} diff --git a/cmd/kubectl-testkube/commands/templates/update.go b/cmd/kubectl-testkube/commands/templates/update.go new file mode 100644 index 00000000000..571f89e8749 --- /dev/null +++ b/cmd/kubectl-testkube/commands/templates/update.go @@ -0,0 +1,52 @@ +package templates + +import ( + "github.com/spf13/cobra" + + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" + "github.com/kubeshop/testkube/pkg/ui" +) + +func UpdateTemplateCmd() *cobra.Command { + var ( + name string + templateType string + labels map[string]string + body string + ) + + cmd := &cobra.Command{ + Use: "template", + Aliases: []string{"templates", "tp"}, + Short: "Update Template", + Long: `Update Template Custom Resource.`, + Run: func(cmd *cobra.Command, args []string) { + if name == "" { + ui.Failf("pass valid name (in '--name' flag)") + } + + client, namespace, err := common.GetClient(cmd) + ui.ExitOnError("getting client", err) + + template, _ := client.GetTemplate(name) + if name != template.Name { + ui.Failf("Template with name '%s' not exists in namespace %s", name, namespace) + } + + options, err := NewUpdateTemplateOptionsFromFlags(cmd) + ui.ExitOnError("getting template options", err) + + _, err = client.UpdateTemplate(options) + ui.ExitOnError("updating template "+name+" in namespace "+namespace, err) + + ui.Success("Template updated", name) + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "unique template name - mandatory") + cmd.Flags().StringVarP(&templateType, "template-type", "", "", "template type one of job|container|cronjob|scraper|pvc|webhook") + cmd.Flags().StringToStringVarP(&labels, "label", "l", nil, "label key value pair: --label key1=value1") + cmd.Flags().StringVarP(&body, "body", "", "", "a path to template file to use as template body") + + return cmd +} diff --git a/cmd/kubectl-testkube/commands/tests/common.go b/cmd/kubectl-testkube/commands/tests/common.go index e8b044e2100..76e6994b320 100644 --- a/cmd/kubectl-testkube/commands/tests/common.go +++ b/cmd/kubectl-testkube/commands/tests/common.go @@ -389,89 +389,76 @@ func newExecutionRequestFromFlags(cmd *cobra.Command) (request *testkube.Executi imageSecrets = append(imageSecrets, testkube.LocalObjectReference{Name: secretName}) } - jobTemplateContent := "" - jobTemplate := cmd.Flag("job-template").Value.String() - if jobTemplate != "" { - b, err := os.ReadFile(jobTemplate) - if err != nil { - return nil, err - } - - jobTemplateContent = string(b) - } - - cronJobTemplateContent := "" - cronJobTemplate := cmd.Flag("cronjob-template").Value.String() - if cronJobTemplate != "" { - b, err := os.ReadFile(cronJobTemplate) - if err != nil { - return nil, err - } - - cronJobTemplateContent = string(b) + jobTemplateReference := cmd.Flag("job-template-reference").Value.String() + cronJobTemplateReference := cmd.Flag("cronjob-template-reference").Value.String() + scraperTemplateReference := cmd.Flag("scraper-template-reference").Value.String() + pvcTemplateReference := cmd.Flag("pvc-template-reference").Value.String() + request = &testkube.ExecutionRequest{ + Name: executionName, + Variables: variables, + Image: image, + Command: command, + Args: executorArgs, + ArgsMode: mode, + ImagePullSecrets: imageSecrets, + Envs: envs, + SecretEnvs: secretEnvs, + HttpProxy: httpProxy, + HttpsProxy: httpsProxy, + ActiveDeadlineSeconds: timeout, + JobTemplateReference: jobTemplateReference, + CronJobTemplateReference: cronJobTemplateReference, + ScraperTemplateReference: scraperTemplateReference, + PvcTemplateReference: pvcTemplateReference, + NegativeTest: negativeTest, } - preRunScriptContent := "" - preRunScript := cmd.Flag("prerun-script").Value.String() - if preRunScript != "" { - b, err := os.ReadFile(preRunScript) - if err != nil { - return nil, err - } - - preRunScriptContent = string(b) + var fields = []struct { + source string + destination *string + }{ + { + cmd.Flag("job-template").Value.String(), + &request.JobTemplate, + }, + { + cmd.Flag("cronjob-template").Value.String(), + &request.CronJobTemplate, + }, + { + cmd.Flag("prerun-script").Value.String(), + &request.PreRunScript, + }, + { + cmd.Flag("postrun-script").Value.String(), + &request.PostRunScript, + }, + { + cmd.Flag("scraper-template").Value.String(), + &request.ScraperTemplate, + }, + { + cmd.Flag("pvc-template").Value.String(), + &request.PvcTemplate, + }, } - postRunScriptContent := "" - postRunScript := cmd.Flag("postrun-script").Value.String() - if postRunScript != "" { - b, err := os.ReadFile(postRunScript) - if err != nil { - return nil, err - } - - postRunScriptContent = string(b) - } + for _, field := range fields { + if field.source != "" { + b, err := os.ReadFile(field.source) + if err != nil { + return nil, err + } - scraperTemplateContent := "" - scraperTemplate := cmd.Flag("scraper-template").Value.String() - if scraperTemplate != "" { - b, err := os.ReadFile(scraperTemplate) - if err != nil { - return nil, err + *field.destination = string(b) } - - scraperTemplateContent = string(b) } - envConfigMaps, envSecrets, err := newEnvReferencesFromFlags(cmd) + request.EnvConfigMaps, request.EnvSecrets, err = newEnvReferencesFromFlags(cmd) if err != nil { return nil, err } - request = &testkube.ExecutionRequest{ - Name: executionName, - Variables: variables, - Image: image, - Command: command, - Args: executorArgs, - ArgsMode: mode, - ImagePullSecrets: imageSecrets, - Envs: envs, - SecretEnvs: secretEnvs, - HttpProxy: httpProxy, - HttpsProxy: httpsProxy, - ActiveDeadlineSeconds: timeout, - JobTemplate: jobTemplateContent, - CronJobTemplate: cronJobTemplateContent, - PreRunScript: preRunScriptContent, - PostRunScript: postRunScriptContent, - ScraperTemplate: scraperTemplateContent, - NegativeTest: negativeTest, - EnvConfigMaps: envConfigMaps, - EnvSecrets: envSecrets, - } - request.ArtifactRequest, err = newArtifactRequestFromFlags(cmd) if err != nil { return nil, err @@ -798,6 +785,22 @@ func newExecutionUpdateRequestFromFlags(cmd *cobra.Command) (request *testkube.E "args-mode", &request.ArgsMode, }, + { + "job-template-reference", + &request.JobTemplateReference, + }, + { + "cronjob-template-reference", + &request.CronJobTemplateReference, + }, + { + "scraper-template-reference", + &request.ScraperTemplateReference, + }, + { + "pvc-template-reference", + &request.PvcTemplateReference, + }, } var nonEmpty bool @@ -919,84 +922,52 @@ func newExecutionUpdateRequestFromFlags(cmd *cobra.Command) (request *testkube.E nonEmpty = true } - if cmd.Flag("job-template").Changed { - jobTemplateContent := "" - jobTemplate := cmd.Flag("job-template").Value.String() - if jobTemplate != "" { - b, err := os.ReadFile(jobTemplate) - if err != nil { - return nil, err - } - - jobTemplateContent = string(b) - } - - request.JobTemplate = &jobTemplateContent - nonEmpty = true - } - - if cmd.Flag("cronjob-template").Changed { - cronJobTemplateContent := "" - cronJobTemplate := cmd.Flag("cronjob-template").Value.String() - if cronJobTemplate != "" { - b, err := os.ReadFile(cronJobTemplate) - if err != nil { - return nil, err - } - - cronJobTemplateContent = string(b) - } - - request.CronJobTemplate = &cronJobTemplateContent - nonEmpty = true - } - - if cmd.Flag("prerun-script").Changed { - preRunScriptContent := "" - preRunScript := cmd.Flag("prerun-script").Value.String() - if preRunScript != "" { - b, err := os.ReadFile(preRunScript) - if err != nil { - return nil, err - } - - preRunScriptContent = string(b) - } - - request.PreRunScript = &preRunScriptContent - nonEmpty = true + var values = []struct { + source string + destination **string + }{ + { + "job-template", + &request.JobTemplate, + }, + { + "cronjob-template", + &request.CronJobTemplate, + }, + { + "prerun-script", + &request.PreRunScript, + }, + { + "postrun-script", + &request.PostRunScript, + }, + { + "scraper-template", + &request.ScraperTemplate, + }, + { + "pvc-template", + &request.PvcTemplate, + }, } - if cmd.Flag("postrun-script").Changed { - postRunScriptContent := "" - postRunScript := cmd.Flag("postrun-script").Value.String() - if postRunScript != "" { - b, err := os.ReadFile(postRunScript) - if err != nil { - return nil, err - } - - postRunScriptContent = string(b) - } - - request.PostRunScript = &postRunScriptContent - nonEmpty = true - } + for _, value := range values { + if cmd.Flag(value.source).Changed { + data := "" + name := cmd.Flag(value.source).Value.String() + if name != "" { + b, err := os.ReadFile(name) + if err != nil { + return nil, err + } - if cmd.Flag("scraper-template").Changed { - scraperTemplateContent := "" - scraperTemplate := cmd.Flag("scraper-template").Value.String() - if scraperTemplate != "" { - b, err := os.ReadFile(scraperTemplate) - if err != nil { - return nil, err + data = string(b) } - scraperTemplateContent = string(b) + *value.destination = &data + nonEmpty = true } - - request.ScraperTemplate = &scraperTemplateContent - nonEmpty = true } if cmd.Flag("mount-configmap").Changed || cmd.Flag("variable-configmap").Changed { @@ -1052,6 +1023,10 @@ func newArtifactUpdateRequestFromFlags(cmd *cobra.Command) (request *testkube.Ar "artifact-volume-mount-path", &request.VolumeMountPath, }, + { + "artifact-storage-bucket", + &request.StorageBucket, + }, } var nonEmpty bool @@ -1073,6 +1048,16 @@ func newArtifactUpdateRequestFromFlags(cmd *cobra.Command) (request *testkube.Ar nonEmpty = true } + if cmd.Flag("artifact-omit-folder-per-execution").Changed { + value, err := cmd.Flags().GetBool("artifact-omit-folder-per-execution") + if err != nil { + return nil, err + } + + request.OmitFolderPerExecution = &value + nonEmpty = true + } + if nonEmpty { return request, nil } diff --git a/cmd/kubectl-testkube/commands/tests/create.go b/cmd/kubectl-testkube/commands/tests/create.go index 7210b343565..eeea2dd3682 100644 --- a/cmd/kubectl-testkube/commands/tests/create.go +++ b/cmd/kubectl-testkube/commands/tests/create.go @@ -39,10 +39,15 @@ type CreateCommonFlags struct { ArtifactVolumeMountPath string ArtifactDirs []string JobTemplate string + JobTemplateReference string CronJobTemplate string + CronJobTemplateReference string PreRunScript string PostRunScript string ScraperTemplate string + ScraperTemplateReference string + PvcTemplate string + PvcTemplateReference string NegativeTest bool MountConfigMaps map[string]string VariableConfigMaps []string @@ -210,10 +215,15 @@ func AddCreateFlags(cmd *cobra.Command, flags *CreateCommonFlags) { cmd.Flags().StringVar(&flags.ArtifactVolumeMountPath, "artifact-volume-mount-path", "", "artifact volume mount path for container executor") cmd.Flags().StringArrayVarP(&flags.ArtifactDirs, "artifact-dir", "", []string{}, "artifact dirs for scraping") cmd.Flags().StringVar(&flags.JobTemplate, "job-template", "", "job template file path for extensions to job template") + cmd.Flags().StringVar(&flags.JobTemplateReference, "job-template-reference", "", "reference to job template to use for the test") cmd.Flags().StringVar(&flags.CronJobTemplate, "cronjob-template", "", "cron job template file path for extensions to cron job template") + cmd.Flags().StringVar(&flags.CronJobTemplateReference, "cronjob-template-reference", "", "reference to cron job template to use for the test") cmd.Flags().StringVarP(&flags.PreRunScript, "prerun-script", "", "", "path to script to be run before test execution") cmd.Flags().StringVarP(&flags.PostRunScript, "postrun-script", "", "", "path to script to be run after test execution") cmd.Flags().StringVar(&flags.ScraperTemplate, "scraper-template", "", "scraper template file path for extensions to scraper template") + cmd.Flags().StringVar(&flags.ScraperTemplateReference, "scraper-template-reference", "", "reference to scraper template to use for the test") + cmd.Flags().StringVar(&flags.PvcTemplate, "pvc-template", "", "pvc template file path for extensions to pvc template") + cmd.Flags().StringVar(&flags.PvcTemplateReference, "pvc-template-reference", "", "reference to pvc template to use for the test") cmd.Flags().BoolVar(&flags.NegativeTest, "negative-test", false, "negative test, if enabled, makes failure an expected and correct test result. If the test fails the result will be set to success, and vice versa") cmd.Flags().StringToStringVarP(&flags.MountConfigMaps, "mount-configmap", "", map[string]string{}, "config map value pair for mounting it to executor pod: --mount-configmap configmap_name=configmap_mountpath") cmd.Flags().StringArrayVar(&flags.VariableConfigMaps, "variable-configmap", []string{}, "config map name used to map all keys to basis variables") diff --git a/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go b/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go index 1096ab30f4d..76882ce7242 100644 --- a/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go +++ b/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go @@ -133,23 +133,43 @@ func TestRenderer(ui *ui.UI, obj interface{}) error { } if test.ExecutionRequest.JobTemplate != "" { - ui.Warn(" Job template: ", "\n", test.ExecutionRequest.JobTemplate) + ui.Warn(" Job template: ", "\n", test.ExecutionRequest.JobTemplate) + } + + if test.ExecutionRequest.JobTemplateReference != "" { + ui.Warn(" Job template reference: ", test.ExecutionRequest.JobTemplateReference) } if test.ExecutionRequest.CronJobTemplate != "" { - ui.Warn(" Cron job template: ", "\n", test.ExecutionRequest.CronJobTemplate) + ui.Warn(" Cron job template: ", "\n", test.ExecutionRequest.CronJobTemplate) + } + + if test.ExecutionRequest.CronJobTemplateReference != "" { + ui.Warn(" Cron job template reference: ", test.ExecutionRequest.CronJobTemplateReference) } if test.ExecutionRequest.PreRunScript != "" { - ui.Warn(" Pre run script: ", "\n", test.ExecutionRequest.PreRunScript) + ui.Warn(" Pre run script: ", "\n", test.ExecutionRequest.PreRunScript) } if test.ExecutionRequest.PostRunScript != "" { - ui.Warn(" Post run script: ", "\n", test.ExecutionRequest.PostRunScript) + ui.Warn(" Post run script: ", "\n", test.ExecutionRequest.PostRunScript) } if test.ExecutionRequest.ScraperTemplate != "" { - ui.Warn(" Scraper template: ", "\n", test.ExecutionRequest.ScraperTemplate) + ui.Warn(" Scraper template: ", "\n", test.ExecutionRequest.ScraperTemplate) + } + + if test.ExecutionRequest.ScraperTemplateReference != "" { + ui.Warn(" Scraper template reference: ", test.ExecutionRequest.ScraperTemplateReference) + } + + if test.ExecutionRequest.PvcTemplate != "" { + ui.Warn(" PVC template: ", "\n", test.ExecutionRequest.PvcTemplate) + } + + if test.ExecutionRequest.PvcTemplateReference != "" { + ui.Warn(" PVC template reference: ", test.ExecutionRequest.PvcTemplateReference) } var mountConfigMaps, mountSecrets []mountParams diff --git a/cmd/kubectl-testkube/commands/tests/run.go b/cmd/kubectl-testkube/commands/tests/run.go index c956082274e..1b03d41e2bc 100644 --- a/cmd/kubectl-testkube/commands/tests/run.go +++ b/cmd/kubectl-testkube/commands/tests/run.go @@ -42,6 +42,7 @@ func NewRunTestCmd() *cobra.Command { artifactVolumeMountPath string artifactDirs []string jobTemplate string + jobTemplateReference string gitBranch string gitCommit string gitPath string @@ -49,6 +50,9 @@ func NewRunTestCmd() *cobra.Command { preRunScript string postRunScript string scraperTemplate string + scraperTemplateReference string + pvcTemplate string + pvcTemplateReference string negativeTest bool mountConfigMaps map[string]string variableConfigMaps []string @@ -82,43 +86,11 @@ func NewRunTestCmd() *cobra.Command { envConfigMaps, envSecrets, err := newEnvReferencesFromFlags(cmd) ui.WarnOnError("getting env config maps and secrets", err) - jobTemplateContent := "" - if jobTemplate != "" { - b, err := os.ReadFile(jobTemplate) - ui.ExitOnError("reading job template", err) - jobTemplateContent = string(b) - } - - preRunScriptContent := "" - if preRunScript != "" { - b, err := os.ReadFile(preRunScript) - ui.ExitOnError("reading pre run script", err) - preRunScriptContent = string(b) - } - - postRunScriptContent := "" - if postRunScript != "" { - b, err := os.ReadFile(postRunScript) - ui.ExitOnError("reading post run script", err) - postRunScriptContent = string(b) - } - - scraperTemplateContent := "" - if scraperTemplate != "" { - b, err := os.ReadFile(scraperTemplate) - ui.ExitOnError("reading scraper template", err) - scraperTemplateContent = string(b) - } - mode := "" if cmd.Flag("args-mode").Changed { mode = argsMode } - var executions []testkube.Execution - client, namespace, err := common.GetClient(cmd) - ui.ExitOnError("getting client", err) - options := apiv1.ExecuteTestOptions{ ExecutionVariables: variables, ExecutionLabels: executionLabels, @@ -130,10 +102,9 @@ func NewRunTestCmd() *cobra.Command { HTTPSProxy: httpsProxy, Envs: envs, Image: image, - JobTemplate: jobTemplateContent, - PreRunScriptContent: preRunScriptContent, - PostRunScriptContent: postRunScriptContent, - ScraperTemplate: scraperTemplateContent, + JobTemplateReference: jobTemplateReference, + ScraperTemplateReference: scraperTemplateReference, + PvcTemplateReference: pvcTemplateReference, IsNegativeTestChangedOnRun: false, EnvConfigMaps: envConfigMaps, EnvSecrets: envSecrets, @@ -143,6 +114,50 @@ func NewRunTestCmd() *cobra.Command { }, } + var fields = []struct { + source string + title string + destination *string + }{ + { + jobTemplate, + "job template", + &options.JobTemplate, + }, + { + preRunScript, + "pre run script", + &options.PreRunScriptContent, + }, + { + postRunScript, + "post run script", + &options.PostRunScriptContent, + }, + { + scraperTemplate, + "scraper template", + &options.ScraperTemplate, + }, + { + pvcTemplate, + "pvc template", + &options.PvcTemplate, + }, + } + + for _, field := range fields { + if field.source != "" { + b, err := os.ReadFile(field.source) + ui.ExitOnError("reading "+field.title, err) + *field.destination = string(b) + } + } + + var executions []testkube.Execution + client, namespace, err := common.GetClient(cmd) + ui.ExitOnError("getting client", err) + if artifactStorageClassName != "" || artifactVolumeMountPath != "" || len(artifactDirs) != 0 || artifactStorageBucket != "" || artifactOmitFolderPerExecution { options.ArtifactRequest = &testkube.ArtifactRequest{ @@ -285,6 +300,7 @@ func NewRunTestCmd() *cobra.Command { cmd.Flags().StringVar(&artifactVolumeMountPath, "artifact-volume-mount-path", "", "artifact volume mount path for container executor") cmd.Flags().StringArrayVarP(&artifactDirs, "artifact-dir", "", []string{}, "artifact dirs for scraping") cmd.Flags().StringVar(&jobTemplate, "job-template", "", "job template file path for extensions to job template") + cmd.Flags().StringVar(&jobTemplateReference, "job-template-reference", "", "reference to job template to use for the test") cmd.Flags().StringVarP(&gitBranch, "git-branch", "", "", "if uri is git repository we can set additional branch parameter") cmd.Flags().StringVarP(&gitCommit, "git-commit", "", "", "if uri is git repository we can use commit id (sha) parameter") cmd.Flags().StringVarP(&gitPath, "git-path", "", "", "if repository is big we need to define additional path to directory/file to checkout partially") @@ -292,6 +308,9 @@ func NewRunTestCmd() *cobra.Command { cmd.Flags().StringVarP(&preRunScript, "prerun-script", "", "", "path to script to be run before test execution") cmd.Flags().StringVarP(&postRunScript, "postrun-script", "", "", "path to script to be run after test execution") cmd.Flags().StringVar(&scraperTemplate, "scraper-template", "", "scraper template file path for extensions to scraper template") + cmd.Flags().StringVar(&scraperTemplateReference, "scraper-template-reference", "", "reference to scraper template to use for the test") + cmd.Flags().StringVar(&pvcTemplate, "pvc-template", "", "pvc template file path for extensions to pvc template") + cmd.Flags().StringVar(&pvcTemplateReference, "pvc-template-reference", "", "reference to pvc template to use for the test") cmd.Flags().BoolVar(&negativeTest, "negative-test", false, "negative test, if enabled, makes failure an expected and correct test result. If the test fails the result will be set to success, and vice versa") cmd.Flags().StringToStringVarP(&mountConfigMaps, "mount-configmap", "", map[string]string{}, "config map value pair for mounting it to executor pod: --mount-configmap configmap_name=configmap_mountpath") cmd.Flags().StringArrayVar(&variableConfigMaps, "variable-configmap", []string{}, "config map name used to map all keys to basis variables") diff --git a/cmd/kubectl-testkube/commands/tests/update.go b/cmd/kubectl-testkube/commands/tests/update.go index 133e0bf37f9..48cc9dc416a 100644 --- a/cmd/kubectl-testkube/commands/tests/update.go +++ b/cmd/kubectl-testkube/commands/tests/update.go @@ -48,10 +48,15 @@ func NewUpdateTestsCmd() *cobra.Command { artifactVolumeMountPath string artifactDirs []string jobTemplate string + jobTemplateReference string cronJobTemplate string + cronJobTemplateReference string preRunScript string postRunScript string scraperTemplate string + scraperTemplateReference string + pvcTemplate string + pvcTemplateReference string negativeTest bool mountConfigMaps map[string]string variableConfigMaps []string @@ -132,10 +137,15 @@ func NewUpdateTestsCmd() *cobra.Command { cmd.Flags().StringVar(&artifactVolumeMountPath, "artifact-volume-mount-path", "", "artifact volume mount path for container executor") cmd.Flags().StringArrayVarP(&artifactDirs, "artifact-dir", "", []string{}, "artifact dirs for scraping") cmd.Flags().StringVar(&jobTemplate, "job-template", "", "job template file path for extensions to job template") + cmd.Flags().StringVar(&jobTemplateReference, "job-template-reference", "", "reference to job template to use for the test") cmd.Flags().StringVar(&cronJobTemplate, "cronjob-template", "", "cron job template file path for extensions to cron job template") + cmd.Flags().StringVar(&cronJobTemplateReference, "cronjob-template-reference", "", "reference to cron job template to use for the test") cmd.Flags().StringVarP(&preRunScript, "prerun-script", "", "", "path to script to be run before test execution") cmd.Flags().StringVarP(&postRunScript, "postrun-script", "", "", "path to script to be run after test execution") cmd.Flags().StringVar(&scraperTemplate, "scraper-template", "", "scraper template file path for extensions to scraper template") + cmd.Flags().StringVar(&scraperTemplateReference, "scraper-template-reference", "", "reference to scraper template to use for the test") + cmd.Flags().StringVar(&pvcTemplate, "pvc-template", "", "pvc template file path for extensions to pvc template") + cmd.Flags().StringVar(&pvcTemplateReference, "pvc-template-reference", "", "reference to pvc template to use for the test") cmd.Flags().BoolVar(&negativeTest, "negative-test", false, "negative test, if enabled, makes failure an expected and correct test result. If the test fails the result will be set to success, and vice versa") cmd.Flags().StringToStringVarP(&mountConfigMaps, "mount-configmap", "", map[string]string{}, "config map value pair for mounting it to executor pod: --mount-configmap configmap_name=configmap_mountpath") cmd.Flags().StringArrayVar(&variableConfigMaps, "variable-configmap", []string{}, "config map name used to map all keys to basis variables") diff --git a/cmd/kubectl-testkube/commands/testsources/create.go b/cmd/kubectl-testkube/commands/testsources/create.go index c451fddd83a..a3a6e3e7e65 100644 --- a/cmd/kubectl-testkube/commands/testsources/create.go +++ b/cmd/kubectl-testkube/commands/testsources/create.go @@ -84,7 +84,7 @@ func NewCreateTestSourceCmd() *cobra.Command { cmd.Flags().StringToStringVarP(&labels, "label", "l", nil, "label key value pair: --label key1=value1") cmd.Flags().StringVarP(&sourceType, "source-type", "", "", "source type of test one of string|file-uri|git") cmd.Flags().StringVarP(&file, "file", "f", "", "source file - will be read from stdin if not specified") - cmd.Flags().StringVarP(&uri, "uri", "u", "", "URI which should be called when given event occurs") + cmd.Flags().StringVarP(&uri, "uri", "u", "", "URI which should be called to get test content") cmd.Flags().StringVarP(&gitUri, "git-uri", "", "", "Git repository uri") cmd.Flags().StringVarP(&gitBranch, "git-branch", "", "", "if uri is git repository we can set additional branch parameter") cmd.Flags().StringVarP(&gitCommit, "git-commit", "", "", "if uri is git repository we can use commit id (sha) parameter") diff --git a/cmd/kubectl-testkube/commands/testsources/update.go b/cmd/kubectl-testkube/commands/testsources/update.go index 4d460ad067a..a5df73be47e 100644 --- a/cmd/kubectl-testkube/commands/testsources/update.go +++ b/cmd/kubectl-testkube/commands/testsources/update.go @@ -58,7 +58,7 @@ func UpdateTestSourceCmd() *cobra.Command { cmd.Flags().StringToStringVarP(&labels, "label", "l", nil, "label key value pair: --label key1=value1") cmd.Flags().StringVarP(&sourceType, "source-type", "", "", "source type of test one of string|file-uri|git") cmd.Flags().StringVarP(&file, "file", "f", "", "source file - will be read from stdin if not specified") - cmd.Flags().StringVarP(&uri, "uri", "u", "", "URI which should be called when given event occurs") + cmd.Flags().StringVarP(&uri, "uri", "u", "", "URI which should be called to get test content") cmd.Flags().StringVarP(&gitUri, "git-uri", "", "", "Git repository uri") cmd.Flags().StringVarP(&gitBranch, "git-branch", "", "", "if uri is git repository we can set additional branch parameter") cmd.Flags().StringVarP(&gitCommit, "git-commit", "", "", "if uri is git repository we can use commit id (sha) parameter") diff --git a/cmd/kubectl-testkube/commands/testsuites/common.go b/cmd/kubectl-testkube/commands/testsuites/common.go index 7403c5a8616..ca7d0b31445 100644 --- a/cmd/kubectl-testkube/commands/testsuites/common.go +++ b/cmd/kubectl-testkube/commands/testsuites/common.go @@ -149,25 +149,55 @@ func NewTestSuiteUpsertOptionsFromFlags(cmd *cobra.Command) (options apiclientv1 return options, fmt.Errorf("validating schedule %w", err) } - cronJobTemplateContent := "" - cronJobTemplate := cmd.Flag("cronjob-template").Value.String() - if cronJobTemplate != "" { - b, err := os.ReadFile(cronJobTemplate) - if err != nil { - return options, err - } - - cronJobTemplateContent = string(b) - } + jobTemplateReference := cmd.Flag("job-template-reference").Value.String() + cronJobTemplateReference := cmd.Flag("cronjob-template-reference").Value.String() + scraperTemplateReference := cmd.Flag("scraper-template-reference").Value.String() + pvcTemplateReference := cmd.Flag("pvc-template-reference").Value.String() options.Schedule = schedule options.ExecutionRequest = &testkube.TestSuiteExecutionRequest{ - Variables: variables, - Name: cmd.Flag("execution-name").Value.String(), - HttpProxy: cmd.Flag("http-proxy").Value.String(), - HttpsProxy: cmd.Flag("https-proxy").Value.String(), - Timeout: timeout, - CronJobTemplate: cronJobTemplateContent, + Variables: variables, + Name: cmd.Flag("execution-name").Value.String(), + HttpProxy: cmd.Flag("http-proxy").Value.String(), + HttpsProxy: cmd.Flag("https-proxy").Value.String(), + Timeout: timeout, + JobTemplateReference: jobTemplateReference, + CronJobTemplateReference: cronJobTemplateReference, + ScraperTemplateReference: scraperTemplateReference, + PvcTemplateReference: pvcTemplateReference, + } + + var fields = []struct { + source string + destination *string + }{ + { + cmd.Flag("job-template").Value.String(), + &options.ExecutionRequest.JobTemplate, + }, + { + cmd.Flag("cronjob-template").Value.String(), + &options.ExecutionRequest.CronJobTemplate, + }, + { + cmd.Flag("scraper-template").Value.String(), + &options.ExecutionRequest.ScraperTemplate, + }, + { + cmd.Flag("pvc-template").Value.String(), + &options.ExecutionRequest.PvcTemplate, + }, + } + + for _, field := range fields { + if field.source != "" { + b, err := os.ReadFile(field.source) + if err != nil { + return options, err + } + + *field.destination = string(b) + } } return options, nil @@ -268,20 +298,44 @@ func NewTestSuiteUpdateOptionsFromFlags(cmd *cobra.Command) (options apiclientv1 nonEmpty = true } - if cmd.Flag("cronjob-template").Changed { - cronJobTemplateContent := "" - cronJobTemplate := cmd.Flag("cronjob-template").Value.String() - if cronJobTemplate != "" { - b, err := os.ReadFile(cronJobTemplate) - if err != nil { - return options, err + var values = []struct { + source string + destination **string + }{ + { + "job-template", + &executionRequest.JobTemplate, + }, + { + "cronjob-template", + &executionRequest.CronJobTemplate, + }, + { + "scraper-template", + &executionRequest.ScraperTemplate, + }, + { + "pvc-template", + &executionRequest.PvcTemplate, + }, + } + + for _, value := range values { + if cmd.Flag(value.source).Changed { + data := "" + name := cmd.Flag(value.source).Value.String() + if name != "" { + b, err := os.ReadFile(name) + if err != nil { + return options, err + } + + data = string(b) } - cronJobTemplateContent = string(b) + *value.destination = &data + nonEmpty = true } - - executionRequest.CronJobTemplate = &cronJobTemplateContent - nonEmpty = true } var executionFields = []struct { @@ -300,6 +354,22 @@ func NewTestSuiteUpdateOptionsFromFlags(cmd *cobra.Command) (options apiclientv1 "https-proxy", &executionRequest.HttpsProxy, }, + { + "job-template-reference", + &executionRequest.JobTemplateReference, + }, + { + "cronjob-template-reference", + &executionRequest.CronJobTemplateReference, + }, + { + "scraper-template-reference", + &executionRequest.ScraperTemplateReference, + }, + { + "pvc-template-reference", + &executionRequest.PvcTemplateReference, + }, } for _, field := range executionFields { diff --git a/cmd/kubectl-testkube/commands/testsuites/create.go b/cmd/kubectl-testkube/commands/testsuites/create.go index 018ff38a907..97d041d4290 100644 --- a/cmd/kubectl-testkube/commands/testsuites/create.go +++ b/cmd/kubectl-testkube/commands/testsuites/create.go @@ -26,7 +26,14 @@ func NewCreateTestSuitesCmd() *cobra.Command { httpProxy, httpsProxy string secretVariableReferences map[string]string timeout int32 + jobTemplate string cronJobTemplate string + scraperTemplate string + pvcTemplate string + jobTemplateReference string + cronJobTemplateReference string + scraperTemplateReference string + pvcTemplateReference string ) cmd := &cobra.Command{ @@ -79,7 +86,14 @@ func NewCreateTestSuitesCmd() *cobra.Command { cmd.Flags().StringVar(&httpsProxy, "https-proxy", "", "https proxy for executor containers") cmd.Flags().StringToStringVarP(&secretVariableReferences, "secret-variable-reference", "", nil, "secret variable references in a form name1=secret_name1=secret_key1") cmd.Flags().Int32Var(&timeout, "timeout", 0, "duration in seconds for test suite to timeout. 0 disables timeout.") + cmd.Flags().StringVar(&jobTemplate, "job-template", "", "job template file path for extensions to job template") cmd.Flags().StringVar(&cronJobTemplate, "cronjob-template", "", "cron job template file path for extensions to cron job template") + cmd.Flags().StringVar(&scraperTemplate, "scraper-template", "", "scraper template file path for extensions to scraper template") + cmd.Flags().StringVar(&pvcTemplate, "pvc-template", "", "pvc template file path for extensions to pvc template") + cmd.Flags().StringVar(&jobTemplateReference, "job-template-reference", "", "reference to job template to use for the test") + cmd.Flags().StringVar(&cronJobTemplateReference, "cronjob-template-reference", "", "reference to cron job template to use for the test") + cmd.Flags().StringVar(&scraperTemplateReference, "scraper-template-reference", "", "reference to scraper template to use for the test") + cmd.Flags().StringVar(&pvcTemplateReference, "pvc-template-reference", "", "reference to pvc template to use for the test") return cmd } diff --git a/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go b/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go index 7595f139ef1..c0540326f3d 100644 --- a/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go +++ b/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go @@ -33,7 +33,7 @@ func TestSuiteRenderer(ui *ui.UI, obj interface{}) error { if ts.ExecutionRequest != nil { ui.Warn("Execution request: ") if ts.ExecutionRequest.Name != "" { - ui.Warn(" Name: ", ts.ExecutionRequest.Name) + ui.Warn(" Name: ", ts.ExecutionRequest.Name) } if len(ts.ExecutionRequest.Variables) > 0 { @@ -41,15 +41,43 @@ func TestSuiteRenderer(ui *ui.UI, obj interface{}) error { } if ts.ExecutionRequest.HttpProxy != "" { - ui.Warn(" Http proxy: ", ts.ExecutionRequest.HttpProxy) + ui.Warn(" Http proxy: ", ts.ExecutionRequest.HttpProxy) } if ts.ExecutionRequest.HttpsProxy != "" { - ui.Warn(" Https proxy: ", ts.ExecutionRequest.HttpsProxy) + ui.Warn(" Https proxy: ", ts.ExecutionRequest.HttpsProxy) + } + + if ts.ExecutionRequest.JobTemplate != "" { + ui.Warn(" Job template: ", "\n", ts.ExecutionRequest.JobTemplate) + } + + if ts.ExecutionRequest.JobTemplateReference != "" { + ui.Warn(" Job template reference: ", ts.ExecutionRequest.JobTemplateReference) } if ts.ExecutionRequest.CronJobTemplate != "" { - ui.Warn(" Cron job template: ", ts.ExecutionRequest.CronJobTemplate) + ui.Warn(" Cron job template: ", "\n", ts.ExecutionRequest.CronJobTemplate) + } + + if ts.ExecutionRequest.CronJobTemplateReference != "" { + ui.Warn(" Cron job template reference: ", ts.ExecutionRequest.CronJobTemplateReference) + } + + if ts.ExecutionRequest.ScraperTemplate != "" { + ui.Warn(" Scraper template: ", "\n", ts.ExecutionRequest.ScraperTemplate) + } + + if ts.ExecutionRequest.ScraperTemplateReference != "" { + ui.Warn(" Scraper template reference: ", ts.ExecutionRequest.ScraperTemplateReference) + } + + if ts.ExecutionRequest.PvcTemplate != "" { + ui.Warn(" PVC template: ", "\n", ts.ExecutionRequest.PvcTemplate) + } + + if ts.ExecutionRequest.PvcTemplateReference != "" { + ui.Warn(" PVC template reference: ", ts.ExecutionRequest.PvcTemplateReference) } } diff --git a/cmd/kubectl-testkube/commands/testsuites/run.go b/cmd/kubectl-testkube/commands/testsuites/run.go index 76fdd1c92e4..33700e2595c 100644 --- a/cmd/kubectl-testkube/commands/testsuites/run.go +++ b/cmd/kubectl-testkube/commands/testsuites/run.go @@ -2,6 +2,7 @@ package testsuites import ( "fmt" + "os" "strings" "time" @@ -31,6 +32,12 @@ func NewRunTestSuiteCmd() *cobra.Command { gitPath string gitWorkingDir string runningContext string + jobTemplate string + scraperTemplate string + pvcTemplate string + jobTemplateReference string + scraperTemplateReference string + pvcTemplateReference string ) cmd := &cobra.Command{ @@ -46,20 +53,53 @@ func NewRunTestSuiteCmd() *cobra.Command { var executions []testkube.TestSuiteExecution - variables, err := common.CreateVariables(cmd, false) - ui.WarnOnError("getting variables", err) options := apiv1.ExecuteTestSuiteOptions{ - ExecutionVariables: variables, - HTTPProxy: httpProxy, - HTTPSProxy: httpsProxy, - ExecutionLabels: executionLabels, + HTTPProxy: httpProxy, + HTTPSProxy: httpsProxy, + ExecutionLabels: executionLabels, RunningContext: &testkube.RunningContext{ Type_: string(testkube.RunningContextTypeUserCLI), Context: runningContext, }, - ConcurrencyLevel: int32(concurrencyLevel), + ConcurrencyLevel: int32(concurrencyLevel), + JobTemplateReference: jobTemplateReference, + ScraperTemplateReference: scraperTemplateReference, + PvcTemplateReference: pvcTemplateReference, + } + + var fields = []struct { + source string + title string + destination *string + }{ + { + jobTemplate, + "job template", + &options.JobTemplate, + }, + { + scraperTemplate, + "scraper template", + &options.ScraperTemplate, + }, + { + pvcTemplate, + "pvc template", + &options.PvcTemplate, + }, } + for _, field := range fields { + if field.source != "" { + b, err := os.ReadFile(field.source) + ui.ExitOnError("reading "+field.title, err) + *field.destination = string(b) + } + } + + options.ExecutionVariables, err = common.CreateVariables(cmd, false) + ui.WarnOnError("getting variables", err) + if gitBranch != "" || gitCommit != "" || gitPath != "" || gitWorkingDir != "" { options.ContentRequest = &testkube.TestContentRequest{ Repository: &testkube.RepositoryParameters{ @@ -139,6 +179,12 @@ func NewRunTestSuiteCmd() *cobra.Command { cmd.Flags().StringVarP(&gitPath, "git-path", "", "", "if repository is big we need to define additional path to directory/file to checkout partially") cmd.Flags().StringVarP(&gitWorkingDir, "git-working-dir", "", "", "if repository contains multiple directories with tests (like monorepo) and one starting directory we can set working directory parameter") cmd.Flags().StringVar(&runningContext, "context", "", "running context description for test suite execution") + cmd.Flags().StringVar(&jobTemplate, "job-template", "", "job template file path for extensions to job template") + cmd.Flags().StringVar(&scraperTemplate, "scraper-template", "", "scraper template file path for extensions to scraper template") + cmd.Flags().StringVar(&pvcTemplate, "pvc-template", "", "pvc template file path for extensions to pvc template") + cmd.Flags().StringVar(&jobTemplateReference, "job-template-reference", "", "reference to job template to use for the test") + cmd.Flags().StringVar(&scraperTemplateReference, "scraper-template-reference", "", "reference to scraper template to use for the test") + cmd.Flags().StringVar(&pvcTemplateReference, "pvc-template-reference", "", "reference to pvc template to use for the test") return cmd } diff --git a/cmd/kubectl-testkube/commands/testsuites/update.go b/cmd/kubectl-testkube/commands/testsuites/update.go index 7e02ce52af9..6a0d3a30c0a 100644 --- a/cmd/kubectl-testkube/commands/testsuites/update.go +++ b/cmd/kubectl-testkube/commands/testsuites/update.go @@ -20,7 +20,14 @@ func UpdateTestSuitesCmd() *cobra.Command { httpProxy, httpsProxy string secretVariableReferences map[string]string timeout int32 + jobTemplate string cronJobTemplate string + scraperTemplate string + pvcTemplate string + jobTemplateReference string + cronJobTemplateReference string + scraperTemplateReference string + pvcTemplateReference string ) cmd := &cobra.Command{ @@ -65,7 +72,14 @@ func UpdateTestSuitesCmd() *cobra.Command { cmd.Flags().StringToStringVarP(&secretVariableReferences, "secret-variable-reference", "", nil, "secret variable references in a form name1=secret_name1=secret_key1") cmd.Flags().StringVar(&httpsProxy, "https-proxy", "", "https proxy for executor containers") cmd.Flags().Int32Var(&timeout, "timeout", 0, "duration in seconds for test suite to timeout. 0 disables timeout.") + cmd.Flags().StringVar(&jobTemplate, "job-template", "", "job template file path for extensions to job template") cmd.Flags().StringVar(&cronJobTemplate, "cronjob-template", "", "cron job template file path for extensions to cron job template") + cmd.Flags().StringVar(&scraperTemplate, "scraper-template", "", "scraper template file path for extensions to scraper template") + cmd.Flags().StringVar(&pvcTemplate, "pvc-template", "", "pvc template file path for extensions to pvc template") + cmd.Flags().StringVar(&jobTemplateReference, "job-template-reference", "", "reference to job template to use for the test") + cmd.Flags().StringVar(&cronJobTemplateReference, "cronjob-template-reference", "", "reference to cron job template to use for the test") + cmd.Flags().StringVar(&scraperTemplateReference, "scraper-template-reference", "", "reference to scraper template to use for the test") + cmd.Flags().StringVar(&pvcTemplateReference, "pvc-template-reference", "", "reference to pvc template to use for the test") return cmd } diff --git a/cmd/kubectl-testkube/commands/update.go b/cmd/kubectl-testkube/commands/update.go index 157a5eb0f38..cd1b77ff3d7 100644 --- a/cmd/kubectl-testkube/commands/update.go +++ b/cmd/kubectl-testkube/commands/update.go @@ -6,6 +6,7 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/validator" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/executors" + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/templates" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/tests" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/testsources" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/testsuites" @@ -37,6 +38,7 @@ func NewUpdateCmd() *cobra.Command { cmd.AddCommand(testsources.UpdateTestSourceCmd()) cmd.AddCommand(executors.UpdateExecutorCmd()) cmd.AddCommand(webhooks.UpdateWebhookCmd()) + cmd.AddCommand(templates.UpdateTemplateCmd()) return cmd } diff --git a/cmd/kubectl-testkube/commands/webhooks/common.go b/cmd/kubectl-testkube/commands/webhooks/common.go index fbff807dd63..c818cc62035 100644 --- a/cmd/kubectl-testkube/commands/webhooks/common.go +++ b/cmd/kubectl-testkube/commands/webhooks/common.go @@ -42,16 +42,18 @@ func NewCreateWebhookOptionsFromFlags(cmd *cobra.Command) (options apiv1.CreateW return options, err } + payloadTemplateReference := cmd.Flag("payload-template-reference").Value.String() options = apiv1.CreateWebhookOptions{ - Name: name, - Namespace: namespace, - Events: webhooksmapper.MapStringArrayToCRDEvents(events), - Uri: uri, - Selector: selector, - Labels: labels, - PayloadObjectField: payloadObjectField, - PayloadTemplate: payloadTemplateContent, - Headers: headers, + Name: name, + Namespace: namespace, + Events: webhooksmapper.MapStringArrayToCRDEvents(events), + Uri: uri, + Selector: selector, + Labels: labels, + PayloadObjectField: payloadObjectField, + PayloadTemplate: payloadTemplateContent, + Headers: headers, + PayloadTemplateReference: payloadTemplateReference, } return options, nil @@ -79,6 +81,10 @@ func NewUpdateWebhookOptionsFromFlags(cmd *cobra.Command) (options apiv1.UpdateW "payload-field", &options.PayloadObjectField, }, + { + "payload-template-reference", + &options.PayloadTemplateReference, + }, } for _, field := range fields { diff --git a/cmd/kubectl-testkube/commands/webhooks/create.go b/cmd/kubectl-testkube/commands/webhooks/create.go index b615e33eab5..141e3e55855 100644 --- a/cmd/kubectl-testkube/commands/webhooks/create.go +++ b/cmd/kubectl-testkube/commands/webhooks/create.go @@ -14,13 +14,14 @@ import ( func NewCreateWebhookCmd() *cobra.Command { var ( - events []string - name, uri string - selector string - labels map[string]string - payloadObjectField string - payloadTemplate string - headers map[string]string + events []string + name, uri string + selector string + labels map[string]string + payloadObjectField string + payloadTemplate string + headers map[string]string + payloadTemplateReference string ) cmd := &cobra.Command{ @@ -71,12 +72,13 @@ func NewCreateWebhookCmd() *cobra.Command { cmd.Flags().StringVarP(&name, "name", "n", "", "unique webhook name - mandatory") cmd.Flags().StringArrayVarP(&events, "events", "e", []string{}, "event types handled by webhook e.g. start-test|end-test") - cmd.Flags().StringVarP(&uri, "uri", "u", "", "URI which should be called when given event occurs") + cmd.Flags().StringVarP(&uri, "uri", "u", "", "URI which should be called when given event occurs (golang template supported)") cmd.Flags().StringVarP(&selector, "selector", "", "", "expression to select tests and test suites for webhook events: --selector app=backend") cmd.Flags().StringToStringVarP(&labels, "label", "l", nil, "label key value pair: --label key1=value1") cmd.Flags().StringVarP(&payloadObjectField, "payload-field", "", "", "field to use for notification object payload") 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: --header Content-Type=application/xml") + 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") return cmd } diff --git a/cmd/kubectl-testkube/commands/webhooks/update.go b/cmd/kubectl-testkube/commands/webhooks/update.go index 81d2a469a33..8f0c524d026 100644 --- a/cmd/kubectl-testkube/commands/webhooks/update.go +++ b/cmd/kubectl-testkube/commands/webhooks/update.go @@ -9,13 +9,14 @@ import ( func UpdateWebhookCmd() *cobra.Command { var ( - events []string - name, uri string - selector string - labels map[string]string - payloadObjectField string - payloadTemplate string - headers map[string]string + events []string + name, uri string + selector string + labels map[string]string + payloadObjectField string + payloadTemplate string + headers map[string]string + payloadTemplateReference string ) cmd := &cobra.Command{ @@ -48,12 +49,13 @@ func UpdateWebhookCmd() *cobra.Command { cmd.Flags().StringVarP(&name, "name", "n", "", "unique webhook name - mandatory") cmd.Flags().StringArrayVarP(&events, "events", "e", []string{}, "event types handled by webhook e.g. start-test|end-test") - cmd.Flags().StringVarP(&uri, "uri", "u", "", "URI which should be called when given event occurs") + cmd.Flags().StringVarP(&uri, "uri", "u", "", "URI which should be called when given event occurs (golang template supported)") cmd.Flags().StringVarP(&selector, "selector", "", "", "expression to select tests and test suites for webhook events: --selector app=backend") cmd.Flags().StringToStringVarP(&labels, "label", "l", nil, "label key value pair: --label key1=value1") cmd.Flags().StringVarP(&payloadObjectField, "payload-field", "", "", "field to use for notification object payload") 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: --header Content-Type=application/xml") + 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") return cmd } diff --git a/cmd/kubectl-testkube/config/config_test.go b/cmd/kubectl-testkube/config/config_test.go index c75069dfebb..52988e3cdf2 100644 --- a/cmd/kubectl-testkube/config/config_test.go +++ b/cmd/kubectl-testkube/config/config_test.go @@ -1,7 +1,6 @@ package config import ( - "io/ioutil" "os" "testing" @@ -10,7 +9,7 @@ import ( func TestSave(t *testing.T) { // override default directory - dir, err := ioutil.TempDir("", "test-config-save") + dir, err := os.MkdirTemp("", "test-config-save") assert.NoError(t, err) defaultDirectory = dir diff --git a/contrib/docker/jmeter/Makefile b/contrib/docker/jmeter/Makefile new file mode 100644 index 00000000000..db7d0c99833 --- /dev/null +++ b/contrib/docker/jmeter/Makefile @@ -0,0 +1,19 @@ +# Variables +DOCKER_REPOSITORY = kubeshop +DOCKER_IMAGE_NAME = jmeter +DOCKER_TAG = 5.5 + +# Build the Docker image +.PHONY: build +build: + @echo "Building Docker image..." + @docker buildx build --platform linux/amd64,linux/arm64 -f jmeter5.5.ubi8.8.Dockerfile -t $(DOCKER_REPOSITORY)/$(DOCKER_IMAGE_NAME):$(DOCKER_TAG) . + +.PHONY: push +push: build + @echo "Pushing Docker image..." + @docker buildx build --push --platform linux/amd64,linux/arm64 -f jmeter5.5.ubi8.8.Dockerfile -t $(DOCKER_REPOSITORY)/$(DOCKER_IMAGE_NAME):$(DOCKER_TAG) . + +test: build + @echo "Testing Docker image..." + @docker run --rm -it $(DOCKER_REPOSITORY)/$(DOCKER_IMAGE_NAME):$(DOCKER_TAG) --version \ No newline at end of file diff --git a/contrib/docker/jmeter/README.md b/contrib/docker/jmeter/README.md new file mode 100644 index 00000000000..c1c386061ef --- /dev/null +++ b/contrib/docker/jmeter/README.md @@ -0,0 +1,25 @@ +# JMeter + +This repository contains Dockerfiles for JMeter builds which are used by the Testkube JMeter Executor. + +Currently supported builds: +* JMeter 5.5 with OpenJDK 17 built on RHEL UBI 8.8 (minimal) + +## Development + +Use the following `make` targets to build and push the images: + +To build the JMeter Docker image use: +```bash +make build +``` + +To do a quick test run of the JMeter Docker image use: +```bash +make test +``` + +To push the JMeter Docker image to the registry use: +```bash +make push +``` \ No newline at end of file diff --git a/contrib/docker/jmeter/jmeter5.5.ubi8.8.Dockerfile b/contrib/docker/jmeter/jmeter5.5.ubi8.8.Dockerfile new file mode 100644 index 00000000000..a84d1ebf2ac --- /dev/null +++ b/contrib/docker/jmeter/jmeter5.5.ubi8.8.Dockerfile @@ -0,0 +1,33 @@ +# Use Red Hat's Universal Base Image 8 +FROM redhat/ubi8-minimal:8.8 + +ENV JAVA_VERSION=17 +ENV JMETER_VERSION=5.5 + +# Labels and authorship +LABEL org.opencontainers.image.title="JMeter" \ + org.opencontainers.image.description="Red Hat UBI with Java $JAVA_VERSION and JMeter $JMETER_VERSION" \ + org.opencontainers.image.version="$JMETER_VERSION" \ + org.opencontainers.image.maintainer="support@testkube.io" \ + org.opencontainers.image.vendor="testkube" \ + org.opencontainers.image.url="https://cloud.testkube.io" \ + org.opencontainers.image.source="https://github.com/kubeshop/testkube/tree/develop/contrib/docker/jmeter" + +# Update the system and install required libraries +RUN microdnf update -y && \ + microdnf install curl unzip java-$JAVA_VERSION-openjdk tar && \ + microdnf clean all + +# Install JMeter +RUN curl -L https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-$JMETER_VERSION.tgz | tar xz -C /opt/ && \ + mv /opt/apache-jmeter-$JMETER_VERSION /opt/jmeter + +# Set JMeter Home and add JMeter bin directory to the PATH +ENV JMETER_HOME /opt/jmeter +ENV PATH $JMETER_HOME/bin:$PATH + +# Expose the required JMeter ports +EXPOSE 60000 + +# Command to run JMeter tests +ENTRYPOINT [ "jmeter" ] diff --git a/contrib/executor/k6/pkg/runner/runner.go b/contrib/executor/k6/pkg/runner/runner.go index 625bd94ab5e..37669472b71 100644 --- a/contrib/executor/k6/pkg/runner/runner.go +++ b/contrib/executor/k6/pkg/runner/runner.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "strings" "github.com/pkg/errors" @@ -237,14 +238,17 @@ func isSuccessful(summary string) bool { // if any of the checks failed func areChecksSuccessful(summary string) bool { lines := splitSummaryBody(summary) + re, err := regexp.Compile(`checks\.+: `) + if err != nil { + outputPkg.PrintLogf("%s Regexp error: %s", ui.IconWarning, err.Error()) + return true + } + for _, line := range lines { - if !strings.Contains(line, "checks") { + if !re.MatchString(line) { continue } - if strings.Contains(line, "100.00%") { - return true - } - return false + return strings.Contains(line, "100.00%") } return true diff --git a/contrib/executor/k6/pkg/runner/runner_test.go b/contrib/executor/k6/pkg/runner/runner_test.go index d8ba785c627..1912b05263e 100644 --- a/contrib/executor/k6/pkg/runner/runner_test.go +++ b/contrib/executor/k6/pkg/runner/runner_test.go @@ -37,6 +37,19 @@ func TestExecutionResult(t *testing.T) { assert.Equal(t, testkube.ExecutionStatusPassed, result.Status) assert.Len(t, result.Steps, 2) }) + + t.Run("Get successful checks for k6 execution result", func(t *testing.T) { + t.Parallel() + // setup + summary, err := os.ReadFile("../../examples/k6-test-scenarios.txt") + if err != nil { + assert.FailNow(t, "Unable to read k6 test summary") + } + + result := areChecksSuccessful(string(summary)) + assert.Equal(t, true, result) + }) + } func TestParse(t *testing.T) { diff --git a/contrib/executor/zap/pkg/runner/runner_test.go b/contrib/executor/zap/pkg/runner/runner_test.go index e99fc77ef95..3aeb02053fd 100644 --- a/contrib/executor/zap/pkg/runner/runner_test.go +++ b/contrib/executor/zap/pkg/runner/runner_test.go @@ -2,7 +2,6 @@ package runner import ( "context" - "io/ioutil" "os" "path/filepath" "testing" @@ -19,7 +18,7 @@ func TestRun(t *testing.T) { t.Run("Run successful API scan", func(t *testing.T) { // given - tempDir, err := ioutil.TempDir(os.TempDir(), "") + tempDir, err := os.MkdirTemp(os.TempDir(), "") assert.NoError(t, err) runner, err := NewRunner(context.TODO(), envs.Params{ DataDir: tempDir, @@ -50,7 +49,7 @@ func TestRun(t *testing.T) { t.Run("Run API scan with PASS and WARN", func(t *testing.T) { // given - tempDir, err := ioutil.TempDir(os.TempDir(), "") + tempDir, err := os.MkdirTemp(os.TempDir(), "") assert.NoError(t, err) runner, err := NewRunner(context.TODO(), envs.Params{ DataDir: tempDir, @@ -81,7 +80,7 @@ func TestRun(t *testing.T) { t.Run("Run API scan with WARN and FailOnWarn", func(t *testing.T) { // given - tempDir, err := ioutil.TempDir(os.TempDir(), "") + tempDir, err := os.MkdirTemp(os.TempDir(), "") assert.NoError(t, err) runner, err := NewRunner(context.TODO(), envs.Params{ DataDir: tempDir, @@ -112,7 +111,7 @@ func TestRun(t *testing.T) { t.Run("Run API scan with FAIL", func(t *testing.T) { // given - tempDir, err := ioutil.TempDir(os.TempDir(), "") + tempDir, err := os.MkdirTemp(os.TempDir(), "") assert.NoError(t, err) runner, err := NewRunner(context.TODO(), envs.Params{ DataDir: tempDir, @@ -143,7 +142,7 @@ func TestRun(t *testing.T) { t.Run("Run Baseline scan with PASS", func(t *testing.T) { // given - tempDir, err := ioutil.TempDir(os.TempDir(), "") + tempDir, err := os.MkdirTemp(os.TempDir(), "") assert.NoError(t, err) runner, err := NewRunner(context.TODO(), envs.Params{ DataDir: tempDir, @@ -172,7 +171,7 @@ func TestRun(t *testing.T) { t.Run("Run Baseline scan with WARN", func(t *testing.T) { // given - tempDir, err := ioutil.TempDir(os.TempDir(), "") + tempDir, err := os.MkdirTemp(os.TempDir(), "") assert.NoError(t, err) runner, err := NewRunner(context.TODO(), envs.Params{ DataDir: tempDir, @@ -202,7 +201,7 @@ func TestRun(t *testing.T) { t.Run("Run Full scan with FAIL", func(t *testing.T) { // given - tempDir, err := ioutil.TempDir(os.TempDir(), "") + tempDir, err := os.MkdirTemp(os.TempDir(), "") assert.NoError(t, err) runner, err := NewRunner(context.TODO(), envs.Params{ DataDir: tempDir, diff --git a/docs/docs/articles/crds.md b/docs/docs/articles/crds.md index d4295c783c4..dd83e1f99b1 100644 --- a/docs/docs/articles/crds.md +++ b/docs/docs/articles/crds.md @@ -12,6 +12,7 @@ kubectl get crds -n testkube NAME CREATED AT executors.executor.testkube.io 2023-06-15T14:49:11Z scripts.tests.testkube.io 2023-06-15T14:49:11Z +templates.tests.testkube.io 2023-06-15T14:49:11Z testexecutions.tests.testkube.io 2023-06-15T14:49:11Z tests.tests.testkube.io 2023-06-15T14:49:11Z testsources.tests.testkube.io 2023-06-15T14:49:11Z diff --git a/docs/docs/articles/templates.mdx b/docs/docs/articles/templates.mdx new file mode 100644 index 00000000000..a705866ede2 --- /dev/null +++ b/docs/docs/articles/templates.mdx @@ -0,0 +1,110 @@ +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Templates + +Templates allow you to store templates for other resources used in Testkube. We support a list of templates job | container | cronjob | scraper | pvc | webhook. To define templates in Testkube, you'll need to provide a template body (in Golang template format) and a type of the template. + +## Creating a Template +The template can be created using the API, CLI, or a Custom Resource. + + + +If you prefer to use the API for creating a template, please visit the API spec for templates in the doc below. + +[OpenAPI spec](../openapi.md) + + + + +Templates can be created with the Testkube CLI using the `create template` command. + +```sh +kubectl testkube create template --name job-template --template-type job --body job-template.yaml +``` + +`--name` - Your template name (in this case `job-template`). +`--template-type` - Your template type (in this case `job` for prebuilt executors). +`--body` - A path to the file with job template content + + + + + +```yaml title="template.yaml" +apiVersion: tests.testkube.io/v1 +kind: Template +metadata: + name: example-webhook + namespace: testkube +spec: + type: job + body: +``` + +Where should be replaced with the Kubernetes job definition in Golang template format. + +And then apply with: + +```sh +kubectl apply -f template.yaml +``` + + + + + +### Using Templates +You will need to refer to a template in the corresponding reference field of the resource. + + + + +Check templateReference fields in API spec. For example, Test -> executionRequest -> jobTemplateReference field. +[OpenAPI spec](../openapi.md) + + + + + +Templates can be created with the Testkube CLI using the `create template` command. + +```sh +kubectl testkube create test --name template-test --type k6/script --job-template-reference=job-template --test-content-type git --git-uri "https://github.com/kubeshop/testkube.git" --git-branch main --git-path test/k6/executor-tests/k6-smoke-test.js +``` + +`--name` - Your test name (in this case `template-test`). +`--type` - Your test type (in this case `k6/script`). +`--job-template-reference` - Job template reference (in this case `job-template`). +`--test-content-type` - Test content type (in this case `git`). +`--git-uri` - Git uri to repository (in this case `https://github.com/kubeshop/testkube.git`). +`--git-branch` - Git branch to use (in this case `main`). +`--git-path` - Git path to the test (in this case `test/k6/executor-tests/k6-smoke-test.js`). + + + + + +```yaml title="test.yaml" +apiVersion: tests.testkube.io/v3 +kind: Test +metadata: + name: template-test + namespace: testkube +spec: + type: k6/script + content: + type: git + repository: + type: git + uri: https://github.com/kubeshop/testkube.git + branch: main + path: test/k6/executor-tests/k6-smoke-test.js + authType: basic + executionRequest: + jobTemplateReference: job-template +``` + + + + diff --git a/docs/docs/articles/webhooks.mdx b/docs/docs/articles/webhooks.mdx index 77cfe991eb2..2a04d701275 100644 --- a/docs/docs/articles/webhooks.mdx +++ b/docs/docs/articles/webhooks.mdx @@ -5,13 +5,134 @@ import TabItem from "@theme/TabItem"; Webhooks allow you to integrate Testkube with external systems by sending HTTP POST payloads containing information about Testkube executions and their current state when specific events occur. To set up webhooks in Testkube, you'll need to have an HTTPS endpoint to receive the events and a payload template to be sent along with the data. -Here's an example format for creating a webhook in Testkube using either the CLI or custom response: +## Creating a Webhook +The webhook can be created using the Dashboard, CLI, or a Custom Resource. + +If you prefer to use the Dashboard, you can view existing webhooks by going to the Webhooks tab. + +![Dashboard menu - webhooks icon](../img/dashboard-webhooks-icon.png) + +Here you can also create a new webhook by clicking the `Create a new webhook` button. + +Then, fill in the webhook details: +![Dashboard webhook - create dialog 1](../img/dashboard-create-webhook-1.png) +- Name - your webhook name (in this case `example-webhook`) +- Resource identifier - the resource (or resources) selected by `label` for which the webhook can be triggered (in the example: `test-type:postman-collection` - any postman test) +- Triggered events - events that will trigger the webhook (in this case `start-test`, `end-test-success`, and `end-test-failed`). All available trigger events can be found in the [Supported Event types](#supported-event-types) section. + +![Dashboard webhook - create dialog 2](../img/dashboard-create-webhook-2.png) + +Set your webhook URI - the HTTPS endpoint where you want to receive the webhook events. +After the webhook is created, the custom payload and headers can be set in Settings->Action. + + + + +Webhooks can be created with Testkube CLI using the `create webhook` command. + +```sh +testkube create webhook --name example-webhook --events start-test --events end-test-success --events end-test-failed --uri +``` +`--name` - Your webhook name (in this case `example-webhook`). +`--events` - Event that will trigger a webhook. Multiple `--events` can be defined (in this case `--events start-test --events end-test-success --events end-test-failed`). All available trigger events can be found in the [Supported Event types](#supported-event-types) section. +`--uri` - The HTTPS endpoint where you want to receive the webhook events. + + + + + +```yaml title="webhook.yaml" +apiVersion: executor.testkube.io/v1 +kind: Webhook +metadata: + name: example-webhook + namespace: testkube +spec: + uri: + events: + - start-test + - end-test-success + - end-test-failed + selector: "" +``` +Where should be replaced with the HTTPS endpoint URL where you want to receive the webhook events. + +And then apply with: + +```sh +kubectl apply -f webhook.yaml +``` + + + + + +### Resource Selector (labels) +In order to limit webhook triggers to a specific resource, or resources, the Resource Selector can be used. It allows you to select the specific resource by label, or labels. + + + + +![Dashboard webhook - resource identifier](../img/dashboard-create-webhook-resource-identifier.png) + + -Create a webhook template payload file: +The Resource Selector can be set with `--selector`. +For example, `--selector test-type=postman-collection` will limit the resources to the postman tests (label: `test-type=postman-collection`) + + + + + +```yaml +spec: + selector: test-type=postman-collection +``` + +So, the complete definition may look like this: + +```yaml title="webhook.yaml" +apiVersion: executor.testkube.io/v1 +kind: Webhook +metadata: + name: example-webhook + namespace: testkube +spec: + uri: + events: + - start-test + - end-test-success + - end-test-failed + selector: test-type=postman-collection +``` + + + + + +### Webhook Payload + +Webhook payload can be configured - in this example, `event id`: +``` +{"text": "event id {{ .Id }}"} +``` + + + +Webhook payload can be configured in Webhook Settings->Action + +![Dashboard webhook - webhook settings action`](../img/dashboard-webhook-settings-action.png) + +![Dashboard webhook - webhook payload](../img/dashboard-webhook-payload.png) + + + + +Create a webhook payload template file: ```json title="template.json" { @@ -19,9 +140,13 @@ Create a webhook template payload file: } ``` +And set it with `--payload-template template.json`. + ```sh -testkube create webhook --name example-webhook --events start-test --events end-test-failed --payload-template template.json --uri +testkube create webhook --name example-webhook --events start-test --events end-test-passed --events end-test-failed --payload-template template.json --uri ``` +Where should be replaced with the HTTPS endpoint URL where you want to receive the webhook events. + ```sh title="Expected output:" Webhook created example-webhook 🥇 @@ -31,7 +156,14 @@ Webhook created example-webhook 🥇 -```yaml title="webhook.yaml" +Payload template can be configured with `spec.payloadTemplate`. +``` + payloadTemplate: | + {"text": "event id {{ .Id }}"} +``` + +Example: +``` apiVersion: executor.testkube.io/v1 kind: Webhook metadata: @@ -47,29 +179,192 @@ spec: payloadObjectField: "" payloadTemplate: | {"text": "event id {{ .Id }}"} - headers: - X-Token: "12345" ``` -And then apply with: + -```sh -kubectl apply -f webhook.yaml + + +### Webhook Payload Variables +Webhook payload can contain **event-specific** variables - they will be replaced with actual data when the events occurs. In the above examples, only the event `Id` is being sent. +However, any of these [supported Event Variables](#supported-event-variables) can be used. + +For example, the following payload: +``` +{"text": "Event {{ .Type_ }} - Test '{{ .TestExecution.TestName }}' execution ({{ .TestExecution.Number }}) finished with '{{ .TestExecution.ExecutionResult.Status }}' status"} +``` +will result in: +``` +{"text": "Event end-test-success - Test 'postman-executor-smoke' execution (948) finished with 'passed' status"} ``` +#### testkube-api-server ENV variables +In addition to event-specific variables, it's also possible to pass testkube-api-server ENV variables: + +```sh title="template.txt" +TESTKUBE_CLOUD_URL: {{ index .Envs "TESTKUBE_CLOUD_URL" }} +``` + +### URI and HTTP Headers +You can add additional HTTP headers like `Authorization` or `x-api-key` to have a secret token. +It's possible to use golang based template string as header or uri value. + + + + +Webhook headers can be configured in Webhook Settings->Action. + +![Dashboard webhook - webhook settings action`](../img/dashboard-webhook-settings-action.png) + +![Dashboard webhook - webhook headers](../img/dashboard-webhook-headers.png) + + +Custom headers can be set using `--header` - for example: + +`--header X-Token="12345"` + + + + + +```yaml +spec: + headers: + X-Token: "12345" +``` + +```yaml title="webhook.yaml" +apiVersion: executor.testkube.io/v1 +kind: Webhook +metadata: + name: example-webhook + namespace: testkube +spec: + uri: + events: + - start-test + - end-test-success + - end-test-failed + selector: "" + headers: + X-Token: "12345" +``` + + -In the example above, replace with the HTTPS endpoint URL where you want to receive the webhook events. The payload template can be customized to include additional information. In the above example, only the event `Id` is being sent. The template's variables will be replaced with data when events occur. +## Supported Event types +Webhooks can be triggered on any of the following events: +- start-test +- end-test-success +- end-test-failed +- end-test-aborted +- end-test-timeout +- start-testsuite +- end-testsuite-success +- end-testsuite-failed +- end-testsuite-aborted +- end-testsuite-timeout +- created +- updated +- deleted -You can add additional HTTP headers like `Authorization` or `x-api-key` to have a secret token. +They can be triggered by the following resources: +- test +- testsuite +- executor +- trigger +- webhook +- testexecution +- testsuiteexecution -It's possible to get access to env variables of testkube-api-server pod in webhook template: +## Supported Event Variables -```sh title="template.txt" -TESTKUBE_CLOUD_URL: {{ index .Envs "TESTKUBE_CLOUD_URL" }} +### Event-specific variables: +- `Id` - event ID (for example, `2a20c7da-3b77-4ea9-a33d-403187d3e9e6`) +- `Resource` +- `ResourceId` +- `Type_` - event Type (for example, `start-test`, `end-test,success`, etc. All available trigger events can be found in the [Supported Event types](#supported-event-types) section). +- `TestExecution` - test execution details (example: [TestExecution (Execution)](#testexecution-execution) section) +- `TestSuiteExecution` - test suite execution details (example: [TestSuiteExecution](#testsuiteexecution) section) +- `ClusterName` - cluster name +- `Envs` (API-server ENV variables) - list of Testkube API-Server ENV variables + +The full Event Data Model can be found [here](https://github.com/kubeshop/testkube/blob/main/pkg/api/v1/testkube/model_event.go). + +### TestExecution (Execution): +- `Id` - Execution ID (for example, `64f8cf3c712890925aea51ce`) +- `TestName` - Test Name (for example, `postman-executor-smoke`) +- `TestSuiteName` - Test Suite name (if run as a part of a Test Suite) +- `TestNamespace` - Execution namespace, where testkube is installed (for example, `testkube`) +- `TestType` - Test type (for example, `postman/collection`) +- `Name` - Execution name (for example, `postman-executor-smoke-937) +- `Number` - Execution number (for example, `937`) +- `Envs` - List of ENV variables for specific Test (if defined) +- `Command` - Command executed inside the Pod (for example, `newman`) +- `Args` - Command arguments (for example, `run -e --reporters cli,json --reporter-json-export `) +- `Variables` - List of variables +- `Content` - Test content +- `StartTime` - Test start time (for example, `2023-09-06 19:23:34.543433547 +0000 UTC`) +- `EndTime` - Time when the test execution finished (for example, `2023-09-06 19:23:42.221493031 +0000 UTC`) +- `Duration` - Test duration in seconds (for example, `7.68s`) +- `DurationMs` - Test duration in miliseconds (for example, `7678`) +- `ExecutionResult` - Execution result (https://github.com/kubeshop/testkube/blob/main/pkg/api/v1/testkube/model_event.go) +- `Labels` Test labels (for example, `[core-tests:executors executor:postman-executor test-type:postman-collection],`) +- `RunningContext` - Running context - how the test has been triggered (for example, `user-ui`) + +The full Execution data model can be found [here](https://github.com/kubeshop/testkube/blob/main/pkg/api/v1/testkube/model_execution.go). + +### TestSuiteExecution: + +- `Id` - TestSuiteExecution ID (for example, `64f8d5b2712890925aea51dc`) +- `Name` - TestSuite name (for example, `ts-executor-postman-smoke-tests-472`) +- `Status` - TestSuite execution status (for example, `running` or `passed`) +- `Envs` - List of ENV variables +- `Variables` - List of variables +- `StartTime` - Test start time (for example, `2023-09-06 19:23:34.543433547 +0000 UTC`) +- `EndTime` - Time when the test execution finished (for example, `2023-09-06 19:23:42.221493031 +0000 UTC`) +- `Duration` - Test duration in seconds (for example, `7.68s`) +- `DurationMs` - Test duration in miliseconds (for example, `7678`) +- `StepResults` +- `Labels` - TestSuite labels (for example, `[app:testkube testsuite:executor-postman-smoke-tests]`) +- `RunningContext` - Running context - how the TestSuite has been triggered (for example, `user-ui`) + +The full TestSuiteExecution data model can be found [here](https://github.com/kubeshop/testkube/blob/main/pkg/api/v1/testkube/model_test_suite_execution.go). + +## Additional Examples + +### Microsoft Teams +Webhooks can also be used to send messages to Microsoft Teams channels. +First, you need to create an incoming webhook in Teams for a specific channel. You can see how to do it in the Teams Docs [here](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=dotnet#create-incoming-webhooks-1). After your Teams incoming webhook is created, you can use it with Testkube webhooks - just use the URL provided (it will probably look like this: `https://xxxxx.webhook.office.com/xxxxxxxxx`). + +In order to send the message when test execution finishes, the following Webhook can be used: ``` +apiVersion: executor.testkube.io/v1 +kind: Webhook +metadata: + name: example-webhook-teams + namespace: testkube +spec: + events: + - end-test-success + - end-test-failed + - end-test-aborted + - end-test-timeout + uri: https://xxxxx.webhook.office.com/xxxxxxxxx + payloadTemplate: "{\"text\": \"Test '{{ .TestExecution.TestName }}' execution ({{ .TestExecution.Number }}) finished with '{{ .TestExecution.ExecutionResult.Status }}' status\"}\n" +``` + +It will result in: + +``` +{"text": "Test 'postman-executor-smoke' execution (949) finished with 'passed' status"} +``` + +and the message: +`Test 'postman-executor-smoke' execution (949) finished with 'passed' status"` being displayed. ## Testing Webhooks diff --git a/docs/docs/cli/testkube_create.md b/docs/docs/cli/testkube_create.md index 63a7f60f50d..87bcdc9163f 100644 --- a/docs/docs/cli/testkube_create.md +++ b/docs/docs/cli/testkube_create.md @@ -27,6 +27,7 @@ testkube create [flags] * [testkube](testkube.md) - Testkube entrypoint for kubectl plugin * [testkube create executor](testkube_create_executor.md) - Create new Executor +* [testkube create template](testkube_create_template.md) - Create a new Template. * [testkube create test](testkube_create_test.md) - Create new Test * [testkube create testsource](testkube_create_testsource.md) - Create new TestSource * [testkube create testsuite](testkube_create_testsuite.md) - Create new TestSuite diff --git a/docs/docs/cli/testkube_create_executor.md b/docs/docs/cli/testkube_create_executor.md index 673c1351fc2..4001cb14bcb 100644 --- a/docs/docs/cli/testkube_create_executor.md +++ b/docs/docs/cli/testkube_create_executor.md @@ -24,6 +24,7 @@ testkube create executor [flags] --image string image used for executor --image-pull-secrets stringArray secret name used to pull the image in executor -j, --job-template string if executor needs to be launched using custom job specification, then a path to template file should be provided + --job-template-reference string reference to job template for using with executor -l, --label stringToString label key value pair: --label key1=value1 (default []) -n, --name string unique executor name - mandatory --tooltip stringToString tooltip key value pair: --tooltip key1=value1 (default []) diff --git a/docs/docs/cli/testkube_create_template.md b/docs/docs/cli/testkube_create_template.md new file mode 100644 index 00000000000..4cc7781a2ed --- /dev/null +++ b/docs/docs/cli/testkube_create_template.md @@ -0,0 +1,37 @@ +## testkube create template + +Create a new Template. + +### Synopsis + +Create a new Template Custom Resource. + +``` +testkube create template [flags] +``` + +### Options + +``` + --body string a path to template file to use as template body + -h, --help help for template + -l, --label stringToString label key value pair: --label key1=value1 (default []) + -n, --name string unique template name - mandatory + --template-type string template type one of job|container|cronjob|scraper|pvc|webhook +``` + +### Options inherited from parent commands + +``` + -a, --api-uri string api uri, default value read from config if set (default "https://demo.testkube.io/results/v1") + -c, --client string client used for connecting to Testkube API one of proxy|direct (default "proxy") + --crd-only generate only crd + --namespace string Kubernetes namespace, default value read from config if set (default "testkube") + --oauth-enabled enable oauth + --verbose show additional debug messages +``` + +### SEE ALSO + +* [testkube create](testkube_create.md) - Create resource + diff --git a/docs/docs/cli/testkube_create_test.md b/docs/docs/cli/testkube_create_test.md index 454a25719f1..a1afc178623 100644 --- a/docs/docs/cli/testkube_create_test.md +++ b/docs/docs/cli/testkube_create_test.md @@ -22,6 +22,7 @@ testkube create test [flags] --command stringArray command passed to image in executor --copy-files stringArray file path mappings from host to pod of form source:destination --cronjob-template string cron job template file path for extensions to cron job template + --cronjob-template-reference string reference to cron job template to use for the test --description string test description --env stringToString envs in a form of name1=val1 passed to executor (default []) --execution-name string execution name, if empty will be autogenerated @@ -44,6 +45,7 @@ testkube create test [flags] --image string image for container executor --image-pull-secrets stringArray secret name used to pull the image in container executor --job-template string job template file path for extensions to job template + --job-template-reference string reference to job template to use for the test -l, --label stringToString label key value pair: --label key1=value1 (default []) --mount-configmap stringToString config map value pair for mounting it to executor pod: --mount-configmap configmap_name=configmap_mountpath (default []) --mount-secret stringToString secret value pair for mounting it to executor pod: --mount-secret secret_name=secret_mountpath (default []) @@ -51,8 +53,11 @@ testkube create test [flags] --negative-test negative test, if enabled, makes failure an expected and correct test result. If the test fails the result will be set to success, and vice versa --postrun-script string path to script to be run after test execution --prerun-script string path to script to be run before test execution + --pvc-template string pvc template file path for extensions to pvc template + --pvc-template-reference string reference to pvc template to use for the test --schedule string test schedule in a cron job form: * * * * * --scraper-template string scraper template file path for extensions to scraper template + --scraper-template-reference string reference to scraper template to use for the test --secret-env stringToString secret envs in a form of secret_key1=secret_name1 passed to executor (default []) -s, --secret-variable stringToString secret variable key value pair: --secret-variable key1=value1 (default []) --secret-variable-reference stringToString secret variable references in a form name1=secret_name1=secret_key1 (default []) diff --git a/docs/docs/cli/testkube_create_testsource.md b/docs/docs/cli/testkube_create_testsource.md index d7df2179afa..5a376c6ee7c 100644 --- a/docs/docs/cli/testkube_create_testsource.md +++ b/docs/docs/cli/testkube_create_testsource.md @@ -29,7 +29,7 @@ testkube create testsource [flags] -l, --label stringToString label key value pair: --label key1=value1 (default []) -n, --name string unique test source name - mandatory --source-type string source type of test one of string|file-uri|git - -u, --uri string URI which should be called when given event occurs + -u, --uri string URI which should be called to get test content ``` ### Options inherited from parent commands diff --git a/docs/docs/cli/testkube_create_testsuite.md b/docs/docs/cli/testkube_create_testsuite.md index 98a83ff2a95..54e848fd3da 100644 --- a/docs/docs/cli/testkube_create_testsuite.md +++ b/docs/docs/cli/testkube_create_testsuite.md @@ -14,14 +14,21 @@ testkube create testsuite [flags] ``` --cronjob-template string cron job template file path for extensions to cron job template + --cronjob-template-reference string reference to cron job template to use for the test --execution-name string execution name, if empty will be autogenerated -f, --file string JSON test suite file - will be read from stdin if not specified, look at testkube.TestUpsertRequest -h, --help help for testsuite --http-proxy string http proxy for executor containers --https-proxy string https proxy for executor containers + --job-template string job template file path for extensions to job template + --job-template-reference string reference to job template to use for the test -l, --label stringToString label key value pair: --label key1=value1 (default []) --name string Set/Override test suite name + --pvc-template string pvc template file path for extensions to pvc template + --pvc-template-reference string reference to pvc template to use for the test --schedule string test suite schedule in a cron job form: * * * * * + --scraper-template string scraper template file path for extensions to scraper template + --scraper-template-reference string reference to scraper template to use for the test -s, --secret-variable stringToString secret variable key value pair: --secret-variable key1=value1 (default []) --secret-variable-reference stringToString secret variable references in a form name1=secret_name1=secret_key1 (default []) --timeout int32 duration in seconds for test suite to timeout. 0 disables timeout. diff --git a/docs/docs/cli/testkube_create_webhook.md b/docs/docs/cli/testkube_create_webhook.md index ae50d764a27..38247f0996b 100644 --- a/docs/docs/cli/testkube_create_webhook.md +++ b/docs/docs/cli/testkube_create_webhook.md @@ -13,15 +13,16 @@ testkube create webhook [flags] ### Options ``` - -e, --events stringArray event types handled by webhook e.g. start-test|end-test - --header stringToString webhook header value pair: --header Content-Type=application/xml (default []) - -h, --help help for webhook - -l, --label stringToString label key value pair: --label key1=value1 (default []) - -n, --name string unique webhook name - mandatory - --payload-field string field to use for notification object payload - --payload-template string if webhook needs to send a custom notification, then a path to template file should be provided - --selector string expression to select tests and test suites for webhook events: --selector app=backend - -u, --uri string URI which should be called when given event occurs + -e, --events stringArray event types handled by webhook e.g. start-test|end-test + --header stringToString webhook header value pair: --header Content-Type=application/xml (default []) + -h, --help help for webhook + -l, --label stringToString label key value pair: --label key1=value1 (default []) + -n, --name string unique webhook name - mandatory + --payload-field string field to use for notification object payload + --payload-template string if webhook needs to send a custom notification, then a path to template file should be provided + --payload-template-reference string reference to payload template to use for the webhook + --selector string expression to select tests and test suites for webhook events: --selector app=backend + -u, --uri string URI which should be called when given event occurs (golang template supported) ``` ### Options inherited from parent commands diff --git a/docs/docs/cli/testkube_delete.md b/docs/docs/cli/testkube_delete.md index 9c214cd836f..1512aa3bc7b 100644 --- a/docs/docs/cli/testkube_delete.md +++ b/docs/docs/cli/testkube_delete.md @@ -26,6 +26,7 @@ testkube delete [flags] * [testkube](testkube.md) - Testkube entrypoint for kubectl plugin * [testkube delete executor](testkube_delete_executor.md) - Delete Executor +* [testkube delete template](testkube_delete_template.md) - Delete a template. * [testkube delete test](testkube_delete_test.md) - Delete Test * [testkube delete testsource](testkube_delete_testsource.md) - Delete test source * [testkube delete testsuite](testkube_delete_testsuite.md) - Delete test suite diff --git a/docs/docs/cli/testkube_delete_template.md b/docs/docs/cli/testkube_delete_template.md new file mode 100644 index 00000000000..265a9ff458f --- /dev/null +++ b/docs/docs/cli/testkube_delete_template.md @@ -0,0 +1,34 @@ +## testkube delete template + +Delete a template. + +### Synopsis + +Delete a template and pass the template name to be deleted. + +``` +testkube delete template [flags] +``` + +### Options + +``` + -h, --help help for template + -l, --label strings label key value pair: --label key1=value1 + -n, --name string unique template name, you can also pass it as first argument +``` + +### Options inherited from parent commands + +``` + -a, --api-uri string api uri, default value read from config if set (default "https://demo.testkube.io/results/v1") + -c, --client string Client used for connecting to testkube API one of proxy|direct (default "proxy") + --namespace string Kubernetes namespace, default value read from config if set (default "testkube") + --oauth-enabled enable oauth + --verbose should I show additional debug messages +``` + +### SEE ALSO + +* [testkube delete](testkube_delete.md) - Delete resources + diff --git a/docs/docs/cli/testkube_generate_tests-crds.md b/docs/docs/cli/testkube_generate_tests-crds.md index 748c4f80c6d..a7e463cfd88 100644 --- a/docs/docs/cli/testkube_generate_tests-crds.md +++ b/docs/docs/cli/testkube_generate_tests-crds.md @@ -22,6 +22,7 @@ testkube generate tests-crds [flags] --command stringArray command passed to image in executor --copy-files stringArray file path mappings from host to pod of form source:destination --cronjob-template string cron job template file path for extensions to cron job template + --cronjob-template-reference string reference to cron job template to use for the test --description string test description --env stringToString envs in a form of name1=val1 passed to executor (default []) --execution-name string execution name, if empty will be autogenerated @@ -32,14 +33,18 @@ testkube generate tests-crds [flags] --image string image for container executor --image-pull-secrets stringArray secret name used to pull the image in container executor --job-template string job template file path for extensions to job template + --job-template-reference string reference to job template to use for the test -l, --label stringToString label key value pair: --label key1=value1 (default []) --mount-configmap stringToString config map value pair for mounting it to executor pod: --mount-configmap configmap_name=configmap_mountpath (default []) --mount-secret stringToString secret value pair for mounting it to executor pod: --mount-secret secret_name=secret_mountpath (default []) --negative-test negative test, if enabled, makes failure an expected and correct test result. If the test fails the result will be set to success, and vice versa --postrun-script string path to script to be run after test execution --prerun-script string path to script to be run before test execution + --pvc-template string pvc template file path for extensions to pvc template + --pvc-template-reference string reference to pvc template to use for the test --schedule string test schedule in a cron job form: * * * * * --scraper-template string scraper template file path for extensions to scraper template + --scraper-template-reference string reference to scraper template to use for the test --secret-env stringToString secret envs in a form of secret_key1=secret_name1 passed to executor (default []) -s, --secret-variable stringToString secret variable key value pair: --secret-variable key1=value1 (default []) --secret-variable-reference stringToString secret variable references in a form name1=secret_name1=secret_key1 (default []) diff --git a/docs/docs/cli/testkube_get.md b/docs/docs/cli/testkube_get.md index d98c5523897..010e0d0899f 100644 --- a/docs/docs/cli/testkube_get.md +++ b/docs/docs/cli/testkube_get.md @@ -35,6 +35,7 @@ testkube get [flags] * [testkube get context](testkube_get_context.md) - Set context for Testkube Cloud * [testkube get execution](testkube_get_execution.md) - Lists or gets test executions * [testkube get executor](testkube_get_executor.md) - Gets executor details +* [testkube get template](testkube_get_template.md) - Get template details. * [testkube get test](testkube_get_test.md) - Get all available tests * [testkube get testsource](testkube_get_testsource.md) - Get test source details * [testkube get testsuite](testkube_get_testsuite.md) - Get test suite by name diff --git a/docs/docs/cli/testkube_get_template.md b/docs/docs/cli/testkube_get_template.md new file mode 100644 index 00000000000..11b7e72a7c8 --- /dev/null +++ b/docs/docs/cli/testkube_get_template.md @@ -0,0 +1,37 @@ +## testkube get template + +Get template details. + +### Synopsis + +Get template allows you to change the output format. To get single details, pass the template name as the first argument. + +``` +testkube get template [flags] +``` + +### Options + +``` + --crd-only show only test crd + -h, --help help for template + -l, --label strings label key value pair: --label key1=value1 + -n, --name string unique template name, you can also pass it as argument +``` + +### Options inherited from parent commands + +``` + -a, --api-uri string api uri, default value read from config if set (default "https://demo.testkube.io/results/v1") + -c, --client string client used for connecting to Testkube API one of proxy|direct (default "proxy") + --go-template string go template to render (default "{{.}}") + --namespace string Kubernetes namespace, default value read from config if set (default "testkube") + --oauth-enabled enable oauth + -o, --output string output type can be one of json|yaml|pretty|go-template (default "pretty") + --verbose show additional debug messages +``` + +### SEE ALSO + +* [testkube get](testkube_get.md) - Get resources + diff --git a/docs/docs/cli/testkube_run_test.md b/docs/docs/cli/testkube_run_test.md index e2a16f8514b..d298f1ab3e3 100644 --- a/docs/docs/cli/testkube_run_test.md +++ b/docs/docs/cli/testkube_run_test.md @@ -38,6 +38,7 @@ testkube run test [flags] --image string execution variable passed to executor --iterations int how many times to run the test (default 1) --job-template string job template file path for extensions to job template + --job-template-reference string reference to job template to use for the test -l, --label strings label key value pair: --label key1=value1 --mask stringArray regexp to filter downloaded files, single or comma separated, like report/.* or .*\.json,.*\.js$ --mount-configmap stringToString config map value pair for mounting it to executor pod: --mount-configmap configmap_name=configmap_mountpath (default []) @@ -46,7 +47,10 @@ testkube run test [flags] --negative-test negative test, if enabled, makes failure an expected and correct test result. If the test fails the result will be set to success, and vice versa --postrun-script string path to script to be run after test execution --prerun-script string path to script to be run before test execution + --pvc-template string pvc template file path for extensions to pvc template + --pvc-template-reference string reference to pvc template to use for the test --scraper-template string scraper template file path for extensions to scraper template + --scraper-template-reference string reference to scraper template to use for the test -s, --secret-variable stringToString execution secret variable passed to executor (default []) --secret-variable-reference stringToString secret variable references in a form name1=secret_name1=secret_key1 (default []) --upload-timeout string timeout to use when uploading files, example: 30s diff --git a/docs/docs/cli/testkube_run_testsuite.md b/docs/docs/cli/testkube_run_testsuite.md index 487a76d331e..e1138fcf6aa 100644 --- a/docs/docs/cli/testkube_run_testsuite.md +++ b/docs/docs/cli/testkube_run_testsuite.md @@ -23,8 +23,14 @@ testkube run testsuite [flags] -h, --help help for testsuite --http-proxy string http proxy for executor containers --https-proxy string https proxy for executor containers + --job-template string job template file path for extensions to job template + --job-template-reference string reference to job template to use for the test -l, --label strings label key value pair: --label key1=value1 -n, --name string execution name, if empty will be autogenerated + --pvc-template string pvc template file path for extensions to pvc template + --pvc-template-reference string reference to pvc template to use for the test + --scraper-template string scraper template file path for extensions to scraper template + --scraper-template-reference string reference to scraper template to use for the test -s, --secret-variable stringToString execution variables passed to executor (default []) --secret-variable-reference stringToString secret variable references in a form name1=secret_name1=secret_key1 (default []) -v, --variable stringToString execution variables passed to executor (default []) diff --git a/docs/docs/cli/testkube_update.md b/docs/docs/cli/testkube_update.md index 7a3de7cc45b..9da43b9874f 100644 --- a/docs/docs/cli/testkube_update.md +++ b/docs/docs/cli/testkube_update.md @@ -26,6 +26,7 @@ testkube update [flags] * [testkube](testkube.md) - Testkube entrypoint for kubectl plugin * [testkube update executor](testkube_update_executor.md) - Update Executor +* [testkube update template](testkube_update_template.md) - Update Template * [testkube update test](testkube_update_test.md) - Update test * [testkube update testsource](testkube_update_testsource.md) - Update TestSource * [testkube update testsuite](testkube_update_testsuite.md) - Update Test Suite diff --git a/docs/docs/cli/testkube_update_executor.md b/docs/docs/cli/testkube_update_executor.md index 2f8cf5c90b9..c65aece5c9a 100644 --- a/docs/docs/cli/testkube_update_executor.md +++ b/docs/docs/cli/testkube_update_executor.md @@ -24,6 +24,7 @@ testkube update executor [flags] --image string image used for executor --image-pull-secrets stringArray secret name used to pull the image in executor -j, --job-template string if executor needs to be launched using custom job specification, then a path to template file should be provided + --job-template-reference string reference to job template for using with executor -l, --label stringToString label key value pair: --label key1=value1 (default []) -n, --name string unique executor name - mandatory --tooltip stringToString tooltip key value pair: --tooltip key1=value1 (default []) diff --git a/docs/docs/cli/testkube_update_template.md b/docs/docs/cli/testkube_update_template.md new file mode 100644 index 00000000000..aedaa7e8bdd --- /dev/null +++ b/docs/docs/cli/testkube_update_template.md @@ -0,0 +1,36 @@ +## testkube update template + +Update Template + +### Synopsis + +Update Template Custom Resource. + +``` +testkube update template [flags] +``` + +### Options + +``` + --body string a path to template file to use as template body + -h, --help help for template + -l, --label stringToString label key value pair: --label key1=value1 (default []) + -n, --name string unique template name - mandatory + --template-type string template type one of job|container|cronjob|scraper|pvc|webhook +``` + +### Options inherited from parent commands + +``` + -a, --api-uri string api uri, default value read from config if set (default "https://demo.testkube.io/results/v1") + -c, --client string client used for connecting to Testkube API one of proxy|direct (default "proxy") + --namespace string Kubernetes namespace, default value read from config if set (default "testkube") + --oauth-enabled enable oauth + --verbose show additional debug messages +``` + +### SEE ALSO + +* [testkube update](testkube_update.md) - Update resource + diff --git a/docs/docs/cli/testkube_update_test.md b/docs/docs/cli/testkube_update_test.md index 8e761d16799..3bddc3448ee 100644 --- a/docs/docs/cli/testkube_update_test.md +++ b/docs/docs/cli/testkube_update_test.md @@ -22,6 +22,7 @@ testkube update test [flags] --command stringArray command passed to image in executor --copy-files stringArray file path mappings from host to pod of form source:destination --cronjob-template string cron job template file path for extensions to cron job template + --cronjob-template-reference string reference to cron job template to use for the test --description string test description --execution-name string execution name, if empty will be autogenerated --executor-args stringArray executor binary additional arguments @@ -43,6 +44,7 @@ testkube update test [flags] -i, --image string image for container executor --image-pull-secrets stringArray secret name used to pull the image in container executor --job-template string job template file path for extensions to job template + --job-template-reference string reference to job template to use for the test -l, --label stringToString label key value pair: --label key1=value1 (default []) --mount-configmap stringToString config map value pair for mounting it to executor pod: --mount-configmap configmap_name=configmap_mountpath (default []) --mount-secret stringToString secret value pair for mounting it to executor pod: --mount-secret secret_name=secret_mountpath (default []) @@ -50,8 +52,11 @@ testkube update test [flags] --negative-test negative test, if enabled, makes failure an expected and correct test result. If the test fails the result will be set to success, and vice versa --postrun-script string path to script to be run after test execution --prerun-script string path to script to be run before test execution + --pvc-template string pvc template file path for extensions to pvc template + --pvc-template-reference string reference to pvc template to use for the test --schedule string test schedule in a cron job form: * * * * * --scraper-template string scraper template file path for extensions to scraper template + --scraper-template-reference string reference to scraper template to use for the test -s, --secret-variable stringToString secret variable key value pair: -s key1=value1 (default []) --secret-variable-reference stringToString secret variable references in a form name1=secret_name1=secret_key1 (default []) --source string source name - will be used together with content parameters diff --git a/docs/docs/cli/testkube_update_testsource.md b/docs/docs/cli/testkube_update_testsource.md index c2f8f68e4b9..3a3ec27d03b 100644 --- a/docs/docs/cli/testkube_update_testsource.md +++ b/docs/docs/cli/testkube_update_testsource.md @@ -29,7 +29,7 @@ testkube update testsource [flags] -l, --label stringToString label key value pair: --label key1=value1 (default []) -n, --name string unique test source name - mandatory --source-type string source type of test one of string|file-uri|git - -u, --uri string URI which should be called when given event occurs + -u, --uri string URI which should be called to get test content ``` ### Options inherited from parent commands diff --git a/docs/docs/cli/testkube_update_testsuite.md b/docs/docs/cli/testkube_update_testsuite.md index f8c6173602e..10253abae79 100644 --- a/docs/docs/cli/testkube_update_testsuite.md +++ b/docs/docs/cli/testkube_update_testsuite.md @@ -14,14 +14,21 @@ testkube update testsuite [flags] ``` --cronjob-template string cron job template file path for extensions to cron job template + --cronjob-template-reference string reference to cron job template to use for the test --execution-name string execution name, if empty will be autogenerated -f, --file string JSON test file - will be read from stdin if not specified, look at testkube.TestUpsertRequest -h, --help help for testsuite --http-proxy string http proxy for executor containers --https-proxy string https proxy for executor containers + --job-template string job template file path for extensions to job template + --job-template-reference string reference to job template to use for the test -l, --label stringToString label key value pair: --label key1=value1 (default []) --name string Set/Override test suite name + --pvc-template string pvc template file path for extensions to pvc template + --pvc-template-reference string reference to pvc template to use for the test --schedule string test suite schedule in a cron job form: * * * * * + --scraper-template string scraper template file path for extensions to scraper template + --scraper-template-reference string reference to scraper template to use for the test -s, --secret-variable stringToString secret variable key value pair: --secret-variable key1=value1 (default []) --secret-variable-reference stringToString secret variable references in a form name1=secret_name1=secret_key1 (default []) --timeout int32 duration in seconds for test suite to timeout. 0 disables timeout. diff --git a/docs/docs/cli/testkube_update_webhook.md b/docs/docs/cli/testkube_update_webhook.md index 08772132bf6..7be51a48f18 100644 --- a/docs/docs/cli/testkube_update_webhook.md +++ b/docs/docs/cli/testkube_update_webhook.md @@ -13,15 +13,16 @@ testkube update webhook [flags] ### Options ``` - -e, --events stringArray event types handled by webhook e.g. start-test|end-test - --header stringToString webhook header value pair: --header Content-Type=application/xml (default []) - -h, --help help for webhook - -l, --label stringToString label key value pair: --label key1=value1 (default []) - -n, --name string unique webhook name - mandatory - --payload-field string field to use for notification object payload - --payload-template string if webhook needs to send a custom notification, then a path to template file should be provided - --selector string expression to select tests and test suites for webhook events: --selector app=backend - -u, --uri string URI which should be called when given event occurs + -e, --events stringArray event types handled by webhook e.g. start-test|end-test + --header stringToString webhook header value pair: --header Content-Type=application/xml (default []) + -h, --help help for webhook + -l, --label stringToString label key value pair: --label key1=value1 (default []) + -n, --name string unique webhook name - mandatory + --payload-field string field to use for notification object payload + --payload-template string if webhook needs to send a custom notification, then a path to template file should be provided + --payload-template-reference string reference to payload template to use for the webhook + --selector string expression to select tests and test suites for webhook events: --selector app=backend + -u, --uri string URI which should be called when given event occurs (golang template supported) ``` ### Options inherited from parent commands diff --git a/docs/docs/img/dashboard-create-webhook-1.png b/docs/docs/img/dashboard-create-webhook-1.png new file mode 100644 index 00000000000..5b6f568773b Binary files /dev/null and b/docs/docs/img/dashboard-create-webhook-1.png differ diff --git a/docs/docs/img/dashboard-create-webhook-2.png b/docs/docs/img/dashboard-create-webhook-2.png new file mode 100644 index 00000000000..a7df407e856 Binary files /dev/null and b/docs/docs/img/dashboard-create-webhook-2.png differ diff --git a/docs/docs/img/dashboard-create-webhook-resource-identifier.png b/docs/docs/img/dashboard-create-webhook-resource-identifier.png new file mode 100644 index 00000000000..46b4b664d16 Binary files /dev/null and b/docs/docs/img/dashboard-create-webhook-resource-identifier.png differ diff --git a/docs/docs/img/dashboard-webhook-headers.png b/docs/docs/img/dashboard-webhook-headers.png new file mode 100644 index 00000000000..f37ca8bbddf Binary files /dev/null and b/docs/docs/img/dashboard-webhook-headers.png differ diff --git a/docs/docs/img/dashboard-webhook-payload.png b/docs/docs/img/dashboard-webhook-payload.png new file mode 100644 index 00000000000..ca344879707 Binary files /dev/null and b/docs/docs/img/dashboard-webhook-payload.png differ diff --git a/docs/docs/img/dashboard-webhook-settings-action.png b/docs/docs/img/dashboard-webhook-settings-action.png new file mode 100644 index 00000000000..01044ed2e58 Binary files /dev/null and b/docs/docs/img/dashboard-webhook-settings-action.png differ diff --git a/docs/docs/img/dashboard-webhooks-icon.png b/docs/docs/img/dashboard-webhooks-icon.png new file mode 100644 index 00000000000..619d58e8918 Binary files /dev/null and b/docs/docs/img/dashboard-webhooks-icon.png differ diff --git a/docs/docs/test-types/executor-ginkgo.md b/docs/docs/test-types/executor-ginkgo.md index 53ad6be2a31..15520ab8e4a 100644 --- a/docs/docs/test-types/executor-ginkgo.md +++ b/docs/docs/test-types/executor-ginkgo.md @@ -4,10 +4,11 @@ import Admonition from "@theme/Admonition"; Our dedicated Ginkgo executor allows running Ginkgo tests with Testkube - directly from your Git repository. -Default command for this executor: ginkgo -Default arguments for this executor command: -r -p --randomize-all --randomize-suites --keep-going --trace --junit-report <reportFile> <envVars> <runPath> +* Default command for this executor: `ginkgo` +* Default arguments for this executor command: `-r` `-p` `--randomize-all` `--randomize-suites` `--keep-going` `--trace` `--junit-report` `` `` `` (parameters in <> are calculated at test execution) + export const ExecutorInfo = () => { return (
diff --git a/docs/redirects.js b/docs/redirects.js index 274f137aafb..80daf150ce1 100644 --- a/docs/redirects.js +++ b/docs/redirects.js @@ -277,6 +277,10 @@ const redirects = [ from: "/guides/uninstall", to: "/articles/uninstall", }, + { + from: "/guides/templates", + to: "/articles/templates", + }, { from: ["/testkube-cloud/intro", "/testkube-cloud"], to: "/testkube-cloud/articles/intro", diff --git a/docs/sidebars.js b/docs/sidebars.js index 8461a87ba3c..cc575ed6e89 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -80,6 +80,7 @@ const sidebars = { "articles/webhooks", "articles/test-sources", "articles/test-executions", + "articles/templates", ], }, { diff --git a/go.mod b/go.mod index 13621e15bda..81c6691928e 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/joshdk/go-junit v1.0.0 github.com/kelseyhightower/envconfig v1.4.0 - github.com/kubeshop/testkube-operator v1.10.8-0.20230824144544-8fda6043174c + github.com/kubeshop/testkube-operator v1.10.8-0.20230905165843-5c2e3838fed8 github.com/minio/minio-go/v7 v7.0.47 github.com/montanaflynn/stats v0.6.6 github.com/moogar0880/problems v0.1.1 diff --git a/go.sum b/go.sum index fdbf611d206..c18a72d21d2 100644 --- a/go.sum +++ b/go.sum @@ -393,8 +393,8 @@ 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= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kubeshop/testkube-operator v1.10.8-0.20230824144544-8fda6043174c h1:/b4Jo/tWoc7EqutFMWpVwoWel5Y8ToQTf0NyXG00xAQ= -github.com/kubeshop/testkube-operator v1.10.8-0.20230824144544-8fda6043174c/go.mod h1:UmigDOKMVJa6Y/imKafWmhcvfLisOzr5X04kuOsH/B0= +github.com/kubeshop/testkube-operator v1.10.8-0.20230905165843-5c2e3838fed8 h1:GApLO59jzvfuwRxBoTdSHpIdt+OayuhjWuqjK25klJo= +github.com/kubeshop/testkube-operator v1.10.8-0.20230905165843-5c2e3838fed8/go.mod h1:UmigDOKMVJa6Y/imKafWmhcvfLisOzr5X04kuOsH/B0= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= diff --git a/internal/app/api/debug/server.go b/internal/app/api/debug/server.go new file mode 100644 index 00000000000..4012a3a18c8 --- /dev/null +++ b/internal/app/api/debug/server.go @@ -0,0 +1,19 @@ +package debug + +import ( + "net/http" + "net/http/pprof" +) + +func NewDebugServer(addr string) *http.Server { + mux := http.NewServeMux() + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + return &http.Server{ + Addr: addr, + Handler: mux, + } +} diff --git a/internal/app/api/v1/executions_test.go b/internal/app/api/v1/executions_test.go index 53fac2e1b33..6bd91c58a07 100644 --- a/internal/app/api/v1/executions_test.go +++ b/internal/app/api/v1/executions_test.go @@ -245,9 +245,10 @@ func getMockExecutorClient() *executorsclientv1.ExecutorsClient { Namespace: "default", }, Spec: executorv1.ExecutorSpec{ - Types: []string{"curl/test"}, - ExecutorType: "", - JobTemplate: "", + Types: []string{"curl/test"}, + ExecutorType: "", + JobTemplate: "", + JobTemplateReference: "", }, Status: executorv1.ExecutorStatus{}, }, diff --git a/internal/app/api/v1/secret.go b/internal/app/api/v1/secret.go new file mode 100644 index 00000000000..9955ea572e4 --- /dev/null +++ b/internal/app/api/v1/secret.go @@ -0,0 +1,34 @@ +package v1 + +import ( + "fmt" + "net/http" + + "github.com/gofiber/fiber/v2" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" +) + +// ListSecretsHandler list secrets and keys +func (s TestkubeAPI) ListSecretsHandler() fiber.Handler { + return func(c *fiber.Ctx) error { + errPrefix := "failed to list secrets" + + list, err := s.SecretClient.List(true) + if err != nil { + return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not list secrets: %s", errPrefix, err)) + } + + results := make([]testkube.Secret, 0) + for name, values := range list { + keys := make([]string, 0) + for value := range values { + keys = append(keys, value) + } + + results = append(results, testkube.Secret{Name: name, Keys: keys}) + } + + return c.JSON(results) + } +} diff --git a/internal/app/api/v1/server.go b/internal/app/api/v1/server.go index c5767902be7..12ec6bb72c2 100644 --- a/internal/app/api/v1/server.go +++ b/internal/app/api/v1/server.go @@ -30,12 +30,14 @@ import ( "github.com/kelseyhightower/envconfig" executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors/v1" + templatesclientv1 "github.com/kubeshop/testkube-operator/client/templates/v1" testsclientv3 "github.com/kubeshop/testkube-operator/client/tests/v3" testsourcesclientv1 "github.com/kubeshop/testkube-operator/client/testsources/v1" testsuitesclientv3 "github.com/kubeshop/testkube-operator/client/testsuites/v3" testkubeclientset "github.com/kubeshop/testkube-operator/pkg/clientset/versioned" "github.com/kubeshop/testkube/internal/app/api/metrics" "github.com/kubeshop/testkube/pkg/event" + "github.com/kubeshop/testkube/pkg/event/bus" "github.com/kubeshop/testkube/pkg/event/kind/cdevent" "github.com/kubeshop/testkube/pkg/event/kind/slack" "github.com/kubeshop/testkube/pkg/event/kind/webhook" @@ -79,10 +81,12 @@ func NewTestkubeAPI( storage storage.Client, graphqlPort string, artifactsStorage storage.ArtifactsStorage, + templatesClient *templatesclientv1.TemplatesClient, cdeventsTarget string, dashboardURI string, helmchartVersion string, mode string, + eventsBus bus.Bus, ) TestkubeAPI { var httpConfig server.Config @@ -122,14 +126,16 @@ func NewTestkubeAPI( Storage: storage, graphqlPort: graphqlPort, artifactsStorage: artifactsStorage, + TemplatesClient: templatesClient, helmchartVersion: helmchartVersion, mode: mode, + eventsBus: eventsBus, } // will be reused in websockets handler s.WebsocketLoader = ws.NewWebsocketLoader() - s.Events.Loader.Register(webhook.NewWebhookLoader(webhookClient)) + s.Events.Loader.Register(webhook.NewWebhookLoader(s.Log, webhookClient, templatesClient)) s.Events.Loader.Register(s.WebsocketLoader) s.Events.Loader.Register(s.slackLoader) @@ -175,8 +181,10 @@ type TestkubeAPI struct { slackLoader *slack.SlackLoader graphqlPort string artifactsStorage storage.ArtifactsStorage + TemplatesClient *templatesclientv1.TemplatesClient helmchartVersion string mode string + eventsBus bus.Bus } type storageParams struct { @@ -345,6 +353,15 @@ func (s *TestkubeAPI) InitRoutes() { testsources.Delete("/:name", s.DeleteTestSourceHandler()) testsources.Delete("/", s.DeleteTestSourcesHandler()) + templates := s.Routes.Group("/templates") + + templates.Post("/", s.CreateTemplateHandler()) + templates.Patch("/:name", s.UpdateTemplateHandler()) + templates.Get("/", s.ListTemplatesHandler()) + templates.Get("/:name", s.GetTemplateHandler()) + templates.Delete("/:name", s.DeleteTemplateHandler()) + templates.Delete("/", s.DeleteTemplatesHandler()) + labels := s.Routes.Group("/labels") labels.Get("/", s.ListLabelsHandler()) @@ -368,6 +385,9 @@ func (s *TestkubeAPI) InitRoutes() { repositories := s.Routes.Group("/repositories") repositories.Post("/", s.ValidateRepositoryHandler()) + secrets := s.Routes.Group("/secrets") + secrets.Get("/", s.ListSecretsHandler()) + // mount everything on results // TODO it should be named /api/ + dashboard refactor s.Mux.Mount("/results", s.Mux) diff --git a/internal/app/api/v1/template.go b/internal/app/api/v1/template.go new file mode 100644 index 00000000000..1f0b7d43399 --- /dev/null +++ b/internal/app/api/v1/template.go @@ -0,0 +1,192 @@ +package v1 + +import ( + "bytes" + "fmt" + "net/http" + + "github.com/gofiber/fiber/v2" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/yaml" + + templatev1 "github.com/kubeshop/testkube-operator/apis/template/v1" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/crd" + templatesmapper "github.com/kubeshop/testkube/pkg/mapper/templates" +) + +func (s TestkubeAPI) CreateTemplateHandler() fiber.Handler { + return func(c *fiber.Ctx) error { + errPrefix := "failed to create template" + var template templatev1.Template + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + templateSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(templateSpec), len(templateSpec)) + if err := decoder.Decode(&template); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } + } else { + var request testkube.TemplateCreateRequest + err := c.BodyParser(&request) + if err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse json request: %w", errPrefix, err)) + } + + if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + if request.Body != "" { + request.Body = fmt.Sprintf("%q", request.Body) + } + + data, err := crd.GenerateYAML(crd.TemplateTemplate, []testkube.TemplateCreateRequest{request}) + return s.getCRDs(c, data, err) + } + + template = templatesmapper.MapAPIToCRD(request) + template.Namespace = s.Namespace + } + + created, err := s.TemplatesClient.Create(&template) + if err != nil { + return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not create template: %w", errPrefix, err)) + } + + c.Status(http.StatusCreated) + return c.JSON(created) + } +} + +func (s TestkubeAPI) UpdateTemplateHandler() fiber.Handler { + return func(c *fiber.Ctx) error { + errPrefix := "failed to update template" + var request testkube.TemplateUpdateRequest + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + var template templatev1.Template + templateSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(templateSpec), len(templateSpec)) + if err := decoder.Decode(&template); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } + + request = templatesmapper.MapSpecToUpdate(&template) + } else { + err := c.BodyParser(&request) + if err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse json request: %w", errPrefix, err)) + } + } + + var name string + if request.Name != nil { + name = *request.Name + } + errPrefix = errPrefix + " " + name + // we need to get resource first and load its metadata.ResourceVersion + template, err := s.TemplatesClient.Get(name) + if err != nil { + if errors.IsNotFound(err) { + return s.Error(c, http.StatusNotFound, fmt.Errorf("%s: client found no template: %w", errPrefix, err)) + } + + return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not get template: %w", errPrefix, err)) + } + + // map update template but load spec only to not override metadata.ResourceVersion + templateSpec := templatesmapper.MapUpdateToSpec(request, template) + + updatedTemplate, err := s.TemplatesClient.Update(templateSpec) + if err != nil { + return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not update template: %w", errPrefix, err)) + } + + return c.JSON(updatedTemplate) + } +} + +func (s TestkubeAPI) ListTemplatesHandler() fiber.Handler { + return func(c *fiber.Ctx) error { + errPrefix := "failed to list templates" + + list, err := s.TemplatesClient.List(c.Query("selector")) + if err != nil { + return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not list templates: %w", errPrefix, err)) + } + + results := []testkube.Template{} + for _, item := range list.Items { + results = append(results, templatesmapper.MapCRDToAPI(item)) + + } + + if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + for i := range results { + if results[i].Body != "" { + results[i].Body = fmt.Sprintf("%q", results[i].Body) + } + } + + data, err := crd.GenerateYAML(crd.TemplateTemplate, results) + return s.getCRDs(c, data, err) + } + + return c.JSON(results) + } +} + +func (s TestkubeAPI) GetTemplateHandler() fiber.Handler { + return func(c *fiber.Ctx) error { + name := c.Params("name") + errPrefix := fmt.Sprintf("failed to get template %s", name) + + item, err := s.TemplatesClient.Get(name) + if err != nil { + if errors.IsNotFound(err) { + return s.Error(c, http.StatusNotFound, fmt.Errorf("%s: template not found: %w", errPrefix, err)) + } + return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not get template: %w", errPrefix, err)) + } + + result := templatesmapper.MapCRDToAPI(*item) + if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + if result.Body != "" { + result.Body = fmt.Sprintf("%q", result.Body) + } + + data, err := crd.GenerateYAML(crd.TemplateTemplate, []testkube.Template{result}) + return s.getCRDs(c, data, err) + } + + return c.JSON(result) + } +} + +func (s TestkubeAPI) DeleteTemplateHandler() fiber.Handler { + return func(c *fiber.Ctx) error { + name := c.Params("name") + errPrefix := fmt.Sprintf("failed to delete template %s", name) + + err := s.TemplatesClient.Delete(name) + if err != nil { + if errors.IsNotFound(err) { + return s.Error(c, http.StatusNotFound, fmt.Errorf("%s: template not found: %w", errPrefix, err)) + } + return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not delete template: %w", errPrefix, err)) + } + + c.Status(http.StatusNoContent) + return nil + } +} + +func (s TestkubeAPI) DeleteTemplatesHandler() fiber.Handler { + return func(c *fiber.Ctx) error { + errPrefix := "failed to delete templates" + + err := s.TemplatesClient.DeleteByLabels(c.Query("selector")) + if err != nil { + return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not delete templates: %w", errPrefix, err)) + } + + c.Status(http.StatusNoContent) + return nil + } +} diff --git a/internal/app/api/v1/testsuites.go b/internal/app/api/v1/testsuites.go index 88cfbadb8a4..3b547c5175e 100644 --- a/internal/app/api/v1/testsuites.go +++ b/internal/app/api/v1/testsuites.go @@ -19,6 +19,7 @@ import ( "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/crd" "github.com/kubeshop/testkube/pkg/datefilter" + "github.com/kubeshop/testkube/pkg/event/bus" testsmapper "github.com/kubeshop/testkube/pkg/mapper/tests" testsuiteexecutionsmapper "github.com/kubeshop/testkube/pkg/mapper/testsuiteexecutions" testsuitesmapper "github.com/kubeshop/testkube/pkg/mapper/testsuites" @@ -803,11 +804,14 @@ func (s TestkubeAPI) AbortTestSuiteHandler() fiber.Handler { for _, execution := range executions { execution.Status = testkube.TestSuiteExecutionStatusAborting - err = s.TestExecutionResults.Update(c.Context(), execution) + s.Log.Infow("aborting test suite execution", "executionID", execution.Id) + err := s.eventsBus.PublishTopic(bus.InternalPublishTopic, testkube.NewEventEndTestSuiteAborted(&execution)) if err != nil { - return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: could not update test suite execution: %w", errPrefix, err)) + return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: could not sent test suite abortion event: %w", errPrefix, err)) } + + s.Log.Infow("test suite execution aborted, event sent", "executionID", c.Params("executionID")) } return c.Status(http.StatusNoContent).SendString("") @@ -828,11 +832,13 @@ func (s TestkubeAPI) AbortTestSuiteExecutionHandler() fiber.Handler { } execution.Status = testkube.TestSuiteExecutionStatusAborting - err = s.TestExecutionResults.Update(c.Context(), execution) + + err = s.eventsBus.PublishTopic(bus.InternalPublishTopic, testkube.NewEventEndTestSuiteAborted(&execution)) if err != nil { - return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: could not update test suite execution: %w", errPrefix, err)) + return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: could not sent test suite abortion event: %w", errPrefix, err)) } + s.Log.Infow("test suite execution aborted, event sent", "executionID", c.Params("executionID")) return c.Status(http.StatusNoContent).SendString("") } diff --git a/internal/config/config.go b/internal/config/config.go index 7f5c32fafd4..a473febc533 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -64,6 +64,8 @@ type Config struct { TestkubeClusterName string `envconfig:"TESTKUBE_CLUSTER_NAME" default:""` CompressArtifacts bool `envconfig:"COMPRESSARTIFACTS" default:"false"` TestkubeHelmchartVersion string `envconfig:"TESTKUBE_HELMCHART_VERSION" default:""` + DebugListenAddr string `envconfig:"DEBUG_LISTEN_ADDR" default:"0.0.0.0:1337"` + EnableDebugServer bool `envconfig:"ENABLE_DEBUG_SERVER" default:"false"` } func Get() (*Config, error) { diff --git a/internal/graphql/resolvers/executors_resolver_test.go b/internal/graphql/resolvers/executors_resolver_test.go index 48888f0e112..65b252657fc 100644 --- a/internal/graphql/resolvers/executors_resolver_test.go +++ b/internal/graphql/resolvers/executors_resolver_test.go @@ -15,18 +15,19 @@ var ( sample = testkube.ExecutorDetails{ Name: "sample", Executor: &testkube.Executor{ - ExecutorType: "job", - Image: "", - ImagePullSecrets: nil, - Command: nil, - Args: nil, - Types: []string{"curl/test"}, - Uri: "", - ContentTypes: nil, - JobTemplate: "", - Labels: map[string]string{"label-name": "label-value"}, - Features: nil, - Meta: nil, + ExecutorType: "job", + Image: "", + ImagePullSecrets: nil, + Command: nil, + Args: nil, + Types: []string{"curl/test"}, + Uri: "", + ContentTypes: nil, + JobTemplate: "", + JobTemplateReference: "", + Labels: map[string]string{"label-name": "label-value"}, + Features: nil, + Meta: nil, }, } ) diff --git a/internal/graphql/services/executors_test.go b/internal/graphql/services/executors_test.go index 58a04aa9312..45cad1eed6a 100644 --- a/internal/graphql/services/executors_test.go +++ b/internal/graphql/services/executors_test.go @@ -31,9 +31,10 @@ var ( }, }, Spec: executorv1.ExecutorSpec{ - Types: []string{"curl/test"}, - ExecutorType: "job", - JobTemplate: "", + Types: []string{"curl/test"}, + ExecutorType: "job", + JobTemplate: "", + JobTemplateReference: "", }, Status: executorv1.ExecutorStatus{}, }, @@ -41,18 +42,19 @@ var ( sample = testkube.ExecutorDetails{ Name: "sample", Executor: &testkube.Executor{ - ExecutorType: "job", - Image: "", - ImagePullSecrets: nil, - Command: nil, - Args: nil, - Types: []string{"curl/test"}, - Uri: "", - ContentTypes: nil, - JobTemplate: "", - Labels: map[string]string{"label-name": "label-value"}, - Features: nil, - Meta: nil, + ExecutorType: "job", + Image: "", + ImagePullSecrets: nil, + Command: nil, + Args: nil, + Types: []string{"curl/test"}, + Uri: "", + ContentTypes: nil, + JobTemplate: "", + JobTemplateReference: "", + Labels: map[string]string{"label-name": "label-value"}, + Features: nil, + Meta: nil, }, } k8sObjects2 = []k8sclient.Object{ @@ -69,9 +71,10 @@ var ( }, }, Spec: executorv1.ExecutorSpec{ - Types: []string{"other/test"}, - ExecutorType: "job", - JobTemplate: "", + Types: []string{"other/test"}, + ExecutorType: "job", + JobTemplate: "", + JobTemplateReference: "", }, Status: executorv1.ExecutorStatus{}, }, @@ -79,18 +82,19 @@ var ( sample2 = testkube.ExecutorDetails{ Name: "sample", Executor: &testkube.Executor{ - ExecutorType: "job", - Image: "", - ImagePullSecrets: nil, - Command: nil, - Args: nil, - Types: []string{"other/test"}, - Uri: "", - ContentTypes: nil, - JobTemplate: "", - Labels: map[string]string{"label-name": "label-value"}, - Features: nil, - Meta: nil, + ExecutorType: "job", + Image: "", + ImagePullSecrets: nil, + Command: nil, + Args: nil, + Types: []string{"other/test"}, + Uri: "", + ContentTypes: nil, + JobTemplate: "", + JobTemplateReference: "", + Labels: map[string]string{"label-name": "label-value"}, + Features: nil, + Meta: nil, }, } ) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 0255d1f1621..049315f9b69 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -28,8 +28,14 @@ import ( const ( apiKeyMeta = "api-key" clusterIDMeta = "cluster-id" - cloudMigrate = "migrate" + cloudMigrateMeta = "migrate" + orgIdMeta = "environment-id" + envIdMeta = "organization-id" healthcheckCommand = "healthcheck" + + cloudMigrateEnvName = "TESTKUBE_CLOUD_MIGRATE" + cloudEnvIdEnvName = "TESTKUBE_CLOUD_ENV_ID" + cloudOrgIdEnvName = "TESTKUBE_CLOUD_ORG_ID" ) // buffer up to five messages per worker @@ -208,7 +214,9 @@ func (ag *Agent) runCommandLoop(ctx context.Context) error { ctx = AddAPIKeyMeta(ctx, ag.apiKey) ctx = metadata.AppendToOutgoingContext(ctx, clusterIDMeta, ag.clusterID) - ctx = metadata.AppendToOutgoingContext(ctx, cloudMigrate, os.Getenv("TESTKUBE_CLOUD_MIGRATE")) + ctx = metadata.AppendToOutgoingContext(ctx, cloudMigrateMeta, os.Getenv(cloudMigrateEnvName)) + ctx = metadata.AppendToOutgoingContext(ctx, envIdMeta, os.Getenv(cloudEnvIdEnvName)) + ctx = metadata.AppendToOutgoingContext(ctx, orgIdMeta, os.Getenv(cloudOrgIdEnvName)) ag.logger.Infow("initiating streaming connection with Cloud API") // creates a new Stream from the client side. ctx is used for the lifetime of the stream. diff --git a/pkg/api/v1/client/api.go b/pkg/api/v1/client/api.go index 7969bc80dcf..b89ba8860b6 100644 --- a/pkg/api/v1/client/api.go +++ b/pkg/api/v1/client/api.go @@ -37,6 +37,7 @@ func NewProxyAPIClient(client kubernetes.Interface, config APIConfig) APIClient ConfigClient: NewConfigClient(NewProxyClient[testkube.Config](client, config)), TestSourceClient: NewTestSourceClient(NewProxyClient[testkube.TestSource](client, config)), CopyFileClient: NewCopyFileProxyClient(client, config), + TemplateClient: NewTemplateClient(NewProxyClient[testkube.Template](client, config)), } } @@ -66,6 +67,7 @@ func NewDirectAPIClient(httpClient *http.Client, sseClient *http.Client, apiURI, ConfigClient: NewConfigClient(NewDirectClient[testkube.Config](httpClient, apiURI, apiPathPrefix)), TestSourceClient: NewTestSourceClient(NewDirectClient[testkube.TestSource](httpClient, apiURI, apiPathPrefix)), CopyFileClient: NewCopyFileDirectClient(httpClient, apiURI, apiPathPrefix), + TemplateClient: NewTemplateClient(NewDirectClient[testkube.Template](httpClient, apiURI, apiPathPrefix)), } } @@ -78,4 +80,5 @@ type APIClient struct { ConfigClient TestSourceClient CopyFileClient + TemplateClient } diff --git a/pkg/api/v1/client/interface.go b/pkg/api/v1/client/interface.go index 3ca1ba414ba..bca67b3f5c6 100644 --- a/pkg/api/v1/client/interface.go +++ b/pkg/api/v1/client/interface.go @@ -19,6 +19,7 @@ type Client interface { ConfigAPI TestSourceAPI CopyFileAPI + TemplateAPI } // TestAPI describes test api methods @@ -91,6 +92,16 @@ type WebhookAPI interface { DeleteWebhooks(selector string) (err error) } +// TemplateAPI describes template api methods +type TemplateAPI interface { + CreateTemplate(options CreateTemplateOptions) (template testkube.Template, err error) + UpdateTemplate(options UpdateTemplateOptions) (template testkube.Template, err error) + GetTemplate(name string) (template testkube.Template, err error) + ListTemplates(selector string) (templates testkube.Templates, err error) + DeleteTemplate(name string) (err error) + DeleteTemplates(selector string) (err error) +} + // ConfigAPI describes config api methods type ConfigAPI interface { UpdateConfig(config testkube.Config) (outputConfig testkube.Config, err error) @@ -154,6 +165,12 @@ type UpsertTestSourceOptions testkube.TestSourceUpsertRequest // if needed can be extended to custom struct type UpdateTestSourceOptions testkube.TestSourceUpdateRequest +// CreateTemplateOptions - is mapping for now to OpenAPI schema for creating/changing template +type CreateTemplateOptions testkube.TemplateCreateRequest + +// UpdateTemplateOptions - is mapping for now to OpenAPI schema for changing template request +type UpdateTemplateOptions testkube.TemplateUpdateRequest + // TODO consider replacing it with testkube.ExecutionRequest - looks almost the samea and redundant // ExecuteTestOptions contains test run options type ExecuteTestOptions struct { @@ -173,10 +190,14 @@ type ExecuteTestOptions struct { BucketName string ArtifactRequest *testkube.ArtifactRequest JobTemplate string + JobTemplateReference string ContentRequest *testkube.TestContentRequest PreRunScriptContent string PostRunScriptContent string ScraperTemplate string + ScraperTemplateReference string + PvcTemplate string + PvcTemplateReference string NegativeTest bool IsNegativeTestChangedOnRun bool EnvConfigMaps []testkube.EnvReference @@ -186,13 +207,19 @@ type ExecuteTestOptions struct { // ExecuteTestSuiteOptions contains test suite run options type ExecuteTestSuiteOptions struct { - ExecutionVariables map[string]testkube.Variable - HTTPProxy string - HTTPSProxy string - ExecutionLabels map[string]string - ContentRequest *testkube.TestContentRequest - RunningContext *testkube.RunningContext - ConcurrencyLevel int32 + ExecutionVariables map[string]testkube.Variable + HTTPProxy string + HTTPSProxy string + ExecutionLabels map[string]string + ContentRequest *testkube.TestContentRequest + RunningContext *testkube.RunningContext + ConcurrencyLevel int32 + JobTemplate string + JobTemplateReference string + ScraperTemplate string + ScraperTemplateReference string + PvcTemplate string + PvcTemplateReference string } // Gettable is an interface of gettable objects @@ -200,7 +227,7 @@ type Gettable interface { testkube.Test | testkube.TestSuite | testkube.ExecutorDetails | testkube.Webhook | testkube.TestWithExecution | testkube.TestSuiteWithExecution | testkube.TestWithExecutionSummary | testkube.TestSuiteWithExecutionSummary | testkube.Artifact | testkube.ServerInfo | testkube.Config | testkube.DebugInfo | - testkube.TestSource + testkube.TestSource | testkube.Template } // Executable is an interface of executable objects diff --git a/pkg/api/v1/client/template.go b/pkg/api/v1/client/template.go new file mode 100644 index 00000000000..aec2f7a3b6c --- /dev/null +++ b/pkg/api/v1/client/template.go @@ -0,0 +1,74 @@ +package client + +import ( + "encoding/json" + "net/http" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" +) + +// NewTemplateClient creates new Template client +func NewTemplateClient(templateTransport Transport[testkube.Template]) TemplateClient { + return TemplateClient{ + templateTransport: templateTransport, + } +} + +// TemplateClient is a client for templates +type TemplateClient struct { + templateTransport Transport[testkube.Template] +} + +// GetTemplate gets template by name +func (c TemplateClient) GetTemplate(name string) (template testkube.Template, err error) { + uri := c.templateTransport.GetURI("/templates/%s", name) + return c.templateTransport.Execute(http.MethodGet, uri, nil, nil) +} + +// ListTemplates list all templates +func (c TemplateClient) ListTemplates(selector string) (templates testkube.Templates, err error) { + uri := c.templateTransport.GetURI("/templates") + params := map[string]string{ + "selector": selector, + } + + return c.templateTransport.ExecuteMultiple(http.MethodGet, uri, nil, params) +} + +// CreateTemplate creates new Template Custom Resource +func (c TemplateClient) CreateTemplate(options CreateTemplateOptions) (template testkube.Template, err error) { + uri := c.templateTransport.GetURI("/templates") + request := testkube.TemplateCreateRequest(options) + + body, err := json.Marshal(request) + if err != nil { + return template, err + } + + return c.templateTransport.Execute(http.MethodPost, uri, body, nil) +} + +// UpdateTemplate updates Template Custom Resource +func (c TemplateClient) UpdateTemplate(options UpdateTemplateOptions) (template testkube.Template, err error) { + uri := c.templateTransport.GetURI("/templates/%s", options.Name) + request := testkube.TemplateUpdateRequest(options) + + body, err := json.Marshal(request) + if err != nil { + return template, err + } + + return c.templateTransport.Execute(http.MethodPatch, uri, body, nil) +} + +// DeleteTemplates deletes all templates +func (c TemplateClient) DeleteTemplates(selector string) (err error) { + uri := c.templateTransport.GetURI("/templates") + return c.templateTransport.Delete(uri, selector, true) +} + +// DeleteTemplate deletes single template by name +func (c TemplateClient) DeleteTemplate(name string) (err error) { + uri := c.templateTransport.GetURI("/templates/%s", name) + return c.templateTransport.Delete(uri, "", true) +} diff --git a/pkg/api/v1/client/test.go b/pkg/api/v1/client/test.go index 717c0207810..cb6bd8f7644 100644 --- a/pkg/api/v1/client/test.go +++ b/pkg/api/v1/client/test.go @@ -148,10 +148,14 @@ func (c TestClient) ExecuteTest(id, executionName string, options ExecuteTestOpt BucketName: options.BucketName, ArtifactRequest: options.ArtifactRequest, JobTemplate: options.JobTemplate, + JobTemplateReference: options.JobTemplateReference, ContentRequest: options.ContentRequest, PreRunScript: options.PreRunScriptContent, PostRunScript: options.PostRunScriptContent, ScraperTemplate: options.ScraperTemplate, + ScraperTemplateReference: options.ScraperTemplateReference, + PvcTemplate: options.PvcTemplate, + PvcTemplateReference: options.PvcTemplateReference, NegativeTest: options.NegativeTest, IsNegativeTestChangedOnRun: options.IsNegativeTestChangedOnRun, EnvConfigMaps: options.EnvConfigMaps, @@ -186,10 +190,14 @@ func (c TestClient) ExecuteTests(selector string, concurrencyLevel int, options BucketName: options.BucketName, ArtifactRequest: options.ArtifactRequest, JobTemplate: options.JobTemplate, + JobTemplateReference: options.JobTemplateReference, ContentRequest: options.ContentRequest, PreRunScript: options.PreRunScriptContent, PostRunScript: options.PostRunScriptContent, ScraperTemplate: options.ScraperTemplate, + ScraperTemplateReference: options.ScraperTemplateReference, + PvcTemplate: options.PvcTemplate, + PvcTemplateReference: options.PvcTemplateReference, NegativeTest: options.NegativeTest, IsNegativeTestChangedOnRun: options.IsNegativeTestChangedOnRun, RunningContext: options.RunningContext, diff --git a/pkg/api/v1/client/testsuite.go b/pkg/api/v1/client/testsuite.go index e3ecd8b3b60..797de0c2ad4 100644 --- a/pkg/api/v1/client/testsuite.go +++ b/pkg/api/v1/client/testsuite.go @@ -143,14 +143,20 @@ func (c TestSuiteClient) GetTestSuiteExecutionArtifacts(executionID string) (art func (c TestSuiteClient) ExecuteTestSuite(id, executionName string, options ExecuteTestSuiteOptions) (execution testkube.TestSuiteExecution, err error) { uri := c.testSuiteExecutionTransport.GetURI("/test-suites/%s/executions", id) executionRequest := testkube.TestSuiteExecutionRequest{ - Name: executionName, - Variables: options.ExecutionVariables, - HttpProxy: options.HTTPProxy, - HttpsProxy: options.HTTPSProxy, - ExecutionLabels: options.ExecutionLabels, - ContentRequest: options.ContentRequest, - RunningContext: options.RunningContext, - ConcurrencyLevel: options.ConcurrencyLevel, + Name: executionName, + Variables: options.ExecutionVariables, + HttpProxy: options.HTTPProxy, + HttpsProxy: options.HTTPSProxy, + ExecutionLabels: options.ExecutionLabels, + ContentRequest: options.ContentRequest, + RunningContext: options.RunningContext, + ConcurrencyLevel: options.ConcurrencyLevel, + JobTemplate: options.JobTemplate, + JobTemplateReference: options.JobTemplateReference, + ScraperTemplate: options.ScraperTemplate, + ScraperTemplateReference: options.ScraperTemplateReference, + PvcTemplate: options.PvcTemplate, + PvcTemplateReference: options.PvcTemplateReference, } body, err := json.Marshal(executionRequest) @@ -166,13 +172,19 @@ func (c TestSuiteClient) ExecuteTestSuite(id, executionName string, options Exec func (c TestSuiteClient) ExecuteTestSuites(selector string, concurrencyLevel int, options ExecuteTestSuiteOptions) (executions []testkube.TestSuiteExecution, err error) { uri := c.testSuiteExecutionTransport.GetURI("/test-suite-executions") executionRequest := testkube.TestSuiteExecutionRequest{ - Variables: options.ExecutionVariables, - HttpProxy: options.HTTPProxy, - HttpsProxy: options.HTTPSProxy, - ExecutionLabels: options.ExecutionLabels, - ContentRequest: options.ContentRequest, - RunningContext: options.RunningContext, - ConcurrencyLevel: options.ConcurrencyLevel, + Variables: options.ExecutionVariables, + HttpProxy: options.HTTPProxy, + HttpsProxy: options.HTTPSProxy, + ExecutionLabels: options.ExecutionLabels, + ContentRequest: options.ContentRequest, + RunningContext: options.RunningContext, + ConcurrencyLevel: options.ConcurrencyLevel, + JobTemplate: options.JobTemplate, + JobTemplateReference: options.JobTemplateReference, + ScraperTemplate: options.ScraperTemplate, + ScraperTemplateReference: options.ScraperTemplateReference, + PvcTemplate: options.PvcTemplate, + PvcTemplateReference: options.PvcTemplateReference, } body, err := json.Marshal(executionRequest) diff --git a/pkg/api/v1/testkube/model_execution_request.go b/pkg/api/v1/testkube/model_execution_request.go index 42f90c64806..962cc091c3f 100644 --- a/pkg/api/v1/testkube/model_execution_request.go +++ b/pkg/api/v1/testkube/model_execution_request.go @@ -67,15 +67,25 @@ type ExecutionRequest struct { ArtifactRequest *ArtifactRequest `json:"artifactRequest,omitempty"` // job template extensions JobTemplate string `json:"jobTemplate,omitempty"` + // name of the template resource + JobTemplateReference string `json:"jobTemplateReference,omitempty"` // cron job template extensions - CronJobTemplate string `json:"cronJobTemplate,omitempty"` - ContentRequest *TestContentRequest `json:"contentRequest,omitempty"` + CronJobTemplate string `json:"cronJobTemplate,omitempty"` + // name of the template resource + CronJobTemplateReference string `json:"cronJobTemplateReference,omitempty"` + ContentRequest *TestContentRequest `json:"contentRequest,omitempty"` // script to run before test execution PreRunScript string `json:"preRunScript,omitempty"` // script to run after test execution PostRunScript string `json:"postRunScript,omitempty"` // scraper template extensions ScraperTemplate string `json:"scraperTemplate,omitempty"` + // name of the template resource + ScraperTemplateReference string `json:"scraperTemplateReference,omitempty"` + // pvc template extensions + PvcTemplate string `json:"pvcTemplate,omitempty"` + // name of the template resource + PvcTemplateReference string `json:"pvcTemplateReference,omitempty"` // config map references EnvConfigMaps []EnvReference `json:"envConfigMaps,omitempty"` // secret references diff --git a/pkg/api/v1/testkube/model_execution_update_request.go b/pkg/api/v1/testkube/model_execution_update_request.go index be509827387..27b8401dad8 100644 --- a/pkg/api/v1/testkube/model_execution_update_request.go +++ b/pkg/api/v1/testkube/model_execution_update_request.go @@ -67,15 +67,25 @@ type ExecutionUpdateRequest struct { ArtifactRequest **ArtifactUpdateRequest `json:"artifactRequest,omitempty"` // job template extensions JobTemplate *string `json:"jobTemplate,omitempty"` + // name of the template resource + JobTemplateReference *string `json:"jobTemplateReference,omitempty"` // cron job template extensions - CronJobTemplate *string `json:"cronJobTemplate,omitempty"` - ContentRequest **TestContentUpdateRequest `json:"contentRequest,omitempty"` + CronJobTemplate *string `json:"cronJobTemplate,omitempty"` + // name of the template resource + CronJobTemplateReference *string `json:"cronJobTemplateReference,omitempty"` + ContentRequest **TestContentUpdateRequest `json:"contentRequest,omitempty"` // script to run before test execution PreRunScript *string `json:"preRunScript,omitempty"` // script to run after test execution PostRunScript *string `json:"postRunScript,omitempty"` // scraper template extensions ScraperTemplate *string `json:"scraperTemplate,omitempty"` + // name of the template resource + ScraperTemplateReference *string `json:"scraperTemplateReference,omitempty"` + // pvc template extensions + PvcTemplate *string `json:"pvcTemplate,omitempty"` + // name of the template resource + PvcTemplateReference *string `json:"pvcTemplateReference,omitempty"` // config *map references EnvConfigMaps *[]EnvReference `json:"envConfigMaps,omitempty"` // secret references diff --git a/pkg/api/v1/testkube/model_executor.go b/pkg/api/v1/testkube/model_executor.go index a71b3c00c7f..1b2a1ac4c06 100644 --- a/pkg/api/v1/testkube/model_executor.go +++ b/pkg/api/v1/testkube/model_executor.go @@ -29,6 +29,8 @@ type Executor struct { ContentTypes []string `json:"contentTypes,omitempty"` // Job template to launch executor JobTemplate string `json:"jobTemplate,omitempty"` + // name of the template resource + JobTemplateReference string `json:"jobTemplateReference,omitempty"` // executor labels Labels map[string]string `json:"labels,omitempty"` // Available executor features diff --git a/pkg/api/v1/testkube/model_executor_update_request.go b/pkg/api/v1/testkube/model_executor_update_request.go index 589078bcc08..8223d7aca81 100644 --- a/pkg/api/v1/testkube/model_executor_update_request.go +++ b/pkg/api/v1/testkube/model_executor_update_request.go @@ -33,6 +33,8 @@ type ExecutorUpdateRequest struct { ContentTypes *[]string `json:"contentTypes,omitempty"` // Job template to launch executor JobTemplate *string `json:"jobTemplate,omitempty"` + // name of the template resource + JobTemplateReference *string `json:"jobTemplateReference,omitempty"` // executor labels Labels *map[string]string `json:"labels,omitempty"` // Available executor features diff --git a/pkg/api/v1/testkube/model_executor_upsert_request.go b/pkg/api/v1/testkube/model_executor_upsert_request.go index 8b8fdd6b764..30c14c2801f 100644 --- a/pkg/api/v1/testkube/model_executor_upsert_request.go +++ b/pkg/api/v1/testkube/model_executor_upsert_request.go @@ -33,6 +33,8 @@ type ExecutorUpsertRequest struct { ContentTypes []string `json:"contentTypes,omitempty"` // Job template to launch executor JobTemplate string `json:"jobTemplate,omitempty"` + // name of the template resource + JobTemplateReference string `json:"jobTemplateReference,omitempty"` // executor labels Labels map[string]string `json:"labels,omitempty"` // Available executor features diff --git a/pkg/api/v1/testkube/model_secret.go b/pkg/api/v1/testkube/model_secret.go new file mode 100644 index 00000000000..3c735eb96e6 --- /dev/null +++ b/pkg/api/v1/testkube/model_secret.go @@ -0,0 +1,18 @@ +/* + * 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 + +// Secret with keys +type Secret struct { + // secret name + Name string `json:"name"` + // secret keys + Keys []string `json:"keys,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_template.go b/pkg/api/v1/testkube/model_template.go new file mode 100644 index 00000000000..7f922b9ab60 --- /dev/null +++ b/pkg/api/v1/testkube/model_template.go @@ -0,0 +1,23 @@ +/* + * 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 + +// Golang based template +type Template struct { + // template name for reference + Name string `json:"name"` + // template namespace + Namespace string `json:"namespace,omitempty"` + Type_ *TemplateType `json:"type"` + // template body to use + Body string `json:"body"` + // template labels + Labels map[string]string `json:"labels,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_template_create_request.go b/pkg/api/v1/testkube/model_template_create_request.go new file mode 100644 index 00000000000..fad9cc071cb --- /dev/null +++ b/pkg/api/v1/testkube/model_template_create_request.go @@ -0,0 +1,23 @@ +/* + * 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 + +// template create request body +type TemplateCreateRequest struct { + // template name for reference + Name string `json:"name"` + // template namespace + Namespace string `json:"namespace,omitempty"` + Type_ *TemplateType `json:"type"` + // template body to use + Body string `json:"body"` + // template labels + Labels map[string]string `json:"labels,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_template_extended.go b/pkg/api/v1/testkube/model_template_extended.go new file mode 100644 index 00000000000..b46eae50289 --- /dev/null +++ b/pkg/api/v1/testkube/model_template_extended.go @@ -0,0 +1,31 @@ +/* + * 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 Templates []Template + +func (list Templates) Table() (header []string, output [][]string) { + header = []string{"Name", "Type", "Labels"} + + for _, e := range list { + templateType := "" + if e.Type_ != nil { + templateType = string(*e.Type_) + } + + output = append(output, []string{ + e.Name, + templateType, + MapToString(e.Labels), + }) + } + + return +} diff --git a/pkg/api/v1/testkube/model_template_type.go b/pkg/api/v1/testkube/model_template_type.go new file mode 100644 index 00000000000..1b715a57edc --- /dev/null +++ b/pkg/api/v1/testkube/model_template_type.go @@ -0,0 +1,23 @@ +/* + * 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 + +// TemplateType : template type by purpose +type TemplateType string + +// List of TemplateType +const ( + JOB_TemplateType TemplateType = "job" + CONTAINER_TemplateType TemplateType = "container" + CRONJOB_TemplateType TemplateType = "cronjob" + SCRAPER_TemplateType TemplateType = "scraper" + PVC_TemplateType TemplateType = "pvc" + WEBHOOK_TemplateType TemplateType = "webhook" +) diff --git a/pkg/api/v1/testkube/model_template_update_request.go b/pkg/api/v1/testkube/model_template_update_request.go new file mode 100644 index 00000000000..77cae63a20b --- /dev/null +++ b/pkg/api/v1/testkube/model_template_update_request.go @@ -0,0 +1,23 @@ +/* + * 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 + +// template update request body +type TemplateUpdateRequest struct { + // template name for reference + Name *string `json:"name"` + // template namespace + Namespace *string `json:"namespace,omitempty"` + Type_ *TemplateType `json:"type"` + // template body to use + Body *string `json:"body"` + // template labels + Labels *map[string]string `json:"labels,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_test_base_extended.go b/pkg/api/v1/testkube/model_test_base_extended.go index 1056540c318..a3209665649 100644 --- a/pkg/api/v1/testkube/model_test_base_extended.go +++ b/pkg/api/v1/testkube/model_test_base_extended.go @@ -84,6 +84,7 @@ func (test *Test) QuoteTestTextFields() { &test.ExecutionRequest.CronJobTemplate, &test.ExecutionRequest.PreRunScript, &test.ExecutionRequest.PostRunScript, + &test.ExecutionRequest.PvcTemplate, &test.ExecutionRequest.ScraperTemplate, } diff --git a/pkg/api/v1/testkube/model_test_suite_base_extended.go b/pkg/api/v1/testkube/model_test_suite_base_extended.go index a7431993af3..298fdc6ac0f 100644 --- a/pkg/api/v1/testkube/model_test_suite_base_extended.go +++ b/pkg/api/v1/testkube/model_test_suite_base_extended.go @@ -66,8 +66,17 @@ func (t *TestSuite) QuoteTestSuiteTextFields() { } } - if t.ExecutionRequest.CronJobTemplate != "" { - t.ExecutionRequest.CronJobTemplate = fmt.Sprintf("%q", t.ExecutionRequest.CronJobTemplate) + var fields = []*string{ + &t.ExecutionRequest.JobTemplate, + &t.ExecutionRequest.CronJobTemplate, + &t.ExecutionRequest.PvcTemplate, + &t.ExecutionRequest.ScraperTemplate, + } + + for _, field := range fields { + if *field != "" { + *field = fmt.Sprintf("%q", *field) + } } } } diff --git a/pkg/api/v1/testkube/model_test_suite_execution_request.go b/pkg/api/v1/testkube/model_test_suite_execution_request.go index fb732540da6..4cbdb6dcdf3 100644 --- a/pkg/api/v1/testkube/model_test_suite_execution_request.go +++ b/pkg/api/v1/testkube/model_test_suite_execution_request.go @@ -34,8 +34,22 @@ type TestSuiteExecutionRequest struct { Timeout int32 `json:"timeout,omitempty"` ContentRequest *TestContentRequest `json:"contentRequest,omitempty"` RunningContext *RunningContext `json:"runningContext,omitempty"` + // job template extensions + JobTemplate string `json:"jobTemplate,omitempty"` + // name of the template resource + JobTemplateReference string `json:"jobTemplateReference,omitempty"` // cron job template extensions CronJobTemplate string `json:"cronJobTemplate,omitempty"` + // name of the template resource + CronJobTemplateReference string `json:"cronJobTemplateReference,omitempty"` + // scraper template extensions + ScraperTemplate string `json:"scraperTemplate,omitempty"` + // name of the template resource + ScraperTemplateReference string `json:"scraperTemplateReference,omitempty"` + // pvc template extensions + PvcTemplate string `json:"pvcTemplate,omitempty"` + // name of the template resource + PvcTemplateReference string `json:"pvcTemplateReference,omitempty"` // number of tests run in parallel ConcurrencyLevel int32 `json:"concurrencyLevel,omitempty"` // test suite execution name started the test suite execution diff --git a/pkg/api/v1/testkube/model_test_suite_execution_update_request.go b/pkg/api/v1/testkube/model_test_suite_execution_update_request.go index 82873ef584e..10a7a8e0796 100644 --- a/pkg/api/v1/testkube/model_test_suite_execution_update_request.go +++ b/pkg/api/v1/testkube/model_test_suite_execution_update_request.go @@ -34,8 +34,22 @@ type TestSuiteExecutionUpdateRequest struct { Timeout *int32 `json:"timeout,omitempty"` ContentRequest **TestContentUpdateRequest `json:"contentRequest,omitempty"` RunningContext *RunningContext `json:"runningContext,omitempty"` + // job template extensions + JobTemplate *string `json:"jobTemplate,omitempty"` + // name of the template resource + JobTemplateReference *string `json:"jobTemplateReference,omitempty"` // cron job template extensions CronJobTemplate *string `json:"cronJobTemplate,omitempty"` + // name of the template resource + CronJobTemplateReference *string `json:"cronJobTemplateReference,omitempty"` + // scraper template extensions + ScraperTemplate *string `json:"scraperTemplate,omitempty"` + // name of the template resource + ScraperTemplateReference *string `json:"scraperTemplateReference,omitempty"` + // pvc template extensions + PvcTemplate *string `json:"pvcTemplate,omitempty"` + // name of the template resource + PvcTemplateReference *string `json:"pvcTemplateReference,omitempty"` // number of tests run in parallel ConcurrencyLevel *int32 `json:"concurrencyLevel,omitempty"` // test suite execution name started the test suite execution diff --git a/pkg/api/v1/testkube/model_test_suite_upsert_request_extended.go b/pkg/api/v1/testkube/model_test_suite_upsert_request_extended.go index 1d54eb5b8df..2dc887ec12c 100644 --- a/pkg/api/v1/testkube/model_test_suite_upsert_request_extended.go +++ b/pkg/api/v1/testkube/model_test_suite_upsert_request_extended.go @@ -21,8 +21,17 @@ func (testSuite *TestSuiteUpsertRequest) QuoteTestSuiteTextFields() { } } - if testSuite.ExecutionRequest.CronJobTemplate != "" { - testSuite.ExecutionRequest.CronJobTemplate = fmt.Sprintf("%q", testSuite.ExecutionRequest.CronJobTemplate) + var fields = []*string{ + &testSuite.ExecutionRequest.JobTemplate, + &testSuite.ExecutionRequest.CronJobTemplate, + &testSuite.ExecutionRequest.ScraperTemplate, + &testSuite.ExecutionRequest.PvcTemplate, + } + + for _, field := range fields { + if *field != "" { + *field = fmt.Sprintf("%q", *field) + } } } } diff --git a/pkg/api/v1/testkube/model_test_upsert_request_extended.go b/pkg/api/v1/testkube/model_test_upsert_request_extended.go index 5e34185ed63..9e72df6e1b3 100644 --- a/pkg/api/v1/testkube/model_test_upsert_request_extended.go +++ b/pkg/api/v1/testkube/model_test_upsert_request_extended.go @@ -27,6 +27,7 @@ func (test *TestUpsertRequest) QuoteTestTextFields() { &test.ExecutionRequest.CronJobTemplate, &test.ExecutionRequest.PreRunScript, &test.ExecutionRequest.PostRunScript, + &test.ExecutionRequest.PvcTemplate, &test.ExecutionRequest.ScraperTemplate, } diff --git a/pkg/api/v1/testkube/model_webhook.go b/pkg/api/v1/testkube/model_webhook.go index 87472bcde21..92685ccc19e 100644 --- a/pkg/api/v1/testkube/model_webhook.go +++ b/pkg/api/v1/testkube/model_webhook.go @@ -21,7 +21,9 @@ type Webhook struct { PayloadObjectField string `json:"payloadObjectField,omitempty"` // golang based template for notification payload PayloadTemplate string `json:"payloadTemplate,omitempty"` - // webhook headers + // name of the template resource + PayloadTemplateReference string `json:"payloadTemplateReference,omitempty"` + // webhook headers (golang template supported) Headers map[string]string `json:"headers,omitempty"` // webhook labels Labels map[string]string `json:"labels,omitempty"` diff --git a/pkg/api/v1/testkube/model_webhook_create_request.go b/pkg/api/v1/testkube/model_webhook_create_request.go index cad406d497d..ed8387ced36 100644 --- a/pkg/api/v1/testkube/model_webhook_create_request.go +++ b/pkg/api/v1/testkube/model_webhook_create_request.go @@ -21,7 +21,9 @@ type WebhookCreateRequest struct { PayloadObjectField string `json:"payloadObjectField,omitempty"` // golang based template for notification payload PayloadTemplate string `json:"payloadTemplate,omitempty"` - // webhook headers + // name of the template resource + PayloadTemplateReference string `json:"payloadTemplateReference,omitempty"` + // webhook headers (golang template supported) Headers map[string]string `json:"headers,omitempty"` // webhook labels Labels map[string]string `json:"labels,omitempty"` diff --git a/pkg/api/v1/testkube/model_webhook_update_request.go b/pkg/api/v1/testkube/model_webhook_update_request.go index dc65e7c4a4f..22e2ec886a7 100644 --- a/pkg/api/v1/testkube/model_webhook_update_request.go +++ b/pkg/api/v1/testkube/model_webhook_update_request.go @@ -21,7 +21,9 @@ type WebhookUpdateRequest struct { PayloadObjectField *string `json:"payloadObjectField,omitempty"` // golang based template for notification payload PayloadTemplate *string `json:"payloadTemplate,omitempty"` - // webhook headers + // name of the template resource + PayloadTemplateReference *string `json:"payloadTemplateReference,omitempty"` + // webhook headers (golang template supported) Headers *map[string]string `json:"headers,omitempty"` // webhook labels Labels *map[string]string `json:"labels,omitempty"` diff --git a/pkg/cloud/client/client_mock_test.go b/pkg/cloud/client/client_mock_test.go index 1a2b172a5db..2f6ec40b414 100644 --- a/pkg/cloud/client/client_mock_test.go +++ b/pkg/cloud/client/client_mock_test.go @@ -2,7 +2,7 @@ package client import ( "bytes" - "io/ioutil" + "io" "net/http" ) @@ -18,6 +18,6 @@ func (c ClientMock) Do(req *http.Request) (*http.Response, error) { return nil, err } return &http.Response{ - Body: ioutil.NopCloser(bytes.NewReader(c.body)), + Body: io.NopCloser(bytes.NewReader(c.body)), }, c.err } diff --git a/pkg/crd/crd.go b/pkg/crd/crd.go index ec97268569f..28630e45620 100644 --- a/pkg/crd/crd.go +++ b/pkg/crd/crd.go @@ -28,6 +28,8 @@ const ( TemplateTestTrigger Template = "testtrigger" // TemplateTestSource is test source crd template TemplateTestSource Template = "testsource" + // TemplateTemplate is template crd template + TemplateTemplate Template = "template" ) // Gettable is an interface of gettable objects @@ -42,7 +44,9 @@ type Gettable interface { testkube.TestTrigger | testkube.TestTriggerUpsertRequest | testkube.TestSource | - testkube.TestSourceUpsertRequest + testkube.TestSourceUpsertRequest | + testkube.Template | + testkube.TemplateCreateRequest } // ExecuteTemplate executes crd template diff --git a/pkg/crd/crd_test.go b/pkg/crd/crd_test.go index 93f0eab4fcf..4b9ef6812c0 100644 --- a/pkg/crd/crd_test.go +++ b/pkg/crd/crd_test.go @@ -12,18 +12,19 @@ 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 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\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" webhooks := []testkube.Webhook{ { - Name: "name1", - Namespace: "namespace1", - Uri: "http://localhost", - Events: []testkube.EventType{*testkube.EventStartTest}, - Selector: "app=backend", - Labels: map[string]string{"key1": "value1"}, - PayloadObjectField: "text", - PayloadTemplate: "{{ .Id }}", - Headers: map[string]string{"Content-Type": "appication/xml"}, + Name: "name1", + Namespace: "namespace1", + Uri: "http://localhost", + Events: []testkube.EventType{*testkube.EventStartTest}, + Selector: "app=backend", + Labels: map[string]string{"key1": "value1"}, + PayloadObjectField: "text", + PayloadTemplate: "{{ .Id }}", + Headers: map[string]string{"Content-Type": "appication/xml"}, + PayloadTemplateReference: "ref", }, } @@ -37,29 +38,31 @@ 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 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 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\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" webhooks := []testkube.Webhook{ { - Name: "name1", - Namespace: "namespace1", - Uri: "http://localhost", - Events: []testkube.EventType{*testkube.EventStartTest}, - Selector: "app=backend", - Labels: map[string]string{"key1": "value1"}, - PayloadObjectField: "text", - PayloadTemplate: "{{ .Id }}", - Headers: map[string]string{"Content-Type": "appication/xml"}, + Name: "name1", + Namespace: "namespace1", + Uri: "http://localhost", + Events: []testkube.EventType{*testkube.EventStartTest}, + Selector: "app=backend", + Labels: map[string]string{"key1": "value1"}, + PayloadObjectField: "text", + PayloadTemplate: "{{ .Id }}", + Headers: map[string]string{"Content-Type": "appication/xml"}, + PayloadTemplateReference: "ref", }, { - Name: "name2", - Namespace: "namespace2", - Uri: "http://localhost", - Events: []testkube.EventType{*testkube.EventEndTestSuccess}, - Selector: "app=backend", - Labels: map[string]string{"key2": "value2"}, - PayloadObjectField: "text", - PayloadTemplate: "{{ .Id }}", - Headers: map[string]string{"Content-Type": "appication/xml"}, + Name: "name2", + Namespace: "namespace2", + Uri: "http://localhost", + Events: []testkube.EventType{*testkube.EventEndTestSuccess}, + Selector: "app=backend", + Labels: map[string]string{"key2": "value2"}, + PayloadObjectField: "text", + PayloadTemplate: "{{ .Id }}", + Headers: map[string]string{"Content-Type": "appication/xml"}, + PayloadTemplateReference: "ref", }, } diff --git a/pkg/crd/templates/executor.tmpl b/pkg/crd/templates/executor.tmpl index 837797f697e..1f206f07478 100644 --- a/pkg/crd/templates/executor.tmpl +++ b/pkg/crd/templates/executor.tmpl @@ -28,6 +28,9 @@ spec: {{- if .JobTemplate }} job_template: {{ .JobTemplate }} {{- end }} + {{- if .JobTemplateReference }} + jobTemplateReference: {{ .JobTemplateReference }} + {{- end }} {{- if gt (len .Args) 0 }} args: {{- range $arg := .Args}} diff --git a/pkg/crd/templates/template.tmpl b/pkg/crd/templates/template.tmpl new file mode 100644 index 00000000000..36c3b1cd386 --- /dev/null +++ b/pkg/crd/templates/template.tmpl @@ -0,0 +1,19 @@ +apiVersion: tests.testkube.io/v1 +kind: Template +metadata: + name: {{ .Name }} + namespace: {{ .Namespace }} + {{- if ne (len .Labels) 0 }} + labels: + {{- range $key, $value := .Labels }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} +spec: + {{- if .Type_ }} + type: {{ .Type_ }} + {{- end }} + {{- if .Body }} + body: {{ .Body }} + {{- end }} + diff --git a/pkg/crd/templates/test.tmpl b/pkg/crd/templates/test.tmpl index 8a52fe7e0c9..b13cf03e639 100644 --- a/pkg/crd/templates/test.tmpl +++ b/pkg/crd/templates/test.tmpl @@ -82,7 +82,7 @@ spec: schedule: {{ .Schedule }} {{- end }} {{- if .ExecutionRequest }} - {{- if or (.ExecutionRequest.Name) (.ExecutionRequest.NegativeTest) (.ExecutionRequest.VariablesFile) (.ExecutionRequest.HttpProxy) (.ExecutionRequest.HttpsProxy) (ne (len .ExecutionRequest.Variables) 0) (ne (len .ExecutionRequest.Args) 0) (ne (len .ExecutionRequest.Envs) 0) (ne (len .ExecutionRequest.SecretEnvs) 0) (.ExecutionRequest.Image) (ne (len .ExecutionRequest.Command) 0) (.ExecutionRequest.ArgsMode) (ne (len .ExecutionRequest.ImagePullSecrets) 0) (ne .ExecutionRequest.ActiveDeadlineSeconds 0) (.ExecutionRequest.ArtifactRequest) (.ExecutionRequest.JobTemplate) (.ExecutionRequest.CronJobTemplate) (.ExecutionRequest.PreRunScript) (.ExecutionRequest.PostRunScript) (.ExecutionRequest.ScraperTemplate) (ne (len .ExecutionRequest.EnvConfigMaps) 0) (ne (len .ExecutionRequest.EnvSecrets) 0) }} + {{- if or (.ExecutionRequest.Name) (.ExecutionRequest.NegativeTest) (.ExecutionRequest.VariablesFile) (.ExecutionRequest.HttpProxy) (.ExecutionRequest.HttpsProxy) (ne (len .ExecutionRequest.Variables) 0) (ne (len .ExecutionRequest.Args) 0) (ne (len .ExecutionRequest.Envs) 0) (ne (len .ExecutionRequest.SecretEnvs) 0) (.ExecutionRequest.Image) (ne (len .ExecutionRequest.Command) 0) (.ExecutionRequest.ArgsMode) (ne (len .ExecutionRequest.ImagePullSecrets) 0) (ne .ExecutionRequest.ActiveDeadlineSeconds 0) (.ExecutionRequest.ArtifactRequest) (.ExecutionRequest.JobTemplate) (.ExecutionRequest.JobTemplateReference) (.ExecutionRequest.CronJobTemplate) (.ExecutionRequest.CronJobTemplateReference) (.ExecutionRequest.PreRunScript) (.ExecutionRequest.PostRunScript) (.ExecutionRequest.ScraperTemplate) (.ExecutionRequest.ScraperTemplateReference) (.ExecutionRequest.PvcTemplate) (.ExecutionRequest.PvcTemplateReference) (ne (len .ExecutionRequest.EnvConfigMaps) 0) (ne (len .ExecutionRequest.EnvSecrets) 0) }} executionRequest: {{- if .ExecutionRequest.Name }} name: {{ .ExecutionRequest.Name }} @@ -198,9 +198,15 @@ spec: {{- if .ExecutionRequest.JobTemplate }} jobTemplate: {{ .ExecutionRequest.JobTemplate }} {{- end }} + {{- if .ExecutionRequest.JobTemplateReference }} + jobTemplateReference: {{ .ExecutionRequest.JobTemplateReference }} + {{- end }} {{- if .ExecutionRequest.CronJobTemplate }} cronJobTemplate: {{ .ExecutionRequest.CronJobTemplate }} {{- end }} + {{- if .ExecutionRequest.CronJobTemplateReference }} + cronJobTemplateReference: {{ .ExecutionRequest.CronJobTemplateReference }} + {{- end }} {{- if .ExecutionRequest.PreRunScript }} preRunScript: {{ .ExecutionRequest.PreRunScript }} {{- end }} @@ -210,6 +216,15 @@ spec: {{- if .ExecutionRequest.ScraperTemplate }} scraperTemplate: {{ .ExecutionRequest.ScraperTemplate }} {{- end }} + {{- if .ExecutionRequest.ScraperTemplateReference }} + scraperTemplateReference: {{ .ExecutionRequest.ScraperTemplateReference }} + {{- end }} + {{- if .ExecutionRequest.PvcTemplate }} + pvcTemplate: {{ .ExecutionRequest.PvcTemplate }} + {{- end }} + {{- if .ExecutionRequest.PvcTemplateReference }} + pvcTemplateReference: {{ .ExecutionRequest.PvcTemplateReference }} + {{- end }} {{- if ne (len .ExecutionRequest.EnvConfigMaps) 0 }} envConfigMaps: {{- range $configMap := .ExecutionRequest.EnvConfigMaps }} diff --git a/pkg/crd/templates/testsuite.tmpl b/pkg/crd/templates/testsuite.tmpl index bb48c772cec..fedb3ce8a7d 100644 --- a/pkg/crd/templates/testsuite.tmpl +++ b/pkg/crd/templates/testsuite.tmpl @@ -71,7 +71,7 @@ spec: repeats: {{ .Repeats }} {{- end }} {{- if .ExecutionRequest }} - {{- if or (.ExecutionRequest.Name) (.ExecutionRequest.HttpProxy) (.ExecutionRequest.HttpsProxy) (ne (len .ExecutionRequest.Variables) 0) (ne .ExecutionRequest.Timeout 0) (.ExecutionRequest.CronJobTemplate)}} + {{- if or (.ExecutionRequest.Name) (.ExecutionRequest.HttpProxy) (.ExecutionRequest.HttpsProxy) (ne (len .ExecutionRequest.Variables) 0) (ne .ExecutionRequest.Timeout 0) (.ExecutionRequest.JobTemplate) (.ExecutionRequest.JobTemplateReference) (.ExecutionRequest.CronJobTemplate) (.ExecutionRequest.CronJobTemplateReference) (.ExecutionRequest.ScraperTemplate) (.ExecutionRequest.ScraperTemplateReference) (.ExecutionRequest.PvcTemplate) (.ExecutionRequest.PvcTemplateReference)}} executionRequest: {{- if .ExecutionRequest.Name }} name: {{ .ExecutionRequest.Name }} @@ -118,9 +118,30 @@ spec: {{- if ne .ExecutionRequest.Timeout 0 }} timeout: {{ .ExecutionRequest.Timeout }} {{- end}} + {{- if .ExecutionRequest.JobTemplate }} + jobTemplate: {{ .ExecutionRequest.JobTemplate }} + {{- end}} + {{- if .ExecutionRequest.JobTemplateReference }} + jobTemplateReference: {{ .ExecutionRequest.JobTemplateReference }} + {{- end}} {{- if .ExecutionRequest.CronJobTemplate }} cronJobTemplate: {{ .ExecutionRequest.CronJobTemplate }} {{- end}} + {{- if .ExecutionRequest.CronJobTemplateReference }} + cronJobTemplateReference: {{ .ExecutionRequest.CronJobTemplateReference }} + {{- end}} + {{- if .ExecutionRequest.ScraperTemplate }} + scraperTemplate: {{ .ExecutionRequest.ScraperTemplate }} + {{- end }} + {{- if .ExecutionRequest.ScraperTemplateReference }} + scraperTemplateReference: {{ .ExecutionRequest.ScraperTemplateReference }} + {{- end }} + {{- if .ExecutionRequest.PvcTemplate }} + pvcTemplate: {{ .ExecutionRequest.PvcTemplate }} + {{- end }} + {{- if .ExecutionRequest.PvcTemplateReference }} + pvcTemplateReference: {{ .ExecutionRequest.PvcTemplateReference }} + {{- end }} {{- end }} {{- end }} {{- if .Status }} diff --git a/pkg/crd/templates/webhook.tmpl b/pkg/crd/templates/webhook.tmpl index 8302e9a7523..dae15d9bb7f 100644 --- a/pkg/crd/templates/webhook.tmpl +++ b/pkg/crd/templates/webhook.tmpl @@ -28,6 +28,9 @@ spec: {{- if .PayloadTemplate }} payloadTemplate: {{ .PayloadTemplate }} {{- end }} + {{- if .PayloadTemplateReference }} + payloadTemplateReference: {{ .PayloadTemplateReference }} + {{- end }} {{- if ne (len .Headers) 0 }} headers: {{- range $key, $value := .Headers }} diff --git a/pkg/cronjob/client.go b/pkg/cronjob/client.go index 96ae56af51b..2943c34883d 100644 --- a/pkg/cronjob/client.go +++ b/pkg/cronjob/client.go @@ -35,6 +35,7 @@ type CronJobOptions struct { Resource string Data string Labels map[string]string + CronJobTemplate string CronJobTemplateExtensions string } @@ -84,6 +85,11 @@ func (c *Client) Get(name string) (*v1.CronJob, error) { // Apply is a method to create or update a cron job func (c *Client) Apply(id, name string, options CronJobOptions) error { + template := c.cronJobTemplate + if options.CronJobTemplate != "" { + template = options.CronJobTemplate + } + cronJobClient := c.ClientSet.BatchV1().CronJobs(c.Namespace) ctx := context.Background() @@ -95,7 +101,7 @@ func (c *Client) Apply(id, name string, options CronJobOptions) error { ServicePort: c.servicePort, Schedule: options.Schedule, Resource: options.Resource, - CronJobTemplate: c.cronJobTemplate, + CronJobTemplate: template, CronJobTemplateExtensions: options.CronJobTemplateExtensions, Data: options.Data, Labels: options.Labels, diff --git a/pkg/event/bus/nats.go b/pkg/event/bus/nats.go index 9ef11429c70..a35f1aff615 100644 --- a/pkg/event/bus/nats.go +++ b/pkg/event/bus/nats.go @@ -11,11 +11,15 @@ import ( "github.com/kubeshop/testkube/pkg/log" ) -var _ Bus = (*NATSBus)(nil) +var ( + _ Bus = (*NATSBus)(nil) +) const ( - SubscribeBuffer = 1 - SubscriptionName = "events" + SubscribeBuffer = 1 + SubscriptionName = "events" + InternalPublishTopic = "internal.all" + InternalSubscribeTopic = "internal.>" ) func NewNATSConnection(uri string) (*nats.EncodedConn, error) { diff --git a/pkg/event/kind/webhook/listener.go b/pkg/event/kind/webhook/listener.go index 30f5faef644..b5dcea11e33 100644 --- a/pkg/event/kind/webhook/listener.go +++ b/pkg/event/kind/webhook/listener.go @@ -87,20 +87,13 @@ func (l *WebhookListener) Notify(event testkube.Event) (result testkube.EventRes var err error if l.payloadTemplate != "" { - var tmpl *template.Template - tmpl, err = template.New("webhook").Parse(l.payloadTemplate) + var data []byte + data, err = l.processTemplate("payload", l.payloadTemplate, event) if err != nil { - log.Errorw("creating webhook template error", "error", err) return testkube.NewFailedEventResult(event.Id, err) } - var buffer bytes.Buffer - if err = tmpl.ExecuteTemplate(&buffer, "webhook", event); err != nil { - log.Errorw("executing webhook template error", "error", err) - return testkube.NewFailedEventResult(event.Id, err) - } - - _, err = body.Write(buffer.Bytes()) + _, err = body.Write(data) } else { err = json.NewEncoder(body).Encode(event) if err == nil && l.payloadObjectField != "" { @@ -111,12 +104,17 @@ func (l *WebhookListener) Notify(event testkube.Event) (result testkube.EventRes } if err != nil { - err = errors.Wrap(err, "webhook send json encode error") - log.Errorw("webhook send json encode error", "error", err) + err = errors.Wrap(err, "webhook send encode error") + log.Errorw("webhook send encode error", "error", err) return testkube.NewFailedEventResult(event.Id, err) } - request, err := http.NewRequest(http.MethodPost, l.Uri, body) + data, err := l.processTemplate("uri", l.Uri, event) + if err != nil { + return testkube.NewFailedEventResult(event.Id, err) + } + + request, err := http.NewRequest(http.MethodPost, string(data), body) if err != nil { log.Errorw("webhook request creating error", "error", err) return testkube.NewFailedEventResult(event.Id, err) @@ -124,6 +122,16 @@ func (l *WebhookListener) Notify(event testkube.Event) (result testkube.EventRes request.Header.Set("Content-Type", "application/json") for key, value := range l.headers { + values := []*string{&key, &value} + for i := range values { + data, err = l.processTemplate("header", *values[i], event) + if err != nil { + return testkube.NewFailedEventResult(event.Id, err) + } + + *values[i] = string(data) + } + request.Header.Set(key, value) } @@ -134,7 +142,7 @@ func (l *WebhookListener) Notify(event testkube.Event) (result testkube.EventRes } defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { log.Errorw("webhook read response error", "error", err) return testkube.NewFailedEventResult(event.Id, err) @@ -155,3 +163,22 @@ func (l *WebhookListener) Notify(event testkube.Event) (result testkube.EventRes func (l *WebhookListener) Kind() string { return "webhook" } + +func (l *WebhookListener) processTemplate(field, body string, event testkube.Event) ([]byte, error) { + log := l.Log.With(event.Log()...) + + var tmpl *template.Template + tmpl, err := template.New(field).Parse(body) + if err != nil { + log.Errorw(fmt.Sprintf("creating webhook %s error", field), "error", err) + return nil, err + } + + var buffer bytes.Buffer + if err = tmpl.ExecuteTemplate(&buffer, field, event); err != nil { + log.Errorw(fmt.Sprintf("executing webhook %s error", field), "error", err) + return nil, err + } + + return buffer.Bytes(), nil +} diff --git a/pkg/event/kind/webhook/loader.go b/pkg/event/kind/webhook/loader.go index a47d708590f..3f8bd77a33e 100644 --- a/pkg/event/kind/webhook/loader.go +++ b/pkg/event/kind/webhook/loader.go @@ -3,7 +3,11 @@ package webhook import ( "fmt" + "go.uber.org/zap" + executorsv1 "github.com/kubeshop/testkube-operator/apis/executor/v1" + templatesclientv1 "github.com/kubeshop/testkube-operator/client/templates/v1" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/event/kind/common" "github.com/kubeshop/testkube/pkg/mapper/webhooks" ) @@ -15,14 +19,18 @@ type WebhooksLister interface { List(selector string) (*executorsv1.WebhookList, error) } -func NewWebhookLoader(webhooksClient WebhooksLister) *WebhooksLoader { +func NewWebhookLoader(log *zap.SugaredLogger, webhooksClient WebhooksLister, templatesClient templatesclientv1.Interface) *WebhooksLoader { return &WebhooksLoader{ - WebhooksClient: webhooksClient, + log: log, + WebhooksClient: webhooksClient, + templatesClient: templatesClient, } } type WebhooksLoader struct { - WebhooksClient WebhooksLister + log *zap.SugaredLogger + WebhooksClient WebhooksLister + templatesClient templatesclientv1.Interface } func (r WebhooksLoader) Kind() string { @@ -38,9 +46,27 @@ func (r WebhooksLoader) Load() (listeners common.Listeners, err error) { // and create listeners for each webhook spec for _, webhook := range webhookList.Items { + payloadTemplate := "" + if webhook.Spec.PayloadTemplateReference != "" { + template, err := r.templatesClient.Get(webhook.Spec.PayloadTemplateReference) + if err != nil { + return listeners, err + } + + if template.Spec.Type_ != nil && testkube.TemplateType(*template.Spec.Type_) == testkube.WEBHOOK_TemplateType { + payloadTemplate = template.Spec.Body + } else { + r.log.Warnw("not matching template type", "template", webhook.Spec.PayloadTemplateReference) + } + } + + if webhook.Spec.PayloadTemplate != "" { + payloadTemplate = webhook.Spec.PayloadTemplate + } + types := webhooks.MapEventArrayToCRDEvents(webhook.Spec.Events) name := fmt.Sprintf("%s.%s", webhook.ObjectMeta.Namespace, webhook.ObjectMeta.Name) - listeners = append(listeners, NewWebhookListener(name, webhook.Spec.Uri, webhook.Spec.Selector, types, webhook.Spec.PayloadObjectField, webhook.Spec.PayloadTemplate, webhook.Spec.Headers)) + listeners = append(listeners, NewWebhookListener(name, webhook.Spec.Uri, webhook.Spec.Selector, types, webhook.Spec.PayloadObjectField, payloadTemplate, webhook.Spec.Headers)) } return listeners, nil diff --git a/pkg/event/kind/webhook/loader_test.go b/pkg/event/kind/webhook/loader_test.go index 9c137b148eb..4703f2410f7 100644 --- a/pkg/event/kind/webhook/loader_test.go +++ b/pkg/event/kind/webhook/loader_test.go @@ -3,9 +3,12 @@ package webhook import ( "testing" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "go.uber.org/zap" executorsv1 "github.com/kubeshop/testkube-operator/apis/executor/v1" + templatesclientv1 "github.com/kubeshop/testkube-operator/client/templates/v1" ) type DummyLoader struct { @@ -21,7 +24,12 @@ func (l DummyLoader) List(selector string) (*executorsv1.WebhookList, error) { func TestWebhookLoader(t *testing.T) { t.Parallel() - webhooksLoader := NewWebhookLoader(&DummyLoader{}) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTemplatesClient := templatesclientv1.NewMockInterface(mockCtrl) + webhooksLoader := NewWebhookLoader(zap.NewNop().Sugar(), &DummyLoader{}, mockTemplatesClient) listeners, err := webhooksLoader.Load() assert.Equal(t, 1, len(listeners)) diff --git a/pkg/executor/client/job.go b/pkg/executor/client/job.go index b2ec1fe5717..e9e694f8984 100644 --- a/pkg/executor/client/job.go +++ b/pkg/executor/client/job.go @@ -30,6 +30,7 @@ import ( kyaml "sigs.k8s.io/kustomize/kyaml/yaml" + templatesv1 "github.com/kubeshop/testkube-operator/client/templates/v1" testexecutionsv1 "github.com/kubeshop/testkube-operator/client/testexecutions/v1" testsv3 "github.com/kubeshop/testkube-operator/client/tests/v3" "github.com/kubeshop/testkube/pkg/api/v1/testkube" @@ -79,6 +80,7 @@ func NewJobExecutor( testsClient testsv3.Interface, clientset kubernetes.Interface, testExecutionsClient testexecutionsv1.Interface, + templatesClient templatesv1.Interface, registry string, podStartTimeout time.Duration, clusterID string, @@ -96,6 +98,7 @@ func NewJobExecutor( configMap: configMap, testsClient: testsClient, testExecutionsClient: testExecutionsClient, + templatesClient: templatesClient, registry: registry, podStartTimeout: podStartTimeout, clusterID: clusterID, @@ -121,6 +124,7 @@ type JobExecutor struct { configMap config.Repository testsClient testsv3.Interface testExecutionsClient testexecutionsv1.Interface + templatesClient templatesv1.Interface registry string podStartTimeout time.Duration clusterID string @@ -302,7 +306,8 @@ func (c *JobExecutor) MonitorJobForTimeout(ctx context.Context, jobName string) // CreateJob creates new Kubernetes job based on execution and execute options func (c *JobExecutor) CreateJob(ctx context.Context, execution testkube.Execution, options ExecuteOptions) error { jobs := c.ClientSet.BatchV1().Jobs(c.Namespace) - jobOptions, err := NewJobOptions(c.images.Init, c.jobTemplate, c.serviceAccountName, c.registry, c.clusterID, execution, options) + jobOptions, err := NewJobOptions(c.Log, c.templatesClient, c.images.Init, c.jobTemplate, c.serviceAccountName, c.registry, + c.clusterID, execution, options) if err != nil { return err } @@ -773,7 +778,7 @@ func NewJobSpec(log *zap.SugaredLogger, options JobOptions) (*batchv1.Job, error return &job, nil } -func NewJobOptions(initImage, jobTemplate string, serviceAccountName, registry, clusterID string, +func NewJobOptions(log *zap.SugaredLogger, templatesClient templatesv1.Interface, initImage, jobTemplate, serviceAccountName, registry, clusterID string, execution testkube.Execution, options ExecuteOptions) (jobOptions JobOptions, err error) { jsn, err := json.Marshal(execution) if err != nil { @@ -789,6 +794,33 @@ func NewJobOptions(initImage, jobTemplate string, serviceAccountName, registry, if jobOptions.JobTemplate == "" { jobOptions.JobTemplate = jobTemplate } + + if options.ExecutorSpec.JobTemplateReference != "" { + template, err := templatesClient.Get(options.ExecutorSpec.JobTemplateReference) + if err != nil { + return jobOptions, err + } + + if template.Spec.Type_ != nil && testkube.TemplateType(*template.Spec.Type_) == testkube.JOB_TemplateType { + jobOptions.JobTemplate = template.Spec.Body + } else { + log.Warnw("Not matched template type", "template", options.ExecutorSpec.JobTemplateReference) + } + } + + if options.Request.JobTemplateReference != "" { + template, err := templatesClient.Get(options.Request.JobTemplateReference) + if err != nil { + return jobOptions, err + } + + if template.Spec.Type_ != nil && testkube.TemplateType(*template.Spec.Type_) == testkube.JOB_TemplateType { + jobOptions.JobTemplate = template.Spec.Body + } else { + log.Warnw("Not matched template type", "template", options.Request.JobTemplateReference) + } + } + jobOptions.Variables = execution.Variables jobOptions.ServiceAccountName = serviceAccountName jobOptions.Registry = registry diff --git a/pkg/executor/containerexecutor/containerexecutor.go b/pkg/executor/containerexecutor/containerexecutor.go index 5b6c3e15b50..0f3f0c05516 100644 --- a/pkg/executor/containerexecutor/containerexecutor.go +++ b/pkg/executor/containerexecutor/containerexecutor.go @@ -21,6 +21,7 @@ import ( executorv1 "github.com/kubeshop/testkube-operator/apis/executor/v1" executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors/v1" + templatesv1 "github.com/kubeshop/testkube-operator/client/templates/v1" testexecutionsv1 "github.com/kubeshop/testkube-operator/client/testexecutions/v1" testsv3 "github.com/kubeshop/testkube-operator/client/tests/v3" "github.com/kubeshop/testkube/pkg/api/v1/testkube" @@ -61,6 +62,7 @@ func NewContainerExecutor( executorsClient executorsclientv1.Interface, testsClient testsv3.Interface, testExecutionsClient testexecutionsv1.Interface, + templatesClient templatesv1.Interface, registry string, podStartTimeout time.Duration, clusterID string, @@ -84,6 +86,7 @@ func NewContainerExecutor( testsClient: testsClient, executorsClient: executorsClient, testExecutionsClient: testExecutionsClient, + templatesClient: templatesClient, registry: registry, podStartTimeout: podStartTimeout, clusterID: clusterID, @@ -109,6 +112,7 @@ type ContainerExecutor struct { testsClient testsv3.Interface executorsClient executorsclientv1.Interface testExecutionsClient testexecutionsv1.Interface + templatesClient templatesv1.Interface registry string podStartTimeout time.Duration clusterID string @@ -129,7 +133,7 @@ type JobOptions struct { ScraperImage string JobTemplate string ScraperTemplate string - PVCTemplate string + PvcTemplate string SecretEnvs map[string]string Envs map[string]string HTTPProxy string @@ -144,6 +148,7 @@ type JobOptions struct { DelaySeconds int JobTemplateExtensions string ScraperTemplateExtensions string + PvcTemplateExtensions string EnvConfigMaps []testkube.EnvReference EnvSecrets []testkube.EnvReference Labels map[string]string @@ -284,7 +289,8 @@ func (c *ContainerExecutor) ExecuteSync(ctx context.Context, execution *testkube func (c *ContainerExecutor) createJob(ctx context.Context, execution testkube.Execution, options client.ExecuteOptions) (*JobOptions, error) { jobsClient := c.clientSet.BatchV1().Jobs(c.namespace) - jobOptions, err := NewJobOptions(c.log, c.images, c.templates, c.serviceAccountName, c.registry, c.clusterID, execution, options) + jobOptions, err := NewJobOptions(c.log, c.templatesClient, c.images, c.templates, c.serviceAccountName, + c.registry, c.clusterID, execution, options) if err != nil { return nil, err } @@ -661,6 +667,7 @@ func NewJobOptionsFromExecutionOptions(options client.ExecuteOptions) *JobOption JobTemplate: options.ExecutorSpec.JobTemplate, JobTemplateExtensions: options.Request.JobTemplate, ScraperTemplateExtensions: options.Request.ScraperTemplate, + PvcTemplateExtensions: options.Request.PvcTemplate, EnvConfigMaps: options.Request.EnvConfigMaps, EnvSecrets: options.Request.EnvSecrets, Labels: labels, diff --git a/pkg/executor/containerexecutor/containerexecutor_test.go b/pkg/executor/containerexecutor/containerexecutor_test.go index ff7f443434e..4cf5d73ac7e 100644 --- a/pkg/executor/containerexecutor/containerexecutor_test.go +++ b/pkg/executor/containerexecutor/containerexecutor_test.go @@ -5,8 +5,7 @@ import ( "testing" "time" - "github.com/kubeshop/testkube/pkg/repository/result" - + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" @@ -16,10 +15,12 @@ import ( executorv1 "github.com/kubeshop/testkube-operator/apis/executor/v1" testsv3 "github.com/kubeshop/testkube-operator/apis/tests/v3" + templatesclientv1 "github.com/kubeshop/testkube-operator/client/templates/v1" v3 "github.com/kubeshop/testkube-operator/client/tests/v3" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/executor" "github.com/kubeshop/testkube/pkg/executor/client" + "github.com/kubeshop/testkube/pkg/repository/result" ) var ctx = context.Background() @@ -76,13 +77,18 @@ func TestNewExecutorJobSpecEmptyArgs(t *testing.T) { t.Parallel() jobOptions := &JobOptions{ - Name: "name", - Namespace: "namespace", - InitImage: "kubeshop/testkube-init-executor:0.7.10", - Image: "ubuntu", - JobTemplate: defaultJobTemplate, - Command: []string{}, - Args: []string{}, + Name: "name", + Namespace: "namespace", + InitImage: "kubeshop/testkube-init-executor:0.7.10", + Image: "ubuntu", + JobTemplate: defaultJobTemplate, + ScraperTemplate: "", + PvcTemplate: "", + JobTemplateExtensions: "", + ScraperTemplateExtensions: "", + PvcTemplateExtensions: "", + Command: []string{}, + Args: []string{}, } spec, err := NewExecutorJobSpec(logger(), jobOptions) assert.NoError(t, err) @@ -93,17 +99,22 @@ func TestNewExecutorJobSpecWithArgs(t *testing.T) { t.Parallel() jobOptions := &JobOptions{ - Name: "name", - Namespace: "namespace", - InitImage: "kubeshop/testkube-init-executor:0.7.10", - Image: "curl", - JobTemplate: defaultJobTemplate, - ImagePullSecrets: []string{"secret-name"}, - Command: []string{"/bin/curl"}, - Args: []string{"-v", "https://testkube.kubeshop.io"}, - ActiveDeadlineSeconds: 100, - Envs: map[string]string{"key": "value"}, - Variables: map[string]testkube.Variable{"aa": {Name: "aa", Value: "bb", Type_: testkube.VariableTypeBasic}}, + Name: "name", + Namespace: "namespace", + InitImage: "kubeshop/testkube-init-executor:0.7.10", + Image: "curl", + JobTemplate: defaultJobTemplate, + ScraperTemplate: "", + PvcTemplate: "", + JobTemplateExtensions: "", + ScraperTemplateExtensions: "", + PvcTemplateExtensions: "", + ImagePullSecrets: []string{"secret-name"}, + Command: []string{"/bin/curl"}, + Args: []string{"-v", "https://testkube.kubeshop.io"}, + ActiveDeadlineSeconds: 100, + Envs: map[string]string{"key": "value"}, + Variables: map[string]testkube.Variable{"aa": {Name: "aa", Value: "bb", Type_: testkube.VariableTypeBasic}}, } spec, err := NewExecutorJobSpec(logger(), jobOptions) assert.NoError(t, err) @@ -140,13 +151,18 @@ func TestNewExecutorJobSpecWithoutInitImage(t *testing.T) { t.Parallel() jobOptions := &JobOptions{ - Name: "name", - Namespace: "namespace", - InitImage: "", - Image: "ubuntu", - JobTemplate: defaultJobTemplate, - Command: []string{}, - Args: []string{}, + Name: "name", + Namespace: "namespace", + InitImage: "", + Image: "ubuntu", + JobTemplate: defaultJobTemplate, + ScraperTemplate: "", + PvcTemplate: "", + JobTemplateExtensions: "", + ScraperTemplateExtensions: "", + PvcTemplateExtensions: "", + Command: []string{}, + Args: []string{}, } spec, err := NewExecutorJobSpec(logger(), jobOptions) assert.NoError(t, err) @@ -156,8 +172,14 @@ func TestNewExecutorJobSpecWithoutInitImage(t *testing.T) { func TestNewExecutorJobSpecWithWorkingDirRelative(t *testing.T) { t.Parallel() + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTemplatesClient := templatesclientv1.NewMockInterface(mockCtrl) + jobOptions, _ := NewJobOptions( logger(), + mockTemplatesClient, executor.Images{}, executor.Templates{}, "", @@ -191,8 +213,14 @@ func TestNewExecutorJobSpecWithWorkingDirRelative(t *testing.T) { func TestNewExecutorJobSpecWithWorkingDirAbsolute(t *testing.T) { t.Parallel() + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTemplatesClient := templatesclientv1.NewMockInterface(mockCtrl) + jobOptions, _ := NewJobOptions( logger(), + mockTemplatesClient, executor.Images{}, executor.Templates{}, "", @@ -226,8 +254,14 @@ func TestNewExecutorJobSpecWithWorkingDirAbsolute(t *testing.T) { func TestNewExecutorJobSpecWithoutWorkingDir(t *testing.T) { t.Parallel() + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTemplatesClient := templatesclientv1.NewMockInterface(mockCtrl) + jobOptions, _ := NewJobOptions( logger(), + mockTemplatesClient, executor.Images{}, executor.Templates{}, "", diff --git a/pkg/executor/containerexecutor/tmpl.go b/pkg/executor/containerexecutor/tmpl.go index 3972da90089..2e945379d87 100644 --- a/pkg/executor/containerexecutor/tmpl.go +++ b/pkg/executor/containerexecutor/tmpl.go @@ -18,6 +18,7 @@ import ( kyaml "sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/kustomize/kyaml/yaml/merge2" + templatesv1 "github.com/kubeshop/testkube-operator/client/templates/v1" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/executor" "github.com/kubeshop/testkube/pkg/executor/client" @@ -197,7 +198,7 @@ func NewScraperJobSpec(log *zap.SugaredLogger, options *JobOptions) (*batchv1.Jo // NewPersistentVolumeClaimSpec is a method to create new persistent volume claim spec func NewPersistentVolumeClaimSpec(log *zap.SugaredLogger, options *JobOptions) (*corev1.PersistentVolumeClaim, error) { - tmpl, err := template.New("volume-claim").Parse(options.PVCTemplate) + tmpl, err := template.New("volume-claim").Parse(options.PvcTemplate) if err != nil { return nil, fmt.Errorf("creating volume claim spec from pvc template error: %w", err) } @@ -209,6 +210,21 @@ func NewPersistentVolumeClaimSpec(log *zap.SugaredLogger, options *JobOptions) ( var pvc corev1.PersistentVolumeClaim pvcSpec := buffer.String() + if options.PvcTemplateExtensions != "" { + tmplExt, err := template.New("jobExt").Parse(options.PvcTemplateExtensions) + if err != nil { + return nil, fmt.Errorf("creating pvc extensions spec from executor template error: %w", err) + } + + var bufferExt bytes.Buffer + if err = tmplExt.ExecuteTemplate(&bufferExt, "jobExt", options); err != nil { + return nil, fmt.Errorf("executing pvc extensions spec executor template: %w", err) + } + + if pvcSpec, err = merge2.MergeStrings(bufferExt.String(), pvcSpec, false, kyaml.MergeOptions{}); err != nil { + return nil, fmt.Errorf("merging spvc spec executor templates: %w", err) + } + } log.Debug("Volume claim specification", pvcSpec) decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(pvcSpec), len(pvcSpec)) @@ -253,7 +269,7 @@ func InspectDockerImage(namespace, registry, image string, imageSecrets []string } // NewJobOptions provides job options for templates -func NewJobOptions(log *zap.SugaredLogger, images executor.Images, templates executor.Templates, +func NewJobOptions(log *zap.SugaredLogger, templatesClient templatesv1.Interface, images executor.Images, templates executor.Templates, serviceAccountName, registry, clusterID string, execution testkube.Execution, options client.ExecuteOptions) (*JobOptions, error) { jobOptions := NewJobOptionsFromExecutionOptions(options) if execution.PreRunScript != "" || execution.PostRunScript != "" { @@ -290,8 +306,60 @@ func NewJobOptions(log *zap.SugaredLogger, images executor.Images, templates exe } } + if options.ExecutorSpec.JobTemplateReference != "" { + template, err := templatesClient.Get(options.ExecutorSpec.JobTemplateReference) + if err != nil { + return jobOptions, err + } + + if template.Spec.Type_ != nil && testkube.TemplateType(*template.Spec.Type_) == testkube.CONTAINER_TemplateType { + jobOptions.JobTemplate = template.Spec.Body + } else { + log.Warnw("Not matched template type", "template", options.ExecutorSpec.JobTemplateReference) + } + } + + if options.Request.JobTemplateReference != "" { + template, err := templatesClient.Get(options.Request.JobTemplateReference) + if err != nil { + return nil, err + } + + if template.Spec.Type_ != nil && testkube.TemplateType(*template.Spec.Type_) == testkube.CONTAINER_TemplateType { + jobOptions.JobTemplate = template.Spec.Body + } else { + log.Warnw("Not matched template type", "template", options.Request.JobTemplateReference) + } + } + jobOptions.ScraperTemplate = templates.Scraper - jobOptions.PVCTemplate = templates.PVC + if options.Request.ScraperTemplateReference != "" { + template, err := templatesClient.Get(options.Request.ScraperTemplateReference) + if err != nil { + return nil, err + } + + if template.Spec.Type_ != nil && testkube.TemplateType(*template.Spec.Type_) == testkube.SCRAPER_TemplateType { + jobOptions.ScraperTemplate = template.Spec.Body + } else { + log.Warnw("Not matched template type", "template", options.Request.ScraperTemplateReference) + } + } + + jobOptions.PvcTemplate = templates.PVC + if options.Request.PvcTemplateReference != "" { + template, err := templatesClient.Get(options.Request.PvcTemplateReference) + if err != nil { + return nil, err + } + + if template.Spec.Type_ != nil && testkube.TemplateType(*template.Spec.Type_) == testkube.PVC_TemplateType { + jobOptions.PvcTemplate = template.Spec.Body + } else { + log.Warnw("Not matched template type", "template", options.Request.PvcTemplateReference) + } + } + jobOptions.Variables = execution.Variables jobOptions.ServiceAccountName = serviceAccountName jobOptions.Registry = registry diff --git a/pkg/mapper/executors/mapper.go b/pkg/mapper/executors/mapper.go index 33c9f500e9e..3ab675f54e7 100644 --- a/pkg/mapper/executors/mapper.go +++ b/pkg/mapper/executors/mapper.go @@ -11,20 +11,21 @@ import ( // MapCRDToAPI maps Executor CRD to OpenAPI spec Executor func MapCRDToAPI(item executorv1.Executor) testkube.ExecutorUpsertRequest { return testkube.ExecutorUpsertRequest{ - Name: item.Name, - Namespace: item.Namespace, - Labels: item.Labels, - ExecutorType: string(item.Spec.ExecutorType), - Types: item.Spec.Types, - Uri: item.Spec.URI, - Image: item.Spec.Image, - ImagePullSecrets: mapImagePullSecretsToAPI(item.Spec.ImagePullSecrets), - Command: item.Spec.Command, - Args: item.Spec.Args, - JobTemplate: item.Spec.JobTemplate, - Features: MapFeaturesToAPI(item.Spec.Features), - ContentTypes: MapContentTypesToAPI(item.Spec.ContentTypes), - Meta: MapMetaToAPI(item.Spec.Meta), + Name: item.Name, + Namespace: item.Namespace, + Labels: item.Labels, + ExecutorType: string(item.Spec.ExecutorType), + Types: item.Spec.Types, + Uri: item.Spec.URI, + Image: item.Spec.Image, + ImagePullSecrets: mapImagePullSecretsToAPI(item.Spec.ImagePullSecrets), + Command: item.Spec.Command, + Args: item.Spec.Args, + JobTemplate: item.Spec.JobTemplate, + JobTemplateReference: item.Spec.JobTemplateReference, + Features: MapFeaturesToAPI(item.Spec.Features), + ContentTypes: MapContentTypesToAPI(item.Spec.ContentTypes), + Meta: MapMetaToAPI(item.Spec.Meta), } } @@ -37,17 +38,18 @@ func MapAPIToCRD(request testkube.ExecutorUpsertRequest) executorv1.Executor { Labels: request.Labels, }, Spec: executorv1.ExecutorSpec{ - ExecutorType: executorv1.ExecutorType(request.ExecutorType), - Types: request.Types, - URI: request.Uri, - Image: request.Image, - ImagePullSecrets: mapImagePullSecretsToCRD(request.ImagePullSecrets), - Command: request.Command, - Args: request.Args, - JobTemplate: request.JobTemplate, - Features: MapFeaturesToCRD(request.Features), - ContentTypes: MapContentTypesToCRD(request.ContentTypes), - Meta: MapMetaToCRD(request.Meta), + ExecutorType: executorv1.ExecutorType(request.ExecutorType), + Types: request.Types, + URI: request.Uri, + Image: request.Image, + ImagePullSecrets: mapImagePullSecretsToCRD(request.ImagePullSecrets), + Command: request.Command, + Args: request.Args, + JobTemplate: request.JobTemplate, + JobTemplateReference: request.JobTemplateReference, + Features: MapFeaturesToCRD(request.Features), + ContentTypes: MapContentTypesToCRD(request.ContentTypes), + Meta: MapMetaToCRD(request.Meta), }, } } @@ -57,18 +59,19 @@ func MapExecutorCRDToExecutorDetails(item executorv1.Executor) testkube.Executor return testkube.ExecutorDetails{ Name: item.Name, Executor: &testkube.Executor{ - ExecutorType: string(item.Spec.ExecutorType), - Image: item.Spec.Image, - ImagePullSecrets: mapImagePullSecretsToAPI(item.Spec.ImagePullSecrets), - Command: item.Spec.Command, - Args: item.Spec.Args, - Types: item.Spec.Types, - Uri: item.Spec.URI, - JobTemplate: item.Spec.JobTemplate, - Labels: item.Labels, - Features: MapFeaturesToAPI(item.Spec.Features), - ContentTypes: MapContentTypesToAPI(item.Spec.ContentTypes), - Meta: MapMetaToAPI(item.Spec.Meta), + ExecutorType: string(item.Spec.ExecutorType), + Image: item.Spec.Image, + ImagePullSecrets: mapImagePullSecretsToAPI(item.Spec.ImagePullSecrets), + Command: item.Spec.Command, + Args: item.Spec.Args, + Types: item.Spec.Types, + Uri: item.Spec.URI, + JobTemplate: item.Spec.JobTemplate, + JobTemplateReference: item.Spec.JobTemplateReference, + Labels: item.Labels, + Features: MapFeaturesToAPI(item.Spec.Features), + ContentTypes: MapContentTypesToAPI(item.Spec.ContentTypes), + Meta: MapMetaToAPI(item.Spec.Meta), }, } } @@ -167,6 +170,10 @@ func MapUpdateToSpec(request testkube.ExecutorUpdateRequest, executor *executorv request.JobTemplate, &executor.Spec.JobTemplate, }, + { + request.JobTemplateReference, + &executor.Spec.JobTemplateReference, + }, } for _, field := range fields { @@ -276,6 +283,10 @@ func MapSpecToUpdate(executor *executorv1.Executor) (request testkube.ExecutorUp &executor.Spec.JobTemplate, &request.JobTemplate, }, + { + &executor.Spec.JobTemplateReference, + &request.JobTemplateReference, + }, } for _, field := range fields { diff --git a/pkg/mapper/templates/mapper.go b/pkg/mapper/templates/mapper.go new file mode 100644 index 00000000000..a2f7444a550 --- /dev/null +++ b/pkg/mapper/templates/mapper.go @@ -0,0 +1,101 @@ +package templates + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + templatev1 "github.com/kubeshop/testkube-operator/apis/template/v1" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" +) + +// MapCRDToAPI maps Template CRD to OpenAPI spec Template +func MapCRDToAPI(item templatev1.Template) testkube.Template { + return testkube.Template{ + Name: item.Name, + Namespace: item.Namespace, + Body: item.Spec.Body, + Type_: (*testkube.TemplateType)(item.Spec.Type_), + Labels: item.Labels, + } +} + +// MapAPIToCRD maps OpenAPI spec TemplateCreateRequest to CRD Template +func MapAPIToCRD(request testkube.TemplateCreateRequest) templatev1.Template { + return templatev1.Template{ + ObjectMeta: metav1.ObjectMeta{ + Name: request.Name, + Namespace: request.Namespace, + Labels: request.Labels, + }, + Spec: templatev1.TemplateSpec{ + Type_: (*templatev1.TemplateType)(request.Type_), + Body: request.Body, + }, + } +} + +// MapUpdateToSpec maps TemplateUpdateRequest to Wehook CRD spec +func MapUpdateToSpec(request testkube.TemplateUpdateRequest, template *templatev1.Template) *templatev1.Template { + var fields = []struct { + source *string + destination *string + }{ + { + request.Name, + &template.Name, + }, + { + request.Namespace, + &template.Namespace, + }, + { + request.Body, + &template.Spec.Body, + }, + } + + for _, field := range fields { + if field.source != nil { + *field.destination = *field.source + } + } + + if request.Type_ != nil { + *template.Spec.Type_ = (templatev1.TemplateType)(*request.Type_) + } + + if request.Labels != nil { + template.Labels = *request.Labels + } + + return template +} + +// MapSpecToUpdate maps Template CRD to TemplateUpdate Request to spec +func MapSpecToUpdate(template *templatev1.Template) (request testkube.TemplateUpdateRequest) { + var fields = []struct { + source *string + destination **string + }{ + { + &template.Name, + &request.Name, + }, + { + &template.Namespace, + &request.Namespace, + }, + { + &template.Spec.Body, + &request.Body, + }, + } + + for _, field := range fields { + *field.destination = field.source + } + + request.Type_ = (*testkube.TemplateType)(template.Spec.Type_) + request.Labels = &template.Labels + + return request +} diff --git a/pkg/mapper/tests/kube_openapi.go b/pkg/mapper/tests/kube_openapi.go index 1f965a6af0f..c16ddf3512b 100644 --- a/pkg/mapper/tests/kube_openapi.go +++ b/pkg/mapper/tests/kube_openapi.go @@ -128,36 +128,41 @@ func MapExecutionRequestFromSpec(specExecutionRequest *testsv3.ExecutionRequest) } return &testkube.ExecutionRequest{ - Name: specExecutionRequest.Name, - TestSuiteName: specExecutionRequest.TestSuiteName, - Number: specExecutionRequest.Number, - ExecutionLabels: specExecutionRequest.ExecutionLabels, - Namespace: specExecutionRequest.Namespace, - IsVariablesFileUploaded: specExecutionRequest.IsVariablesFileUploaded, - VariablesFile: specExecutionRequest.VariablesFile, - Variables: MergeVariablesAndParams(specExecutionRequest.Variables, nil), - TestSecretUUID: specExecutionRequest.TestSecretUUID, - TestSuiteSecretUUID: specExecutionRequest.TestSuiteSecretUUID, - Command: specExecutionRequest.Command, - Args: specExecutionRequest.Args, - ArgsMode: string(specExecutionRequest.ArgsMode), - Image: specExecutionRequest.Image, - ImagePullSecrets: MapImagePullSecrets(specExecutionRequest.ImagePullSecrets), - Envs: specExecutionRequest.Envs, - SecretEnvs: specExecutionRequest.SecretEnvs, - Sync: specExecutionRequest.Sync, - HttpProxy: specExecutionRequest.HttpProxy, - HttpsProxy: specExecutionRequest.HttpsProxy, - ActiveDeadlineSeconds: specExecutionRequest.ActiveDeadlineSeconds, - ArtifactRequest: artifactRequest, - JobTemplate: specExecutionRequest.JobTemplate, - CronJobTemplate: specExecutionRequest.CronJobTemplate, - PreRunScript: specExecutionRequest.PreRunScript, - PostRunScript: specExecutionRequest.PostRunScript, - ScraperTemplate: specExecutionRequest.ScraperTemplate, - NegativeTest: specExecutionRequest.NegativeTest, - EnvConfigMaps: MapEnvReferences(specExecutionRequest.EnvConfigMaps), - EnvSecrets: MapEnvReferences(specExecutionRequest.EnvSecrets), + Name: specExecutionRequest.Name, + TestSuiteName: specExecutionRequest.TestSuiteName, + Number: specExecutionRequest.Number, + ExecutionLabels: specExecutionRequest.ExecutionLabels, + Namespace: specExecutionRequest.Namespace, + IsVariablesFileUploaded: specExecutionRequest.IsVariablesFileUploaded, + VariablesFile: specExecutionRequest.VariablesFile, + Variables: MergeVariablesAndParams(specExecutionRequest.Variables, nil), + TestSecretUUID: specExecutionRequest.TestSecretUUID, + TestSuiteSecretUUID: specExecutionRequest.TestSuiteSecretUUID, + Command: specExecutionRequest.Command, + Args: specExecutionRequest.Args, + ArgsMode: string(specExecutionRequest.ArgsMode), + Image: specExecutionRequest.Image, + ImagePullSecrets: MapImagePullSecrets(specExecutionRequest.ImagePullSecrets), + Envs: specExecutionRequest.Envs, + SecretEnvs: specExecutionRequest.SecretEnvs, + Sync: specExecutionRequest.Sync, + HttpProxy: specExecutionRequest.HttpProxy, + HttpsProxy: specExecutionRequest.HttpsProxy, + ActiveDeadlineSeconds: specExecutionRequest.ActiveDeadlineSeconds, + ArtifactRequest: artifactRequest, + JobTemplate: specExecutionRequest.JobTemplate, + JobTemplateReference: specExecutionRequest.JobTemplateReference, + CronJobTemplate: specExecutionRequest.CronJobTemplate, + CronJobTemplateReference: specExecutionRequest.CronJobTemplateReference, + PreRunScript: specExecutionRequest.PreRunScript, + PostRunScript: specExecutionRequest.PostRunScript, + PvcTemplate: specExecutionRequest.PvcTemplate, + PvcTemplateReference: specExecutionRequest.PvcTemplateReference, + ScraperTemplate: specExecutionRequest.ScraperTemplate, + ScraperTemplateReference: specExecutionRequest.ScraperTemplateReference, + NegativeTest: specExecutionRequest.NegativeTest, + EnvConfigMaps: MapEnvReferences(specExecutionRequest.EnvConfigMaps), + EnvSecrets: MapEnvReferences(specExecutionRequest.EnvSecrets), } } @@ -403,6 +408,10 @@ func MapSpecExecutionRequestToExecutionUpdateRequest( &request.JobTemplate, &executionRequest.JobTemplate, }, + { + &request.JobTemplateReference, + &executionRequest.JobTemplateReference, + }, { &request.PreRunScript, &executionRequest.PreRunScript, @@ -415,10 +424,26 @@ func MapSpecExecutionRequestToExecutionUpdateRequest( &request.CronJobTemplate, &executionRequest.CronJobTemplate, }, + { + &request.CronJobTemplateReference, + &executionRequest.CronJobTemplateReference, + }, + { + &request.PvcTemplate, + &executionRequest.PvcTemplate, + }, + { + &request.PvcTemplateReference, + &executionRequest.PvcTemplateReference, + }, { &request.ScraperTemplate, &executionRequest.ScraperTemplate, }, + { + &request.ScraperTemplateReference, + &executionRequest.ScraperTemplateReference, + }, } for _, field := range fields { diff --git a/pkg/mapper/tests/openapi_kube.go b/pkg/mapper/tests/openapi_kube.go index 37a8468c0da..3be14cfe786 100644 --- a/pkg/mapper/tests/openapi_kube.go +++ b/pkg/mapper/tests/openapi_kube.go @@ -140,36 +140,41 @@ func MapExecutionRequestToSpecExecutionRequest(executionRequest *testkube.Execut } return &testsv3.ExecutionRequest{ - Name: executionRequest.Name, - TestSuiteName: executionRequest.TestSuiteName, - Number: executionRequest.Number, - ExecutionLabels: executionRequest.ExecutionLabels, - Namespace: executionRequest.Namespace, - IsVariablesFileUploaded: executionRequest.IsVariablesFileUploaded, - VariablesFile: executionRequest.VariablesFile, - Variables: MapCRDVariables(executionRequest.Variables), - TestSecretUUID: executionRequest.TestSecretUUID, - TestSuiteSecretUUID: executionRequest.TestSuiteSecretUUID, - Args: executionRequest.Args, - ArgsMode: testsv3.ArgsModeType(executionRequest.ArgsMode), - Envs: executionRequest.Envs, - SecretEnvs: executionRequest.SecretEnvs, - Sync: executionRequest.Sync, - HttpProxy: executionRequest.HttpProxy, - HttpsProxy: executionRequest.HttpsProxy, - Image: executionRequest.Image, - ImagePullSecrets: mapImagePullSecrets(executionRequest.ImagePullSecrets), - ActiveDeadlineSeconds: executionRequest.ActiveDeadlineSeconds, - Command: executionRequest.Command, - ArtifactRequest: artifactRequest, - JobTemplate: executionRequest.JobTemplate, - CronJobTemplate: executionRequest.CronJobTemplate, - PreRunScript: executionRequest.PreRunScript, - PostRunScript: executionRequest.PostRunScript, - ScraperTemplate: executionRequest.ScraperTemplate, - NegativeTest: executionRequest.NegativeTest, - EnvConfigMaps: mapEnvReferences(executionRequest.EnvConfigMaps), - EnvSecrets: mapEnvReferences(executionRequest.EnvSecrets), + Name: executionRequest.Name, + TestSuiteName: executionRequest.TestSuiteName, + Number: executionRequest.Number, + ExecutionLabels: executionRequest.ExecutionLabels, + Namespace: executionRequest.Namespace, + IsVariablesFileUploaded: executionRequest.IsVariablesFileUploaded, + VariablesFile: executionRequest.VariablesFile, + Variables: MapCRDVariables(executionRequest.Variables), + TestSecretUUID: executionRequest.TestSecretUUID, + TestSuiteSecretUUID: executionRequest.TestSuiteSecretUUID, + Args: executionRequest.Args, + ArgsMode: testsv3.ArgsModeType(executionRequest.ArgsMode), + Envs: executionRequest.Envs, + SecretEnvs: executionRequest.SecretEnvs, + Sync: executionRequest.Sync, + HttpProxy: executionRequest.HttpProxy, + HttpsProxy: executionRequest.HttpsProxy, + Image: executionRequest.Image, + ImagePullSecrets: mapImagePullSecrets(executionRequest.ImagePullSecrets), + ActiveDeadlineSeconds: executionRequest.ActiveDeadlineSeconds, + Command: executionRequest.Command, + ArtifactRequest: artifactRequest, + JobTemplate: executionRequest.JobTemplate, + JobTemplateReference: executionRequest.JobTemplateReference, + CronJobTemplate: executionRequest.CronJobTemplate, + CronJobTemplateReference: executionRequest.CronJobTemplateReference, + PreRunScript: executionRequest.PreRunScript, + PostRunScript: executionRequest.PostRunScript, + PvcTemplate: executionRequest.PvcTemplate, + PvcTemplateReference: executionRequest.PvcTemplateReference, + ScraperTemplate: executionRequest.ScraperTemplate, + ScraperTemplateReference: executionRequest.ScraperTemplateReference, + NegativeTest: executionRequest.NegativeTest, + EnvConfigMaps: mapEnvReferences(executionRequest.EnvConfigMaps), + EnvSecrets: mapEnvReferences(executionRequest.EnvSecrets), } } @@ -464,6 +469,10 @@ func MapExecutionUpdateRequestToSpecExecutionRequest(executionRequest *testkube. executionRequest.JobTemplate, &request.JobTemplate, }, + { + executionRequest.JobTemplateReference, + &request.JobTemplateReference, + }, { executionRequest.PreRunScript, &request.PreRunScript, @@ -476,10 +485,26 @@ func MapExecutionUpdateRequestToSpecExecutionRequest(executionRequest *testkube. executionRequest.CronJobTemplate, &request.CronJobTemplate, }, + { + executionRequest.CronJobTemplateReference, + &request.CronJobTemplateReference, + }, + { + executionRequest.PvcTemplate, + &request.PvcTemplate, + }, + { + executionRequest.PvcTemplateReference, + &request.PvcTemplateReference, + }, { executionRequest.ScraperTemplate, &request.ScraperTemplate, }, + { + executionRequest.ScraperTemplateReference, + &request.ScraperTemplateReference, + }, } for _, field := range fields { diff --git a/pkg/mapper/testsuites/kube_openapi.go b/pkg/mapper/testsuites/kube_openapi.go index 1d2244bed67..75386cfb298 100644 --- a/pkg/mapper/testsuites/kube_openapi.go +++ b/pkg/mapper/testsuites/kube_openapi.go @@ -163,17 +163,24 @@ func MapExecutionRequestFromSpec(specExecutionRequest *testsuitesv3.TestSuiteExe } return &testkube.TestSuiteExecutionRequest{ - Name: specExecutionRequest.Name, - Labels: specExecutionRequest.Labels, - ExecutionLabels: specExecutionRequest.ExecutionLabels, - Namespace: specExecutionRequest.Namespace, - Variables: MergeVariablesAndParams(specExecutionRequest.Variables, nil), - SecretUUID: specExecutionRequest.SecretUUID, - Sync: specExecutionRequest.Sync, - HttpProxy: specExecutionRequest.HttpProxy, - HttpsProxy: specExecutionRequest.HttpsProxy, - Timeout: specExecutionRequest.Timeout, - CronJobTemplate: specExecutionRequest.CronJobTemplate, + Name: specExecutionRequest.Name, + Labels: specExecutionRequest.Labels, + ExecutionLabels: specExecutionRequest.ExecutionLabels, + Namespace: specExecutionRequest.Namespace, + Variables: MergeVariablesAndParams(specExecutionRequest.Variables, nil), + SecretUUID: specExecutionRequest.SecretUUID, + Sync: specExecutionRequest.Sync, + HttpProxy: specExecutionRequest.HttpProxy, + HttpsProxy: specExecutionRequest.HttpsProxy, + Timeout: specExecutionRequest.Timeout, + JobTemplate: specExecutionRequest.JobTemplate, + JobTemplateReference: specExecutionRequest.JobTemplateReference, + CronJobTemplate: specExecutionRequest.CronJobTemplate, + CronJobTemplateReference: specExecutionRequest.CronJobTemplateReference, + PvcTemplate: specExecutionRequest.PvcTemplate, + PvcTemplateReference: specExecutionRequest.PvcTemplateReference, + ScraperTemplate: specExecutionRequest.ScraperTemplate, + ScraperTemplateReference: specExecutionRequest.ScraperTemplateReference, } } @@ -287,10 +294,38 @@ func MapSpecExecutionRequestToExecutionUpdateRequest(request *testsuitesv3.TestS &request.HttpsProxy, &executionRequest.HttpsProxy, }, + { + &request.JobTemplate, + &executionRequest.JobTemplate, + }, + { + &request.JobTemplateReference, + &executionRequest.JobTemplateReference, + }, { &request.CronJobTemplate, &executionRequest.CronJobTemplate, }, + { + &request.CronJobTemplateReference, + &executionRequest.CronJobTemplateReference, + }, + { + &request.PvcTemplate, + &executionRequest.PvcTemplate, + }, + { + &request.PvcTemplateReference, + &executionRequest.PvcTemplateReference, + }, + { + &request.ScraperTemplate, + &executionRequest.ScraperTemplate, + }, + { + &request.ScraperTemplateReference, + &executionRequest.ScraperTemplateReference, + }, } for _, field := range fields { diff --git a/pkg/mapper/testsuites/openapi_kube.go b/pkg/mapper/testsuites/openapi_kube.go index 94e34e85294..75015201847 100644 --- a/pkg/mapper/testsuites/openapi_kube.go +++ b/pkg/mapper/testsuites/openapi_kube.go @@ -200,17 +200,24 @@ func MapExecutionRequestToSpecExecutionRequest(executionRequest *testkube.TestSu } return &testsuitesv3.TestSuiteExecutionRequest{ - Name: executionRequest.Name, - Labels: executionRequest.Labels, - ExecutionLabels: executionRequest.ExecutionLabels, - Namespace: executionRequest.Namespace, - Variables: MapCRDVariables(executionRequest.Variables), - SecretUUID: executionRequest.SecretUUID, - Sync: executionRequest.Sync, - HttpProxy: executionRequest.HttpProxy, - HttpsProxy: executionRequest.HttpsProxy, - Timeout: executionRequest.Timeout, - CronJobTemplate: executionRequest.CronJobTemplate, + Name: executionRequest.Name, + Labels: executionRequest.Labels, + ExecutionLabels: executionRequest.ExecutionLabels, + Namespace: executionRequest.Namespace, + Variables: MapCRDVariables(executionRequest.Variables), + SecretUUID: executionRequest.SecretUUID, + Sync: executionRequest.Sync, + HttpProxy: executionRequest.HttpProxy, + HttpsProxy: executionRequest.HttpsProxy, + Timeout: executionRequest.Timeout, + JobTemplate: executionRequest.JobTemplate, + JobTemplateReference: executionRequest.JobTemplateReference, + CronJobTemplate: executionRequest.CronJobTemplate, + CronJobTemplateReference: executionRequest.CronJobTemplateReference, + ScraperTemplate: executionRequest.ScraperTemplate, + ScraperTemplateReference: executionRequest.ScraperTemplateReference, + PvcTemplate: executionRequest.PvcTemplate, + PvcTemplateReference: executionRequest.PvcTemplateReference, } } @@ -318,10 +325,38 @@ func MapExecutionUpdateRequestToSpecExecutionRequest(executionRequest *testkube. executionRequest.HttpsProxy, &request.HttpsProxy, }, + { + executionRequest.JobTemplate, + &request.JobTemplate, + }, + { + executionRequest.JobTemplateReference, + &request.JobTemplateReference, + }, { executionRequest.CronJobTemplate, &request.CronJobTemplate, }, + { + executionRequest.CronJobTemplateReference, + &request.CronJobTemplateReference, + }, + { + executionRequest.ScraperTemplate, + &request.ScraperTemplate, + }, + { + executionRequest.ScraperTemplateReference, + &request.ScraperTemplateReference, + }, + { + executionRequest.PvcTemplate, + &request.PvcTemplate, + }, + { + executionRequest.PvcTemplateReference, + &request.PvcTemplateReference, + }, } for _, field := range fields { diff --git a/pkg/mapper/webhooks/mapper.go b/pkg/mapper/webhooks/mapper.go index 63e23755196..39b3ba63ae7 100644 --- a/pkg/mapper/webhooks/mapper.go +++ b/pkg/mapper/webhooks/mapper.go @@ -10,15 +10,16 @@ import ( // MapCRDToAPI maps Webhook CRD to OpenAPI spec Webhook func MapCRDToAPI(item executorv1.Webhook) testkube.Webhook { return testkube.Webhook{ - Name: item.Name, - Namespace: item.Namespace, - Uri: item.Spec.Uri, - Events: MapEventArrayToCRDEvents(item.Spec.Events), - Selector: item.Spec.Selector, - Labels: item.Labels, - PayloadObjectField: item.Spec.PayloadObjectField, - PayloadTemplate: item.Spec.PayloadTemplate, - Headers: item.Spec.Headers, + Name: item.Name, + Namespace: item.Namespace, + Uri: item.Spec.Uri, + Events: MapEventArrayToCRDEvents(item.Spec.Events), + Selector: item.Spec.Selector, + Labels: item.Labels, + PayloadObjectField: item.Spec.PayloadObjectField, + PayloadTemplate: item.Spec.PayloadTemplate, + PayloadTemplateReference: item.Spec.PayloadTemplateReference, + Headers: item.Spec.Headers, } } @@ -47,12 +48,13 @@ func MapAPIToCRD(request testkube.WebhookCreateRequest) executorv1.Webhook { Labels: request.Labels, }, Spec: executorv1.WebhookSpec{ - Uri: request.Uri, - Events: MapEventTypesToStringArray(request.Events), - Selector: request.Selector, - PayloadObjectField: request.PayloadObjectField, - PayloadTemplate: request.PayloadTemplate, - Headers: request.Headers, + Uri: request.Uri, + Events: MapEventTypesToStringArray(request.Events), + Selector: request.Selector, + PayloadObjectField: request.PayloadObjectField, + PayloadTemplate: request.PayloadTemplate, + PayloadTemplateReference: request.PayloadTemplateReference, + Headers: request.Headers, }, } } @@ -95,6 +97,10 @@ func MapUpdateToSpec(request testkube.WebhookUpdateRequest, webhook *executorv1. request.PayloadTemplate, &webhook.Spec.PayloadTemplate, }, + { + request.PayloadTemplateReference, + &webhook.Spec.PayloadTemplateReference, + }, } for _, field := range fields { @@ -148,6 +154,10 @@ func MapSpecToUpdate(webhook *executorv1.Webhook) (request testkube.WebhookUpdat &webhook.Spec.PayloadTemplate, &request.PayloadTemplate, }, + { + &webhook.Spec.PayloadTemplateReference, + &request.PayloadTemplateReference, + }, } for _, field := range fields { diff --git a/pkg/repository/storage/mongo.go b/pkg/repository/storage/mongo.go index 3c5ac512c55..94c754ad296 100644 --- a/pkg/repository/storage/mongo.go +++ b/pkg/repository/storage/mongo.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "os" "time" @@ -81,7 +80,7 @@ func getDocDBTLSConfig() (*tls.Config, error) { }() tlsConfig := new(tls.Config) - certs, err := ioutil.ReadFile(caFilePath) + certs, err := os.ReadFile(caFilePath) if err != nil { return nil, fmt.Errorf("could not read CA file: %s", err) } diff --git a/pkg/scheduler/service.go b/pkg/scheduler/service.go index 2952a8b654c..6939ab373c9 100644 --- a/pkg/scheduler/service.go +++ b/pkg/scheduler/service.go @@ -3,6 +3,7 @@ package scheduler import ( "go.uber.org/zap" + "github.com/kubeshop/testkube/pkg/event/bus" "github.com/kubeshop/testkube/pkg/repository/config" executorsv1 "github.com/kubeshop/testkube-operator/client/executors/v1" @@ -35,6 +36,7 @@ type Scheduler struct { configMap config.Repository configMapClient configmap.Interface testSuiteExecutionsClient testsuiteexecutionsclientv1.Interface + eventsBus bus.Bus } func NewScheduler( @@ -53,6 +55,7 @@ func NewScheduler( configMap config.Repository, configMapClient configmap.Interface, testSuiteExecutionsClient testsuiteexecutionsclientv1.Interface, + eventsBus bus.Bus, ) *Scheduler { return &Scheduler{ metrics: metrics, @@ -70,5 +73,6 @@ func NewScheduler( configMap: configMap, configMapClient: configMapClient, testSuiteExecutionsClient: testSuiteExecutionsClient, + eventsBus: eventsBus, } } diff --git a/pkg/scheduler/test_scheduler.go b/pkg/scheduler/test_scheduler.go index 8ffb3f0a1c5..0fb857ea530 100644 --- a/pkg/scheduler/test_scheduler.go +++ b/pkg/scheduler/test_scheduler.go @@ -290,6 +290,10 @@ func (s *Scheduler) getExecuteOptions(namespace, id string, request testkube.Exe test.ExecutionRequest.JobTemplate, &request.JobTemplate, }, + { + test.ExecutionRequest.JobTemplateReference, + &request.JobTemplateReference, + }, { test.ExecutionRequest.PreRunScript, &request.PreRunScript, @@ -302,6 +306,18 @@ func (s *Scheduler) getExecuteOptions(namespace, id string, request testkube.Exe test.ExecutionRequest.ScraperTemplate, &request.ScraperTemplate, }, + { + test.ExecutionRequest.ScraperTemplateReference, + &request.ScraperTemplateReference, + }, + { + test.ExecutionRequest.PvcTemplate, + &request.PvcTemplate, + }, + { + test.ExecutionRequest.PvcTemplateReference, + &request.PvcTemplateReference, + }, { test.ExecutionRequest.ArgsMode, &request.ArgsMode, @@ -479,26 +495,6 @@ func mergeContents(test testsv3.TestSpec, testSource testsourcev1.TestSourceSpec test.Content.Repository = &testsv3.Repository{} } - if test.Content.Repository.Type_ == "" { - test.Content.Repository.Type_ = testSource.Repository.Type_ - } - - if test.Content.Repository.Uri == "" { - test.Content.Repository.Uri = testSource.Repository.Uri - } - - if test.Content.Repository.Branch == "" { - test.Content.Repository.Branch = testSource.Repository.Branch - } - - if test.Content.Repository.Commit == "" { - test.Content.Repository.Commit = testSource.Repository.Commit - } - - if test.Content.Repository.Path == "" { - test.Content.Repository.Path = testSource.Repository.Path - } - if test.Content.Repository.UsernameSecret == nil && testSource.Repository.UsernameSecret != nil { test.Content.Repository.UsernameSecret = &testsv3.SecretRef{ Name: testSource.Repository.UsernameSecret.Name, @@ -513,16 +509,48 @@ func mergeContents(test testsv3.TestSpec, testSource testsourcev1.TestSourceSpec } } - if test.Content.Repository.WorkingDir == "" { - test.Content.Repository.WorkingDir = testSource.Repository.WorkingDir + if test.Content.Repository.AuthType == "" { + test.Content.Repository.AuthType = testsv3.GitAuthType(testSource.Repository.AuthType) } - if test.Content.Repository.CertificateSecret == "" { - test.Content.Repository.CertificateSecret = testSource.Repository.CertificateSecret + var fields = []struct { + source string + destination *string + }{ + { + testSource.Repository.Type_, + &test.Content.Repository.Type_, + }, + { + testSource.Repository.Uri, + &test.Content.Repository.Uri, + }, + { + testSource.Repository.Branch, + &test.Content.Repository.Branch, + }, + { + testSource.Repository.Commit, + &test.Content.Repository.Commit, + }, + { + testSource.Repository.Path, + &test.Content.Repository.Path, + }, + { + testSource.Repository.WorkingDir, + &test.Content.Repository.WorkingDir, + }, + { + testSource.Repository.CertificateSecret, + &test.Content.Repository.CertificateSecret, + }, } - if test.Content.Repository.AuthType == "" { - test.Content.Repository.AuthType = testsv3.GitAuthType(testSource.Repository.AuthType) + for _, field := range fields { + if *field.destination == "" { + *field.destination = field.source + } } } @@ -557,22 +585,34 @@ func mergeArtifacts(artifactBase *testkube.ArtifactRequest, artifactAdjust *test case artifactBase != nil && artifactAdjust == nil: return artifactBase default: - if artifactBase.StorageClassName == "" && artifactAdjust.StorageClassName != "" { - artifactBase.StorageClassName = artifactAdjust.StorageClassName - } + artifactBase.Dirs = append(artifactBase.Dirs, artifactAdjust.Dirs...) - if artifactBase.VolumeMountPath == "" && artifactAdjust.VolumeMountPath != "" { - artifactBase.VolumeMountPath = artifactAdjust.VolumeMountPath + if !artifactBase.OmitFolderPerExecution && artifactAdjust.OmitFolderPerExecution { + artifactBase.OmitFolderPerExecution = artifactAdjust.OmitFolderPerExecution } - artifactBase.Dirs = append(artifactBase.Dirs, artifactAdjust.Dirs...) - - if artifactBase.StorageBucket == "" && artifactAdjust.StorageBucket != "" { - artifactBase.StorageBucket = artifactAdjust.StorageBucket + var fields = []struct { + source string + destination *string + }{ + { + artifactAdjust.StorageClassName, + &artifactBase.StorageClassName, + }, + { + artifactAdjust.VolumeMountPath, + &artifactBase.VolumeMountPath, + }, + { + artifactAdjust.StorageBucket, + &artifactBase.StorageBucket, + }, } - if !artifactBase.OmitFolderPerExecution && artifactAdjust.OmitFolderPerExecution { - artifactBase.OmitFolderPerExecution = artifactAdjust.OmitFolderPerExecution + for _, field := range fields { + if *field.destination == "" && field.source != "" { + *field.destination = field.source + } } } @@ -591,20 +631,32 @@ func adjustContent(test testsv3.TestSpec, content *testkube.TestContentRequest) } if content.Repository != nil { - if content.Repository.Branch != "" { - test.Content.Repository.Branch = content.Repository.Branch - } - - if content.Repository.Commit != "" { - test.Content.Repository.Commit = content.Repository.Commit - } - - if content.Repository.Path != "" { - test.Content.Repository.Path = content.Repository.Path + var fields = []struct { + source string + destination *string + }{ + { + content.Repository.Branch, + &test.Content.Repository.Branch, + }, + { + content.Repository.Commit, + &test.Content.Repository.Commit, + }, + { + content.Repository.Path, + &test.Content.Repository.Path, + }, + { + content.Repository.WorkingDir, + &test.Content.Repository.WorkingDir, + }, } - if content.Repository.WorkingDir != "" { - test.Content.Repository.WorkingDir = content.Repository.WorkingDir + for _, field := range fields { + if field.source != "" { + *field.destination = field.source + } } } } diff --git a/pkg/scheduler/test_scheduler_test.go b/pkg/scheduler/test_scheduler_test.go index 5332c90b237..5e276bcd07c 100644 --- a/pkg/scheduler/test_scheduler_test.go +++ b/pkg/scheduler/test_scheduler_test.go @@ -94,17 +94,18 @@ func TestGetExecuteOptions(t *testing.T) { TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "cypress"}, Spec: v1.ExecutorSpec{ - Types: []string{mockExecutorTypes}, - ExecutorType: "job", - URI: "", - Image: "cypress", - Args: []string{}, - Command: []string{"run"}, - ImagePullSecrets: []k8sv1.LocalObjectReference{{Name: "secret-name1"}, {Name: "secret-name2"}}, - Features: nil, - ContentTypes: nil, - JobTemplate: "", - Meta: nil, + Types: []string{mockExecutorTypes}, + ExecutorType: "job", + URI: "", + Image: "cypress", + Args: []string{}, + Command: []string{"run"}, + ImagePullSecrets: []k8sv1.LocalObjectReference{{Name: "secret-name1"}, {Name: "secret-name2"}}, + Features: nil, + ContentTypes: nil, + JobTemplate: "", + JobTemplateReference: "", + Meta: nil, }, } @@ -130,17 +131,22 @@ func TestGetExecuteOptions(t *testing.T) { SecretEnvs: map[string]string{ "secretEnv": "secretVar", }, - Sync: false, - HttpProxy: "", - HttpsProxy: "", - Uploads: []string{}, - ActiveDeadlineSeconds: 10, - ArtifactRequest: &testkube.ArtifactRequest{}, - JobTemplate: "", - CronJobTemplate: "", - PreRunScript: "", - PostRunScript: "", - ScraperTemplate: "", + Sync: false, + HttpProxy: "", + HttpsProxy: "", + Uploads: []string{}, + ActiveDeadlineSeconds: 10, + ArtifactRequest: &testkube.ArtifactRequest{}, + JobTemplate: "", + JobTemplateReference: "", + CronJobTemplate: "", + CronJobTemplateReference: "", + PreRunScript: "", + PostRunScript: "", + ScraperTemplate: "", + ScraperTemplateReference: "", + PvcTemplate: "", + PvcTemplateReference: "", EnvConfigMaps: []testkube.EnvReference{ { Reference: &testkube.LocalObjectReference{ diff --git a/pkg/scheduler/testsuite_scheduler.go b/pkg/scheduler/testsuite_scheduler.go index 9edbf55afa7..d2123b6cdf2 100644 --- a/pkg/scheduler/testsuite_scheduler.go +++ b/pkg/scheduler/testsuite_scheduler.go @@ -11,6 +11,7 @@ import ( testsuitesv3 "github.com/kubeshop/testkube-operator/apis/testsuite/v3" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/event/bus" testsuiteexecutionsmapper "github.com/kubeshop/testkube/pkg/mapper/testsuiteexecutions" testsuitesmapper "github.com/kubeshop/testkube/pkg/mapper/testsuites" "github.com/kubeshop/testkube/pkg/telemetry" @@ -56,20 +57,56 @@ func (s *Scheduler) executeTestSuite(ctx context.Context, testSuite testkube.Tes request.SecretUUID = secretUUID if testSuite.ExecutionRequest != nil { - if request.Name == "" && testSuite.ExecutionRequest.Name != "" { - request.Name = testSuite.ExecutionRequest.Name - } - - if request.HttpProxy == "" && testSuite.ExecutionRequest.HttpProxy != "" { - request.HttpProxy = testSuite.ExecutionRequest.HttpProxy + if request.Timeout == 0 && testSuite.ExecutionRequest.Timeout != 0 { + request.Timeout = testSuite.ExecutionRequest.Timeout } - if request.HttpsProxy == "" && testSuite.ExecutionRequest.HttpsProxy != "" { - request.HttpsProxy = testSuite.ExecutionRequest.HttpsProxy + var fields = []struct { + source string + destination *string + }{ + { + testSuite.ExecutionRequest.Name, + &request.Name, + }, + { + testSuite.ExecutionRequest.HttpProxy, + &request.HttpProxy, + }, + { + testSuite.ExecutionRequest.HttpsProxy, + &request.HttpsProxy, + }, + { + testSuite.ExecutionRequest.JobTemplate, + &request.JobTemplate, + }, + { + testSuite.ExecutionRequest.JobTemplateReference, + &request.JobTemplateReference, + }, + { + testSuite.ExecutionRequest.ScraperTemplate, + &request.ScraperTemplate, + }, + { + testSuite.ExecutionRequest.ScraperTemplateReference, + &request.ScraperTemplateReference, + }, + { + testSuite.ExecutionRequest.PvcTemplate, + &request.PvcTemplate, + }, + { + testSuite.ExecutionRequest.PvcTemplateReference, + &request.PvcTemplateReference, + }, } - if request.Timeout == 0 && testSuite.ExecutionRequest.Timeout != 0 { - request.Timeout = testSuite.ExecutionRequest.Timeout + for _, field := range fields { + if *field.destination == "" && field.source != "" { + *field.destination = field.source + } } } @@ -105,22 +142,49 @@ func (s *Scheduler) runSteps(ctx context.Context, wg *sync.WaitGroup, testsuiteE s.logger.Infow("Running steps", "test", testsuiteExecution.Name) + statusChan := make(chan *testkube.TestSuiteExecutionStatus) hasFailedSteps := false cancelSteps := false var batchStepResult *testkube.TestSuiteBatchStepExecutionResult var abortionStatus *testkube.TestSuiteExecutionStatus - abortChan := make(chan *testkube.TestSuiteExecutionStatus) - go s.abortionCheck(ctx, testsuiteExecution, request.Timeout, abortChan) + go s.timeoutCheck(ctx, testsuiteExecution, request.Timeout) + + err := s.eventsBus.SubscribeTopic(bus.InternalSubscribeTopic, testsuiteExecution.Name, func(event testkube.Event) error { + s.logger.Infow("test suite abortion event in runSteps", "event", event) + if event.TestSuiteExecution != nil && + event.TestSuiteExecution.Id == testsuiteExecution.Id && + event.Type_ != nil && + (*event.Type_ == testkube.END_TESTSUITE_ABORTED_EventType || *event.Type_ == testkube.END_TESTSUITE_TIMEOUT_EventType) { + s.logger.Infow("Aborting test suite execution", "execution", testsuiteExecution.Id) + + status := testkube.TestSuiteExecutionStatusAborting + if *event.Type_ == testkube.END_TESTSUITE_TIMEOUT_EventType { + status = testkube.TestSuiteExecutionStatusTimeout + } + statusChan <- status + } + return nil + }) + + if err != nil { + s.logger.Errorw("error subscribing to event", "error", err) + } for i := range testsuiteExecution.ExecuteStepResults { batchStepResult = &testsuiteExecution.ExecuteStepResults[i] - select { - case abortionStatus = <-abortChan: - s.logger.Infow("Aborting test suite execution", "execution", testsuiteExecution.Id, "i", i) + s.logger.Debugw("Running batch step", "step", batchStepResult.Execute, "i", i) + select { + case status := <-statusChan: + abortionStatus = status cancelSteps = true + default: + } + + if cancelSteps { + s.logger.Infow("Aborting batch step", "step", batchStepResult.Execute, "i", i) for j := range batchStepResult.Execute { if batchStepResult.Execute[j].Execution != nil && batchStepResult.Execute[j].Execution.ExecutionResult != nil { batchStepResult.Execute[j].Execution.ExecutionResult.Abort() @@ -128,63 +192,58 @@ func (s *Scheduler) runSteps(ctx context.Context, wg *sync.WaitGroup, testsuiteE } testsuiteExecution.Status = testkube.TestSuiteExecutionStatusAborting - default: - s.logger.Debugw("Running batch step", "step", batchStepResult.Execute, "i", i) - - if cancelSteps { - s.logger.Debugw("Aborting batch step", "step", batchStepResult.Execute, "i", i) - - for j := range batchStepResult.Execute { - if batchStepResult.Execute[j].Execution != nil && batchStepResult.Execute[j].Execution.ExecutionResult != nil { - batchStepResult.Execute[j].Execution.ExecutionResult.Abort() - } - } - continue - } - - // start execution of given step for j := range batchStepResult.Execute { if batchStepResult.Execute[j].Execution != nil && batchStepResult.Execute[j].Execution.ExecutionResult != nil { - batchStepResult.Execute[j].Execution.ExecutionResult.InProgress() + batchStepResult.Execute[j].Execution.ExecutionResult.Abort() } } - err := s.testExecutionResults.Update(ctx, *testsuiteExecution) - if err != nil { - s.logger.Infow("Updating test execution", "error", err) + continue + } + + // start execution of given step + for j := range batchStepResult.Execute { + if batchStepResult.Execute[j].Execution != nil && batchStepResult.Execute[j].Execution.ExecutionResult != nil { + batchStepResult.Execute[j].Execution.ExecutionResult.InProgress() } + } + + err := s.testExecutionResults.Update(ctx, *testsuiteExecution) + if err != nil { + s.logger.Infow("Updating test execution", "error", err) + } - s.executeTestStep(ctx, *testsuiteExecution, request, batchStepResult) + s.executeTestStep(ctx, *testsuiteExecution, request, batchStepResult) - var results []*testkube.ExecutionResult - for j := range batchStepResult.Execute { - if batchStepResult.Execute[j].Execution != nil && batchStepResult.Execute[j].Execution.ExecutionResult != nil { - results = append(results, batchStepResult.Execute[j].Execution.ExecutionResult) - } + var results []*testkube.ExecutionResult + for j := range batchStepResult.Execute { + if batchStepResult.Execute[j].Execution != nil && batchStepResult.Execute[j].Execution.ExecutionResult != nil { + results = append(results, batchStepResult.Execute[j].Execution.ExecutionResult) } + } - s.logger.Debugw("Batch step execution result", "step", batchStepResult.Execute, "results", results) + s.logger.Debugw("Batch step execution result", "step", batchStepResult.Execute, "results", results) - err = s.testExecutionResults.Update(ctx, *testsuiteExecution) - if err != nil { - s.logger.Errorw("saving test suite execution results error", "error", err) + err = s.testExecutionResults.Update(ctx, *testsuiteExecution) + if err != nil { + s.logger.Errorw("saving test suite execution results error", "error", err) - hasFailedSteps = true - continue - } + hasFailedSteps = true + continue + } - for j := range batchStepResult.Execute { - if batchStepResult.Execute[j].IsFailed() { - hasFailedSteps = true - if batchStepResult.Step != nil && batchStepResult.Step.StopOnFailure { - cancelSteps = true - break - } + for j := range batchStepResult.Execute { + if batchStepResult.Execute[j].IsFailed() { + hasFailedSteps = true + if batchStepResult.Step != nil && batchStepResult.Step.StopOnFailure { + cancelSteps = true + break } } } } + s.logger.Infow("Finished running steps", "test", testsuiteExecution.Name, "hasFailedSteps", hasFailedSteps, "cancelSteps", cancelSteps, "status", testsuiteExecution.Status) if testsuiteExecution.Status != nil && *testsuiteExecution.Status == testkube.ABORTING_TestSuiteExecutionStatus { if abortionStatus != nil && *abortionStatus == testkube.TIMEOUT_TestSuiteExecutionStatus { @@ -204,10 +263,12 @@ func (s *Scheduler) runSteps(ctx context.Context, wg *sync.WaitGroup, testsuiteE s.metrics.IncExecuteTestSuite(*testsuiteExecution) - err := s.testExecutionResults.Update(ctx, *testsuiteExecution) + err = s.testExecutionResults.Update(ctx, *testsuiteExecution) if err != nil { s.logger.Errorw("saving final test suite execution result error", "error", err) } + + s.eventsBus.Unsubscribe(testsuiteExecution.Name) } func (s *Scheduler) runAfterEachStep(ctx context.Context, execution *testkube.TestSuiteExecution, wg *sync.WaitGroup) { @@ -286,52 +347,34 @@ func (s *Scheduler) runAfterEachStep(ctx context.Context, execution *testkube.Te } } -// abortionCheck is polling database to see if the user aborted the test suite execution -func (s *Scheduler) abortionCheck(ctx context.Context, testsuiteExecution *testkube.TestSuiteExecution, timeout int32, abortChan chan *testkube.TestSuiteExecutionStatus) { - s.logger.Infow("Abortion check started", "test", testsuiteExecution.Name, "timeout", timeout) +// timeoutCheck is checking if the testsuite has timed out +func (s *Scheduler) timeoutCheck(ctx context.Context, testsuiteExecution *testkube.TestSuiteExecution, timeout int32) { + s.logger.Infow("timeout check started", "test", testsuiteExecution.Name, "timeout", timeout) - ticker := time.NewTicker(abortionPollingInterval) timer := time.NewTimer(time.Duration(timeout) * time.Second) defer func() { timer.Stop() - ticker.Stop() }() for testsuiteExecution.Status == testkube.TestSuiteExecutionStatusRunning { select { case <-timer.C: - s.logger.Debugw("Abortion check timeout", "test", testsuiteExecution.Name) + s.logger.Debugw("testsuite timeout occured", "test suite", testsuiteExecution.Name) if timeout > 0 { - s.logger.Debugw("Aborting test suite execution due to timeout", "execution", testsuiteExecution.Id) + s.logger.Debugw("aborting test suite execution due to timeout", "execution", testsuiteExecution.Id) - abortChan <- testkube.TestSuiteExecutionStatusTimeout - return - } - case <-ticker.C: - if s.wasTestSuiteAborted(ctx, testsuiteExecution.Id) { - s.logger.Debugw("Aborting test suite execution", "execution", testsuiteExecution.Id) - - abortChan <- testkube.TestSuiteExecutionStatusAborted + err := s.eventsBus.PublishTopic(bus.InternalPublishTopic, testkube.NewEventEndTestSuiteTimeout(testsuiteExecution)) + if err != nil { + s.logger.Errorw("error publishing event", "error", err) + } return } } } - s.logger.Debugw("Abortion check, finished checking", "test", testsuiteExecution.Name) -} - -func (s *Scheduler) wasTestSuiteAborted(ctx context.Context, id string) bool { - execution, err := s.testExecutionResults.Get(ctx, id) - if err != nil { - s.logger.Errorw("getting test execution", "error", err) - return false - } - - s.logger.Debugw("Checking if test suite execution was aborted", "id", id, "status", execution.Status) - - return execution.Status != nil && *execution.Status == testkube.ABORTING_TestSuiteExecutionStatus + s.logger.Debugw("Timeout check, finished checking", "test", testsuiteExecution.Name) } func (s *Scheduler) executeTestStep(ctx context.Context, testsuiteExecution testkube.TestSuiteExecution, @@ -413,6 +456,12 @@ func (s *Scheduler) executeTestStep(ctx context.Context, testsuiteExecution test Type_: string(testkube.RunningContextTypeTestSuite), Context: testsuiteExecution.Name, }, + JobTemplate: request.JobTemplate, + JobTemplateReference: request.JobTemplateReference, + ScraperTemplate: request.ScraperTemplate, + ScraperTemplateReference: request.ScraperTemplateReference, + PvcTemplate: request.PvcTemplate, + PvcTemplateReference: request.PvcTemplateReference, } requests := make([]workerpool.Request[testkube.Test, testkube.ExecutionRequest, testkube.Execution], len(testTuples)) @@ -460,13 +509,30 @@ func (s *Scheduler) executeTestStep(ctx context.Context, testsuiteExecution test func (s *Scheduler) delayWithAbortionCheck(duration time.Duration, testSuiteId string, result *testkube.TestSuiteBatchStepExecutionResult) { timer := time.NewTimer(duration) - ticker := time.NewTicker(abortionPollingInterval) defer func() { timer.Stop() - ticker.Stop() }() + abortChan := make(chan bool) + + err := s.eventsBus.SubscribeTopic(bus.InternalSubscribeTopic, testSuiteId, func(event testkube.Event) error { + s.logger.Infow("test suite abortion event in delay handling", "event", event) + if event.TestSuiteExecution != nil && + event.TestSuiteExecution.Id == testSuiteId && + event.Type_ != nil && + *event.Type_ == testkube.END_TESTSUITE_ABORTED_EventType { + + s.logger.Infow("delay aborted", "testSuiteId", testSuiteId, "duration", duration) + abortChan <- true + } + return nil + }) + + if err != nil { + s.logger.Errorw("error subscribing to event", "error", err) + } + for { select { case <-timer.C: @@ -480,30 +546,26 @@ func (s *Scheduler) delayWithAbortionCheck(duration time.Duration, testSuiteId s } return - case <-ticker.C: - if s.wasTestSuiteAborted(context.Background(), testSuiteId) { - s.logger.Infow("delay aborted", "testSuiteId", testSuiteId, "duration", duration) - - for i := range result.Execute { - if result.Execute[i].Step != nil && result.Execute[i].Step.Delay != "" && - result.Execute[i].Execution != nil && result.Execute[i].Execution.ExecutionResult != nil { - delay, err := time.ParseDuration(result.Execute[i].Step.Delay) - if err != nil { - result.Execute[i].Err(err) - continue - } - - if delay < duration { - result.Execute[i].Execution.ExecutionResult.Success() - continue - } - - result.Execute[i].Execution.ExecutionResult.Abort() + case <-abortChan: + + for i := range result.Execute { + if result.Execute[i].Step != nil && result.Execute[i].Step.Delay != "" && + result.Execute[i].Execution != nil && result.Execute[i].Execution.ExecutionResult != nil { + delay, err := time.ParseDuration(result.Execute[i].Step.Delay) + if err != nil { + result.Execute[i].Err(err) + continue } - } - return + if delay < duration { + result.Execute[i].Execution.ExecutionResult.Success() + continue + } + + result.Execute[i].Execution.ExecutionResult.Abort() + } } + return } } } diff --git a/pkg/secret/client.go b/pkg/secret/client.go index fc863426ead..5b08339a131 100644 --- a/pkg/secret/client.go +++ b/pkg/secret/client.go @@ -20,7 +20,7 @@ const testkubeTestSecretLabel = "tests-secrets" type Interface interface { Get(id string) (map[string]string, error) GetObject(id string) (*v1.Secret, error) - List() (map[string]map[string]string, error) + List(all bool) (map[string]map[string]string, error) Create(id string, labels, stringData map[string]string) error Apply(id string, labels, stringData map[string]string) error Update(id string, labels, stringData map[string]string) error @@ -81,12 +81,17 @@ func (c *Client) GetObject(id string) (*v1.Secret, error) { } // List is a method to retrieve all existing secrets -func (c *Client) List() (map[string]map[string]string, error) { +func (c *Client) List(all bool) (map[string]map[string]string, error) { secretsClient := c.ClientSet.CoreV1().Secrets(c.Namespace) ctx := context.Background() + selector := "" + if !all { + selector = fmt.Sprintf("testkube=%s", testkubeTestSecretLabel) + } + secretList, err := secretsClient.List(ctx, metav1.ListOptions{ - LabelSelector: fmt.Sprintf("testkube=%s", testkubeTestSecretLabel)}) + LabelSelector: selector}) if err != nil { return nil, err } diff --git a/pkg/secret/mock_client.go b/pkg/secret/mock_client.go index f3e32086f73..41559e44496 100644 --- a/pkg/secret/mock_client.go +++ b/pkg/secret/mock_client.go @@ -121,18 +121,18 @@ func (mr *MockInterfaceMockRecorder) GetObject(arg0 interface{}) *gomock.Call { } // List mocks base method. -func (m *MockInterface) List() (map[string]map[string]string, error) { +func (m *MockInterface) List(arg0 bool) (map[string]map[string]string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "List") + ret := m.ctrl.Call(m, "List", arg0) ret0, _ := ret[0].(map[string]map[string]string) ret1, _ := ret[1].(error) return ret0, ret1 } // List indicates an expected call of List. -func (mr *MockInterfaceMockRecorder) List() *gomock.Call { +func (mr *MockInterfaceMockRecorder) List(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInterface)(nil).List)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInterface)(nil).List), arg0) } // Update mocks base method. diff --git a/pkg/triggers/executor_test.go b/pkg/triggers/executor_test.go index dd795361600..73a8321fb58 100644 --- a/pkg/triggers/executor_test.go +++ b/pkg/triggers/executor_test.go @@ -39,6 +39,7 @@ func TestExecute(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() + mockBus := bus.NewEventBusMock() mockResultRepository := result.NewMockRepository(mockCtrl) mockTestResultRepository := testresult.NewMockRepository(mockCtrl) @@ -80,17 +81,18 @@ func TestExecute(t *testing.T) { TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "cypress"}, Spec: v1.ExecutorSpec{ - Types: []string{mockExecutorTypes}, - ExecutorType: "job", - URI: "", - Image: "cypress", - Args: nil, - Command: []string{"run"}, - ImagePullSecrets: nil, - Features: nil, - ContentTypes: nil, - JobTemplate: "", - Meta: nil, + Types: []string{mockExecutorTypes}, + ExecutorType: "job", + URI: "", + Image: "cypress", + Args: nil, + Command: []string{"run"}, + ImagePullSecrets: nil, + Features: nil, + ContentTypes: nil, + JobTemplate: "", + JobTemplateReference: "", + Meta: nil, }, } mockExecutorsClient.EXPECT().GetByType(mockExecutorTypes).Return(&mockExecutorV1, nil).AnyTimes() @@ -115,6 +117,7 @@ func TestExecute(t *testing.T) { configMapConfig, mockConfigMapClient, mockTestSuiteExecutionsClient, + mockBus, ) s := &Service{ triggerStatus: make(map[statusKey]*triggerStatus), diff --git a/pkg/triggers/service_test.go b/pkg/triggers/service_test.go index 4d8ca820f4b..608421a790c 100644 --- a/pkg/triggers/service_test.go +++ b/pkg/triggers/service_test.go @@ -45,6 +45,7 @@ func TestService_Run(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() + mockBus := bus.NewEventBusMock() mockResultRepository := result.NewMockRepository(mockCtrl) mockTestResultRepository := testresult.NewMockRepository(mockCtrl) @@ -86,17 +87,18 @@ func TestService_Run(t *testing.T) { TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "cypress"}, Spec: executorv1.ExecutorSpec{ - Types: []string{mockExecutorTypes}, - ExecutorType: "job", - URI: "", - Image: "cypress", - Args: nil, - Command: []string{"run"}, - ImagePullSecrets: nil, - Features: nil, - ContentTypes: nil, - JobTemplate: "", - Meta: nil, + Types: []string{mockExecutorTypes}, + ExecutorType: "job", + URI: "", + Image: "cypress", + Args: nil, + Command: []string{"run"}, + ImagePullSecrets: nil, + Features: nil, + ContentTypes: nil, + JobTemplate: "", + JobTemplateReference: "", + Meta: nil, }, } mockExecutorsClient.EXPECT().GetByType(mockExecutorTypes).Return(&mockExecutorV1, nil).AnyTimes() @@ -129,6 +131,7 @@ func TestService_Run(t *testing.T) { configMapConfig, mockConfigMapClient, mockTestSuiteExecutionsClient, + mockBus, ) mockLeaseBackend := NewMockLeaseBackend(mockCtrl) diff --git a/test/artillery/executor-smoke/crd/crd.yaml b/test/artillery/executor-smoke/crd/crd.yaml index dbdc33b252c..558bc70c9bb 100644 --- a/test/artillery/executor-smoke/crd/crd.yaml +++ b/test/artillery/executor-smoke/crd/crd.yaml @@ -15,6 +15,7 @@ spec: path: test/artillery/executor-smoke/artillery-smoke-test.yaml executionRequest: jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 128m\n" + activeDeadlineSeconds: 60 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -33,4 +34,5 @@ spec: path: test/artillery/executor-smoke/artillery-smoke-test-negative.yaml executionRequest: negativeTest: true - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 128m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 128m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/container-executor/executor-smoke/crd/curl.yaml b/test/container-executor/executor-smoke/crd/curl.yaml index 46eb3bd52ac..0a1f2dbac0b 100644 --- a/test/container-executor/executor-smoke/crd/curl.yaml +++ b/test/container-executor/executor-smoke/crd/curl.yaml @@ -15,6 +15,7 @@ spec: type: basic value: https://testkube.kubeshop.io/ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 32Mi\n cpu: 32m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -34,3 +35,4 @@ spec: value: https://testkube.non.existing.url.example negativeTest: true jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 32Mi\n cpu: 32m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/container-executor/executor-smoke/crd/cypress.yaml b/test/container-executor/executor-smoke/crd/cypress.yaml index c6b1e422125..e01af334ac7 100644 --- a/test/container-executor/executor-smoke/crd/cypress.yaml +++ b/test/container-executor/executor-smoke/crd/cypress.yaml @@ -31,7 +31,7 @@ spec: dirs: - ./ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" - + activeDeadlineSeconds: 600 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -65,4 +65,5 @@ spec: volumeMountPath: /data/artifacts dirs: - ./ - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 \ No newline at end of file diff --git a/test/container-executor/executor-smoke/crd/k6.yaml b/test/container-executor/executor-smoke/crd/k6.yaml index 216a962553d..4c66abd728c 100644 --- a/test/container-executor/executor-smoke/crd/k6.yaml +++ b/test/container-executor/executor-smoke/crd/k6.yaml @@ -17,6 +17,7 @@ spec: executionRequest: args: ["run", "k6-smoke-test-without-envs.js"] jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 64Mi\n cpu: 128m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -36,4 +37,5 @@ spec: workingDir: test/k6/executor-tests executionRequest: args: ["run", "k6-smoke-test-without-envs.js"] - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 64Mi\n cpu: 128m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 64Mi\n cpu: 128m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/container-executor/executor-smoke/crd/playwright.yaml b/test/container-executor/executor-smoke/crd/playwright.yaml index 7d648be5d9b..4b61119076d 100644 --- a/test/container-executor/executor-smoke/crd/playwright.yaml +++ b/test/container-executor/executor-smoke/crd/playwright.yaml @@ -20,4 +20,5 @@ spec: volumeMountPath: /data/artifacts dirs: - ./ - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 \ No newline at end of file diff --git a/test/curl/executor-tests/crd/smoke.yaml b/test/curl/executor-tests/crd/smoke.yaml index b078536441f..043a8c7ce4c 100644 --- a/test/curl/executor-tests/crd/smoke.yaml +++ b/test/curl/executor-tests/crd/smoke.yaml @@ -15,6 +15,7 @@ spec: path: test/curl/executor-tests/curl-smoke-test.json executionRequest: jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 32Mi\n cpu: 32m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -33,4 +34,5 @@ spec: path: test/curl/executor-tests/curl-smoke-test-negative.json executionRequest: negativeTest: true - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 32Mi\n cpu: 32m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 32Mi\n cpu: 32m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/cypress/executor-tests/crd/crd.yaml b/test/cypress/executor-tests/crd/crd.yaml index 26cb71e5bd3..15c1b5aa98b 100644 --- a/test/cypress/executor-tests/crd/crd.yaml +++ b/test/cypress/executor-tests/crd/crd.yaml @@ -25,6 +25,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -55,6 +56,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -85,6 +87,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -113,6 +116,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -143,6 +147,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -173,6 +178,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -201,6 +207,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -229,6 +236,7 @@ spec: - --config - video=true jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -261,6 +269,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -289,6 +298,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 --- # cypress-default-executor-smoke-electron-testsource TestSource apiVersion: tests.testkube.io/v1 @@ -326,6 +336,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 --- # cypress-default-executor-smoke-electron-testsource-git-dir - TestSource apiVersion: tests.testkube.io/v1 @@ -363,6 +374,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -391,6 +403,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -421,6 +434,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -451,6 +465,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -469,6 +484,7 @@ spec: path: test/cypress/executor-tests/cypress-9 executionRequest: jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -492,6 +508,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -515,6 +532,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -536,6 +554,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -559,6 +578,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -582,6 +602,7 @@ spec: - --config - video=false jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -602,4 +623,5 @@ spec: args: - --some-incorrect-argument negativeTest: true - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 1Gi\n cpu: 1\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 1Gi\n cpu: 1\n" + activeDeadlineSeconds: 180 diff --git a/test/cypress/executor-tests/cypress-12/cypress/e2e/smoke.cy.js b/test/cypress/executor-tests/cypress-12/cypress/e2e/smoke.cy.js index 0ea71f2e66c..c6554cc2dec 100644 --- a/test/cypress/executor-tests/cypress-12/cypress/e2e/smoke.cy.js +++ b/test/cypress/executor-tests/cypress-12/cypress/e2e/smoke.cy.js @@ -1,7 +1,4 @@ -describe('Testkube website', () => { - it.skip('Open Testkube website', () => { - cy.visit('/') - }) +describe('Smoke test', () => { it(`Validate CYPRESS_CUSTOM_ENV ENV (${Cypress.env('CUSTOM_ENV')})`, () => { expect('CYPRESS_CUSTOM_ENV_value').to.equal(Cypress.env('CUSTOM_ENV')) //CYPRESS_CUSTOM_ENV - "cypress" prefix - auto-loaded from global ENVs }) diff --git a/test/cypress/executor-tests/cypress-12/cypress/e2e/smoke2.cy.js b/test/cypress/executor-tests/cypress-12/cypress/e2e/smoke2.cy.js new file mode 100644 index 00000000000..efbec61b67c --- /dev/null +++ b/test/cypress/executor-tests/cypress-12/cypress/e2e/smoke2.cy.js @@ -0,0 +1,5 @@ +describe('Smoke test 2', () => { + it(`expect 1=1`, () => { + expect('1').to.equal('1') + }) +}) diff --git a/test/cypress/executor-tests/cypress-without-envs/cypress/e2e/smoke-without-envs-2.cy.js b/test/cypress/executor-tests/cypress-without-envs/cypress/e2e/smoke-without-envs-2.cy.js new file mode 100644 index 00000000000..5ff6102fe8b --- /dev/null +++ b/test/cypress/executor-tests/cypress-without-envs/cypress/e2e/smoke-without-envs-2.cy.js @@ -0,0 +1,5 @@ +describe('Smoke test 2', () => { + it(`expect 2=2`, () => { + expect('2').to.equal('2') + }) +}) diff --git a/test/cypress/executor-tests/cypress-without-envs/cypress/e2e/smoke-without-envs.cy.js b/test/cypress/executor-tests/cypress-without-envs/cypress/e2e/smoke-without-envs.cy.js index 7d12ff498ec..f800dac1365 100644 --- a/test/cypress/executor-tests/cypress-without-envs/cypress/e2e/smoke-without-envs.cy.js +++ b/test/cypress/executor-tests/cypress-without-envs/cypress/e2e/smoke-without-envs.cy.js @@ -1,5 +1,5 @@ -describe('Testkube website', () => { - it('Open Testkube website', () => { - cy.visit('/') +describe('Smoke test 1', () => { + it(`expect 1=1`, () => { + expect('1').to.equal('1') }) }) diff --git a/test/dashboard-e2e/crd/crd.yaml b/test/dashboard-e2e/crd/crd.yaml index 18e59e65ad4..7d77fe64317 100644 --- a/test/dashboard-e2e/crd/crd.yaml +++ b/test/dashboard-e2e/crd/crd.yaml @@ -31,4 +31,5 @@ spec: artifactRequest: storageClassName: standard jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 4Gi\n cpu: 3\n" + activeDeadlineSeconds: 600 schedule: "15 */4 * * *" \ No newline at end of file diff --git a/test/ginkgo/executor-tests/crd/smoke.yaml b/test/ginkgo/executor-tests/crd/smoke.yaml index 1041b23b911..82d4d820ff7 100644 --- a/test/ginkgo/executor-tests/crd/smoke.yaml +++ b/test/ginkgo/executor-tests/crd/smoke.yaml @@ -15,6 +15,7 @@ spec: path: test/ginkgo/executor-tests/smoke executionRequest: jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 @@ -34,4 +35,5 @@ spec: path: test/ginkgo/executor-tests/smoke-negative executionRequest: negativeTest: true - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/gradle/executor-smoke/crd/crd.yaml b/test/gradle/executor-smoke/crd/crd.yaml index 44a9d0529ed..fa9ebd5c038 100644 --- a/test/gradle/executor-smoke/crd/crd.yaml +++ b/test/gradle/executor-smoke/crd/crd.yaml @@ -22,6 +22,7 @@ spec: value: "true" type: basic jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -45,6 +46,7 @@ spec: value: "true" type: basic jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -68,6 +70,7 @@ spec: value: "true" type: basic jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -91,6 +94,7 @@ spec: value: "true" type: basic jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -109,4 +113,5 @@ spec: path: examples/hello-gradle-jdk18 executionRequest: negativeTest: true - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/jmeter/executor-tests/crd/smoke.yaml b/test/jmeter/executor-tests/crd/smoke.yaml index 3d4aa8b3807..0b29bef8ce8 100644 --- a/test/jmeter/executor-tests/crd/smoke.yaml +++ b/test/jmeter/executor-tests/crd/smoke.yaml @@ -15,6 +15,7 @@ spec: path: test/jmeter/executor-tests/jmeter-executor-smoke.jmx executionRequest: jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -35,6 +36,7 @@ spec: args: - "jmeter-executor-smoke.jmx" jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -60,6 +62,7 @@ spec: args: - "-JURL_PROPERTY=testkube.io" jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -78,4 +81,5 @@ spec: path: test/jmeter/executor-tests/jmeter-executor-smoke-negative.jmx executionRequest: negativeTest: true - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/k6/executor-tests/crd/other.yaml b/test/k6/executor-tests/crd/other.yaml index cabc35acbd0..e60694fbeb6 100644 --- a/test/k6/executor-tests/crd/other.yaml +++ b/test/k6/executor-tests/crd/other.yaml @@ -24,4 +24,5 @@ spec: - -e - K6_ENV_FROM_PARAM=K6_ENV_FROM_PARAM_value - k6-smoke-test.js - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 128Mi\n cpu: 128m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 128Mi\n cpu: 128m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/k6/executor-tests/crd/smoke.yaml b/test/k6/executor-tests/crd/smoke.yaml index f776a09cf9b..60abb1f4b38 100644 --- a/test/k6/executor-tests/crd/smoke.yaml +++ b/test/k6/executor-tests/crd/smoke.yaml @@ -23,6 +23,7 @@ spec: - -e - K6_ENV_FROM_PARAM=K6_ENV_FROM_PARAM_value jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 128Mi\n cpu: 128m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -49,6 +50,7 @@ spec: - -e - K6_ENV_FROM_PARAM=K6_ENV_FROM_PARAM_value jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 128Mi\n cpu: 128m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -67,4 +69,5 @@ spec: path: test/k6/executor-tests/k6-smoke-test-negative.js executionRequest: negativeTest: true - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 128Mi\n cpu: 128m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 128Mi\n cpu: 128m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/kubepug/executor-smoke/crd/crd.yaml b/test/kubepug/executor-smoke/crd/crd.yaml index 0d997dcb270..2017bf5d99f 100644 --- a/test/kubepug/executor-smoke/crd/crd.yaml +++ b/test/kubepug/executor-smoke/crd/crd.yaml @@ -15,6 +15,7 @@ spec: path: test/kubepug/executor-smoke/crd/crd.yaml executionRequest: jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 64Mi\n cpu: 128m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -33,4 +34,5 @@ spec: path: test/kubepug/executor-smoke/kubepug-smoke-test-negative.yaml executionRequest: negativeTest: true - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 64Mi\n cpu: 128m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 64Mi\n cpu: 128m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/maven/executor-smoke/crd/crd.yaml b/test/maven/executor-smoke/crd/crd.yaml index ab896d5db2f..0d8c4a2f65f 100644 --- a/test/maven/executor-smoke/crd/crd.yaml +++ b/test/maven/executor-smoke/crd/crd.yaml @@ -22,6 +22,7 @@ spec: value: "true" type: basic jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 256m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -45,6 +46,7 @@ spec: value: "true" type: basic jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 256m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -63,4 +65,5 @@ spec: path: examples/hello-maven-jdk18 executionRequest: negativeTest: true - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 256m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 256m\n" + activeDeadlineSeconds: 180 \ No newline at end of file diff --git a/test/playwright/executor-tests/crd/crd.yaml b/test/playwright/executor-tests/crd/crd.yaml index e35835895c4..16b9bc30c96 100644 --- a/test/playwright/executor-tests/crd/crd.yaml +++ b/test/playwright/executor-tests/crd/crd.yaml @@ -14,4 +14,5 @@ spec: branch: main path: test/playwright/executor-tests/playwright-project executionRequest: - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 2Gi\n cpu: 2\n" + activeDeadlineSeconds: 600 \ No newline at end of file diff --git a/test/soapui/executor-smoke/crd/crd.yaml b/test/soapui/executor-smoke/crd/crd.yaml index 0738d84f40a..0468b0e3554 100644 --- a/test/soapui/executor-smoke/crd/crd.yaml +++ b/test/soapui/executor-smoke/crd/crd.yaml @@ -15,6 +15,7 @@ spec: path: test/soapui/executor-smoke/soapui-smoke-test.xml executionRequest: jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 --- apiVersion: tests.testkube.io/v3 kind: Test @@ -33,4 +34,5 @@ spec: path: test/soapui/executor-smoke/soapui-smoke-test-negative.xml executionRequest: negativeTest: true - jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 512m\n" \ No newline at end of file + jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 512m\n" + activeDeadlineSeconds: 180 \ No newline at end of file