From 9d0734479c505f02dd05f4a0fd8aba315260d979 Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Wed, 21 Jun 2023 16:36:55 +0300 Subject: [PATCH] feat: yaml content type (#4070) * feat: create yaml support * feat: test source yaml update * feat: update test suite yaml * feat: test trigger yaml update * docs: openapi yaml input * Revert "docs: openapi yaml input" This reverts commit 1710b0e9faeaa454093919492c34bca9ea4ad7b1. * Revert "Revert "docs: openapi yaml input"" This reverts commit 90f769785d4d6cb60e7d76dfb463a53ecda2fab2. * fix: update flow token * fix: lint version * fix: check linting * fix: lint version check * fix: change lint version * fix: test linter * fix: openapi linter * fix: var shadow --- .github/workflows/docusaurus.yml | 2 +- .github/workflows/lint.yaml | 5 +- .github/workflows/lint_pr.yaml | 2 +- api/v1/testkube.yaml | 47 +++-- docs/docs/articles/scheduling-tests.md | 2 +- internal/app/api/v1/executor.go | 54 +++-- internal/app/api/v1/tests.go | 89 +++++--- internal/app/api/v1/testsource.go | 62 ++++-- internal/app/api/v1/testsuites.go | 127 +++++++----- internal/app/api/v1/testtriggers.go | 98 ++++----- internal/app/api/v1/webhook.go | 37 ++-- pkg/mapper/executors/mapper.go | 79 ++++++++ pkg/mapper/tests/kube_openapi.go | 259 ++++++++++++++++++++++++ pkg/mapper/testsources/mapper.go | 100 ++++++++- pkg/mapper/testsuites/kube_openapi.go | 118 +++++++++++ pkg/mapper/testtriggers/kube_openapi.go | 15 ++ 16 files changed, 897 insertions(+), 199 deletions(-) diff --git a/.github/workflows/docusaurus.yml b/.github/workflows/docusaurus.yml index 112eff4acad..f26b289f99a 100644 --- a/.github/workflows/docusaurus.yml +++ b/.github/workflows/docusaurus.yml @@ -35,7 +35,7 @@ jobs: - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: - github_token: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.CI_BOT_TOKEN }} # Build output to publish to the `gh-pages` branch: publish_dir: ./docs/build # The following lines assign commit authorship to the official diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 4c516745b63..ae57e2f57a1 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -20,10 +20,9 @@ jobs: uses: actions/checkout@v3 - name: OpenAPI Lint Checks - uses: nwestfall/openapi-action@v1.0.1 + uses: char0n/swagger-editor-validate@v1 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - file: api/v1/testkube.yaml + definition-file: api/v1/testkube.yaml lint-go: name: Lint Go diff --git a/.github/workflows/lint_pr.yaml b/.github/workflows/lint_pr.yaml index ff3aa10a978..2ecda1bf35f 100644 --- a/.github/workflows/lint_pr.yaml +++ b/.github/workflows/lint_pr.yaml @@ -17,4 +17,4 @@ jobs: - name: Lint PR uses: amannn/action-semantic-pull-request@v3.4.6 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.CI_BOT_TOKEN }} \ No newline at end of file diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index ea2d346a170..502cceb2209 100644 --- a/api/v1/testkube.yaml +++ b/api/v1/testkube.yaml @@ -109,6 +109,9 @@ paths: application/json: schema: $ref: "#/components/schemas/TestTriggerUpsertRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" @@ -161,9 +164,6 @@ paths: type: array items: $ref: "#/components/schemas/TestTrigger" - text/yaml: - schema: - type: string 400: description: "problem with test trigger definition - probably some bad input occurs (invalid JSON body or similar)" content: @@ -274,6 +274,9 @@ paths: application/json: schema: $ref: "#/components/schemas/TestTriggerUpsertRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" @@ -281,9 +284,6 @@ paths: application/json: schema: $ref: "#/components/schemas/TestTrigger" - text/yaml: - schema: - type: string 400: description: "problem with test trigger definition - probably some bad input occurs (invalid JSON body or similar)" content: @@ -354,6 +354,9 @@ paths: application/json: schema: $ref: "#/components/schemas/TestSuiteUpsertRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" @@ -512,6 +515,9 @@ paths: application/json: schema: $ref: "#/components/schemas/TestSuiteUpdateRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" @@ -592,9 +598,6 @@ paths: application/json: schema: $ref: "#/components/schemas/ExecutionsMetrics" - text/yaml: - schema: - type: string 500: description: "problem with read information from storage" content: @@ -649,7 +652,7 @@ paths: - test-suites - api parameters: - - $ref: "#/components/parameters/Name" + - $ref: "#/components/parameters/ID" summary: "Abort all executions of a test suite" description: "Abort all test executions of a test suite" operationId: abortTestSuiteExecutions @@ -1460,6 +1463,9 @@ paths: application/json: schema: $ref: "#/components/schemas/TestUpsertRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" @@ -1543,6 +1549,9 @@ paths: application/json: schema: $ref: "#/components/schemas/TestUpdateRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" @@ -1712,9 +1721,6 @@ paths: application/json: schema: $ref: "#/components/schemas/ExecutionsMetrics" - text/yaml: - schema: - type: string 500: description: "problem with getting metrics" content: @@ -2051,6 +2057,9 @@ paths: application/json: schema: $ref: "#/components/schemas/ExecutorUpsertRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" @@ -2183,6 +2192,9 @@ paths: application/json: schema: $ref: "#/components/schemas/ExecutorUpdateRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" @@ -2301,6 +2313,9 @@ paths: application/json: schema: $ref: "#/components/schemas/WebhookCreateRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" @@ -2590,6 +2605,9 @@ paths: application/json: schema: $ref: "#/components/schemas/TestSourceUpsertRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" @@ -2694,6 +2712,9 @@ paths: application/json: schema: $ref: "#/components/schemas/TestSourceUpdateRequest" + text/yaml: + schema: + type: string responses: 200: description: "successful operation" diff --git a/docs/docs/articles/scheduling-tests.md b/docs/docs/articles/scheduling-tests.md index 0ca0ac43167..25a045d5e47 100644 --- a/docs/docs/articles/scheduling-tests.md +++ b/docs/docs/articles/scheduling-tests.md @@ -8,7 +8,7 @@ Testkube's schedule data format is the same that is used to define Kubernetes Cr Testkube uses the scheduling engine from Kubernetes Cron jobs. In fact, for each scheduled Test or Test Suite, a special cron job is created from this template: -. +. Technically, it is a callback to the Testkube API server method, launching either Test or Test Suite execution. This works similarly to scheduled Test and Test Suite executions done by external scheduling platforms. diff --git a/internal/app/api/v1/executor.go b/internal/app/api/v1/executor.go index 7d3c7e4333e..e68e51bfa83 100644 --- a/internal/app/api/v1/executor.go +++ b/internal/app/api/v1/executor.go @@ -1,12 +1,15 @@ package v1 import ( + "bytes" "fmt" "net/http" "github.com/gofiber/fiber/v2" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/yaml" + executorv1 "github.com/kubeshop/testkube-operator/apis/executor/v1" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/crd" executorsmapper "github.com/kubeshop/testkube/pkg/mapper/executors" @@ -15,20 +18,29 @@ import ( func (s TestkubeAPI) CreateExecutorHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to create executor" - var request testkube.ExecutorUpsertRequest - err := c.BodyParser(&request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse request: %w", errPrefix, err)) - } + var executor executorv1.Executor + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + executorSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(executorSpec), len(executorSpec)) + if err := decoder.Decode(&executor); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } + } else { + var request testkube.ExecutorUpsertRequest + 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 { - request.QuoteExecutorTextFields() - data, err := crd.GenerateYAML(crd.TemplateExecutor, []testkube.ExecutorUpsertRequest{request}) - return s.getCRDs(c, data, err) - } + if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + request.QuoteExecutorTextFields() + data, err := crd.GenerateYAML(crd.TemplateExecutor, []testkube.ExecutorUpsertRequest{request}) + return s.getCRDs(c, data, err) + } - executor := executorsmapper.MapAPIToCRD(request) - executor.Namespace = s.Namespace + executor = executorsmapper.MapAPIToCRD(request) + executor.Namespace = s.Namespace + } created, err := s.ExecutorsClient.Create(&executor) if err != nil { @@ -50,9 +62,20 @@ func (s TestkubeAPI) UpdateExecutorHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to update executor" var request testkube.ExecutorUpdateRequest - err := c.BodyParser(&request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse request: %w", errPrefix, err)) + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + var executor executorv1.Executor + executorSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(executorSpec), len(executorSpec)) + if err := decoder.Decode(&executor); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } + + request = executorsmapper.MapSpecToUpdate(&executor) + } 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 @@ -60,7 +83,6 @@ func (s TestkubeAPI) UpdateExecutorHandler() fiber.Handler { name = *request.Name } errPrefix = errPrefix + " " + name - // we need to get resource first and load its metadata.ResourceVersion executor, err := s.ExecutorsClient.Get(name) if err != nil { diff --git a/internal/app/api/v1/tests.go b/internal/app/api/v1/tests.go index 4b6a4abecc2..48ead3108e9 100644 --- a/internal/app/api/v1/tests.go +++ b/internal/app/api/v1/tests.go @@ -1,6 +1,7 @@ package v1 import ( + "bytes" "context" "fmt" "net/http" @@ -11,6 +12,7 @@ import ( "github.com/gofiber/fiber/v2" "go.mongodb.org/mongo-driver/mongo" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/yaml" testsv3 "github.com/kubeshop/testkube-operator/apis/tests/v3" "github.com/kubeshop/testkube-operator/client/tests/v3" @@ -368,41 +370,54 @@ func (s TestkubeAPI) ListTestWithExecutionsHandler() fiber.Handler { func (s TestkubeAPI) CreateTestHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to create test" + var test *testsv3.Test + var secrets map[string]string + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + test = &testsv3.Test{} + testSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testSpec), len(testSpec)) + if err := decoder.Decode(&test); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } - var request testkube.TestUpsertRequest - err := c.BodyParser(&request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: failed to unmarshal request: %w", errPrefix, err)) - } - err = testkube.ValidateUpsertTestRequest(request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: invalid test: %w", errPrefix, err)) - } - - errPrefix = errPrefix + " " + request.Name - if request.ExecutionRequest != nil && request.ExecutionRequest.Args != nil { - request.ExecutionRequest.Args, err = testkube.PrepareExecutorArgs(request.ExecutionRequest.Args) + errPrefix = errPrefix + " " + test.Name + } else { + var request testkube.TestUpsertRequest + err := c.BodyParser(&request) if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not prepare executor args: %w", errPrefix, err)) + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: failed to unmarshal request: %w", errPrefix, err)) } - } - if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { - request.QuoteTestTextFields() - data, err := crd.GenerateYAML(crd.TemplateTest, []testkube.TestUpsertRequest{request}) + err = testkube.ValidateUpsertTestRequest(request) if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not build CRD: %w", errPrefix, err)) + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: invalid test: %w", errPrefix, err)) + } + + errPrefix = errPrefix + " " + request.Name + if request.ExecutionRequest != nil && request.ExecutionRequest.Args != nil { + request.ExecutionRequest.Args, err = testkube.PrepareExecutorArgs(request.ExecutionRequest.Args) + if err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not prepare executor args: %w", errPrefix, err)) + } } - return s.getCRDs(c, data, err) - } - s.Log.Infow("creating test", "request", request) + if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + request.QuoteTestTextFields() + data, err := crd.GenerateYAML(crd.TemplateTest, []testkube.TestUpsertRequest{request}) + if err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not build CRD: %w", errPrefix, err)) + } - test := testsmapper.MapUpsertToSpec(request) - test.Namespace = s.Namespace - var secrets map[string]string - if request.Content != nil && request.Content.Repository != nil { - secrets = createTestSecretsData(request.Content.Repository.Username, request.Content.Repository.Token) + return s.getCRDs(c, data, err) + } + + s.Log.Infow("creating test", "request", request) + + test = testsmapper.MapUpsertToSpec(request) + test.Namespace = s.Namespace + if request.Content != nil && request.Content.Repository != nil { + secrets = createTestSecretsData(request.Content.Repository.Username, request.Content.Repository.Token) + } } createdTest, err := s.TestsClient.Create(test, tests.Option{Secrets: secrets}) @@ -423,17 +438,29 @@ func (s TestkubeAPI) UpdateTestHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to update test" var request testkube.TestUpdateRequest - err := c.BodyParser(&request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse request: %w", errPrefix, err)) + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + var test testsv3.Test + testSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testSpec), len(testSpec)) + if err := decoder.Decode(&test); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } + + request = testsmapper.MapSpecToUpdate(&test) + } 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 } - err = testkube.ValidateUpdateTestRequest(request) + err := testkube.ValidateUpdateTestRequest(request) if err != nil { return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: invalid test: %w", errPrefix, err)) } diff --git a/internal/app/api/v1/testsource.go b/internal/app/api/v1/testsource.go index 27390fcdcba..8dc51d08838 100644 --- a/internal/app/api/v1/testsource.go +++ b/internal/app/api/v1/testsource.go @@ -1,11 +1,13 @@ package v1 import ( + "bytes" "fmt" "net/http" "github.com/gofiber/fiber/v2" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/yaml" testsourcev1 "github.com/kubeshop/testkube-operator/apis/testsource/v1" "github.com/kubeshop/testkube-operator/client/testsources/v1" @@ -19,26 +21,35 @@ import ( func (s TestkubeAPI) CreateTestSourceHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to create test source" - var request testkube.TestSourceUpsertRequest - err := c.BodyParser(&request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse request: %s", errPrefix, err)) - } - - if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { - if request.Data != "" { - request.Data = fmt.Sprintf("%q", request.Data) + var testSource testsourcev1.TestSource + var secrets map[string]string + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + testSourceSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testSourceSpec), len(testSourceSpec)) + if err := decoder.Decode(&testSource); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } + } else { + var request testkube.TestSourceUpsertRequest + err := c.BodyParser(&request) + if err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse json request: %s", errPrefix, err)) } - data, err := crd.GenerateYAML(crd.TemplateTestSource, []testkube.TestSourceUpsertRequest{request}) - return s.getCRDs(c, data, err) - } + if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + if request.Data != "" { + request.Data = fmt.Sprintf("%q", request.Data) + } - testSource := testsourcesmapper.MapAPIToCRD(request) - testSource.Namespace = s.Namespace - var secrets map[string]string - if request.Repository != nil { - secrets = createTestSecretsData(request.Repository.Username, request.Repository.Token) + data, err := crd.GenerateYAML(crd.TemplateTestSource, []testkube.TestSourceUpsertRequest{request}) + return s.getCRDs(c, data, err) + } + + testSource = testsourcesmapper.MapAPIToCRD(request) + testSource.Namespace = s.Namespace + if request.Repository != nil { + secrets = createTestSecretsData(request.Repository.Username, request.Repository.Token) + } } created, err := s.TestSourcesClient.Create(&testSource, testsources.Option{Secrets: secrets}) @@ -55,9 +66,20 @@ func (s TestkubeAPI) UpdateTestSourceHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to update test source" var request testkube.TestSourceUpdateRequest - err := c.BodyParser(&request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse request: %s", errPrefix, err)) + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + var testSource testsourcev1.TestSource + testSourceSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testSourceSpec), len(testSourceSpec)) + if err := decoder.Decode(&testSource); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } + + request = testsourcesmapper.MapSpecToUpdate(&testSource) + } else { + err := c.BodyParser(&request) + if err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse json jrequest: %s", errPrefix, err)) + } } var name string diff --git a/internal/app/api/v1/testsuites.go b/internal/app/api/v1/testsuites.go index 8d072a1105f..aa59845bcf6 100644 --- a/internal/app/api/v1/testsuites.go +++ b/internal/app/api/v1/testsuites.go @@ -1,6 +1,7 @@ package v1 import ( + "bytes" "context" "encoding/json" "fmt" @@ -12,6 +13,7 @@ import ( "github.com/gofiber/fiber/v2" "go.mongodb.org/mongo-driver/mongo" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/yaml" testsuitesv3 "github.com/kubeshop/testkube-operator/apis/testsuite/v3" "github.com/kubeshop/testkube/pkg/api/v1/testkube" @@ -30,46 +32,58 @@ import ( func (s TestkubeAPI) CreateTestSuiteHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to create test suite" - var request testkube.TestSuiteUpsertRequest - data := c.Body() - if string(c.Request().Header.ContentType()) != mediaTypeJSON { - return s.Error(c, http.StatusBadRequest, fiber.ErrUnprocessableEntity) - } + var testSuite testsuitesv3.TestSuite + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + testSuiteSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testSuiteSpec), len(testSuiteSpec)) + if err := decoder.Decode(&testSuite); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } - if err := json.Unmarshal(data, &request); err != nil { - s.Log.Warnw("could not parse request", "error", err) - } - errPrefix = errPrefix + " " + request.Name + errPrefix = errPrefix + " " + testSuite.Name + } else { + var request testkube.TestSuiteUpsertRequest + data := c.Body() + if string(c.Request().Header.ContentType()) != mediaTypeJSON { + return s.Error(c, http.StatusBadRequest, fiber.ErrUnprocessableEntity) + } - emptyBatch := true - for _, step := range request.Steps { - if len(step.Execute) != 0 { - emptyBatch = false - break + err := json.Unmarshal(data, &request) + if err != nil { + s.Log.Warnw("could not parse json request", "error", err) } - } + errPrefix = errPrefix + " " + request.Name - if emptyBatch { - var requestV2 testkube.TestSuiteUpsertRequestV2 - if err := json.Unmarshal(data, &requestV2); err != nil { - return s.Error(c, http.StatusBadRequest, err) + emptyBatch := true + for _, step := range request.Steps { + if len(step.Execute) != 0 { + emptyBatch = false + break + } } - request = *requestV2.ToTestSuiteUpsertRequest() - } + if emptyBatch { + var requestV2 testkube.TestSuiteUpsertRequestV2 + if err := json.Unmarshal(data, &requestV2); err != nil { + return s.Error(c, http.StatusBadRequest, err) + } - if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { - request.QuoteTestSuiteTextFields() - data, err := crd.GenerateYAML(crd.TemplateTestSuite, []testkube.TestSuiteUpsertRequest{request}) - return s.getCRDs(c, data, err) - } + request = *requestV2.ToTestSuiteUpsertRequest() + } - testSuite, err := testsuitesmapper.MapTestSuiteUpsertRequestToTestCRD(request) - if err != nil { - return s.Error(c, http.StatusBadRequest, err) - } + if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + request.QuoteTestSuiteTextFields() + data, err := crd.GenerateYAML(crd.TemplateTestSuite, []testkube.TestSuiteUpsertRequest{request}) + return s.getCRDs(c, data, err) + } - testSuite.Namespace = s.Namespace + testSuite, err = testsuitesmapper.MapTestSuiteUpsertRequestToTestCRD(request) + if err != nil { + return s.Error(c, http.StatusBadRequest, err) + } + + testSuite.Namespace = s.Namespace + } s.Log.Infow("creating test suite", "testSuite", testSuite) @@ -90,33 +104,44 @@ func (s TestkubeAPI) CreateTestSuiteHandler() fiber.Handler { func (s TestkubeAPI) UpdateTestSuiteHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to update test suite" - var request testkube.TestSuiteUpdateRequest - data := c.Body() - if string(c.Request().Header.ContentType()) != mediaTypeJSON { - return s.Error(c, http.StatusBadRequest, fiber.ErrUnprocessableEntity) - } + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + var testSuite testsuitesv3.TestSuite + testSuiteSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testSuiteSpec), len(testSuiteSpec)) + if err := decoder.Decode(&testSuite); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } - if err := json.Unmarshal(data, &request); err != nil { - s.Log.Warnw("could not parse request", "error", err) - } + request = testsuitesmapper.MapTestSuiteTestCRDToUpdateRequest(&testSuite) + } else { + data := c.Body() + if string(c.Request().Header.ContentType()) != mediaTypeJSON { + return s.Error(c, http.StatusBadRequest, fiber.ErrUnprocessableEntity) + } - if request.Steps != nil { - emptyBatch := true - for _, step := range *request.Steps { - if len(step.Execute) != 0 { - emptyBatch = false - break - } + err := json.Unmarshal(data, &request) + if err != nil { + s.Log.Warnw("could not parse json request", "error", err) } - if emptyBatch { - var requestV2 testkube.TestSuiteUpdateRequestV2 - if err := json.Unmarshal(data, &requestV2); err != nil { - return s.Error(c, http.StatusBadRequest, err) + if request.Steps != nil { + emptyBatch := true + for _, step := range *request.Steps { + if len(step.Execute) != 0 { + emptyBatch = false + break + } } - request = *requestV2.ToTestSuiteUpdateRequest() + if emptyBatch { + var requestV2 testkube.TestSuiteUpdateRequestV2 + if err := json.Unmarshal(data, &requestV2); err != nil { + return s.Error(c, http.StatusBadRequest, err) + } + + request = *requestV2.ToTestSuiteUpdateRequest() + } } } diff --git a/internal/app/api/v1/testtriggers.go b/internal/app/api/v1/testtriggers.go index eeb7f80dd33..6576a229d33 100644 --- a/internal/app/api/v1/testtriggers.go +++ b/internal/app/api/v1/testtriggers.go @@ -1,26 +1,24 @@ package v1 import ( + "bytes" "fmt" "net/http" "strings" - "github.com/kubeshop/testkube/pkg/utils" - - testtriggersv1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" - - "k8s.io/apimachinery/pkg/labels" - "github.com/gofiber/fiber/v2" k8serrors "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/yaml" + testtriggersv1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/crd" "github.com/kubeshop/testkube/pkg/keymap/triggers" triggerskeymapmapper "github.com/kubeshop/testkube/pkg/mapper/keymap/triggers" testtriggersmapper "github.com/kubeshop/testkube/pkg/mapper/testtriggers" - - "github.com/kubeshop/testkube/pkg/api/v1/testkube" - "github.com/kubeshop/testkube/pkg/crd" + "github.com/kubeshop/testkube/pkg/utils" ) const testTriggerMaxNameLength = 57 @@ -29,22 +27,36 @@ const testTriggerMaxNameLength = 57 func (s *TestkubeAPI) CreateTestTriggerHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to create test trigger" + var testTrigger testtriggersv1.TestTrigger + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + testTriggerSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testTriggerSpec), len(testTriggerSpec)) + if err := decoder.Decode(&testTriggerSpec); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } + } else { + var request testkube.TestTriggerUpsertRequest + 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 request testkube.TestTriggerUpsertRequest - err := c.BodyParser(&request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse request: %w", errPrefix, err)) - } + testTrigger = testtriggersmapper.MapTestTriggerUpsertRequestToTestTriggerCRD(request) + // default namespace if not defined in upsert request + if testTrigger.Namespace == "" { + testTrigger.Namespace = s.Namespace + } + // default trigger name if not defined in upsert request + if testTrigger.Name == "" { + testTrigger.Name = generateTestTriggerName(&testTrigger) + } - testTrigger := testtriggersmapper.MapTestTriggerUpsertRequestToTestTriggerCRD(request) - // default namespace if not defined in upsert request - if testTrigger.Namespace == "" { - testTrigger.Namespace = s.Namespace - } - // default trigger name if not defined in upsert request - if testTrigger.Name == "" { - testTrigger.Name = generateTestTriggerName(&testTrigger) + if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + data, err := crd.GenerateYAML(crd.TemplateTestTrigger, []testkube.TestTrigger{testtriggersmapper.MapCRDToAPI(&testTrigger)}) + return s.getCRDs(c, data, err) + } } + errPrefix = errPrefix + " " + testTrigger.Name s.Log.Infow("creating test trigger", "testTrigger", testTrigger) @@ -58,15 +70,7 @@ func (s *TestkubeAPI) CreateTestTriggerHandler() fiber.Handler { } c.Status(http.StatusCreated) - - apiTestTrigger := testtriggersmapper.MapCRDToAPI(created) - - if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { - data, err := crd.GenerateYAML(crd.TemplateTestTrigger, []testkube.TestTrigger{apiTestTrigger}) - return s.getCRDs(c, data, err) - } - - return c.JSON(apiTestTrigger) + return c.JSON(testtriggersmapper.MapCRDToAPI(created)) } } @@ -74,11 +78,21 @@ func (s *TestkubeAPI) CreateTestTriggerHandler() fiber.Handler { func (s *TestkubeAPI) UpdateTestTriggerHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to update test trigger" - var request testkube.TestTriggerUpsertRequest - err := c.BodyParser(&request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse request: %w", errPrefix, err)) + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + var testTrigger testtriggersv1.TestTrigger + testTriggerSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testTriggerSpec), len(testTriggerSpec)) + if err := decoder.Decode(&testTrigger); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } + + request = testtriggersmapper.MapTestTriggerCRDToTestTriggerUpsertRequest(testTrigger) + } 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)) + } } namespace := s.Namespace @@ -108,14 +122,7 @@ func (s *TestkubeAPI) UpdateTestTriggerHandler() fiber.Handler { return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not update test trigger: %w", errPrefix, err)) } - apiTestTrigger := testtriggersmapper.MapCRDToAPI(testTrigger) - - if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { - data, err := crd.GenerateYAML(crd.TemplateTestTrigger, []testkube.TestTrigger{apiTestTrigger}) - return s.getCRDs(c, data, err) - } - - return c.JSON(apiTestTrigger) + return c.JSON(testtriggersmapper.MapCRDToAPI(testTrigger)) } } @@ -181,11 +188,6 @@ func (s *TestkubeAPI) BulkUpdateTestTriggersHandler() fiber.Handler { s.Metrics.IncBulkUpdateTestTrigger(nil) - if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { - data, err := crd.GenerateYAML(crd.TemplateTestTrigger, testTriggers) - return s.getCRDs(c, data, err) - } - return c.JSON(testTriggers) } } diff --git a/internal/app/api/v1/webhook.go b/internal/app/api/v1/webhook.go index 871d55df234..26972b4f438 100644 --- a/internal/app/api/v1/webhook.go +++ b/internal/app/api/v1/webhook.go @@ -1,12 +1,15 @@ package v1 import ( + "bytes" "fmt" "net/http" "github.com/gofiber/fiber/v2" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/yaml" + executorv1 "github.com/kubeshop/testkube-operator/apis/executor/v1" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/crd" webhooksmapper "github.com/kubeshop/testkube/pkg/mapper/webhooks" @@ -15,25 +18,33 @@ import ( func (s TestkubeAPI) CreateWebhookHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to create webhook" + var webhook executorv1.Webhook + if string(c.Request().Header.ContentType()) == mediaTypeYAML { + webhookSpec := string(c.Body()) + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(webhookSpec), len(webhookSpec)) + if err := decoder.Decode(&webhookSpec); err != nil { + return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) + } + } else { + var request testkube.WebhookCreateRequest + 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 request testkube.WebhookCreateRequest - err := c.BodyParser(&request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse request: %w", errPrefix, err)) - } + if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + if request.PayloadTemplate != "" { + request.PayloadTemplate = fmt.Sprintf("%q", request.PayloadTemplate) + } - if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { - if request.PayloadTemplate != "" { - request.PayloadTemplate = fmt.Sprintf("%q", request.PayloadTemplate) + data, err := crd.GenerateYAML(crd.TemplateWebhook, []testkube.WebhookCreateRequest{request}) + return s.getCRDs(c, data, err) } - data, err := crd.GenerateYAML(crd.TemplateWebhook, []testkube.WebhookCreateRequest{request}) - return s.getCRDs(c, data, err) + webhook = webhooksmapper.MapAPIToCRD(request) + webhook.Namespace = s.Namespace } - webhook := webhooksmapper.MapAPIToCRD(request) - webhook.Namespace = s.Namespace - created, err := s.WebhooksClient.Create(&webhook) if err != nil { return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not create webhook: %w", errPrefix, err)) diff --git a/pkg/mapper/executors/mapper.go b/pkg/mapper/executors/mapper.go index 95725798cee..33c9f500e9e 100644 --- a/pkg/mapper/executors/mapper.go +++ b/pkg/mapper/executors/mapper.go @@ -249,3 +249,82 @@ func MapUpdateToSpec(request testkube.ExecutorUpdateRequest, executor *executorv return executor } + +// MapSpecToUpdate maps Executor CRD to ExecutorUpdate Request to spec +func MapSpecToUpdate(executor *executorv1.Executor) (request testkube.ExecutorUpdateRequest) { + var fields = []struct { + source *string + destination **string + }{ + { + &executor.Name, + &request.Name, + }, + { + &executor.Namespace, + &request.Namespace, + }, + { + &executor.Spec.Image, + &request.Image, + }, + { + &executor.Spec.URI, + &request.Uri, + }, + { + &executor.Spec.JobTemplate, + &request.JobTemplate, + }, + } + + for _, field := range fields { + *field.destination = field.source + } + + request.ExecutorType = (*string)(&executor.Spec.ExecutorType) + + var slices = []struct { + source *[]string + destination **[]string + }{ + { + &executor.Spec.Command, + &request.Command, + }, + { + &executor.Spec.Args, + &request.Args, + }, + { + &executor.Spec.Types, + &request.Types, + }, + } + + for _, slice := range slices { + *slice.destination = slice.source + } + + request.Labels = &executor.Labels + + imagePullSecrets := mapImagePullSecretsToAPI(executor.Spec.ImagePullSecrets) + request.ImagePullSecrets = &imagePullSecrets + + features := MapFeaturesToAPI(executor.Spec.Features) + request.Features = &features + + contentTypes := MapContentTypesToAPI(executor.Spec.ContentTypes) + request.ContentTypes = &contentTypes + + if executor.Spec.Meta != nil { + executorMeta := &testkube.ExecutorMetaUpdate{ + IconURI: &executor.Spec.Meta.IconURI, + DocsURI: &executor.Spec.Meta.DocsURI, + Tooltips: &executor.Spec.Meta.Tooltips, + } + request.Meta = &(executorMeta) + } + + return request +} diff --git a/pkg/mapper/tests/kube_openapi.go b/pkg/mapper/tests/kube_openapi.go index 4570442294c..913721ca275 100644 --- a/pkg/mapper/tests/kube_openapi.go +++ b/pkg/mapper/tests/kube_openapi.go @@ -206,3 +206,262 @@ func MapEnvReferences(envs []testsv3.EnvReference) []testkube.EnvReference { return res } + +// MapSpecToUpdate maps Test CRD spec to TestUpdateRequest +func MapSpecToUpdate(test *testsv3.Test) (request testkube.TestUpdateRequest) { + var fields = []struct { + source *string + destination **string + }{ + { + &test.Name, + &request.Name, + }, + { + &test.Namespace, + &request.Namespace, + }, + { + &test.Spec.Type_, + &request.Type_, + }, + { + &test.Spec.Source, + &request.Source, + }, + { + &test.Spec.Schedule, + &request.Schedule, + }, + } + + for _, field := range fields { + *field.destination = field.source + } + + if test.Spec.Content != nil { + content := MapSpecContentToUpdateContent(test.Spec.Content) + request.Content = &content + } + + if test.Spec.ExecutionRequest != nil { + executionRequest := MapSpecExecutionRequestToExecutionUpdateRequest(test.Spec.ExecutionRequest) + request.ExecutionRequest = &executionRequest + } + + request.Labels = &test.Labels + + request.Uploads = &test.Spec.Uploads + + return request +} + +// MapSpecContentToUpdateContent maps TestContent CRD spec to TestUpdateContent OpenAPI spec +func MapSpecContentToUpdateContent(testContent *testsv3.TestContent) (content *testkube.TestContentUpdate) { + content = &testkube.TestContentUpdate{} + + var fields = []struct { + source *string + destination **string + }{ + { + &testContent.Data, + &content.Data, + }, + { + &testContent.Uri, + &content.Uri, + }, + } + + for _, field := range fields { + *field.destination = field.source + } + + content.Type_ = (*string)(&testContent.Type_) + + if testContent.Repository != nil { + repository := &testkube.RepositoryUpdate{} + content.Repository = &repository + + var fields = []struct { + source *string + destination **string + }{ + { + &testContent.Repository.Type_, + &(*content.Repository).Type_, + }, + { + &testContent.Repository.Uri, + &(*content.Repository).Uri, + }, + { + &testContent.Repository.Branch, + &(*content.Repository).Branch, + }, + { + &testContent.Repository.Commit, + &(*content.Repository).Commit, + }, + { + &testContent.Repository.Path, + &(*content.Repository).Path, + }, + { + &testContent.Repository.WorkingDir, + &(*content.Repository).WorkingDir, + }, + { + &testContent.Repository.CertificateSecret, + &(*content.Repository).CertificateSecret, + }, + } + + for _, field := range fields { + *field.destination = field.source + } + + (*content.Repository).AuthType = (*string)(&testContent.Repository.AuthType) + + if testContent.Repository.UsernameSecret != nil { + secetRef := &testkube.SecretRef{ + Name: testContent.Repository.UsernameSecret.Name, + Key: testContent.Repository.UsernameSecret.Key, + } + + (*content.Repository).TokenSecret = &secetRef + } + + if testContent.Repository.TokenSecret != nil { + secretRef := &testkube.SecretRef{ + Name: testContent.Repository.TokenSecret.Name, + Key: testContent.Repository.TokenSecret.Key, + } + + (*content.Repository).TokenSecret = &secretRef + } + } + + return content +} + +// MapSpecExecutionRequestToExecutionUpdateRequest maps ExecutionRequest CRD spec to ExecutionUpdateRequest OpenAPI spec to +func MapSpecExecutionRequestToExecutionUpdateRequest( + request *testsv3.ExecutionRequest) (executionRequest *testkube.ExecutionUpdateRequest) { + executionRequest = &testkube.ExecutionUpdateRequest{} + + var fields = []struct { + source *string + destination **string + }{ + { + &request.Name, + &executionRequest.Name, + }, + { + &request.TestSuiteName, + &executionRequest.TestSuiteName, + }, + { + &request.Namespace, + &executionRequest.Namespace, + }, + { + &request.VariablesFile, + &executionRequest.VariablesFile, + }, + { + &request.TestSecretUUID, + &executionRequest.TestSecretUUID, + }, + { + &request.TestSuiteSecretUUID, + &executionRequest.TestSuiteSecretUUID, + }, + { + &request.HttpProxy, + &executionRequest.HttpProxy, + }, + { + &request.HttpsProxy, + &executionRequest.HttpsProxy, + }, + { + &request.Image, + &executionRequest.Image, + }, + { + &request.JobTemplate, + &executionRequest.JobTemplate, + }, + { + &request.PreRunScript, + &executionRequest.PreRunScript, + }, + { + &request.CronJobTemplate, + &executionRequest.CronJobTemplate, + }, + { + &request.ScraperTemplate, + &executionRequest.ScraperTemplate, + }, + } + + for _, field := range fields { + *field.destination = field.source + } + + executionRequest.ArgsMode = (*string)(&request.ArgsMode) + + var slices = []struct { + source *map[string]string + destination **map[string]string + }{ + { + &request.ExecutionLabels, + &executionRequest.ExecutionLabels, + }, + { + &request.Envs, + &executionRequest.Envs, + }, + { + &request.SecretEnvs, + &executionRequest.SecretEnvs, + }, + } + + for _, slice := range slices { + *slice.destination = slice.source + } + + executionRequest.Number = &request.Number + executionRequest.Sync = &request.Sync + executionRequest.NegativeTest = &request.NegativeTest + executionRequest.ActiveDeadlineSeconds = &request.ActiveDeadlineSeconds + executionRequest.Args = &request.Args + executionRequest.Command = &request.Command + + vars := MergeVariablesAndParams(request.Variables, nil) + executionRequest.Variables = &vars + imagePullSecrets := MapImagePullSecrets(request.ImagePullSecrets) + executionRequest.ImagePullSecrets = &imagePullSecrets + envConfigMaps := MapEnvReferences(request.EnvConfigMaps) + executionRequest.EnvConfigMaps = &envConfigMaps + envSecrets := MapEnvReferences(request.EnvSecrets) + executionRequest.EnvSecrets = &envSecrets + + if request.ArtifactRequest != nil { + artifactRequest := &testkube.ArtifactUpdateRequest{ + StorageClassName: &request.ArtifactRequest.StorageClassName, + VolumeMountPath: &request.ArtifactRequest.VolumeMountPath, + Dirs: &request.ArtifactRequest.Dirs, + } + + executionRequest.ArtifactRequest = &artifactRequest + } + + return executionRequest +} diff --git a/pkg/mapper/testsources/mapper.go b/pkg/mapper/testsources/mapper.go index 365ebc9f754..1cc3d00190e 100644 --- a/pkg/mapper/testsources/mapper.go +++ b/pkg/mapper/testsources/mapper.go @@ -93,7 +93,7 @@ func MapAPIToCRD(request testkube.TestSourceUpsertRequest) testsourcev1.TestSour } } -// MapUpdateToSpec maps TestUpdateRequest to Test CRD spec +// MapUpdateToSpec maps TestSourceUpdateRequest to TestSource CRD spec func MapUpdateToSpec(request testkube.TestSourceUpdateRequest, testSource *testsourcev1.TestSource) *testsourcev1.TestSource { var fields = []struct { source *string @@ -235,3 +235,101 @@ func MapUpdateToSpec(request testkube.TestSourceUpdateRequest, testSource *tests return testSource } + +// MapSpecToUpdate maps TestSource CRD spec TestSourceUpdateRequest to +func MapSpecToUpdate(testSource *testsourcev1.TestSource) (request testkube.TestSourceUpdateRequest) { + var fields = []struct { + source *string + destination **string + }{ + { + &testSource.Name, + &request.Name, + }, + { + &testSource.Namespace, + &request.Namespace, + }, + { + &testSource.Spec.Data, + &request.Data, + }, + { + &testSource.Spec.Uri, + &request.Uri, + }, + } + + for _, field := range fields { + *field.destination = field.source + } + + request.Type_ = (*string)(&testSource.Spec.Type_) + + request.Labels = &testSource.Labels + + if testSource.Spec.Repository != nil { + repository := &testkube.RepositoryUpdate{} + request.Repository = &repository + + var fields = []struct { + source *string + destination **string + }{ + { + &testSource.Spec.Repository.Type_, + &(*request.Repository).Type_, + }, + { + &testSource.Spec.Repository.Uri, + &(*request.Repository).Uri, + }, + { + &testSource.Spec.Repository.Branch, + &(*request.Repository).Branch, + }, + { + &testSource.Spec.Repository.Commit, + &(*request.Repository).Commit, + }, + { + &testSource.Spec.Repository.Path, + &(*request.Repository).Path, + }, + { + &testSource.Spec.Repository.WorkingDir, + &(*request.Repository).WorkingDir, + }, + { + &testSource.Spec.Repository.CertificateSecret, + &(*request.Repository).CertificateSecret, + }, + } + + for _, field := range fields { + *field.destination = field.source + } + + (*request.Repository).AuthType = (*string)(&testSource.Spec.Repository.AuthType) + + if testSource.Spec.Repository.UsernameSecret != nil { + secretRef := &testkube.SecretRef{ + Name: testSource.Spec.Repository.UsernameSecret.Name, + Key: testSource.Spec.Repository.UsernameSecret.Key, + } + + (*request.Repository).UsernameSecret = &secretRef + } + + if testSource.Spec.Repository.TokenSecret != nil { + secretRef := &testkube.SecretRef{ + Name: testSource.Spec.Repository.TokenSecret.Name, + Key: testSource.Spec.Repository.TokenSecret.Key, + } + + (*request.Repository).TokenSecret = &secretRef + } + } + + return request +} diff --git a/pkg/mapper/testsuites/kube_openapi.go b/pkg/mapper/testsuites/kube_openapi.go index 55a706dd192..ec3a42df7d2 100644 --- a/pkg/mapper/testsuites/kube_openapi.go +++ b/pkg/mapper/testsuites/kube_openapi.go @@ -192,3 +192,121 @@ func MapStatusFromSpec(specStatus testsuitesv3.TestSuiteStatus) *testkube.TestSu }, } } + +// MapTestSuiteTestCRDToUpdateRequest maps TestSuite CRD spec to TestSuiteUpdateRequest OpenAPI spec +func MapTestSuiteTestCRDToUpdateRequest(testSuite *testsuitesv3.TestSuite) (request testkube.TestSuiteUpdateRequest) { + var fields = []struct { + source *string + destination **string + }{ + { + &testSuite.Name, + &request.Name, + }, + { + &testSuite.Namespace, + &request.Namespace, + }, + { + &testSuite.Spec.Description, + &request.Description, + }, + { + &testSuite.Spec.Schedule, + &request.Schedule, + }, + } + + for _, field := range fields { + *field.destination = field.source + } + + before := mapCRDToTestBatchSteps(testSuite.Spec.Before) + request.Before = &before + + steps := mapCRDToTestBatchSteps(testSuite.Spec.Steps) + request.Before = &steps + + after := mapCRDToTestBatchSteps(testSuite.Spec.After) + request.Before = &after + + request.Labels = &testSuite.Labels + + repeats := int32(testSuite.Spec.Repeats) + request.Repeats = &repeats + + if testSuite.Spec.ExecutionRequest != nil { + value := MapSpecExecutionRequestToExecutionUpdateRequest(testSuite.Spec.ExecutionRequest) + request.ExecutionRequest = &value + } + + return request +} + +func mapCRDToTestBatchSteps(in []testsuitesv3.TestSuiteBatchStep) (batches []testkube.TestSuiteBatchStep) { + for _, batch := range in { + steps := make([]testkube.TestSuiteStep, len(batch.Execute)) + for i := range batch.Execute { + steps[i] = mapCRStepToAPI(batch.Execute[i]) + } + + batches = append(batches, testkube.TestSuiteBatchStep{ + StopOnFailure: batch.StopOnFailure, + Execute: steps, + }) + } + + return batches +} + +// MapSpecExecutionRequestToExecutionUpdateRequest maps ExecutionRequest CRD spec to ExecutionUpdateRequest OpenAPI spec +func MapSpecExecutionRequestToExecutionUpdateRequest(request *testsuitesv3.TestSuiteExecutionRequest) (executionRequest *testkube.TestSuiteExecutionUpdateRequest) { + executionRequest = &testkube.TestSuiteExecutionUpdateRequest{} + + var fields = []struct { + source *string + destination **string + }{ + { + &request.Name, + &executionRequest.Name, + }, + { + &request.Namespace, + &executionRequest.Namespace, + }, + { + &request.SecretUUID, + &executionRequest.SecretUUID, + }, + { + &request.HttpProxy, + &executionRequest.HttpProxy, + }, + { + &request.HttpsProxy, + &executionRequest.HttpsProxy, + }, + { + &request.CronJobTemplate, + &executionRequest.CronJobTemplate, + }, + } + + for _, field := range fields { + *field.destination = field.source + } + + executionRequest.Labels = &request.Labels + + executionRequest.ExecutionLabels = &request.ExecutionLabels + + executionRequest.Sync = &request.Sync + + executionRequest.Timeout = &request.Timeout + + vars := MergeVariablesAndParams(request.Variables, nil) + executionRequest.Variables = &vars + + return executionRequest +} diff --git a/pkg/mapper/testtriggers/kube_openapi.go b/pkg/mapper/testtriggers/kube_openapi.go index 02f751b13e1..3a90853fd65 100644 --- a/pkg/mapper/testtriggers/kube_openapi.go +++ b/pkg/mapper/testtriggers/kube_openapi.go @@ -86,3 +86,18 @@ func mapConditionSpecFromCRD(conditionSpec *testsv1.TestTriggerConditionSpec) *t Conditions: conditions, } } + +func MapTestTriggerCRDToTestTriggerUpsertRequest(request testsv1.TestTrigger) testkube.TestTriggerUpsertRequest { + return testkube.TestTriggerUpsertRequest{ + Name: request.Name, + Namespace: request.Namespace, + Labels: request.Labels, + Resource: (*testkube.TestTriggerResources)(&request.Spec.Resource), + ResourceSelector: mapSelectorFromCRD(request.Spec.ResourceSelector), + Event: string(request.Spec.Event), + ConditionSpec: mapConditionSpecFromCRD(request.Spec.ConditionSpec), + Action: (*testkube.TestTriggerActions)(&request.Spec.Action), + Execution: (*testkube.TestTriggerExecutions)(&request.Spec.Execution), + TestSelector: mapSelectorFromCRD(request.Spec.TestSelector), + } +}