diff --git a/.builds-linux.goreleaser.yml b/.builds-linux.goreleaser.yml index 54335af8365..512281a9b20 100644 --- a/.builds-linux.goreleaser.yml +++ b/.builds-linux.goreleaser.yml @@ -1,3 +1,15 @@ +env: + # Goreleaser always uses the docker buildx builder with name "default"; see + # https://github.com/goreleaser/goreleaser/pull/3199 + # To use a builder other than "default", set this variable. + # Necessary for, e.g., GitHub actions cache integration. + - DOCKER_BUILDX_BUILDER={{ if index .Env "DOCKER_BUILDX_BUILDER" }}{{ .Env.DOCKER_BUILDX_BUILDER }}{{ else }}default{{ end }} + # Setup to enable Docker to use, e.g., the GitHub actions cache; see + # https://docs.docker.com/build/building/cache/backends/ + # https://github.com/moby/buildkit#export-cache + - DOCKER_BUILDX_CACHE_FROM={{ if index .Env "DOCKER_BUILDX_CACHE_FROM" }}{{ .Env.DOCKER_BUILDX_CACHE_FROM }}{{ else }}type=registry,ref=docker.io/kubeshop/testkube-cli:latest{{ end }} + - DOCKER_BUILDX_CACHE_TO={{ if index .Env "DOCKER_BUILDX_CACHE_TO" }}{{ .Env.DOCKER_BUILDX_CACHE_TO }}{{ else }}type=inline{{ end }} + before: hooks: - go mod tidy @@ -22,3 +34,57 @@ builds: - -X github.com/kubeshop/testkube/pkg/telemetry.TestkubeMeasurementSecret={{.Env.ANALYTICS_API_KEY}} archives: - format: binary + +dockers: + - dockerfile: ./build/kubectl-testkube/Dockerfile + use: buildx + goos: linux + goarch: amd64 + image_templates: + - "kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}-amd64" + build_flag_templates: + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.created={{ .Date}}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--builder={{ .Env.DOCKER_BUILDX_BUILDER }}" + - "--cache-to={{ .Env.DOCKER_BUILDX_CACHE_TO }}" + - "--cache-from={{ .Env.DOCKER_BUILDX_CACHE_FROM }}" + - "--build-arg=ALPINE_IMAGE={{ .Env.ALPINE_IMAGE }}" + + - dockerfile: ./build/kubectl-testkube/Dockerfile + use: buildx + goos: linux + goarch: arm64 + image_templates: + - "kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8" + build_flag_templates: + - "--platform=linux/arm64/v8" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--builder={{ .Env.DOCKER_BUILDX_BUILDER }}" + - "--cache-to={{ .Env.DOCKER_BUILDX_CACHE_TO }}" + - "--cache-from={{ .Env.DOCKER_BUILDX_CACHE_FROM }}" + - "--build-arg=ALPINE_IMAGE={{ .Env.ALPINE_IMAGE }}" + +docker_manifests: + - name_template: kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }} + image_templates: + - kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}-amd64 + - kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8 + - name_template: kubeshop/testkube-cli:latest + image_templates: + - kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}-amd64 + - kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8 + +docker_signs: + - cmd: cosign + artifacts: all + output: true + args: + - 'sign' + - '${artifact}' + - "--yes" 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/.github/workflows/release-dev.yaml b/.github/workflows/release-dev.yaml index 8e6cd886ef4..2910b263031 100644 --- a/.github/workflows/release-dev.yaml +++ b/.github/workflows/release-dev.yaml @@ -9,6 +9,9 @@ permissions: id-token: write # needed for keyless signing contents: write +env: + ALPINE_IMAGE: alpine:3.18.0 + jobs: pre_build: name: Pre-build @@ -27,10 +30,21 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + + - name: Set up QEMU + if: matrix.name == 'linux' + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + if: matrix.name == 'linux' + id: buildx + uses: docker/setup-buildx-action@v1 + - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.18 + - name: Go Cache uses: actions/cache@v2 with: @@ -40,18 +54,54 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- + + - name: Login to DockerHub + if: matrix.name == 'linux' + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Get github sha + id: github_sha + run: echo "::set-output name=sha_short::${GITHUB_SHA::7}" + - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 + uses: goreleaser/goreleaser-action@v4 with: distribution: goreleaser-pro version: latest - args: release -f ${{ matrix.path }} --skip-publish + args: release -f ${{ matrix.path }} --snapshot env: GITHUB_TOKEN: ${{ secrets.CI_BOT_TOKEN }} ANALYTICS_TRACKING_ID: "${{secrets.TESTKUBE_CLI_GA_MEASUREMENT_ID}}" ANALYTICS_API_KEY: "${{secrets.TESTKUBE_CLI_GA_MEASUREMENT_SECRET}}" # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + DOCKER_BUILDX_BUILDER: "${{ steps.buildx.outputs.name }}" + DOCKER_BUILDX_CACHE_FROM: "type=gha" + DOCKER_BUILDX_CACHE_TO: "type=gha,mode=max" + ALPINE_IMAGE: ${{ env.ALPINE_IMAGE }} + DOCKER_IMAGE_TAG: ${{ steps.github_sha.outputs.sha_short }} + + - name: Push Docker images + if: matrix.name == 'linux' + run: | + docker push kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-arm64v8 + docker push kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-amd64 + + - name: Push README to Dockerhub + if: matrix.name == 'linux' + uses: christian-korneck/update-container-description-action@v1 + env: + DOCKER_USER: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASS: ${{ secrets.DOCKERHUB_TOKEN }} + with: + destination_container_repo: kubeshop/testkube-cli + provider: dockerhub + short_description: 'Testkube CLI Docker image' + readme_file: './README.md' + - name: Upload Artifacts uses: actions/upload-artifact@master with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b3b81cbc5b0..3b11ae2a2d5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,6 +11,7 @@ permissions: env: TESTKUBE_CHOCO_REPO: https://chocolatey.kubeshop.io/ + ALPINE_IMAGE: alpine:3.18.0 jobs: pre_build: @@ -30,10 +31,21 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + + - name: Set up QEMU + if: matrix.name == 'linux' + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + if: matrix.name == 'linux' + id: buildx + uses: docker/setup-buildx-action@v1 + - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.18 + - name: Go Cache uses: actions/cache@v2 with: @@ -43,6 +55,18 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- + + - name: Login to DockerHub + if: matrix.name == 'linux' + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Get github sha + id: github_sha + run: echo "::set-output name=sha_short::${GITHUB_SHA::7}" + - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: @@ -55,6 +79,18 @@ jobs: ANALYTICS_API_KEY: "${{secrets.TESTKUBE_CLI_GA_MEASUREMENT_SECRET}}" # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + DOCKER_BUILDX_BUILDER: "${{ steps.buildx.outputs.name }}" + DOCKER_BUILDX_CACHE_FROM: "type=gha" + DOCKER_BUILDX_CACHE_TO: "type=gha,mode=max" + ALPINE_IMAGE: ${{ env.ALPINE_IMAGE }} + DOCKER_IMAGE_TAG: ${{ steps.github_sha.outputs.sha_short }} + + - name: Push Docker images + if: matrix.name == 'linux' + run: | + docker push kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-arm64v8 + docker push kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-amd64 + - name: Upload Artifacts uses: actions/upload-artifact@master with: diff --git a/Makefile b/Makefile index b1969be09cf..68a57ffa967 100644 --- a/Makefile +++ b/Makefile @@ -78,6 +78,11 @@ build-testkube-bin-intel: docker-build-api: goreleaser release -f goreleaser_files/.goreleaser-docker-build-api.yml --rm-dist --snapshot +#make docker-build-cli SLACK_BOT_CLIENT_ID=** SLACK_BOT_CLIENT_SECRET=** ANALYTICS_TRACKING_ID=** ANALYTICS_API_KEY=** SEGMENTIO_KEY=** CLOUD_SEGMENTIO_KEY=** DOCKER_BUILDX_CACHE_FROM=type=registry,ref=docker.io/kubeshop/testkube-cli:latest ALPINE_IMAGE=alpine:3.18.0 +docker-build-cli: + goreleaser release -f .builds-linux.goreleaser.yml --rm-dist --snapshot + + #make docker-build-executor EXECUTOR=zap GITHUB_TOKEN=*** DOCKER_BUILDX_CACHE_FROM=type=registry,ref=docker.io/kubeshop/testkube-zap-executor:latest #add ALPINE_IMAGE=alpine:3.18.0 env var for building of curl and scraper executor docker-build-executor: diff --git a/README.md b/README.md index 75b19f82e2e..5c83dc888cc 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Checkout the [Getting Started](https://docs.testkube.io/articles/getting-started # Documentation -Is available at [docs.testkube.io](docs.testkube.io) +Is available at [docs.testkube.io](https://docs.testkube.io) ## Contributing diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index f7329997688..5a0b8989b09 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" @@ -3038,7 +3059,6 @@ components: required: - name - status - - steps properties: name: type: string @@ -3052,7 +3072,73 @@ components: before: type: array items: - $ref: "#/components/schemas/TestSuiteStep" + $ref: "#/components/schemas/TestSuiteBatchStep" + description: Run these batch steps before whole suite + example: + - stopOnFailure: true + execute: + - test: "example-test" + steps: + type: array + items: + $ref: "#/components/schemas/TestSuiteBatchStep" + description: Batch steps to run + example: + - stopOnFailure: true + execute: + - test: "example-test" + after: + type: array + items: + $ref: "#/components/schemas/TestSuiteBatchStep" + description: Run these batch steps after whole suite + example: + - stopOnFailure: true + execute: + - test: "example-test" + labels: + type: object + description: "test suite labels" + additionalProperties: + type: string + example: + env: "prod" + app: "backend" + schedule: + type: string + description: schedule to run test suite + example: "* * * * *" + repeats: + type: integer + default: 1 + example: 1 + created: + type: string + format: date-time + executionRequest: + $ref: "#/components/schemas/TestSuiteExecutionRequest" + status: + $ref: "#/components/schemas/TestSuiteStatus" + + TestSuiteV2: + type: object + required: + - name + - status + properties: + name: + type: string + example: "test-suite1" + namespace: + type: string + example: "testkube" + description: + type: string + example: "collection of tests" + before: + type: array + items: + $ref: "#/components/schemas/TestSuiteStepV2" description: Run this step before whole suite example: - stopTestOnFailure: true @@ -3062,7 +3148,7 @@ components: steps: type: array items: - $ref: "#/components/schemas/TestSuiteStep" + $ref: "#/components/schemas/TestSuiteStepV2" description: Steps to run example: - stopTestOnFailure: true @@ -3072,7 +3158,7 @@ components: after: type: array items: - $ref: "#/components/schemas/TestSuiteStep" + $ref: "#/components/schemas/TestSuiteStepV2" description: Run this step after whole suite example: - stopTestOnFailure: true @@ -3109,7 +3195,34 @@ components: - executeTest - delay + TestSuiteBatchStep: + description: set of steps run in parallel + type: object + required: + - stopOnFailure + properties: + stopOnFailure: + type: boolean + default: true + execute: + type: array + items: + $ref: "#/components/schemas/TestSuiteStep" + TestSuiteStep: + type: object + properties: + test: + type: string + description: object name + example: "name" + delay: + type: string + format: duration + example: 1s + description: delay duration in time units + + TestSuiteStepV2: type: object required: - name @@ -3120,19 +3233,18 @@ components: type: boolean default: true execute: - $ref: "#/components/schemas/TestSuiteStepExecuteTest" + $ref: "#/components/schemas/TestSuiteStepExecuteTestV2" delay: - $ref: "#/components/schemas/TestSuiteStepDelay" + $ref: "#/components/schemas/TestSuiteStepDelayV2" - TestSuiteStepExecuteTest: + TestSuiteStepExecuteTestV2: allOf: - $ref: "#/components/schemas/ObjectRef" - TestSuiteStepDelay: + TestSuiteStepDelayV2: type: object required: - duration - - name properties: duration: type: integer @@ -3145,7 +3257,7 @@ components: required: - id - name - - test + - testSuite properties: id: type: string @@ -3197,7 +3309,13 @@ components: type: array description: "steps execution results" items: - $ref: "#/components/schemas/TestSuiteStepExecutionResult" + $ref: "#/components/schemas/TestSuiteStepExecutionResultV2" + description: test execution results + executeStepResults: + type: array + description: "batch steps execution results" + items: + $ref: "#/components/schemas/TestSuiteBatchStepExecutionResult" description: test execution results labels: type: object @@ -3225,8 +3343,6 @@ components: TestSuiteStepExecutionResult: description: execution result returned from executor type: object - required: - - status properties: step: $ref: "#/components/schemas/TestSuiteStep" @@ -3237,6 +3353,30 @@ components: $ref: "#/components/schemas/Execution" description: test step execution + TestSuiteStepExecutionResultV2: + description: execution result returned from executor + type: object + properties: + step: + $ref: "#/components/schemas/TestSuiteStepV2" + test: + $ref: "#/components/schemas/ObjectRef" + description: object name and namespace + execution: + $ref: "#/components/schemas/Execution" + description: test step execution + + TestSuiteBatchStepExecutionResult: + description: execution result returned from executor + type: object + properties: + step: + $ref: "#/components/schemas/TestSuiteBatchStep" + execute: + type: array + items: + $ref: "#/components/schemas/TestSuiteStepExecutionResult" + TestSuiteExecutionsResult: description: the result for a page of executions type: object @@ -3296,7 +3436,7 @@ components: execution: type: array items: - $ref: "#/components/schemas/TestSuiteStepExecutionSummary" + $ref: "#/components/schemas/TestSuiteBatchStepExecutionSummary" labels: type: object description: "test suite and execution labels" @@ -3330,6 +3470,15 @@ components: type: $ref: "#/components/schemas/TestSuiteStepType" + TestSuiteBatchStepExecutionSummary: + description: "Test suite batch execution summary" + type: object + properties: + execute: + type: array + items: + $ref: "#/components/schemas/TestSuiteStepExecutionSummary" + TestSuiteStatus: type: object description: test suite status @@ -3412,7 +3561,14 @@ components: properties: type: type: string - description: test type + description: | + type of sources a runner can get data from. + string: String content (e.g. Postman JSON file). + file-uri: content stored on the webserver. + git-file: the file stored in the Git repo in the given repository.path field (Deprecated: use git instead). + git-dir: the entire git repo or git subdirectory depending on the repository.path field (Testkube does a shadow clone and sparse checkout to limit IOs in the case of monorepos). (Deprecated: use git instead). + git: automatically provisions either a file, directory or whole git repository depending on the repository.path field. + enum: - string - file-uri @@ -3689,8 +3845,12 @@ components: description: configuration parameters for storing test artifacts preRunScript: type: string - description: script to run before test execution + description: script to run before test execution (not supported for container executors) example: "echo -n '$SECRET_ENV' > ./secret_file" + postRunScript: + type: string + description: script to run after test execution (not supported for container executors) + example: "sleep 30" runningContext: $ref: "#/components/schemas/RunningContext" description: running context for the test execution @@ -4043,6 +4203,11 @@ components: description: test execution request body type: object properties: + id: + type: string + description: execution id + format: bson objectId + example: "62f395e004109209b50edfc1" name: type: string description: test execution custom name @@ -4183,8 +4348,12 @@ components: description: adjusting parameters for test content preRunScript: type: string - description: script to run before test execution + description: script to run before test execution (not supported for container executors) example: "echo -n '$SECRET_ENV' > ./secret_file" + postRunScript: + type: string + description: script to run after test execution (not supported for container executors) + example: "sleep 30" scraperTemplate: type: string description: scraper template extensions @@ -4273,6 +4442,11 @@ components: cronJobTemplate: type: string description: cron job template extensions + concurrencyLevel: + type: integer + format: int32 + description: number of tests run in parallel + example: 10 TestSuiteExecutionUpdateRequest: description: test suite execution update request body @@ -4304,6 +4478,16 @@ components: - $ref: "#/components/schemas/TestSuite" - $ref: "#/components/schemas/ObjectRef" + TestSuiteUpsertRequestV2: + description: test suite create request body + type: object + required: + - name + - namespace + allOf: + - $ref: "#/components/schemas/TestSuiteV2" + - $ref: "#/components/schemas/ObjectRef" + TestSuiteUpdateRequest: description: test suite update body type: object @@ -4312,6 +4496,14 @@ components: - $ref: "#/components/schemas/TestSuite" - $ref: "#/components/schemas/ObjectRef" + TestSuiteUpdateRequestV2: + description: test suite update body + type: object + nullable: true + allOf: + - $ref: "#/components/schemas/TestSuiteV2" + - $ref: "#/components/schemas/ObjectRef" + TestTriggerUpsertRequest: description: test trigger create or update request body type: object @@ -4533,6 +4725,17 @@ components: payloadObjectField: type: string description: will load the generated payload for notification inside the object + payloadTemplate: + type: string + description: golang based template for notification payload + headers: + type: object + description: "webhook headers" + additionalProperties: + type: string + example: + env: "Content-Type" + app: "application/xml" labels: type: object description: "webhook labels" @@ -4543,7 +4746,7 @@ components: app: "backend" Event: - description: CRD based executor data + description: Event data type: object required: - type @@ -4565,6 +4768,9 @@ components: $ref: "#/components/schemas/Execution" testSuiteExecution: $ref: "#/components/schemas/TestSuiteExecution" + clusterName: + type: string + description: cluster name of event EventResource: type: string @@ -4905,6 +5111,12 @@ components: "pod": ["created", "modified", "deleted"], "deployment": ["created", "modified", "deleted"], } + conditions: + type: array + items: + type: string + description: list of supported values for conditions + example: ["Available", "Progressing"] TestSourceBatchRequest: description: Test source batch request @@ -5138,4 +5350,4 @@ components: - execution filePath: type: string - example: folder/file.txt + example: folder/file.txt \ No newline at end of file diff --git a/build/kubectl-testkube/Dockerfile b/build/kubectl-testkube/Dockerfile new file mode 100644 index 00000000000..a51eca1d67e --- /dev/null +++ b/build/kubectl-testkube/Dockerfile @@ -0,0 +1,7 @@ +# syntax=docker/dockerfile:1 +ARG ALPINE_IMAGE +FROM ${ALPINE_IMAGE} +COPY kubectl-testkube /bin/kubectl-testkube +RUN mkdir /.testkube && echo "{}" > /.testkube/config.json && chmod -R 755 /.testkube +USER 1001 +ENTRYPOINT ["/bin/kubectl-testkube"] diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go index 6f672e321ee..53ac4e0cb64 100644 --- a/cmd/api-server/main.go +++ b/cmd/api-server/main.go @@ -63,11 +63,13 @@ import ( testsclientv3 "github.com/kubeshop/testkube-operator/client/tests/v3" testsourcesclientv1 "github.com/kubeshop/testkube-operator/client/testsources/v1" testsuitesclientv2 "github.com/kubeshop/testkube-operator/client/testsuites/v2" + testsuitesclientv3 "github.com/kubeshop/testkube-operator/client/testsuites/v3" apiv1 "github.com/kubeshop/testkube/internal/app/api/v1" "github.com/kubeshop/testkube/internal/migrations" "github.com/kubeshop/testkube/pkg/configmap" "github.com/kubeshop/testkube/pkg/log" "github.com/kubeshop/testkube/pkg/migrator" + "github.com/kubeshop/testkube/pkg/reconciler" "github.com/kubeshop/testkube/pkg/secret" "github.com/kubeshop/testkube/pkg/ui" ) @@ -154,7 +156,8 @@ func main() { testsClientV3 := testsclientv3.NewClient(kubeClient, cfg.TestkubeNamespace) executorsClient := executorsclientv1.NewClient(kubeClient, cfg.TestkubeNamespace) webhooksClient := executorsclientv1.NewWebhooksClient(kubeClient, cfg.TestkubeNamespace) - testsuitesClient := testsuitesclientv2.NewClient(kubeClient, cfg.TestkubeNamespace) + testsuitesClientV2 := testsuitesclientv2.NewClient(kubeClient, cfg.TestkubeNamespace) + testsuitesClientV3 := testsuitesclientv3.NewClient(kubeClient, cfg.TestkubeNamespace) testsourcesClient := testsourcesclientv1.NewClient(kubeClient, cfg.TestkubeNamespace) clientset, err := k8sclient.ConnectToK8s() @@ -270,7 +273,7 @@ func main() { log.DefaultLogger.Debugw("Getting unique clusterId", "clusterId", clusterId, "error", err) // TODO check if this version exists somewhere in stats (probably could be removed) - migrations.Migrator.Add(migrations.NewVersion_0_9_2(scriptsClient, testsClientV1, testsClientV3, testsuitesClient)) + migrations.Migrator.Add(migrations.NewVersion_0_9_2(scriptsClient, testsClientV1, testsClientV3, testsuitesClientV2)) if err := runMigrations(); err != nil { ui.ExitOnError("Running server migrations", err) } @@ -283,7 +286,7 @@ func main() { log.DefaultLogger.Errorw("error creating NATS connection", "error", err) } eventBus := bus.NewNATSBus(nc) - eventsEmitter := event.NewEmitter(eventBus) + eventsEmitter := event.NewEmitter(eventBus, cfg.TestkubeClusterName) metrics := metrics.NewMetrics() @@ -353,7 +356,7 @@ func main() { testResultsRepository, executorsClient, testsClientV3, - testsuitesClient, + testsuitesClientV3, testsourcesClient, secretClient, eventsEmitter, @@ -373,7 +376,7 @@ func main() { testResultsRepository, testsClientV3, executorsClient, - testsuitesClient, + testsuitesClientV3, secretClient, webhooksClient, clientset, @@ -398,7 +401,17 @@ func main() { if mode == common.ModeAgent { log.DefaultLogger.Info("starting agent service") - agentHandle, err := agent.NewAgent(log.DefaultLogger, api.Mux.Handler(), cfg.TestkubeCloudAPIKey, grpcClient, cfg.TestkubeCloudWorkerCount, cfg.TestkubeCloudLogStreamWorkerCount, api.GetLogsStream, clusterId) + agentHandle, err := agent.NewAgent( + log.DefaultLogger, + api.Mux.Handler(), + cfg.TestkubeCloudAPIKey, + grpcClient, + cfg.TestkubeCloudWorkerCount, + cfg.TestkubeCloudLogStreamWorkerCount, + api.GetLogsStream, + clusterId, + cfg.TestkubeClusterName, + ) if err != nil { ui.ExitOnError("Starting agent", err) } @@ -419,7 +432,7 @@ func main() { sched, clientset, testkubeClientset, - testsuitesClient, + testsuitesClientV3, testsClientV3, resultsRepository, testResultsRepository, @@ -437,6 +450,19 @@ func main() { log.DefaultLogger.Info("test triggers are disabled") } + if !cfg.DisableReconciler { + reconcilerClient := reconciler.NewClient(clientset, + resultsRepository, + testResultsRepository, + log.DefaultLogger, + cfg.TestkubeNamespace) + g.Go(func() error { + return reconcilerClient.Run(ctx) + }) + } else { + log.DefaultLogger.Info("reconclier is disabled") + } + // telemetry based functions api.SendTelemetryStartEvent(ctx) api.StartTelemetryHeartbeats(ctx) @@ -544,7 +570,7 @@ func newSlackLoader(cfg *config.Config) (*slack.SlackLoader, error) { return nil, err } - return slack.NewSlackLoader(slackTemplate, slackConfig, testkube.AllEventTypes), nil + return slack.NewSlackLoader(slackTemplate, slackConfig, cfg.TestkubeClusterName, testkube.AllEventTypes), nil } func loadFromBase64StringOrFile(base64Val string, configDir, filename, configType string) (raw string, err error) { diff --git a/cmd/kubectl-testkube/commands/cloud.go b/cmd/kubectl-testkube/commands/cloud.go index d2130642707..64a68c1d8d9 100644 --- a/cmd/kubectl-testkube/commands/cloud.go +++ b/cmd/kubectl-testkube/commands/cloud.go @@ -19,6 +19,7 @@ func NewCloudCmd() *cobra.Command { cmd.AddCommand(cloud.NewConnectCmd()) cmd.AddCommand(cloud.NewDisconnectCmd()) cmd.AddCommand(cloud.NewInitCmd()) + cmd.AddCommand(cloud.NewLoginCmd()) return cmd } diff --git a/cmd/kubectl-testkube/commands/cloud/connect.go b/cmd/kubectl-testkube/commands/cloud/connect.go index ee2d7da3549..f41b55cd45c 100644 --- a/cmd/kubectl-testkube/commands/cloud/connect.go +++ b/cmd/kubectl-testkube/commands/cloud/connect.go @@ -79,10 +79,13 @@ func NewConnectCmd() *cobra.Command { {ui.Separator, ""}, } - var token string + var ( + token string + refreshToken string + ) // if no agent is passed create new environment and get its token if opts.CloudAgentToken == "" && opts.CloudOrgId == "" && opts.CloudEnvId == "" { - token, err = LoginUser(opts) + token, refreshToken, err = LoginUser(opts) ui.ExitOnError("login", err) orgId, orgName, err := uiGetOrganizationId(opts.CloudRootDomain, token) @@ -165,11 +168,11 @@ func NewConnectCmd() *cobra.Command { ui.H2("Testkube Cloud is connected to your Testkube instance, saving local configuration") ui.H2("Saving testkube cli cloud context") - if token == "" { - token, err = LoginUser(opts) + if token == "" && !common.IsUserLoggedIn(cfg, opts) { + token, refreshToken, err = LoginUser(opts) ui.ExitOnError("user login", err) } - err = common.PopulateLoginDataToContext(opts.CloudOrgId, opts.CloudEnvId, token, opts, cfg) + err = common.PopulateLoginDataToContext(opts.CloudOrgId, opts.CloudEnvId, token, refreshToken, opts, cfg) ui.ExitOnError("Setting cloud environment context", err) @@ -217,21 +220,21 @@ var contextDescription = map[string]string{ "cloud": "Testkube in Cloud mode", } -func uiGetToken(tokenChan chan string) (string, error) { +func uiGetToken(tokenChan chan cloudlogin.Tokens) (string, string, error) { // wait for token received to browser s := ui.NewSpinner("waiting for auth token") - var token string + var token cloudlogin.Tokens select { case token = <-tokenChan: s.Success() case <-time.After(5 * time.Minute): s.Fail("Timeout waiting for auth token") - return "", fmt.Errorf("timeout waiting for auth token") + return "", "", fmt.Errorf("timeout waiting for auth token") } ui.NL() - return token, nil + return token.IDToken, token.RefreshToken, nil } func uiGetEnvName() (string, error) { @@ -245,10 +248,10 @@ func uiGetEnvName() (string, error) { return "", fmt.Errorf("environment name cannot be empty") } -func LoginUser(opts common.HelmOptions) (string, error) { +func LoginUser(opts common.HelmOptions) (string, string, error) { authUrl, tokenChan, err := cloudlogin.CloudLogin(context.Background(), opts.CloudUris.Auth) if err != nil { - return "", fmt.Errorf("cloud login: %w", err) + return "", "", fmt.Errorf("cloud login: %w", err) } ui.H1("Login") ui.Paragraph("Your browser should open automatically. If not, please open this link in your browser:") @@ -257,15 +260,15 @@ func LoginUser(opts common.HelmOptions) (string, error) { ui.Paragraph("") if ok := ui.Confirm("Continue"); !ok { - return "", fmt.Errorf("login cancelled") + return "", "", fmt.Errorf("login cancelled") } // open browser with login page and redirect to localhost open.Run(authUrl) - token, err := uiGetToken(tokenChan) + idToken, refreshToken, err := uiGetToken(tokenChan) if err != nil { - return "", fmt.Errorf("getting token") + return "", "", fmt.Errorf("getting token") } - return token, nil + return idToken, refreshToken, nil } diff --git a/cmd/kubectl-testkube/commands/cloud/init.go b/cmd/kubectl-testkube/commands/cloud/init.go index cf55b1ccf9a..7afe1606365 100644 --- a/cmd/kubectl-testkube/commands/cloud/init.go +++ b/cmd/kubectl-testkube/commands/cloud/init.go @@ -54,9 +54,12 @@ func NewInitCmd() *cobra.Command { ui.NL() ui.H2("Saving testkube cli cloud context") - token, err := LoginUser(options) - ui.ExitOnError("user login", err) - err = common.PopulateLoginDataToContext(options.CloudOrgId, options.CloudEnvId, token, options, cfg) + var token, refreshToken string + if !common.IsUserLoggedIn(cfg, options) { + token, refreshToken, err = LoginUser(options) + ui.ExitOnError("user login", err) + } + err = common.PopulateLoginDataToContext(options.CloudOrgId, options.CloudEnvId, token, refreshToken, options, cfg) ui.ExitOnError("Setting cloud environment context", err) ui.Info(" Happy Testing! 🚀") diff --git a/cmd/kubectl-testkube/commands/cloud/login.go b/cmd/kubectl-testkube/commands/cloud/login.go index 20c8048316c..3bd64502582 100644 --- a/cmd/kubectl-testkube/commands/cloud/login.go +++ b/cmd/kubectl-testkube/commands/cloud/login.go @@ -37,7 +37,7 @@ func NewLoginCmd() *cobra.Command { // open browser with login page and redirect to localhost open.Run(authUrl) - token, err := uiGetToken(tokenChan) + token, refreshToken, err := uiGetToken(tokenChan) ui.ExitOnError("getting token", err) orgID := opts.CloudOrgId @@ -54,20 +54,7 @@ func NewLoginCmd() *cobra.Command { cfg, err := config.Load() ui.ExitOnError("loading config file", err) - cfg.ContextType = config.ContextTypeCloud - cfg.CloudContext.OrganizationId = orgID - cfg.CloudContext.EnvironmentId = envID - - uris := opts.CloudUris - cfg.CloudContext.ApiUri = uris.Api - cfg.CloudContext.UiUri = uris.Ui - cfg.CloudContext.AgentUri = uris.Agent - cfg.CloudContext.ApiKey = token - cfg.CloudContext.TokenType = config.TokenTypeOIDC - - cfg = common.PopulateCloudConfig(cfg, token, orgID, envID, opts.CloudRootDomain) - - err = config.Save(cfg) + err = common.PopulateLoginDataToContext(orgID, envID, token, refreshToken, opts, cfg) ui.ExitOnError("saving config file", err) ui.Success("Your config was updated with new values") diff --git a/cmd/kubectl-testkube/commands/common/client.go b/cmd/kubectl-testkube/commands/common/client.go index 4d40f867f0e..a6c7591f6ce 100644 --- a/cmd/kubectl-testkube/commands/common/client.go +++ b/cmd/kubectl-testkube/commands/common/client.go @@ -1,6 +1,7 @@ package common import ( + "context" "errors" "fmt" "os" @@ -11,6 +12,7 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/config" "github.com/kubeshop/testkube/pkg/api/v1/client" + "github.com/kubeshop/testkube/pkg/cloudlogin" ) // GetClient returns api client @@ -69,9 +71,22 @@ func GetClient(cmd *cobra.Command) (client.Client, string, error) { } } case config.ContextTypeCloud: + + token := cfg.CloudContext.ApiKey + + if cfg.CloudContext.ApiKey != "" && cfg.CloudContext.RefreshToken != "" { + var refreshToken string + token, refreshToken, err = cloudlogin.CheckAndRefreshToken(context.Background(), fmt.Sprintf("%s/idp", cfg.CloudContext.ApiUri), cfg.CloudContext.ApiKey, cfg.CloudContext.RefreshToken) + if err != nil { + return nil, "", fmt.Errorf("error checking token: %w", err) + } + if err := UpdateTokens(cfg, token, refreshToken); err != nil { + return nil, "", fmt.Errorf("error storing new token: %w", err) + } + } clientType = string(client.ClientCloud) options.CloudApiPathPrefix = fmt.Sprintf("/organizations/%s/environments/%s/agent", cfg.CloudContext.OrganizationId, cfg.CloudContext.EnvironmentId) - options.CloudApiKey = cfg.CloudContext.ApiKey + options.CloudApiKey = token options.CloudEnvironment = cfg.CloudContext.EnvironmentId options.CloudOrganization = cfg.CloudContext.OrganizationId options.ApiUri = cfg.CloudContext.ApiUri diff --git a/cmd/kubectl-testkube/commands/common/helper.go b/cmd/kubectl-testkube/commands/common/helper.go index 19cfacbcf60..54191e93468 100644 --- a/cmd/kubectl-testkube/commands/common/helper.go +++ b/cmd/kubectl-testkube/commands/common/helper.go @@ -179,7 +179,7 @@ func PopulateHelmFlags(cmd *cobra.Command, options *HelmOptions) { cmd.Flags().BoolVar(&options.DryRun, "dry-run", false, "dry run mode - only print commands that would be executed") } -func PopulateLoginDataToContext(orgID, envID, token string, options HelmOptions, cfg config.Data) error { +func PopulateLoginDataToContext(orgID, envID, token, refreshToken string, options HelmOptions, cfg config.Data) error { if options.CloudAgentToken != "" { cfg.CloudContext.AgentKey = options.CloudAgentToken } @@ -195,7 +195,13 @@ func PopulateLoginDataToContext(orgID, envID, token string, options HelmOptions, cfg.ContextType = config.ContextTypeCloud cfg.CloudContext.OrganizationId = orgID cfg.CloudContext.EnvironmentId = envID - cfg.CloudContext.ApiKey = token + cfg.CloudContext.TokenType = config.TokenTypeOIDC + if token != "" { + cfg.CloudContext.ApiKey = token + } + if refreshToken != "" { + cfg.CloudContext.RefreshToken = refreshToken + } cfg, err := PopulateOrgAndEnvNames(cfg, orgID, envID, options.CloudRootDomain) if err != nil { @@ -243,6 +249,37 @@ func PopulateAgentDataToContext(options HelmOptions, cfg config.Data) error { return nil } +func IsUserLoggedIn(cfg config.Data, options HelmOptions) bool { + if options.CloudUris.Api != cfg.CloudContext.ApiUri { + //different environment + return false + } + + if cfg.CloudContext.ApiKey != "" && cfg.CloudContext.RefreshToken != "" { + // users with refresh token don't need to login again + // since on expired token they will be logged in automatically + return true + } + return false +} +func UpdateTokens(cfg config.Data, token, refreshToken string) error { + var updated bool + if token != cfg.CloudContext.ApiKey { + cfg.CloudContext.ApiKey = token + updated = true + } + if refreshToken != cfg.CloudContext.RefreshToken { + cfg.CloudContext.RefreshToken = refreshToken + updated = true + } + + if updated { + return config.Save(cfg) + } + + return nil +} + func KubectlScaleDeployment(namespace, deployment string, replicas int) (string, error) { kubectl, err := exec.LookPath("kubectl") if err != nil { diff --git a/cmd/kubectl-testkube/commands/crds/tests_crds.go b/cmd/kubectl-testkube/commands/crds/tests_crds.go index df5de85f1b2..67ca9546562 100644 --- a/cmd/kubectl-testkube/commands/crds/tests_crds.go +++ b/cmd/kubectl-testkube/commands/crds/tests_crds.go @@ -97,7 +97,7 @@ func processPostmanFiles(cmd *cobra.Command, args []string) error { testEnvs := make(map[string]map[string]string, 0) testSecretEnvs := make(map[string]map[string]string, 0) - var script []byte + var preRunScript, postRunScript []byte err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { if err != nil { @@ -143,9 +143,14 @@ func processPostmanFiles(cmd *cobra.Command, args []string) error { testName = test.Name } - scriptBody := string(script) - if scriptBody != "" { - scriptBody = fmt.Sprintf("%q", strings.TrimSpace(scriptBody)) + preRunScriptBody := string(preRunScript) + if preRunScriptBody != "" { + preRunScriptBody = fmt.Sprintf("%q", strings.TrimSpace(preRunScriptBody)) + } + + postRunScriptBody := string(postRunScript) + if postRunScriptBody != "" { + postRunScriptBody = fmt.Sprintf("%q", strings.TrimSpace(postRunScriptBody)) } for key, value := range flags.Envs { @@ -167,12 +172,13 @@ func processPostmanFiles(cmd *cobra.Command, args []string) error { } test.ExecutionRequest = &testkube.ExecutionRequest{ - Command: flags.Command, - Args: flags.ExecutorArgs, - ArgsMode: flags.ArgsMode, - Envs: flags.Envs, - Variables: vars, - PreRunScript: scriptBody, + Command: flags.Command, + Args: flags.ExecutorArgs, + ArgsMode: flags.ArgsMode, + Envs: flags.Envs, + Variables: vars, + PreRunScript: preRunScriptBody, + PostRunScript: postRunScriptBody, } detectedTests[testName] = test return nil diff --git a/cmd/kubectl-testkube/commands/get.go b/cmd/kubectl-testkube/commands/get.go index c7cfb17dfad..3cde0dcc96f 100644 --- a/cmd/kubectl-testkube/commands/get.go +++ b/cmd/kubectl-testkube/commands/get.go @@ -34,8 +34,7 @@ func NewGetCmd() *cobra.Command { validator.PersistentPreRunVersionCheck(cmd, common.Version) - }, - } + }} cmd.AddCommand(tests.NewGetTestsCmd()) cmd.AddCommand(testsuites.NewGetTestSuiteCmd()) diff --git a/cmd/kubectl-testkube/commands/root.go b/cmd/kubectl-testkube/commands/root.go index c0b27a0cd0d..fad8bd64236 100644 --- a/cmd/kubectl-testkube/commands/root.go +++ b/cmd/kubectl-testkube/commands/root.go @@ -72,6 +72,8 @@ var RootCmd = &cobra.Command{ cfg, err := config.Load() ui.ExitOnError("loading config", err) + common.UiContextHeader(cmd, cfg) + if err = validator.ValidateCloudContext(cfg); err != nil { common.UiCloudContextValidationError(err) } diff --git a/cmd/kubectl-testkube/commands/tests/common.go b/cmd/kubectl-testkube/commands/tests/common.go index 936283c46d6..0e79c1dd640 100644 --- a/cmd/kubectl-testkube/commands/tests/common.go +++ b/cmd/kubectl-testkube/commands/tests/common.go @@ -337,7 +337,10 @@ func newExecutionRequestFromFlags(cmd *cobra.Command) (request *testkube.Executi return nil, err } - argsMode := cmd.Flag("args-mode").Value.String() + mode := "" + if cmd.Flag("args-mode").Changed { + mode = cmd.Flag("args-mode").Value.String() + } executionName := cmd.Flag("execution-name").Value.String() envs, err := cmd.Flags().GetStringToString("env") if err != nil { @@ -410,6 +413,17 @@ func newExecutionRequestFromFlags(cmd *cobra.Command) (request *testkube.Executi preRunScriptContent = string(b) } + postRunScriptContent := "" + postRunScript := cmd.Flag("postrun-script").Value.String() + if postRunScript != "" { + b, err := os.ReadFile(postRunScript) + if err != nil { + return nil, err + } + + postRunScriptContent = string(b) + } + scraperTemplateContent := "" scraperTemplate := cmd.Flag("scraper-template").Value.String() if scraperTemplate != "" { @@ -432,7 +446,7 @@ func newExecutionRequestFromFlags(cmd *cobra.Command) (request *testkube.Executi Image: image, Command: command, Args: executorArgs, - ArgsMode: argsMode, + ArgsMode: mode, ImagePullSecrets: imageSecrets, Envs: envs, SecretEnvs: secretEnvs, @@ -442,6 +456,7 @@ func newExecutionRequestFromFlags(cmd *cobra.Command) (request *testkube.Executi JobTemplate: jobTemplateContent, CronJobTemplate: cronJobTemplateContent, PreRunScript: preRunScriptContent, + PostRunScript: postRunScriptContent, ScraperTemplate: scraperTemplateContent, NegativeTest: negativeTest, EnvConfigMaps: envConfigMaps, @@ -937,6 +952,22 @@ func newExecutionUpdateRequestFromFlags(cmd *cobra.Command) (request *testkube.E nonEmpty = true } + 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 + } + if cmd.Flag("scraper-template").Changed { scraperTemplateContent := "" scraperTemplate := cmd.Flag("scraper-template").Value.String() diff --git a/cmd/kubectl-testkube/commands/tests/create.go b/cmd/kubectl-testkube/commands/tests/create.go index 12615339311..513c85dd64d 100644 --- a/cmd/kubectl-testkube/commands/tests/create.go +++ b/cmd/kubectl-testkube/commands/tests/create.go @@ -41,6 +41,7 @@ type CreateCommonFlags struct { JobTemplate string CronJobTemplate string PreRunScript string + PostRunScript string ScraperTemplate string NegativeTest bool MountConfigMaps map[string]string @@ -208,6 +209,7 @@ func AddCreateFlags(cmd *cobra.Command, flags *CreateCommonFlags) { cmd.Flags().StringVar(&flags.JobTemplate, "job-template", "", "job template file path for extensions to job template") cmd.Flags().StringVar(&flags.CronJobTemplate, "cronjob-template", "", "cron job template file path for extensions to cron job template") 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().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") diff --git a/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go b/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go index c67352b2440..21099d04156 100644 --- a/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go +++ b/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go @@ -93,6 +93,10 @@ func TestRenderer(ui *ui.UI, obj interface{}) error { } ui.Warn(" Args mode: ", test.ExecutionRequest.ArgsMode) + if test.ExecutionRequest.ArgsMode != "" { + ui.Warn(" Args mode: ", test.ExecutionRequest.ArgsMode) + } + if len(test.ExecutionRequest.Envs) > 0 { ui.NL() ui.Warn("(deprecated) Envs: ", testkube.MapToString(test.ExecutionRequest.Envs)) @@ -135,6 +139,10 @@ func TestRenderer(ui *ui.UI, obj interface{}) error { ui.Warn(" Pre run script: ", "\n", test.ExecutionRequest.PreRunScript) } + if test.ExecutionRequest.PostRunScript != "" { + ui.Warn(" Post run script: ", "\n", test.ExecutionRequest.PostRunScript) + } + if test.ExecutionRequest.ScraperTemplate != "" { ui.Warn(" Scraper template: ", "\n", test.ExecutionRequest.ScraperTemplate) } diff --git a/cmd/kubectl-testkube/commands/tests/run.go b/cmd/kubectl-testkube/commands/tests/run.go index bf8be4e4250..2d6323b364b 100644 --- a/cmd/kubectl-testkube/commands/tests/run.go +++ b/cmd/kubectl-testkube/commands/tests/run.go @@ -47,6 +47,7 @@ func NewRunTestCmd() *cobra.Command { gitPath string gitWorkingDir string preRunScript string + postRunScript string scraperTemplate string negativeTest bool mountConfigMaps map[string]string @@ -93,6 +94,13 @@ func NewRunTestCmd() *cobra.Command { 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) @@ -122,6 +130,7 @@ func NewRunTestCmd() *cobra.Command { Image: image, JobTemplate: jobTemplateContent, PreRunScriptContent: preRunScriptContent, + PostRunScriptContent: postRunScriptContent, ScraperTemplate: scraperTemplateContent, IsNegativeTestChangedOnRun: false, EnvConfigMaps: envConfigMaps, @@ -276,6 +285,7 @@ func NewRunTestCmd() *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().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().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") diff --git a/cmd/kubectl-testkube/commands/tests/update.go b/cmd/kubectl-testkube/commands/tests/update.go index 28ad44163eb..f9628d1207c 100644 --- a/cmd/kubectl-testkube/commands/tests/update.go +++ b/cmd/kubectl-testkube/commands/tests/update.go @@ -50,6 +50,7 @@ func NewUpdateTestsCmd() *cobra.Command { jobTemplate string cronJobTemplate string preRunScript string + postRunScript string scraperTemplate string negativeTest bool mountConfigMaps map[string]string @@ -130,6 +131,7 @@ func NewUpdateTestsCmd() *cobra.Command { 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().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().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") diff --git a/cmd/kubectl-testkube/commands/testsuites/common.go b/cmd/kubectl-testkube/commands/testsuites/common.go index 01de6f0bcf1..7403c5a8616 100644 --- a/cmd/kubectl-testkube/commands/testsuites/common.go +++ b/cmd/kubectl-testkube/commands/testsuites/common.go @@ -90,9 +90,35 @@ func NewTestSuiteUpsertOptionsFromFlags(cmd *cobra.Command) (options apiclientv1 return options, fmt.Errorf("empty test suite content") } - err = json.Unmarshal([]byte(*data), &options) - if err != nil { - return options, err + if err = json.Unmarshal([]byte(*data), &options); err != nil { + ui.Debug("json unmarshaling", err.Error()) + } + + emptyBatch := true + for _, step := range options.Steps { + if len(step.Execute) != 0 { + emptyBatch = false + break + } + } + + if emptyBatch { + var testSuite testkube.TestSuiteUpsertRequestV2 + err = json.Unmarshal([]byte(*data), &testSuite) + if err != nil { + return options, err + } + + options = apiclientv1.UpsertTestSuiteOptions(*testSuite.ToTestSuiteUpsertRequest()) + if len(options.Steps) == 0 { + return options, fmt.Errorf("no test suite batch steps provided") + } + } + + for _, step := range options.Steps { + if len(step.Execute) == 0 { + return options, fmt.Errorf("no steps defined for batch step") + } } name := cmd.Flag("name").Value.String() @@ -155,9 +181,29 @@ func NewTestSuiteUpdateOptionsFromFlags(cmd *cobra.Command) (options apiclientv1 } if data != nil { - err = json.Unmarshal([]byte(*data), &options) - if err != nil { - return options, err + if err = json.Unmarshal([]byte(*data), &options); err != nil { + ui.Debug("json unmarshaling", err.Error()) + } + + if options.Steps != nil { + emptyBatch := true + for _, step := range *options.Steps { + if len(step.Execute) != 0 { + emptyBatch = false + break + } + } + + if emptyBatch { + var testSuite testkube.TestSuiteUpdateRequestV2 + err = json.Unmarshal([]byte(*data), &testSuite) + if err != nil { + return options, err + } + + options = apiclientv1.UpdateTestSuiteOptions(*testSuite.ToTestSuiteUpdateRequest()) + } + } } diff --git a/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go b/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go index 2d400c8f45a..399481f2db1 100644 --- a/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go +++ b/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go @@ -2,6 +2,7 @@ package renderer import ( "fmt" + "strings" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/renderer" "github.com/kubeshop/testkube/pkg/api/v1/testkube" @@ -48,17 +49,21 @@ func TestSuiteRenderer(ui *ui.UI, obj interface{}) error { } } - steps := append(ts.Before, ts.Steps...) - steps = append(steps, ts.After...) + batches := append(ts.Before, ts.Steps...) + batches = append(batches, ts.After...) ui.NL() - ui.Warn("Test steps:", fmt.Sprintf("%d", len(steps))) - d := [][]string{{"Name", "Stop on failure", "Type"}} - for _, step := range steps { + ui.Warn("Test batches:", fmt.Sprintf("%d", len(batches))) + d := [][]string{{"Names", "Stop on failure"}} + for _, batch := range batches { + var names []string + for _, step := range batch.Execute { + names = append(names, step.FullName()) + } + d = append(d, []string{ - step.FullName(), - fmt.Sprintf("%v", step.StopTestOnFailure), - string(*step.Type()), + fmt.Sprintf("[%s]", strings.Join(names, ", ")), + fmt.Sprintf("%v", batch.StopOnFailure), }) } diff --git a/cmd/kubectl-testkube/commands/testsuites/run.go b/cmd/kubectl-testkube/commands/testsuites/run.go index 702e62343bb..76fdd1c92e4 100644 --- a/cmd/kubectl-testkube/commands/testsuites/run.go +++ b/cmd/kubectl-testkube/commands/testsuites/run.go @@ -57,6 +57,7 @@ func NewRunTestSuiteCmd() *cobra.Command { Type_: string(testkube.RunningContextTypeUserCLI), Context: runningContext, }, + ConcurrencyLevel: int32(concurrencyLevel), } if gitBranch != "" || gitCommit != "" || gitPath != "" || gitWorkingDir != "" { diff --git a/cmd/kubectl-testkube/commands/webhooks/create.go b/cmd/kubectl-testkube/commands/webhooks/create.go index 666ca7f42c8..81d91c35756 100644 --- a/cmd/kubectl-testkube/commands/webhooks/create.go +++ b/cmd/kubectl-testkube/commands/webhooks/create.go @@ -1,6 +1,8 @@ package webhooks import ( + "fmt" + "os" "strconv" "github.com/spf13/cobra" @@ -19,6 +21,8 @@ func NewCreateWebhookCmd() *cobra.Command { selector string labels map[string]string payloadObjectField string + payloadTemplate string + headers map[string]string ) cmd := &cobra.Command{ @@ -35,6 +39,14 @@ func NewCreateWebhookCmd() *cobra.Command { } namespace := cmd.Flag("namespace").Value.String() + payloadTemplate = cmd.Flag("payload-template").Value.String() + payloadTemplateContent := "" + if payloadTemplate != "" { + b, err := os.ReadFile(payloadTemplate) + ui.ExitOnError("reading job template", err) + payloadTemplateContent = string(b) + } + var client apiv1.Client if !crdOnly { client, namespace, err = common.GetClient(cmd) @@ -54,6 +66,8 @@ func NewCreateWebhookCmd() *cobra.Command { Selector: selector, Labels: labels, PayloadObjectField: payloadObjectField, + PayloadTemplate: payloadTemplateContent, + Headers: headers, } if !crdOnly { @@ -62,6 +76,10 @@ func NewCreateWebhookCmd() *cobra.Command { ui.Success("Webhook created", name) } else { + if options.PayloadTemplate != "" { + options.PayloadTemplate = fmt.Sprintf("%q", options.PayloadTemplate) + } + data, err := crd.ExecuteTemplate(crd.TemplateWebhook, options) ui.ExitOnError("executing crd template", err) @@ -76,6 +94,8 @@ func NewCreateWebhookCmd() *cobra.Command { 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") return cmd } diff --git a/cmd/kubectl-testkube/commands/webhooks/get.go b/cmd/kubectl-testkube/commands/webhooks/get.go index 571012a4f25..1ea05b02f56 100644 --- a/cmd/kubectl-testkube/commands/webhooks/get.go +++ b/cmd/kubectl-testkube/commands/webhooks/get.go @@ -1,6 +1,7 @@ package webhooks import ( + "fmt" "os" "strings" @@ -33,6 +34,10 @@ func NewGetWebhookCmd() *cobra.Command { ui.ExitOnError("getting webhook: "+name, err) if crdOnly { + if webhook.PayloadTemplate != "" { + webhook.PayloadTemplate = fmt.Sprintf("%q", webhook.PayloadTemplate) + } + common.UIPrintCRD(crd.TemplateWebhook, webhook, &firstEntry) return } @@ -45,6 +50,10 @@ func NewGetWebhookCmd() *cobra.Command { if crdOnly { for _, webhook := range webhooks { + if webhook.PayloadTemplate != "" { + webhook.PayloadTemplate = fmt.Sprintf("%q", webhook.PayloadTemplate) + } + common.UIPrintCRD(crd.TemplateWebhook, webhook, &firstEntry) } diff --git a/cmd/kubectl-testkube/config/data.go b/cmd/kubectl-testkube/config/data.go index 904b61d3722..eb28be90344 100644 --- a/cmd/kubectl-testkube/config/data.go +++ b/cmd/kubectl-testkube/config/data.go @@ -22,6 +22,7 @@ type CloudContext struct { OrganizationId string `json:"organization,omitempty"` OrganizationName string `json:"organizationName,omitempty"` ApiKey string `json:"apiKey,omitempty"` + RefreshToken string `json:"refreshToken,omitempty"` ApiUri string `json:"apiUri,omitempty"` AgentKey string `json:"agentKey,omitempty"` AgentUri string `json:"agentUri,omitempty"` diff --git a/docs/docs/articles/adding-timeout.mdx b/docs/docs/articles/adding-timeout.mdx index a2465cc968b..3c2598012b2 100644 --- a/docs/docs/articles/adding-timeout.mdx +++ b/docs/docs/articles/adding-timeout.mdx @@ -4,7 +4,7 @@ import TabItem from "@theme/TabItem"; # Adding Timeouts - + Click on a *Test > Settings > General > Timout*: diff --git a/docs/docs/articles/cd-events.md b/docs/docs/articles/cd-events.md new file mode 100644 index 00000000000..41a1f39e35a --- /dev/null +++ b/docs/docs/articles/cd-events.md @@ -0,0 +1,81 @@ +# Emitting standard CDEvents + +[CDEvents](https://cdevents.dev/) is a common specification for Continuous Delivery events. Testkube is a proud supporter of this specification and we have added in the specification support for [Testing Events](https://github.com/cdevents/spec/blob/main/testing-events.md) to be release on v0.3.0 of the spec. + +As of Testkube 1.12 release, Testkube can emit standard CDEvents to a webhook endpoint. This can be used to integrate with any CD tool that supports the CDEvents standard. + +## Step 1 - Enable CDEvents + +To enable CDEvents, you need to set the following Helm +values: + +```sh +helm upgrade \ + --install \ + --create-namespace \ + --namespace testkube \ + testkube \ + kubeshop/testkube \ + --set testkube-api.cdeventsTarget=https://YOUR_WEBHOOK_URL +``` + +For testing purposes you can use [webhook.site](https://webhook.site/) to get a webhook URL. + +## Step 2 - Test emmiting CDEvents + +To test emitting CDEvents, create a sample test with Testkube and run it. + +```sh +cat << EOF | > curl-test.json +{ + "command": [ + "curl", + "https://example.com" + ], + "expected_status": "200" +} +EOF + +testkube create test --name test-cdevents --type curl/test -f curl-test.json + +testkube run test test-cdevents +``` + +Check the webhook sink to see the CD Event emitted by Testkube. An event like the following should have been emmitted: + +```json +{ + "context": { + "version": "0.2.0", + "id": "85e4cef0-e5bf-4bfd-9e62-5b227867b064", + "source": "cluster56c26628bece30eb07f01a64daaa3f27", + "type": "dev.cdevents.testcaserun.finished.0.1.0", + "timestamp": "2023-06-08T11:30:22.30535521Z" + }, + "subject": { + "id": "test-cdevents-7", + "source": "cluster56c26628bece30eb07f01a64daaa3f27", + "type": "testCaseRun", + "content": { + "environment": { + "id": "testkube", + "source": "cluster56c26628bece30eb07f01a64daaa3f27" + }, + "outcome": "pass", + "testCase": { + "id": "test-cdevents", + "type": "functional", + "uri": "/tests/executions/test-cdevents" + } + } + } +} +``` + +## Reference + +For more information about CDEvents, please visit the [CDEvents](https://cdevents.dev/) website. + +To know more about the Testing Events specification, please visit the [Testing Events](https://github.com/cdevents/spec/blob/main/testing-events.md) + + diff --git a/docs/docs/articles/common-issues.md b/docs/docs/articles/common-issues.md index 8f1ac11bbff..b8d78d4f606 100644 --- a/docs/docs/articles/common-issues.md +++ b/docs/docs/articles/common-issues.md @@ -155,10 +155,13 @@ metadata: name: s3-access namespace: testkube ``` +In the Helm values.yaml file: +2. Add the ARN annotation from above to `testkube-api.serviceAccount.annotations`. +3. Link the ServiceAccount to the `testkube-api.minio.serviceAccountName` and to `testkube-api.jobServiceAccountName`. +4. Leave `minio.minioRootUser`, `minio.minioRootPassword` and `storage.port` empty. +5. Set `storage.endpoint` to `s3.amazonaws.com`. -2. In the Helm values.yaml file, link the ServiceAccount to the `testkube-api.minio.serviceAccountName` and to `testkube-api.jobServiceAccountName` then leave `minio.minioRootUser`, `minio.minioRootPassword` and `storage.port` empty and set `storage.endpoint` to `s3.amazonaws.com`. - -3. Install using Helm and the values file with the above modifications. +6. Install using Helm and the values file with the above modifications. ## Observability diff --git a/docs/docs/articles/crds-reference.md b/docs/docs/articles/crds-reference.md new file mode 100644 index 00000000000..89359d4a105 --- /dev/null +++ b/docs/docs/articles/crds-reference.md @@ -0,0 +1,1185 @@ +# CRDs Reference + +CRDs (Custom Resource Definitions) reference. Read more Testkube's CRDs in [Testkube Custom Resources](./crds.md) section. + +## Packages + +- [executor.testkube.io/v1](#executortestkubeiov1) +- [tests.testkube.io/v1](#teststestkubeiov1) +- [tests.testkube.io/v2](#teststestkubeiov2) +- [tests.testkube.io/v3](#teststestkubeiov3) + +## executor.testkube.io/v1 + +Package v1 contains API Schema definitions for the executor v1 API group + +### Resource Types + +- [Executor](#executor) +- [ExecutorList](#executorlist) +- [Webhook](#webhook) +- [WebhookList](#webhooklist) + +#### EventType + +_Underlying type:_ `string` + +_Appears in:_ + +- [WebhookSpec](#webhookspec) + +#### Executor + +Executor is the Schema for the executors API + +_Appears in:_ + +- [ExecutorList](#executorlist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `executor.testkube.io/v1` | +| `kind` _string_ | `Executor` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[ExecutorSpec](#executorspec)_ | | + +#### ExecutorList + +ExecutorList contains a list of Executor + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `executor.testkube.io/v1` | +| `kind` _string_ | `ExecutorList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Executor](#executor) array_ | | + +#### ExecutorMeta + +Executor meta data + +_Appears in:_ + +- [ExecutorSpec](#executorspec) + +| Field | Description | +| ------------------------------------------------ | --------------------- | +| `iconURI` _string_ | URI for executor icon | +| `docsURI` _string_ | URI for executor docs | +| `tooltips` _object (keys:string, values:string)_ | executor tooltips | + +#### ExecutorSpec + +ExecutorSpec defines the desired state of Executor + +_Appears in:_ + +- [Executor](#executor) + +| Field | Description | +| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `types` _string array_ | Types defines what types can be handled by executor e.g. "postman/collection", ":curl/command" etc | +| `executor_type` _[ExecutorType](#executortype)_ | ExecutorType one of "rest" for rest openapi based executors or "job" which will be default runners for testkube or "container" for container executors | +| `uri` _string_ | URI for rest based executors | +| `image` _string_ | Image for kube-job | +| `args` _string array_ | executor binary arguments | +| `command` _string array_ | executor default binary command | +| `imagePullSecrets` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#localobjectreference-v1-core) array_ | container executor default image pull secrets | +| `features` _[Feature](#feature) array_ | Features list of possible features which executor handles | +| `content_types` _[ScriptContentType](#scriptcontenttype) array_ | ContentTypes list of handled content types | +| `job_template` _string_ | Job template to launch executor | +| `meta` _[ExecutorMeta](#executormeta)_ | Meta data about executor | + +#### ExecutorType + +_Underlying type:_ `string` + +_Appears in:_ + +- [ExecutorSpec](#executorspec) + +#### Feature + +_Underlying type:_ `string` + +_Appears in:_ + +- [ExecutorSpec](#executorspec) + +#### ScriptContentType + +_Underlying type:_ `string` + +_Appears in:_ + +- [ExecutorSpec](#executorspec) + +#### Webhook + +Webhook is the Schema for the webhooks API + +_Appears in:_ + +- [WebhookList](#webhooklist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `executor.testkube.io/v1` | +| `kind` _string_ | `Webhook` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[WebhookSpec](#webhookspec)_ | | + +#### WebhookList + +WebhookList contains a list of Webhook + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `executor.testkube.io/v1` | +| `kind` _string_ | `WebhookList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Webhook](#webhook) array_ | | + +#### WebhookSpec + +WebhookSpec defines the desired state of Webhook + +_Appears in:_ + +- [Webhook](#webhook) + +| Field | Description | +| ----------------------------------------------- | ------------------------------------------------------------------ | +| `uri` _string_ | Uri is address where webhook should be made | +| `events` _[EventType](#eventtype) array_ | Events declare list if events on which webhook should be called | +| `selector` _string_ | Labels to filter for tests and test suites | +| `payloadObjectField` _string_ | will load the generated payload for notification inside the object | +| `payloadTemplate` _string_ | golang based template for notification payload | +| `headers` _object (keys:string, values:string)_ | webhook headers | + +## tests.testkube.io/v1 + +Package v1 contains API Schema definitions for the testkube v1 API group + +### Resource Types + +- [Script](#script) +- [ScriptList](#scriptlist) +- [Test](#test) +- [TestList](#testlist) +- [TestSource](#testsource) +- [TestSourceList](#testsourcelist) +- [TestSuite](#testsuite) +- [TestSuiteList](#testsuitelist) +- [TestTrigger](#testtrigger) +- [TestTriggerList](#testtriggerlist) + +#### GitAuthType + +_Underlying type:_ `string` + +GitAuthType defines git auth type + +_Appears in:_ + +- [Repository](#repository) + +#### Repository + +Repository represents VCS repo, currently we're handling Git only + +_Appears in:_ + +- [TestSourceSpec](#testsourcespec) + +| Field | Description | +| ------------------------------------------ | ---------------------------------------------------------------------------------------- | +| `type` _string_ | VCS repository type | +| `uri` _string_ | uri of content file or git directory | +| `branch` _string_ | branch/tag name for checkout | +| `commit` _string_ | commit id (sha) for checkout | +| `path` _string_ | if needed we can checkout particular path (dir or file) in case of BIG/mono repositories | +| `usernameSecret` _[SecretRef](#secretref)_ | | +| `tokenSecret` _[SecretRef](#secretref)_ | | +| `certificateSecret` _string_ | git auth certificate secret for private repositories | +| `workingDir` _string_ | if provided we checkout the whole repository and run test from this directory | +| `authType` _[GitAuthType](#gitauthtype)_ | auth type for git requests | + +#### Script + +Script is the Schema for the scripts API + +_Appears in:_ + +- [ScriptList](#scriptlist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v1` | +| `kind` _string_ | `Script` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[ScriptSpec](#scriptspec)_ | | + +#### ScriptList + +ScriptList contains a list of Script + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v1` | +| `kind` _string_ | `ScriptList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Script](#script) array_ | | + +#### ScriptSpec + +ScriptSpec defines the desired state of Script + +_Appears in:_ + +- [Script](#script) + +| Field | Description | +| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` _string_ | script type | +| `name` _string_ | script execution custom name | +| `params` _object (keys:string, values:string)_ | execution params passed to executor | +| `content` _string_ | script content as string (content depends from executor) | +| `input-type` _string_ | script content type can be: - direct content - created from file, - git repo directory checkout in case when test is some kind of project or have more than one file, | +| `repository` _[Repository](#repository)_ | repository details if exists | +| `tags` _string array_ | | + +#### SecretRef + +Testkube internal reference for secret storage in Kubernetes secrets + +_Appears in:_ + +- [Repository](#repository) + +| Field | Description | +| -------------------- | --------------------------- | +| `namespace` _string_ | object kubernetes namespace | +| `name` _string_ | object name | +| `key` _string_ | object key | + +#### Test + +Test is the Schema for the tests API + +_Appears in:_ + +- [TestList](#testlist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v1` | +| `kind` _string_ | `Test` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[TestSpec](#testspec)_ | | + +#### TestList + +TestList contains a list of Test + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v1` | +| `kind` _string_ | `TestList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Test](#test) array_ | | + +#### TestSource + +TestSource is the Schema for the testsources API + +_Appears in:_ + +- [TestSourceList](#testsourcelist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v1` | +| `kind` _string_ | `TestSource` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[TestSourceSpec](#testsourcespec)_ | | + +#### TestSourceList + +TestSourceList contains a list of TestSource + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v1` | +| `kind` _string_ | `TestSourceList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[TestSource](#testsource) array_ | | + +#### TestSourceSpec + +TestSourceSpec defines the desired state of TestSource + +_Appears in:_ + +- [TestSource](#testsource) + +| Field | Description | +| ------------------------------------------ | -------------------------- | +| `type` _[TestSourceType](#testsourcetype)_ | | +| `repository` _[Repository](#repository)_ | repository of test content | +| `data` _string_ | test content body | +| `uri` _string_ | uri of test content | + +#### TestSourceType + +_Underlying type:_ `string` + +_Appears in:_ + +- [TestSourceSpec](#testsourcespec) + +#### TestSpec + +TestSpec defines the desired state of Test + +_Appears in:_ + +- [Test](#test) + +| Field | Description | +| ---------------------------------------------- | ----------------------------------------------------------------------- | +| `before` _[TestStepSpec](#teststepspec) array_ | Before steps is list of scripts which will be sequentially orchestrated | +| `steps` _[TestStepSpec](#teststepspec) array_ | Steps is list of scripts which will be sequentially orchestrated | +| `after` _[TestStepSpec](#teststepspec) array_ | After steps is list of scripts which will be sequentially orchestrated | +| `repeats` _integer_ | | +| `description` _string_ | | +| `tags` _string array_ | | + +#### TestStepDelay + +_Appears in:_ + +- [TestStepSpec](#teststepspec) + +| Field | Description | +| -------------------- | -------------- | +| `duration` _integer_ | Duration in ms | + +#### TestStepExecute + +_Appears in:_ + +- [TestStepSpec](#teststepspec) + +| Field | Description | +| ------------------------- | ----------- | +| `namespace` _string_ | | +| `name` _string_ | | +| `stopOnFailure` _boolean_ | | + +#### TestStepSpec + +TestStepSpec will of particular type will have config for possible step types + +_Appears in:_ + +- [TestSpec](#testspec) + +| Field | Description | +| ----------------------------------------------- | ----------- | +| `type` _string_ | | +| `execute` _[TestStepExecute](#teststepexecute)_ | | +| `delay` _[TestStepDelay](#teststepdelay)_ | | + +#### TestSuite + +TestSuite is the Schema for the testsuites API + +_Appears in:_ + +- [TestSuiteList](#testsuitelist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v1` | +| `kind` _string_ | `TestSuite` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[TestSuiteSpec](#testsuitespec)_ | | + +#### TestSuiteList + +TestSuiteList contains a list of TestSuite + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v1` | +| `kind` _string_ | `TestSuiteList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[TestSuite](#testsuite) array_ | | + +#### TestSuiteSpec + +TestSuiteSpec defines the desired state of TestSuite + +_Appears in:_ + +- [TestSuite](#testsuite) + +| Field | Description | +| ---------------------------------------------------------------- | --------------------------------------------------------------------- | +| `before` _[TestSuiteStepSpec](#testsuitestepspec) array_ | Before steps is list of tests which will be sequentially orchestrated | +| `steps` _[TestSuiteStepSpec](#testsuitestepspec) array_ | Steps is list of tests which will be sequentially orchestrated | +| `after` _[TestSuiteStepSpec](#testsuitestepspec) array_ | After steps is list of tests which will be sequentially orchestrated | +| `repeats` _integer_ | | +| `description` _string_ | | +| `schedule` _string_ | schedule in cron job format for scheduled test execution | +| `params` _object (keys:string, values:string)_ | DEPRECATED execution params passed to executor | +| `variables` _object (keys:string, values:[Variable](#variable))_ | Variables are new params with secrets attached | + +#### TestSuiteStepDelay + +TestSuiteStepDelay contains step delay parameters + +_Appears in:_ + +- [TestSuiteStepSpec](#testsuitestepspec) + +| Field | Description | +| -------------------- | -------------- | +| `duration` _integer_ | Duration in ms | + +#### TestSuiteStepExecute + +TestSuiteStepExecute defines step to be executed + +_Appears in:_ + +- [TestSuiteStepSpec](#testsuitestepspec) + +| Field | Description | +| ------------------------- | ----------- | +| `namespace` _string_ | | +| `name` _string_ | | +| `stopOnFailure` _boolean_ | | + +#### TestSuiteStepSpec + +TestSuiteStepSpec will of particular type will have config for possible step types + +_Appears in:_ + +- [TestSuiteSpec](#testsuitespec) + +| Field | Description | +| --------------------------------------------------------- | ----------- | +| `type` _string_ | | +| `execute` _[TestSuiteStepExecute](#testsuitestepexecute)_ | | +| `delay` _[TestSuiteStepDelay](#testsuitestepdelay)_ | | + +#### TestTrigger + +TestTrigger is the Schema for the testtriggers API + +_Appears in:_ + +- [TestTriggerList](#testtriggerlist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v1` | +| `kind` _string_ | `TestTrigger` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[TestTriggerSpec](#testtriggerspec)_ | | + +#### TestTriggerAction + +_Underlying type:_ `string` + +TestTriggerAction defines action for test triggers + +_Appears in:_ + +- [TestTriggerSpec](#testtriggerspec) + +#### TestTriggerCondition + +TestTriggerCondition is used for definition of the condition for test triggers + +_Appears in:_ + +- [TestTriggerConditionSpec](#testtriggerconditionspec) + +| Field | Description | +| ----------------- | ----------------------------------------------------------------------------------- | +| `type` _string_ | test trigger condition | +| `reason` _string_ | test trigger condition reason | +| `ttl` _integer_ | duration in seconds in the past from current time when the condition is still valid | + +#### TestTriggerConditionSpec + +TestTriggerConditionSpec defines the condition specification for TestTrigger + +_Appears in:_ + +- [TestTriggerSpec](#testtriggerspec) + +| Field | Description | +| ------------------------------------------------------------------ | ---------------------------------------------------------------------------- | +| `conditions` _[TestTriggerCondition](#testtriggercondition) array_ | list of test trigger conditions | +| `timeout` _integer_ | duration in seconds the test trigger waits for conditions, until its stopped | + +#### TestTriggerEvent + +_Underlying type:_ `string` + +TestTriggerEvent defines event for test triggers + +_Appears in:_ + +- [TestTriggerSpec](#testtriggerspec) + +#### TestTriggerExecution + +_Underlying type:_ `string` + +TestTriggerExecution defines execution for test triggers + +_Appears in:_ + +- [TestTriggerSpec](#testtriggerspec) + +#### TestTriggerList + +TestTriggerList contains a list of TestTrigger + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v1` | +| `kind` _string_ | `TestTriggerList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[TestTrigger](#testtrigger) array_ | | + +#### TestTriggerResource + +_Underlying type:_ `string` + +TestTriggerResource defines resource for test triggers + +_Appears in:_ + +- [TestTriggerSpec](#testtriggerspec) + +#### TestTriggerSelector + +TestTriggerSelector is used for selecting Kubernetes Objects + +_Appears in:_ + +- [TestTriggerSpec](#testtriggerspec) + +| Field | Description | +| ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | +| `name` _string_ | Name selector is used to identify a Kubernetes Object based on the metadata name | +| `namespace` _string_ | Namespace of the Kubernetes object | +| `labelSelector` _[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#labelselector-v1-meta)_ | LabelSelector is used to identify a group of Kubernetes Objects based on their metadata labels | + +#### TestTriggerSpec + +TestTriggerSpec defines the desired state of TestTrigger + +_Appears in:_ + +- [TestTrigger](#testtrigger) + +| Field | Description | +| ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `resource` _[TestTriggerResource](#testtriggerresource)_ | For which Resource do we monitor Event which triggers an Action on certain conditions | +| `resourceSelector` _[TestTriggerSelector](#testtriggerselector)_ | ResourceSelector identifies which Kubernetes Objects should be watched | +| `event` _[TestTriggerEvent](#testtriggerevent)_ | On which Event for a Resource should an Action be triggered | +| `conditionSpec` _[TestTriggerConditionSpec](#testtriggerconditionspec)_ | What resource conditions should be matched | +| `action` _[TestTriggerAction](#testtriggeraction)_ | Action represents what needs to be executed for selected Execution | +| `execution` _[TestTriggerExecution](#testtriggerexecution)_ | Execution identifies for which test execution should an Action be executed | +| `testSelector` _[TestTriggerSelector](#testtriggerselector)_ | TestSelector identifies on which Testkube Kubernetes Objects an Action should be taken | +| `delay` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#duration-v1-meta)_ | Delay is a duration string which specifies how long should the test be delayed after a trigger is matched | + +#### Variable + +_Appears in:_ + +- [ExecutionRequest](#executionrequest) +- [TestSpec](#testspec) +- [TestSuiteExecutionRequest](#testsuiteexecutionrequest) +- [TestSuiteSpec](#testsuitespec) + +| Field | Description | +| ----------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `type` _string_ | variable type | +| `name` _string_ | variable name | +| `value` _string_ | variable string value | +| `valueFrom` _[EnvVarSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#envvarsource-v1-core)_ | or load it from var source | + +## tests.testkube.io/v2 + +Package v2 contains API Schema definitions for the testkube v2 API group + +### Resource Types + +- [Script](#script) +- [ScriptList](#scriptlist) +- [Test](#test) +- [TestList](#testlist) +- [TestSuite](#testsuite) +- [TestSuiteList](#testsuitelist) + +#### Repository + +Repository represents VCS repo, currently we're handling Git only + +_Appears in:_ + +- [TestContent](#testcontent) + +| Field | Description | +| ------------------- | ---------------------------------------------------------------------------------------- | +| `type` _string_ | VCS repository type | +| `uri` _string_ | uri of content file or git directory | +| `branch` _string_ | branch/tag name for checkout | +| `commit` _string_ | commit id (sha) for checkout | +| `path` _string_ | if needed we can checkout particular path (dir or file) in case of BIG/mono repositories | +| `username` _string_ | git auth username for private repositories | +| `token` _string_ | git auth token for private repositories | + +#### RunningContext + +running context for test or test suite execution + +_Appears in:_ + +- [TestSuiteExecutionRequest](#testsuiteexecutionrequest) + +| Field | Description | +| -------------------------------------------------- | ------------------------------------- | +| `type` _[RunningContextType](#runningcontexttype)_ | One of possible context types | +| `context` _string_ | Context value depending from its type | + +#### RunningContextType + +_Underlying type:_ `string` + +_Appears in:_ + +- [RunningContext](#runningcontext) + +#### Script + +Script is the Schema for the scripts API + +_Appears in:_ + +- [ScriptList](#scriptlist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v2` | +| `kind` _string_ | `Script` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[ScriptSpec](#scriptspec)_ | | + +#### ScriptContent + +_Appears in:_ + +- [ScriptSpec](#scriptspec) + +| Field | Description | +| ---------------------------------------- | ---------------------------- | +| `type` _string_ | script type | +| `repository` _[Repository](#repository)_ | repository of script content | +| `data` _string_ | script content body | +| `uri` _string_ | uri of script content | + +#### ScriptList + +ScriptList contains a list of Script + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v2` | +| `kind` _string_ | `ScriptList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Script](#script) array_ | | + +#### ScriptSpec + +ScriptSpec defines the desired state of Script + +_Appears in:_ + +- [Script](#script) + +| Field | Description | +| ---------------------------------------------- | ----------------------------------- | +| `type` _string_ | script type | +| `name` _string_ | script execution custom name | +| `params` _object (keys:string, values:string)_ | execution params passed to executor | +| `content` _[ScriptContent](#scriptcontent)_ | script content object | +| `tags` _string array_ | script tags | + +#### Test + +Test is the Schema for the tests API + +_Appears in:_ + +- [TestList](#testlist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v2` | +| `kind` _string_ | `Test` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[TestSpec](#testspec)_ | | + +#### TestContent + +TestContent defines test content + +_Appears in:_ + +- [TestSpec](#testspec) + +| Field | Description | +| ---------------------------------------- | -------------------------- | +| `type` _string_ | test type | +| `repository` _[Repository](#repository)_ | repository of test content | +| `data` _string_ | test content body | +| `uri` _string_ | uri of test content | + +#### TestList + +TestList contains a list of Test + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v2` | +| `kind` _string_ | `TestList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Test](#test) array_ | | + +#### TestSpec + +TestSpec defines the desired state of Test + +_Appears in:_ + +- [Test](#test) + +| Field | Description | +| ---------------------------------------------------------------- | -------------------------------------------------------- | +| `type` _string_ | test type | +| `name` _string_ | test execution custom name | +| `params` _object (keys:string, values:string)_ | DEPRECATED execution params passed to executor | +| `variables` _object (keys:string, values:[Variable](#variable))_ | Variables are new params with secrets attached | +| `content` _[TestContent](#testcontent)_ | test content object | +| `schedule` _string_ | schedule in cron job format for scheduled test execution | +| `executorArgs` _string array_ | additional executor binary arguments | + +#### TestSuite + +TestSuite is the Schema for the testsuites API + +_Appears in:_ + +- [TestSuiteList](#testsuitelist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v2` | +| `kind` _string_ | `TestSuite` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[TestSuiteSpec](#testsuitespec)_ | | + +#### TestSuiteExecutionCore + +test suite execution core + +_Appears in:_ + +- [TestSuiteStatus](#testsuitestatus) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------- | ------------------------------- | +| `id` _string_ | execution id | +| `startTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#time-v1-meta)_ | test suite execution start time | +| `endTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#time-v1-meta)_ | test suite execution end time | + +#### TestSuiteExecutionRequest + +test suite execution request body + +_Appears in:_ + +- [TestSuiteSpec](#testsuitespec) + +| Field | Description | +| ---------------------------------------------------------------- | ----------------------------------------------------- | +| `name` _string_ | test execution custom name | +| `namespace` _string_ | test kubernetes namespace (\"testkube\" when not set) | +| `variables` _object (keys:string, values:[Variable](#variable))_ | | +| `secretUUID` _string_ | secret uuid | +| `labels` _object (keys:string, values:string)_ | test suite labels | +| `executionLabels` _object (keys:string, values:string)_ | execution labels | +| `sync` _boolean_ | whether to start execution sync or async | +| `httpProxy` _string_ | http proxy for executor containers | +| `httpsProxy` _string_ | https proxy for executor containers | +| `timeout` _integer_ | timeout for test suite execution | +| `runningContext` _[RunningContext](#runningcontext)_ | | +| `cronJobTemplate` _string_ | cron job template extensions | + +#### TestSuiteList + +TestSuiteList contains a list of TestSuite + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v2` | +| `kind` _string_ | `TestSuiteList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[TestSuite](#testsuite) array_ | | + +#### TestSuiteSpec + +TestSuiteSpec defines the desired state of TestSuite + +_Appears in:_ + +- [TestSuite](#testsuite) + +| Field | Description | +| ---------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| `before` _[TestSuiteStepSpec](#testsuitestepspec) array_ | Before steps is list of tests which will be sequentially orchestrated | +| `steps` _[TestSuiteStepSpec](#testsuitestepspec) array_ | Steps is list of tests which will be sequentially orchestrated | +| `after` _[TestSuiteStepSpec](#testsuitestepspec) array_ | After steps is list of tests which will be sequentially orchestrated | +| `repeats` _integer_ | | +| `description` _string_ | | +| `schedule` _string_ | schedule in cron job format for scheduled test execution | +| `executionRequest` _[TestSuiteExecutionRequest](#testsuiteexecutionrequest)_ | | + +#### TestSuiteStepDelay + +TestSuiteStepDelay contains step delay parameters + +_Appears in:_ + +- [TestSuiteStepSpec](#testsuitestepspec) + +| Field | Description | +| -------------------- | -------------- | +| `duration` _integer_ | Duration in ms | + +#### TestSuiteStepExecute + +TestSuiteStepExecute defines step to be executed + +_Appears in:_ + +- [TestSuiteStepSpec](#testsuitestepspec) + +| Field | Description | +| ------------------------- | ----------- | +| `namespace` _string_ | | +| `name` _string_ | | +| `stopOnFailure` _boolean_ | | + +#### TestSuiteStepSpec + +TestSuiteStepSpec for particular type will have config for possible step types + +_Appears in:_ + +- [TestSuiteSpec](#testsuitespec) + +| Field | Description | +| --------------------------------------------------------- | ----------- | +| `type` _[TestSuiteStepType](#testsuitesteptype)_ | | +| `execute` _[TestSuiteStepExecute](#testsuitestepexecute)_ | | +| `delay` _[TestSuiteStepDelay](#testsuitestepdelay)_ | | + +#### TestSuiteStepType + +_Underlying type:_ `string` + +TestSuiteStepType defines different type of test suite steps + +_Appears in:_ + +- [TestSuiteStepSpec](#testsuitestepspec) + +#### Variable + +_Appears in:_ + +- [ExecutionRequest](#executionrequest) +- [TestSpec](#testspec) +- [TestSuiteExecutionRequest](#testsuiteexecutionrequest) +- [TestSuiteSpec](#testsuitespec) + +| Field | Description | +| ----------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `type` _string_ | variable type | +| `name` _string_ | variable name | +| `value` _string_ | variable string value | +| `valueFrom` _[EnvVarSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#envvarsource-v1-core)_ | or load it from var source | + +## tests.testkube.io/v3 + +Package v3 contains API Schema definitions for the tests v3 API group + +### Resource Types + +- [Test](#test) +- [TestList](#testlist) + +#### ArgsModeType + +_Underlying type:_ `string` + +ArgsModeType defines args mode type + +_Appears in:_ + +- [ExecutionRequest](#executionrequest) + +#### ArtifactRequest + +artifact request body with test artifacts + +_Appears in:_ + +- [ExecutionRequest](#executionrequest) + +| Field | Description | +| --------------------------- | -------------------------------------------------- | +| `storageClassName` _string_ | artifact storage class name for container executor | +| `volumeMountPath` _string_ | artifact volume mount path for container executor | +| `dirs` _string array_ | artifact directories for scraping | + +#### EnvReference + +Reference to env resource + +_Appears in:_ + +- [ExecutionRequest](#executionrequest) + +| Field | Description | +| --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| `reference` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#localobjectreference-v1-core)_ | | +| `mount` _boolean_ | whether we shoud mount resource | +| `mountPath` _string_ | where we shoud mount resource | +| `mapToVariables` _boolean_ | whether we shoud map to variables from resource | + +#### ExecutionCore + +test execution core + +_Appears in:_ + +- [TestStatus](#teststatus) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------- | ---------------- | +| `id` _string_ | execution id | +| `number` _integer_ | execution number | +| `startTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#time-v1-meta)_ | test start time | +| `endTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#time-v1-meta)_ | test end time | + +#### ExecutionRequest + +test execution request body + +_Appears in:_ + +- [TestSpec](#testspec) + +| Field | Description | +| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `name` _string_ | test execution custom name | +| `testSuiteName` _string_ | unique test suite name (CRD Test suite name), if it's run as a part of test suite | +| `number` _integer_ | test execution number | +| `executionLabels` _object (keys:string, values:string)_ | test execution labels | +| `namespace` _string_ | test kubernetes namespace (\"testkube\" when not set) | +| `variablesFile` _string_ | variables file content - need to be in format for particular executor (e.g. postman envs file) | +| `isVariablesFileUploaded` _boolean_ | | +| `variables` _object (keys:string, values:[Variable](#variable))_ | | +| `testSecretUUID` _string_ | test secret uuid | +| `testSuiteSecretUUID` _string_ | test suite secret uuid, if it's run as a part of test suite | +| `args` _string array_ | additional executor binary arguments | +| `argsMode` _[ArgsModeType](#argsmodetype)_ | usage mode for arguments | +| `command` _string array_ | executor binary command | +| `image` _string_ | container executor image | +| `imagePullSecrets` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#localobjectreference-v1-core) array_ | container executor image pull secrets | +| `envs` _object (keys:string, values:string)_ | Environment variables passed to executor. Deprecated: use Basic Variables instead | +| `secretEnvs` _object (keys:string, values:string)_ | Execution variables passed to executor from secrets. Deprecated: use Secret Variables instead | +| `sync` _boolean_ | whether to start execution sync or async | +| `httpProxy` _string_ | http proxy for executor containers | +| `httpsProxy` _string_ | https proxy for executor containers | +| `negativeTest` _boolean_ | negative test will fail the execution if it is a success and it will succeed if it is a failure | +| `activeDeadlineSeconds` _integer_ | Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer. | +| `artifactRequest` _[ArtifactRequest](#artifactrequest)_ | | +| `jobTemplate` _string_ | job template extensions | +| `cronJobTemplate` _string_ | cron job template extensions | +| `preRunScript` _string_ | script to run before test execution | +| `scraperTemplate` _string_ | scraper template extensions | +| `envConfigMaps` _[EnvReference](#envreference) array_ | config map references | +| `envSecrets` _[EnvReference](#envreference) array_ | secret references | +| `runningContext` _[RunningContext](#runningcontext)_ | | + +#### GitAuthType + +_Underlying type:_ `string` + +GitAuthType defines git auth type + +_Appears in:_ + +- [Repository](#repository) + +#### Repository + +Repository represents VCS repo, currently we're handling Git only + +_Appears in:_ + +- [TestContent](#testcontent) + +| Field | Description | +| ------------------------------------------ | ---------------------------------------------------------------------------------------- | +| `type` _string_ | VCS repository type | +| `uri` _string_ | uri of content file or git directory | +| `branch` _string_ | branch/tag name for checkout | +| `commit` _string_ | commit id (sha) for checkout | +| `path` _string_ | if needed we can checkout particular path (dir or file) in case of BIG/mono repositories | +| `usernameSecret` _[SecretRef](#secretref)_ | | +| `tokenSecret` _[SecretRef](#secretref)_ | | +| `certificateSecret` _string_ | git auth certificate secret for private repositories | +| `workingDir` _string_ | if provided we checkout the whole repository and run test from this directory | +| `authType` _[GitAuthType](#gitauthtype)_ | auth type for git requests | + +#### RunningContext + +running context for test or test suite execution + +_Appears in:_ + +- [ExecutionRequest](#executionrequest) + +| Field | Description | +| -------------------------------------------------- | ------------------------------------- | +| `type` _[RunningContextType](#runningcontexttype)_ | One of possible context types | +| `context` _string_ | Context value depending from its type | + +#### RunningContextType + +_Underlying type:_ `string` + +_Appears in:_ + +- [RunningContext](#runningcontext) + +#### SecretRef + +Testkube internal reference for secret storage in Kubernetes secrets + +_Appears in:_ + +- [Repository](#repository) + +| Field | Description | +| -------------------- | --------------------------- | +| `namespace` _string_ | object kubernetes namespace | +| `name` _string_ | object name | +| `key` _string_ | object key | + +#### Test + +Test is the Schema for the tests API + +_Appears in:_ + +- [TestList](#testlist) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v3` | +| `kind` _string_ | `Test` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[TestSpec](#testspec)_ | | + +#### TestContent + +TestContent defines test content + +_Appears in:_ + +- [TestSpec](#testspec) + +| Field | Description | +| -------------------------------------------- | -------------------------- | +| `type` _[TestContentType](#testcontenttype)_ | test type | +| `repository` _[Repository](#repository)_ | repository of test content | +| `data` _string_ | test content body | +| `uri` _string_ | uri of test content | + +#### TestContentType + +_Underlying type:_ `string` + +_Appears in:_ + +- [TestContent](#testcontent) + +#### TestList + +TestList contains a list of Test + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `apiVersion` _string_ | `tests.testkube.io/v3` | +| `kind` _string_ | `TestList` | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Test](#test) array_ | | + +#### TestSpec + +TestSpec defines the desired state of Test + +_Appears in:_ + +- [Test](#test) + +| Field | Description | +| ---------------------------------------------------------- | -------------------------------------------------------- | +| `type` _string_ | test type | +| `name` _string_ | test name | +| `content` _[TestContent](#testcontent)_ | test content object | +| `source` _string_ | reference to test source resource | +| `schedule` _string_ | schedule in cron job format for scheduled test execution | +| `executionRequest` _[ExecutionRequest](#executionrequest)_ | | +| `uploads` _string array_ | files to be used from minio uploads | + +#### Variable + +_Appears in:_ + +- [ExecutionRequest](#executionrequest) +- [TestSpec](#testspec) +- [TestSuiteExecutionRequest](#testsuiteexecutionrequest) +- [TestSuiteSpec](#testsuitespec) + +| Field | Description | +| ----------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `type` _string_ | variable type | +| `name` _string_ | variable name | +| `value` _string_ | variable string value | +| `valueFrom` _[EnvVarSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#envvarsource-v1-core)_ | or load it from var source | diff --git a/docs/docs/articles/crds.md b/docs/docs/articles/crds.md index a144fed13ae..77164c21429 100644 --- a/docs/docs/articles/crds.md +++ b/docs/docs/articles/crds.md @@ -1,32 +1,28 @@ # Testkube Custom Resources -In Testkube, Tests, Test Suites, Executors and Webhooks are defined using [Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/). The current definitions can be found in the [kubeshop/testkube-operator](https://github.com/kubeshop/testkube-operator/tree/main/config/crd) repository. +In Testkube, Tests, Test Suites, Executors and Webhooks, Test Sources and Test Triggers are defined using [Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/). The current definitions can be found in the [kubeshop/testkube-operator](https://github.com/kubeshop/testkube-operator/tree/main/config/crd) repository. You can always check the list of all CRDs using `kubectl` configured to point to your Kubernetes cluster with Testkube installed: ```sh -$ kubectl get crds +kubectl get crds -n testkube ``` ```sh title="Expected output:" NAME CREATED AT -certificaterequests.cert-manager.io 2022-04-01T10:53:54Z -certificates.cert-manager.io 2022-04-01T10:53:54Z -challenges.acme.cert-manager.io 2022-04-01T10:53:54Z -clusterissuers.cert-manager.io 2022-04-01T10:53:54Z -executors.executor.testkube.io 2022-04-13T11:44:22Z -issuers.cert-manager.io 2022-04-01T10:53:54Z -orders.acme.cert-manager.io 2022-04-01T10:53:54Z -scripts.tests.testkube.io 2022-04-13T11:44:22Z -tests.tests.testkube.io 2022-04-13T11:44:22Z -testsuites.tests.testkube.io 2022-04-13T11:44:22Z -webhooks.executor.testkube.io 2022-04-13T11:44:22Z +executors.executor.testkube.io 2023-06-15T14:49:11Z +scripts.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 +testsuites.tests.testkube.io 2023-06-15T14:49:11Z +testtriggers.tests.testkube.io 2023-06-15T14:49:11Z +webhooks.executor.testkube.io 2023-06-15T14:49:11Z ``` To check details on one of the CRDs, use `describe`: ```sh -$ kubectl describe crd tests.tests.testkube.io +kubectl describe crd tests.tests.testkube.io ``` ```sh title="Expected output:" @@ -41,138 +37,4 @@ Kind: CustomResourceDefinition ... ``` -Below, you will find short descriptions and example declarations of the custom resources defined by Testkube. - -## Tests - -Testkube Tests can be defined as a single executable unit of tests. Depending on the test type, this can mean one or multiple test files. - -To get all the test types available in your cluster, check the executors: - -```sh -testkube get executors -o yaml | grep -A1 types -``` - -```sh title="Expected output:" - types: - - postman/collection --- - types: - - curl/test --- - types: - - cypress/project --- - types: - - k6/script --- - types: - - postman/collection --- - types: - - soapui/xml -``` - -When creating a Testkube Test, there are multiple supported input types: - -- String -- Git directory -- Git file -- File URI - -Variables can be configured using the `variables` field as shown below. - -```yaml -apiVersion: tests.testkube.io/v3 -kind: Test -metadata: - name: example-test - namespace: testkube -spec: - content: - data: "{...}" - type: string - type: postman/collection - executionRequest: - variables: - var1: - name: var1 - type: basic - value: val1 - sec1: - name: sec1 - type: secret - valueFrom: - secretKeyRef: - key: sec1 - name: vartest4-testvars -``` - -## Test Suites - -Testkube Test Suites are collections of Testkube Tests of the same or different types. - -```yaml -apiVersion: tests.testkube.io/v2 -kind: TestSuite -metadata: - name: example-testsuite - namespace: testkube -spec: - description: Example Test Suite - steps: - - execute: - name: example-test1 - namespace: testkube - - delay: - duration: 1000 - - execute: - name: example-test2 - namespace: testkube -``` - -## Executors - -Executors are Testkube-specific test runners. There are predefined Executors avialable in Testkube. You can also write your own custom Testkube Executor using [this guide](http://docs.testkube.io/test-types/container-executor/). - -```yaml title="Example:" -apiVersion: executor.testkube.io/v1 -kind: Executor -metadata: - name: example-executor - namespace: testkube -spec: - executor_type: job - image: YOUR_USER/testkube-executor-example:1.0.0 - types: - - example/test - content_types: - - string - - file-uri - - git - features: - - artifacts - - junit-report - meta: - iconURI: http://mydomain.com/icon.jpg - docsURI: http://mydomain.com/docs - tooltips: - name: please enter executor name -``` - -## Webhooks - -Testkube Webhooks are HTTP POST calls having the Testkube Execution object and its current state as payload. They are sent when a test is either started or finished. This can be defined under `events`. - -```yaml -apiVersion: executor.testkube.io/v1 -kind: Webhook -metadata: - name: example-webhook - namespace: testkube -spec: - uri: http://localhost:8080/events - events: - - start-test - - end-test -``` +You can find the description of each CRD in the [CRDs Reference](./crds-reference.md) section of the documentation. diff --git a/docs/docs/articles/creating-test-suites.md b/docs/docs/articles/creating-test-suites.md index feed291cc46..bc5aae6e22e 100644 --- a/docs/docs/articles/creating-test-suites.md +++ b/docs/docs/articles/creating-test-suites.md @@ -7,7 +7,9 @@ A QA leader is responsible for release trains and wants to be sure that before t This is easily done with Testkube. Each team can run their tests against clusters on their own, and the QA manager can create test resources and add tests written by all teams. -`Test Suites` stands for the orchestration of different test steps such as test execution, delay, or other (future) steps. +`Test Suites` stands for the orchestration of different test steps, which can run sequentially or/and in parallel. +On each batch step you can define either one or multiple steps such as test execution, delay, or other (future) steps. +By default the concurrency level for parallel tests is set to 10, you can redefine it using `--concurency` option for CLI command. ## Test Suite Creation @@ -21,13 +23,13 @@ echo ' "name": "testkube-suite", "description": "Testkube test suite, api, dashboard and performance", "steps": [ - {"execute": {"name": "testkube-api"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-dashboard"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-api-performance"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-homepage-performance"}} + {"execute": [{"test": "testkube-api"}, {""test": "testkube-dashboard"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-dashboard"}, {"delay": "1s"}, {""test": "testkube-homepage"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-api-performance"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-homepage-performance"}]} ] }' | kubectl testkube create testsuite ``` @@ -51,32 +53,42 @@ kubectl get testsuites -ntestkube testkube-suite -oyaml ``` ```yaml title="Expected output:" -apiVersion: tests.testkube.io/v1 -kind: Test +apiVersion: tests.testkube.io/v3 +kind: TestSuite metadata: creationTimestamp: "2022-01-11T07:46:12Z" generation: 4 - name: test-example - namespace: testkube-suite + name: testkube-suite + namespace: testkube resourceVersion: "57695094" uid: ea90a79e-bb46-49ee-a3ef-a5d99cee0a2c spec: - description: Example simple tests orchestration + description: "Testkube test suite, api, dashboard and performance" steps: - - execute: - name: testkube-api - - delay: - duration: 1000 - - execute: - name: testkube-dashboard - - delay: - duration: 1000 - - execute: - name: testkube-api-performance - - delay: - duration: 1000 - - execute: - name: testkube-homepage-performance + - stopOnFailure: false + execute: + - test: testkube-api + - test: testkube-dashboard + - stopOnFailure: false + execute: + - delay: 1s + - stopOnFailure: false + execute: + - test: testkube-dashboard + - delay: 1s + - test: testkube-homepage + - stopOnFailure: false + execute: + - delay: 1s + - stopOnFailure: false + execute: + - test: testkube-api-performance + - stopOnFailure: false + execute: + - delay: 1s + - stopOnFailure: false + execute: + - test: testkube-homepage-performance ``` Your `Test Suite` is defined and you can start running testing workflows. diff --git a/docs/docs/articles/creating-tests.md b/docs/docs/articles/creating-tests.md index fb33dc03421..72d625d4561 100644 --- a/docs/docs/articles/creating-tests.md +++ b/docs/docs/articles/creating-tests.md @@ -425,21 +425,30 @@ metadata: When such a test is created you will see additional annotations for its cron job, when the default cron job template doesn't have any annotations. -### Executing a Prerun Script +### Executing a Prerun/Postrun Script -If you need to provide additional configuration for your executor environment, you can submit a prerun script to be executed before the test is started. For example, we have a simple shell script stored in `script.sh` file: +If you need to provide additional configuration for your executor environment, you can submit a prerun/postrun script to be executed before the test is started and after test is completed. For example, we have a simple shell script stored in `pre_script.sh` and `post_script.sh` files: ```sh -!/bin/sh +#!/bin/sh echo "Storing ssl certificate in file from env secret env" echo "$SSL_CERT" > /data/ssl.crt ``` -Provide the script when you create or run the test using `--prerun-script` parameter: +and ```sh -testkube create test --file test/postman/LocalHealth.postman_collection.json --name script-test --type postman/collection --prerun-script script.sh --secret-env SSL_CERT=your-k8s-secret +#!/bin/sh + +echo "Sleeping for 30 seconds" +sleep 30 +``` + +Provide the script when you create or run the test using `--prerun-script` and `--postrun-script` parameters: + +```sh +testkube create test --file test/postman/LocalHealth.postman_collection.json --name script-test --type postman/collection --prerun-script pre_script.sh --postrun-script post_script.sh --secret-env SSL_CERT=your-k8s-secret ``` ### Changing the Default Scraper Job Template Used for Container Executor Tests diff --git a/docs/docs/articles/deploying-in-aws.md b/docs/docs/articles/deploying-in-aws.md index 00021dc2a60..1413019b88f 100644 --- a/docs/docs/articles/deploying-in-aws.md +++ b/docs/docs/articles/deploying-in-aws.md @@ -10,7 +10,7 @@ Once the cluster is up and running we need to deploy the AWS Load Balancer Contr Another important point is [ExternalDNS](https://github.com/kubernetes-sigs/external-dns). It is not compulsory to deploy it into your cluster, but it helps you dynamically manage your DNS records via k8s resources. -And last, but not least - install the Testkube CLI. You can download a binary file from our [installation](./step1-installing-cli.md) page. For how to deploy Testkube to your cluster with all the necessary changes, please see the next section. +And last, but not least - install the Testkube CLI. You can download a binary file from our [installation](./step1-installing-cli) page. For how to deploy Testkube to your cluster with all the necessary changes, please see the next section. ## Ingress and Service Resources Configuration @@ -143,22 +143,22 @@ Except for the Ingress annotation, you need to update the Service manifests with ```yaml service: - type: ClusterIP - port: 8080 - annotations: - alb.ingress.kubernetes.io/healthcheck-path: "/" - alb.ingress.kubernetes.io/healthcheck-port: "8080" + type: ClusterIP + port: 8080 + annotations: + alb.ingress.kubernetes.io/healthcheck-path: "/" + alb.ingress.kubernetes.io/healthcheck-port: "8080" ``` **Testkube API Service:** ```yaml service: - type: ClusterIP - port: 8088 - annotations: - alb.ingress.kubernetes.io/healthcheck-path: "/health" - alb.ingress.kubernetes.io/healthcheck-port: "8088" + type: ClusterIP + port: 8088 + annotations: + alb.ingress.kubernetes.io/healthcheck-path: "/health" + alb.ingress.kubernetes.io/healthcheck-port: "8088" ``` :::caution diff --git a/docs/docs/articles/exposing-testkube-with-ingress-nginx.md b/docs/docs/articles/exposing-testkube-with-ingress-nginx.md index 3e11f228634..286494cf373 100644 --- a/docs/docs/articles/exposing-testkube-with-ingress-nginx.md +++ b/docs/docs/articles/exposing-testkube-with-ingress-nginx.md @@ -1,86 +1,99 @@ -# Ingress-NGINX +# Exposing Testkube Dashboard with NGINX Ingress -## Prerequisites - -Add the repo to Helm: - -```sh -helm repo add kubeshop https://kubeshop.github.io/helm-charts && helm repo update -``` - -A values file for guidance can be found [here](https://github.com/kubeshop/helm-charts/blob/39f73098630b333ba66db137e7fc016c39d92876/testkube/charts/testkube/values-demo.yaml). - -## Configure Ingress-NGINX to Expose Testkube API +Usually, you would want to share the Testkube Dashboard with your internal company VPN to allow access to other team members without having to provide them access to your Kubernetes cluster. This is achieved by exposing the Testkube Dashboard. -The Testkube Dashboard needs the Testkube API to be exposed. For this, you can run: - -```sh -helm upgrade testkube kubeshop/testkube --set testkube-api.ingress.enabled="true" -``` +In this section we cover multiple solutions for different cloud providers. -By default, the API's entry point is the path `/results`, so the results will be accessible at `$INGRESS_HOST/results/` +## Prerequisites -The Ingress configuration used is available in the [Testkube Helm Repo](https://github.com/kubeshop/helm-charts). +1. Deploy NGINX Ingress Controller into your k8s cluster. Please see the link for more details: [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/). -## Exposing the Testkube Dashboard +2. (Optional) To use TLS, the installation of any certificate management tool is required. At Testkube and in this guide we will use [cert-manager](https://cert-manager.io/), but it might differ depending on your set-up. -To expose the Dashboard and the API together, run: +3. Add the Testkube helm-chart to your repositories, using this command: ```sh -helm install testkube kubeshop/testkube --set testkube-dashboard.enabled="true" --set testkube-dashboard.ingress.enabled="true" --set testkube-api.ingress.enabled="true" +helm repo add kubeshop https://kubeshop.github.io/helm-charts && helm repo update ``` -To get the address of Ingress use: +## Exposing Testkube -```sh -kubectl get ingress -n testkube -``` +In order to expose Testkube to the outside world we need to enable two Ingresses - Testkube's UI API and Testkube's dashboard. Update the `values.yaml` file that will later be passed to the `helm install` command. To enable the Testkube Ingresses, please use the following code configuration: -## HTTPS/TLS Configuration +```aidl +testkube-api: + uiIngress: + enabled: true + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/force-ssl-redirect: "false" + nginx.ingress.kubernetes.io/ssl-redirect: "false" + nginx.ingress.kubernetes.io/rewrite-target: /$1 + cert-manager.io/cluster-issuer: letsencrypt-prod + acme.cert-manager.io/http01-edit-in-place: "true" + path: /results/(v\d/.*) + hosts: + - your-host.com + tlsenabled: "true" + tls: + - hosts: + - your-host.com + secretName: testkube-cert-secret -To have secure access to Testkube Dashboard and the API, a certificate should be provided. The Helm charts can be configured from the Ingress section of the values file: -```yaml -ingress: +testkube-dashboard: + ingress: enabled: "true" - annotations: + annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/force-ssl-redirect: "false" nginx.ingress.kubernetes.io/ssl-redirect: "false" - nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/cors-allow-methods: "GET" - nginx.ingress.kubernetes.io/cors-allow-credentials: "false" - # add an annotation indicating the issuer to use. cert-manager.io/cluster-issuer: letsencrypt-prod - # controls whether the ingress is modified ‘in-place’, - # or a new one is created specifically for the HTTP01 challenge. acme.cert-manager.io/http01-edit-in-place: "true" path: / hosts: - - demo.testkube.io + - your-host.com tlsenabled: "true" - tls: # < placing a host in the TLS config will indicate a certificate should be created - - hosts: - - demo.testkube.io - secretName: testkube-demo-cert-secret + tls: + - hosts: + - your-host.com + secretName: testkube-cert-secret + + apiServerEndpoint: "your-host.com/results" ``` -Certificates are automatically generated using Let's Encrypt and cert-manager, but can be configured for any particular case. A full values file example can be found [here](https://github.com/kubeshop/helm-charts/blob/39f73098630b333ba66db137e7fc016c39d92876/testkube/charts/testkube/values-demo.yaml). +:::note + +Keep in mind that hosts have to be identical for the `dashboard` and for the `api` with different paths. + +Also, do not forget to add `apiServerEndpoint` to the `values.yaml` for the `testkube-dashboard`, e.g.: apiServerEndpoint: "your-host.com/results". + +::: -If there is no need for a TLS (Transport Layer Security) to be enabled, omit the TLS configuration. +Please note that the snippet includes annotations for cert manager as well. Certificates are automatically generated using Let's Encrypt and cert-manager, but can be configured for any particular case. If there is no need for a TLS (Transport Layer Security) to be enabled, omit the TLS configuration. :::important + We highly discourage working in a non-safe environment which is exposed without the use of a TLS-based connection. Please do so in a private internal environment for testing or development purposes only. + ::: -To pass specific values to the Ingress annotations, the Helm "--set" option can be used: +## Deployment -```sh -helm install testkube kubeshop/testkube --set testkube-dashboard.enabled="true" --set testkube-dashboard.ingress.enabled="true" --set testkube-api.ingress.enabled="true" --set testkube-api.ingress.annotations.kubernetes\\.io/ingress\\.class="anything_needed" +Once the `values.yaml` file is ready, we can deploy Testkube into a cluster: + +```aidl +helm install --create-namespace testkube kubeshop/testkube --namespace testkube --values values.yaml ``` -A better approach is to configure and call a values file with the Ingress custom values: +After the installation is complete, discover the address of the Ingresses with the following commands: ```sh -helm install testkube kubeshop/testkube --values https://github.com/kubeshop/helm-charts/blob/39f73098630b333ba66db137e7fc016c39d92876/testkube/charts/testkube/values-demo.yaml +kubectl get ingress -n testkube ``` + +By default, the API's entry point is the path `/results`, so the results will be accessible at `$INGRESS_HOST/results/` + +The Ingress configuration used is available in the [Testkube Helm Repo](https://github.com/kubeshop/helm-charts). + +A values file for guidance can be found [here](https://github.com/kubeshop/helm-charts/blob/main/charts/testkube/values-demo.yaml#L334). diff --git a/docs/docs/articles/flux-integration.md b/docs/docs/articles/flux-integration.md index ce9de1f4689..91e5eb41804 100644 --- a/docs/docs/articles/flux-integration.md +++ b/docs/docs/articles/flux-integration.md @@ -69,7 +69,7 @@ flux create kustomization testkube-test \ ### 6. Install Testkube in the cluster. -If you don't have the Testkube CLI, follow the instructions [here](./step1-installing-cli.md) to install it. +If you don't have the Testkube CLI, follow the instructions [here](./step1-installing-cli) to install it. Run the following command to install Testkube and its components in the cluster: diff --git a/docs/docs/articles/getting-started-overview.md b/docs/docs/articles/getting-started-overview.md index 0390202cc4c..dc645b9c3a4 100644 --- a/docs/docs/articles/getting-started-overview.md +++ b/docs/docs/articles/getting-started-overview.md @@ -2,8 +2,8 @@ In this section you will: -1. [Install the Testkube CLI](./step1-installing-cli.md). -2. [Install the Testkube Server components in your cluster](./step2-installing-cluster-components.md). [Alternatively you can use Helm](./helm-chart.md) to do that. +1. [Install the Testkube CLI](./step1-installing-cli). +2. [Install the Testkube Agent](./step2-installing-cluster-components.md). 3. [Creating your first Test](./step3-creating-first-test.md). You can also see the full installation video from our product experts: [Testkube Installation Video](https://www.youtube.com/watch?v=bjQboi3Etys): diff --git a/docs/docs/articles/getting-test-suites-results.md b/docs/docs/articles/getting-test-suites-results.md index f18b48be5e3..a70ceb970ae 100644 --- a/docs/docs/articles/getting-test-suites-results.md +++ b/docs/docs/articles/getting-test-suites-results.md @@ -1,51 +1,65 @@ # Getting Results -To get recent results, call the `tests executions` subcommand: +To get recent results, call the `testsuites executions` subcommand: ```sh testkube get tse ``` ```sh title="Expected output:" - ID | TEST NAME | EXECUTION NAME | STATUS | STEPS -+--------------------------+--------------+-------------------------------------+---------+-------+ - 61e1142465e59a318346512b | test-example | test-example.equally-enabled-heron | success | 3 - 61e1136165e59a3183465125 | test-example | test-example.fairly-humble-tick | success | 3 - 61dff61867326ad521b2a0d6 | test-example | test-example.verbally-merry-hagfish | success | 3 - 61dfe0de69b7bfcb9058dad0 | test-example | test-example.overly-exciting-krill | success | 3 + + ID | TEST SUITE NAME | EXECUTION NAME | STATUS | STEPS | LABELS +---------------------------+--------------------------------+---------------------------------------+---------+-------+--------------- + 63d401e5fed6933f342ccc67 | executor-maven-smoke-tests | ts-executor-maven-smoke-tests-680 | failed | 3 | app=testkube + 63d401a9fed6933f342ccc61 | executor-artillery-smoke-tests | ts-executor-artillery-smoke-tests-682 | passed | 2 | app=testkube + 63d3fed9fed6933f342ccc5b | executor-jmeter-smoke-tests | ts-executor-jmeter-smoke-tests-500 | passed | 2 | app=testkube + 63d3fd35fed6933f342ccc51 | executor-postman-smoke-tests | ts-executor-postman-smoke-tests-671 | passed | 4 | app=testkube + 63d3fb91fed6933f342ccc4b | executor-container-smoke-tests | ts-executor-container-smoke-tests-683 | failed | 2 | app=testkube ``` -## Getting a Single Test Execution +## **Getting a Single Test Suite Execution** -With the test execution ID, you can get single test results: +With the test suite execution ID, you can get single test suite results: ```sh testkube get tse 61e1136165e59a3183465125 ``` ```sh title="Expected output:" -Name: test-example.fairly-humble-tick -Status: success - - STEP | STATUS | ID | ERROR -+----------------------------+---------+--------------------------+-------+ - run test: testkube/test1 | success | 61e1136165e59a3183465127 | - delay 2000ms | success | | - run test: testkube/test1 | success | 61e1136765e59a3183465129 | - - - -Use the following command to get test execution details: -$ testkube get tse 61e1136165e59a3183465125 +Id: 63d3cd05c6768fc8b574e2e8 +Name: ts-testsuite-parallel-19 +Status: passed +Duration: 22.138s + +Labels: + STATUSES | STEP | IDS | ERRORS +-------------------------+--------------------------------+--------------------------------+------------- + passed, passed, passed | run:testkube/cli-test, | 63d3cd05c6768fc8b574e2e9, | "", "", "" + | run:testkube/demo-test, delay | 63d3cd05c6768fc8b574e2ea, "" | + | 1000ms | | + passed | delay 5000ms | "" | "" + + +Use the following command to get test suite execution details: +$ kubectl testkube get tse 61e1136165e59a3183465125 ``` Test Suite steps that are running workflows based on `Test` Custom Resources have a Test Execution ID. You can get the details of each in a separate command: -```sh -kubectl testkube get execution 61e1136165e59a3183465127Name: test-example-test1, Status: success, Duration: 4.677s +```sh +kubectl testkube get execution 63d3cd05c6768fc8b574e2e9 + +ID: 63d3cd05c6768fc8b574e2e9 +Name: testsuite-parallel-cli-test-46 +Number: 46 +Test name: cli-test +Type: cli/test +Status: passed +Start time: 2023-01-27 13:09:25.54 +0000 UTC +End time: 2023-01-27 13:09:42.432 +0000 UTC +Duration: 00:00:16 -newman TODO @@ -101,18 +115,22 @@ To get the Test Suite CRD status of a particular test suite, pass the test suite kubectl testkube get testsuites test-suite-example --crd-only ``` +Output: + ```yaml title="Expected output:" -apiVersion: tests.testkube.io/v2 +apiVersion: tests.testkube.io/v3 kind: TestSuite metadata: name: test-suite-example namespace: testkube spec: steps: + - stopOnFailure: false execute: - stopOnFailure: false - namespace: testkube - name: test-case + - test: testkube-dashboard + - delay: 1s + - test: testkube-homepage + status: latestExecution: id: 63b7551cb2a16c73e8cfa1bf diff --git a/docs/docs/articles/helm-chart.md b/docs/docs/articles/helm-chart.md index 0e1e71ad7d3..9dbb3802d47 100644 --- a/docs/docs/articles/helm-chart.md +++ b/docs/docs/articles/helm-chart.md @@ -114,9 +114,25 @@ The following Helm defaults are used in the `testkube` chart: | testkube-api.logs.bucket | no | "testkube-logs" | | testkube-api.cdeventsTarget | yes | "" | | testkube-api.dashboardUri | yes | "" | +| testkube-api.clusterName | yes | "" | >For more configuration parameters of a `MongoDB` chart please visit: >For more configuration parameters of an `NATS` chart please visit: + +:::note + +Please note that we use **global** parameters in our `values.yaml`: +``` +global: + imageRegistry: "" + imagePullSecrets: [] + labels: {} + annotations: {} +``` + +They override all sub-chart values for the image parameters if specified. + +::: \ No newline at end of file diff --git a/docs/docs/articles/run-tests-with-github-actions.md b/docs/docs/articles/run-tests-with-github-actions.md new file mode 100644 index 00000000000..2f78d27d00b --- /dev/null +++ b/docs/docs/articles/run-tests-with-github-actions.md @@ -0,0 +1,153 @@ +# Run Tests with GitHub Actions + +**Run on Testkube** is a GitHub Action for running tests on the Testkube platform. + +Use it to run tests and test suites and obtain results directly in the GitHub's workflow. + +## Usage +To use the action in your GitHub workflow, use the ``kubeshop/testkube-run-action@v1`` action. The configuration options are described in the Inputs section and may vary depending on the Testkube solution you are using (cloud or self-hosted) and your needs. + +The most important options you will need are **test** and **testSuite** - you should pass a test or test suite name there. + +### Testkube Cloud +To use this GitHub Action for the Testkube Cloud, you need to create an API token. + +Then, pass the **organization** and **environment** IDs for the test, along with the **token** and other parameters specific for your use case: + +```yaml +uses: kubeshop/testkube-run-action@v1 +with: + # Instance + organization: tkcorg_0123456789abcdef + environment: tkcenv_fedcba9876543210 + token: tkcapi_0123456789abcdef0123456789abcd + # Options + test: some-test-id-to-run + ``` + +It will probably be unsafe to keep this directly in the workflow's YAML configuration, so you may want to use [GitHub's secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) instead. + +```yaml +uses: kubeshop/testkube-run-action@v1 +with: + # Instance + organization: ${{ secrets.TkOrganization }} + environment: ${{ secrets.TkEnvironment }} + token: ${{ secrets.TkToken }} + # Options + test: some-test-id-to-run + ``` + +### Self-hosted Instance + + +To run the test on self-hosted instance, simply pass the URL that points to the API, i.e.: + +```yaml +uses: kubeshop/testkube-run-action@v1 +with: + # Instance + url: https://demo.testkube.io/results/v1 + # Options + test: some-test-id-to-run + ``` + +### Examples + +Run a test on a self-hosted instance: + +```yaml +uses: kubeshop/testkube-run-action@v1 +with: + url: https://demo.testkube.io/results/v1 + test: container-executor-curl-smoke + ``` + +Run a test suite on a self-hosted instance: + +```yaml +uses: kubeshop/testkube-run-action@v1 +with: + url: https://demo.testkube.io/results/v1 + testSuite: executor-soapui-smoke-tests + ``` + +Run tests from a local repository for the PR: + +```yaml +uses: kubeshop/testkube-run-action@v1 +with: + organization: ${{ secrets.TkOrganization }} + environment: ${{ secrets.TkEnvironment }} + token: ${{ secrets.TkToken }} + test: e2e-dashboard-tests + ref: ${{ github.head_ref }} + ``` + +Run tests with custom environment variables: + +```yaml +uses: kubeshop/testkube-run-action@v1 +with: + organization: ${{ secrets.TkOrganization }} + environment: ${{ secrets.TkEnvironment }} + token: ${{ secrets.TkToken }} + test: e2e-dashboard-tests + variables: | + URL="https://some.domain.com" + EXECUTED_FROM="${{ github.head_ref }}" + secretVariables: | + SOME_SECRET_ENV="abc" + OTHER_ENV="${{ secrets.ExternalToken }}" +``` + +## Inputs +There are different inputs available for tests and test suites, as well as for Cloud and your own instance. + +### Test + +```sh +| Required | Name | Description ++----------+-----------------+----------------------------------------------------------- +| ✓ | test | Test name in the Testkube environment. +| ✗ | ref | Override Git reference (branch, commit, tag) for the test. +| ✗ | preRunScript | Override pre-run script for the test. +| ✗ | variables | Basic variables in the dotenv format. +| ✗ | secretVariables | Secret variables in the dotenv format. +| ✗ | executionName | Override execution name, so you may i.e. mention the PR. +| ✗ | namespace | Set namespace to run test in. +``` + +### Test Suite + +```sh +| Required | Name | Description ++----------+-----------------+--------------------------------------------------------- +| ✓ | testSuite | Test suite name in the Testkube environment. +| ✗ | variables | Basic variables in the dotenv format. +| ✗ | secretVariables | Variables Secret variables in the dotenv format. +| ✗ | executionName | Override execution name, so you may i.e. mention the PR. +| ✗ | namespace | Set namespace to run test suite in. +``` + +### Cloud and Enterprise + +```sh +| Required | Name | Description ++----------+--------------+------------------------------------------------------------------------------------------------------------------------------ +| ✓ | organization | The organization ID from Testkube Cloud or Enterprise - it starts with tkc_org, you may find it i.e. in the dashboard's URL. +| ✓ | environment | The environment ID from Testkube Cloud or Enterprise - it starts with tkc_env, you may find it i.e. in the dashboard's URL. +| ✓ | token | API token that has at least a permission to run specific test or test suite. Read more about creating API token in Testkube Cloud or Enterprise. +| ✗ | url | URL of the Testkube Enterprise instance, if applicable. +| ✗ | dashboardUrl | URL of the Testkube Enterprise dashboard, if applicable, to display links for the execution. +``` + +### Own Instance + +```sh +| Required | Name | Description ++----------+--------------+---------------------------------------------------------------------------------------- +| ✓ | url | URL for the API of the own Testkube instance. +| ✗ | ws | Override WebSocket API URL of the own Testkube instance (use it only if auto-detection doesn't work). +| ✗ | dashboardUrl | URL for the Dashboard of the own Testkube instance, to display links for the execution. +``` diff --git a/docs/docs/articles/running-test-suites.md b/docs/docs/articles/running-test-suites.md index 8ce45c8860b..e09e0902cc9 100644 --- a/docs/docs/articles/running-test-suites.md +++ b/docs/docs/articles/running-test-suites.md @@ -8,10 +8,10 @@ testkube run testsuite test-example ```sh title="Expected output:" Name: test-example.fairly-humble-tick -Status: pending +Status: running - STEP | STATUS | ID | ERROR -+------+--------+----+-------+ + STATUSES | STEP | IDS | ERRORS ++----------+------+-----+-------+ @@ -35,43 +35,51 @@ testkube run testsuite test-example -f ```sh title="Expected output:" -Name: test-example.equally-enabled-heron -Status: pending +Name : testsuite-parallel +Execution ID : 63d3cd05c6768fc8b574e2e8 +Execution name: ts-testsuite-parallel-19 +Status : running +Duration: 38.530756ms - STEP | STATUS | ID | ERROR -+------+--------+----+-------+ + STATUSES | STEP | IDS | ERRORS +----------------------------+--------------------------------+--------------------------------+------------- + running, running, running | run:testkube/cli-test, | 63d3cd05c6768fc8b574e2e9, | "", "", "" + | run:testkube/demo-test, delay | 63d3cd05c6768fc8b574e2ea, "" | + | 1000ms | | + queued | delay 5000ms | "" | "" ... +Id: 63d3cd05c6768fc8b574e2e8 +Name: ts-testsuite-parallel-19 +Status: running +Duration: 22.138s - STEP | STATUS | ID | ERROR -+----------------------------+---------+--------------------------+-------+ - run test: testkube/test1 | success | 61e1142465e59a318346512d | - - -Name: test-example.equally-enabled-heron -Status: pending - - STEP | STATUS | ID | ERROR -+----------------------------+---------+--------------------------+-------+ - run test: testkube/test1 | success | 61e1142465e59a318346512d | - delay 2000ms | success | | - +Labels: + STATUSES | STEP | IDS | ERRORS +-------------------------+--------------------------------+--------------------------------+------------- + passed, passed, passed | run:testkube/cli-test, | 63d3cd05c6768fc8b574e2e9, | "", "", "" + | run:testkube/demo-test, delay | 63d3cd05c6768fc8b574e2ea, "" | + | 1000ms | | + running | delay 5000ms | "" | "" ... -Name: test-example.equally-enabled-heron -Status: success - - STEP | STATUS | ID | ERROR -+----------------------------+---------+--------------------------+-------+ - run test: testkube/test1 | success | 61e1142465e59a318346512d | - delay 2000ms | success | | - run test: testkube/test1 | success | 61e1142a65e59a318346512f | +Id: 63d3cd05c6768fc8b574e2e8 +Name: ts-testsuite-parallel-19 +Status: passed +Duration: 22.138s +Labels: + STATUSES | STEP | IDS | ERRORS +-------------------------+--------------------------------+--------------------------------+------------- + passed, passed, passed | run:testkube/cli-test, | 63d3cd05c6768fc8b574e2e9, | "", "", "" + | run:testkube/demo-test, delay | 63d3cd05c6768fc8b574e2ea, "" | + | 1000ms | | + passed | delay 5000ms | "" | "" -Use the following command to get test execution details: +Use the following command to get test suite execution details: $ kubectl testkube get tse 61e1142465e59a318346512b ``ì diff --git a/docs/docs/articles/scheduling-tests.md b/docs/docs/articles/scheduling-tests.md index 8767a373cc1..25a045d5e47 100644 --- a/docs/docs/articles/scheduling-tests.md +++ b/docs/docs/articles/scheduling-tests.md @@ -1,4 +1,4 @@ -# Test and Test Suite Scheduling +# Scheduling Tests In order to run Tests and Test Suites on a regular basis, we support a scheduling mechanism for these objects. CRDs both for tests and test suites contain a **schedule** field used to define rules for launching them in time. @@ -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/docs/docs/articles/slack-integration.md b/docs/docs/articles/slack-integration.md index 0de4814c6eb..c1a0da137da 100644 --- a/docs/docs/articles/slack-integration.md +++ b/docs/docs/articles/slack-integration.md @@ -1,118 +1,122 @@ # Integrating with Slack -In order to receive notifications in Slack about the status of the testing process, Testkube provides integration with Slack. Below are the steps to configure it. +In order to receive notifications in Slack about the status of the testing process, Testkube provides integration with Slack. Below are the steps to configure it. Click [here](#video-tutorial) for a video tutorial. - - -## Install the Testkube bot to Your Slack Workspace +## Step 1 - Install the Testkube bot to Your Slack Workspace Testkube bot: Add Testkube bot to your Slack workspace -![img.gif](../img/add-testkube-bot-to-workspace.gif) +Once you add Slack bot to your workspace, you will get the bot token, you will need it in the next step. + +``` +Authentification was succesfull! +Please use the following token in the helm values for slackToken: +xoxb-123456789012-1234567890123-123456789012345678901234 +``` + +## Step 2 - Configure Testkube to Use the Slack bot Token + +Populate `slackToken` in the Helm values or apply it directly with: + +```sh +helm upgrade \ + --install \ + --namespace testkube \ + --create-namespace \ + --set testkube-api.slackToken="YOUR_SLACK_TOKEN" \ + kubeshop/testkube +``` + +To see all the values that can be configured, check the helm chart [here](./helm-chart.md). + +## Step 3 - Add the Testkube bot to the Slack Channel -## Configure Testkube to Use the Slack bot Token +Add the Testkube bot to the Slack channel where you want to receive notifications. You can do it by inviting the bot to a specific channel. The Testkube bot by default will send to the first channel that the Testkube bot is member of, you can update the config in the next step. -Populate slackToken in the Helm values file, then install Testkube using Helm install, see [Manual Testkube Helm Charts Installation](../articles/getting-started-overview) for more information. +![Add Testkube bot to Slack channel](../img/slack-running-example.png) -## Adjust Slack Config File +## Step 4 - (Optional) Adjust Slack Config File By default the configuration [/charts/testkube-api/slack-config.json](https://github.com/kubeshop/helm-charts/blob/704c71fa3b8f0138f983ea9a2fa598ecbe3868ae/charts/testkube-api/slack-config.json) looks like below; it will send notification for all events and all test or test suite names with any labels. If the channel is left empty, it will send to the first channel that the Testkube bot is member of. It is an array of config objects and can use any config combinations: -```json -[ - { - "ChannelID": "", - "selector": {}, - "testName": [], - "testSuiteName": [], - "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" - ] - } - ] +```yaml title="config.yaml" +- ChannelID: C058AGQ25D2 + selector: {} + testName: [] + testSuiteName: [] + 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 ``` -For example: -```json -[ - { - "ChannelID": "C01234567", - "selector": {"label1":"value1"}, - "testName": ["sanity", "testName2"], - "testSuiteName": ["test-suite1", "test-suite2"], - "events": [ - "end-test-failed", - "end-test-timeout", - "end-testsuite-failed", - "end-testsuite-timeout" - ] - }, - { - "ChannelID": "C07654342", - "selector": {"label3":"value4"}, - "testName": ["integration-test1", "integration-test2"], - "testSuiteName": ["integration-test-suite1", "integration-test-suite2"], - "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" - ] - }, -] +To apply, pass the file to the Helm values: + +```sh +helm upgrade \ + --install \ + --create-namespace \ + --namespace testkube \ + testkube \ + kubeshop/testkube \ + --set testkube-api.slackToken="$SLACK_BOT_TOKEN" \ + --set testkube-api.slackConfig="$(cat config.yaml)" ``` -This will send notifications to the channel with the id `C01234567` for the test and test suites with labels `label1:value1`; tests with the labels "sanity" and "testName2" and test suites with the labels "test-suite1" and "test-suite2"; on events with the labels "end-test-failed", "end-test-timeout", "end-testsuite-failed" and "end-testsuite-timeout"; and to the channel with the id `C07654342` for tests with labels `label3:value4`, tests with the labels "integration-test1" and "integration-test2" and test suites with the labels "integration-test-suite1" and "integration-test-suite2" on all events. - - -## Adjust Slack Config Using Helm Values - -For convenience, you can also adjust the Slack config using Helm values but you have to use YAML format. For example: - -```yaml - # -- Slack config for the events, tests, testsuites and channels - slackConfig: - - ChannelID: "" - selector: - label1: "value1" - testName: - - "sanity" - - "testName2" - testSuiteName: - - "test-suite1" - - "test-suite2" - 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" +For example: + +```yaml title="config.yaml" +- ChannelID: C01234567 + selector: + label1: value1 + testName: + - sanity + - testName2 + testSuiteName: + - test-suite1 + - test-suite2 + events: + - end-test-failed + - end-test-timeout + - end-testsuite-failed + - end-testsuite-timeout +- ChannelID: C07654342 + selector: + label3: value4 + testName: + - integration-test1 + - integration-test2 + testSuiteName: + - integration-test-suite1 + - integration-test-suite2 + 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 + ``` -## Configure Message Template + +This will send notifications to the channel with the id `C01234567` for the test and test suites with labels `label1:value1`. Tests with the labels "sanity" and "testName2" and test suites with the labels "test-suite1" and "test-suite2". On events with the labels "end-test-failed", "end-test-timeout", "end-testsuite-failed" and "end-testsuite-timeout". And to the channel with the id `C07654342` for tests with labels `label3:value4`, tests with the labels "integration-test1" and "integration-test2" and test suites with the labels "integration-test-suite1" and "integration-test-suite2" on all events. + +### Configure Message Template The default message is [/charts/testkube-api/slack-template.json](https://github.com/kubeshop/helm-charts/blob/311ff9f6fc38dfb5196b91a6f63ee7d3f59f7f4b/charts/testkube-api/slack-template.json) and is written using [Slack block kit builder](https://app.slack.com/block-kit-builder) and Golang templates. You can customize it depending on your needs. The following structure is referenced in the template where it is getting the data to show: @@ -134,7 +138,6 @@ type MessageArgs struct { } ``` -## Add the Testkube bot to a Channel +## Video Tutorial -If the goal is to receive all the notifications in one channel, add the Testkube bot to the channel and leave the `ChannelID` field empty in the `slack-config.json` file. -![img.gif](../img/add-testkube-bot-to-channel.gif) + diff --git a/docs/docs/articles/step1-installing-cli.md b/docs/docs/articles/step1-installing-cli.mdx similarity index 53% rename from docs/docs/articles/step1-installing-cli.md rename to docs/docs/articles/step1-installing-cli.mdx index 207c36bbcd3..a7e6b25f641 100644 --- a/docs/docs/articles/step1-installing-cli.md +++ b/docs/docs/articles/step1-installing-cli.mdx @@ -1,44 +1,61 @@ +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + # Step 1 - Installing Testkube CLI -To install Testkube CLI you'll need the following tools: + + -- [Kubectl](https://kubernetes.io/docs/tasks/tools/), Kubernetes command-line tool -- [Helm](https://helm.sh/) +```sh +brew install testkube +``` -:::note + -Installing the Testkube CLI with Chocolatey and Homebrew will automatically install these dependencies if they are not present. For Linux-based systems please install them manually in advance. + -::: +```sh +choco source add --name=kubeshop_repo --source=https://chocolatey.kubeshop.io/chocolatey +choco install testkube -y +``` -#### MacOS + -```bash -brew install testkube -``` + -#### Windows +To install Testkube CLI you'll need the following tools: -```bash -choco source add --name=kubeshop_repo --source=https://chocolatey.kubeshop.io/chocolatey -choco install testkube -y +- [Kubectl](https://kubernetes.io/docs/tasks/tools/), Kubernetes command-line tool +- [Helm](https://helm.sh/) + +```sh +wget -qO - https://repo.testkube.io/key.pub | sudo apt-key add - +echo "deb https://repo.testkube.io/linux linux main" | sudo tee -a /etc/apt/sources.list +sudo apt-get update +sudo apt-get install -y testkube ``` -#### Ubuntu/Debian + -```bash -wget -qO - https://repo.testkube.io/key.pub | sudo apt-key add - && echo "deb https://repo.testkube.io/linux linux main" | sudo tee -a /etc/apt/sources.list && sudo apt-get update && sudo apt-get install -y testkube -``` + + +To install Testkube CLI you'll need the following tools: -#### Script Installation +- [Kubectl](https://kubernetes.io/docs/tasks/tools/), Kubernetes command-line tool +- [Helm](https://helm.sh/) ```bash curl -sSLf https://get.testkube.io | sh ``` -#### Manual Download + + + + +To install Testkube CLI you'll need the following tools: -If you don't want to use scripts or package managers you can always do a manual install: +- [Kubectl](https://kubernetes.io/docs/tasks/tools/), Kubernetes command-line tool +- [Helm](https://helm.sh/) 1. Download the binary for the version and platform of your choice [here](https://github.com/kubeshop/testkube/releases) 2. Unpack it. For example, in Linux use `tar -zxvf testkube_1.5.1_Linux_x86_64.tar.gz` @@ -52,4 +69,8 @@ For Windows, you will need to unpack the binary and add it to the `%PATH%` as we :::note If you use a package manager that we don't support, please let us know here [#161](https://github.com/kubeshop/testkube/issues/161). -::: \ No newline at end of file +::: + + + + diff --git a/docs/docs/articles/step2-installing-cluster-components.md b/docs/docs/articles/step2-installing-cluster-components.md index c7056b73f2d..294ff57d1fc 100644 --- a/docs/docs/articles/step2-installing-cluster-components.md +++ b/docs/docs/articles/step2-installing-cluster-components.md @@ -1,33 +1,53 @@ -# Step 2 - Install Testkube Cluster Components Using Testkube's CLI +# Step 2 - Installing the Testkube Agent -:::note +Now that you've successfully installed Testkube's CLI, you'll need to install Testkube's agent to initiate a new environment. -Alternative option of installing Testkube Cluster Components is by using Helm. You can find installation instructions [here](./helm-chart.md). +To get started, sign into [Testkube](https://cloud.testkube.io) and create an account: -::: +![Sign in to Testkube](../img/sign-in.png) -The Testkube CLI provides a command to easily deploy the Testkube server components to your cluster. -Run: +## Installation Steps -```bash -testkube init -``` +1. After signing in, create your first environment + +![Create Environment](../img/create-first-environment.png) + +2. Fill in the environment name: + +![Fill in Env Name](../img/fill-in-env-name.png) + +3. Copy the Helm install command into your terminal to install the environment and deploy the Testkube agent in your cluster: + +![Copy Helm Command](../img/copy-helm-command.png) + +4. Run the command in your terminal and wait for Testkube to detect the connection. + +You will need *Helm* installed and `kubectl` configured with access to your Kubernetes cluster: +- To install `helm` just follow the [install instructions on the Helm web site](https://helm.sh/docs/intro/install/). +- To install `kubectl` follow [Kubernetes docs](https://kubernetes.io/docs/tasks/tools/). -note: you must have your KUBECONFIG pointing to the desired location of the installation. +![Install Steps 1](../img/install-steps.png) -The above command will install the following components in your Kubernetes cluster: +5. After some time, you should see the Helm installation notice: -1. Testkube API -2. `testkube` namespace -3. CRDs for Tests, TestSuites, Executors -4. MongoDB -5. Minio - default (can be disabled with `--no-minio`) -6. Dashboard - default (can be disabled with `--no-dashboard` flag) +![Install Steps 2](../img/install-steps-2.png) -Confirm that Testkube is running: -```bash -kubectl get all -n testkube +## Validating the Installation + +Testkube Cloud will notify if the installation is successful. + +* A green indicator means that your cluster was able to connect to the Testkube Cloud. +* A red indicator indicates that the Testkube Agent can't connect to the Testkube Cloud API (Testkube needs some time to establish a connection, max time is 2-3 minutes). + +![Validate Install](../img/validate-install.png) + +In case of a RED status you can try to debug the issues with the command below: + +```sh +testkube agent debug ``` + + By default, Testkube is installed in the `testkube` namespace. \ No newline at end of file diff --git a/docs/docs/articles/test-sources.mdx b/docs/docs/articles/test-sources.mdx index d1001d06685..5c2ceda4a59 100644 --- a/docs/docs/articles/test-sources.mdx +++ b/docs/docs/articles/test-sources.mdx @@ -1,7 +1,7 @@ import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; -# Test Sources +# Sources Testkube connect to your git repository through a Test Source. Once you create a Test Source you can use it in as many tests as you have. diff --git a/docs/docs/articles/test-triggers.md b/docs/docs/articles/test-triggers.md index 8d45aec32ab..bf7674d6ced 100644 --- a/docs/docs/articles/test-triggers.md +++ b/docs/docs/articles/test-triggers.md @@ -1,40 +1,21 @@ -# Test Triggers +# Triggers Testkube allows you to automate running tests and test suites by defining triggers on certain events for various Kubernetes resources. ## What is a Testkube Test Trigger? -In generic terms, a **trigger** defines an **action** which will be executed for a given **execution** when a certain **event** on a specific **resource** occurs. For example, we could define a **trigger** which **runs** a **test** when a **configmap** gets **modified**. +In generic terms, a _Trigger_ defines an _action_ which will be executed for a given _execution_ when a certain _event_ on a specific _resource_ occurs. For example, we could define a _TestTrigger_ which _runs_ a _Test_ when a _ConfigMap_ gets _modified_. -Watch our guide on using Testkube Test Triggers to perform **Asynchronous Testing in Kubernetes**: - +Watch our [video guide](#video-tutorial) on using Testkube Test Triggers to perform **Asynchronous Testing in Kubernetes**: ## Custom Resource Definition Model - -```yaml -apiVersion: tests.testkube.io/v1 -kind: TestTrigger -metadata: - name: Kubernetes object name - namespace: Kubernetes object namespace -spec: - resource: for which Resource do we monitor Event which triggers an Action - resourceSelector: resourceSelector identifies which Kubernetes objects should be watched - event: on which Event for a Resource should an Action be triggered - conditionSpec: which resource conditions should be matched - action: action represents what needs to be executed for selected execution - execution: execution identifies for which test execution should an action be executed - delay: "OPTIONAL: add a delay before scheduling a test or testsuite when a trigger is matched to an event" - testSelector: testSelector identifies on which Testkube Kubernetes Objects an action should be taken -``` - ### Selectors -The **resourceSelector** and **testSelector** fields support selecting resources either by name or using +The `resourceSelector` and `testSelector` fields support selecting resources either by name or using the Kubernetes [Label Selector](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#resources-that-support-set-based-requirements). -Each selector should specify the **namespace** of the object, otherwise the namespace defaults to **testkube**. +Each selector should specify the `namespace` of the object, otherwise the namespace defaults to `testkube`. ``` selector := resourceSelector | testSelector @@ -61,7 +42,7 @@ selector: matchLabels: map of key-value pairs matchExpressions: - key: label name - operator: one of In, NotIn, Exists, DoesNotExist + operator: [In | NotIn | Exists | DoesNotExist values: list of values ``` @@ -132,3 +113,8 @@ Kubernetes API and gets notified by Kubernetes on each event on the watched reso ## API Testkube exposes CRUD operations on test triggers in the REST API. Check out the [Open API](../openapi.md) docs for more info. + +## Video Tutorial + + + diff --git a/docs/docs/articles/testkube-cli-docker.md b/docs/docs/articles/testkube-cli-docker.md new file mode 100644 index 00000000000..beae780c73d --- /dev/null +++ b/docs/docs/articles/testkube-cli-docker.md @@ -0,0 +1,67 @@ +# Testkube Docker CLI + +Starting with Testkube version 1.13, the easiest way to start managing your team's tests on a remote Testkube server is to run the Testkube CLI using the official Docker image. The Testkube CLI Docker image is a self-contained environment that allows you to run Testkube commands in a consistent and isolated manner. + +## Prerequisites + +Before using the Testkube CLI Docker image, ensure that you have Docker installed and running on your system. You can download and install Docker from the official Docker website (). + +To pull the image, run: + +```bash +docker pull kubeshop/testkube-cli:latest +``` + +## Obtaining the Testkube CLI Docker Image + +To obtain the Testkube CLI Docker image, you have two options: + +### 1. Pulling from Docker Hub + +The Testkube CLI Docker image is available on Docker Hub. You can pull it using the following command: + +```bash +docker pull testkube/cli:latest +``` + +### 2. Building from Source + +If you prefer to build the Docker image from source, you can clone the Testkube CLI repository from GitHub and build it locally using GoReleaser, the provided Dockerfile and the Makefile. Follow these steps: + +1. Clone the Testkube CLI repository: + +```bash +git clone https://github.com/kubeshop/testkube.git +``` + +2. Build the Docker image: + +```bash +make docker-build-cli DOCKER_BUILDX_CACHE_FROM=type=registry,ref=docker.io/kubeshop/testkube-cli:latest ALPINE_IMAGE=alpine:3.18.0 DOCKER_IMAGE_TAG=local ANALYTICS_TRACKING_ID="" ANALYTICS_API_KEY="" +``` + +## Running the Testkube CLI Docker Image + +Once you have the image, run the following command pointing to the Testkube demo: + +```bash +docker run kubeshop/testkube-cli:latest version --namespace testkube --api-uri https://demo.testkube.io/results --client direct +``` + +This command starts a new Docker container with the Testkube CLI image and executes the command `testkube version`, pointing to the api-server running on the Testkube demo environment. + +There are multiple *client types* you can set for the Testkube CLI: + +* direct - for connecting to a remotely deployed environment +* proxy - for connecting to local environments, not relevant in the case of a Docker container +* cloud - for connecting to Testkube Cloud + +You can also use your existing `kubectl` configuration file as a volume: + +```bash +docker run -v ~/.testkube:/.testkube kubeshop/testkube-cli:1.11.13-SNAPSHOT-5f34248fd-arm64v8 --api-uri https://demo.testkube.io/results --client direct version +``` + +## Conclusion + +This user documentation has provided an overview of the Testkube CLI Docker image and guided you through the process of obtaining, running, and using the Testkube CLI within the Docker container. With the Testkube CLI, you can conveniently manage your Testkube deployments and perform various operations with ease. diff --git a/docs/docs/articles/webhooks.mdx b/docs/docs/articles/webhooks.mdx new file mode 100644 index 00000000000..bc5e2bcd45e --- /dev/null +++ b/docs/docs/articles/webhooks.mdx @@ -0,0 +1,62 @@ +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Webhooks + +Webhooks allow you to build or set up integrations and send HTTP POST payloads (your Testkube Execution and its current state) whenever an event is triggered. In this case, when your Tests start or finish. + +To set them up when using Testkube, you'll need to create your _Webhook_ as shown in the following format example (you can use https://webhook.site to create a webhook to test with): + + + + + +Create a webhook template payload file: + +```json title="template.json" +{ + "text": "event id {{ .Id }}" +} +``` + +```sh +testkube create webhook --name example-webhook --events start-test --events end-test-failed --payload-template template.json --uri https://webhook.site/34d499d1-2e09-4e2d-a89c-618a15e1d3df +``` + +```sh title="Expected output:" +Webhook created example-webhook 🥇 +``` + + + + + +```yaml title="webhook.yaml" +apiVersion: executor.testkube.io/v1 +kind: Webhook +metadata: + name: example-webhook + namespace: testkube +spec: + uri: http://localhost:8080/events + events: + - start-test + - end-test-success + - end-test-failed + selector: "" + payloadObjectField: "" + payloadTemplate: | + {"text": "event id {{ .Id }}"} + headers: + X-Token: "12345" +``` + +And then apply with: + +```sh +kubectl apply -f webhook.yaml +``` + + + + diff --git a/docs/docs/cli/testkube.md b/docs/docs/cli/testkube.md index c56b2bcdc95..b65faeba20b 100644 --- a/docs/docs/cli/testkube.md +++ b/docs/docs/cli/testkube.md @@ -21,6 +21,7 @@ testkube [flags] * [testkube abort](testkube_abort.md) - Abort tests or test suites * [testkube agent](testkube_agent.md) - Testkube Cloud Agent related commands +* [testkube cloud](testkube_cloud.md) - Testkube Cloud commands * [testkube completion](testkube_completion.md) - Generate the autocompletion script for the specified shell * [testkube config](testkube_config.md) - Set feature configuration value * [testkube create](testkube_create.md) - Create resource @@ -34,6 +35,7 @@ testkube [flags] * [testkube generate](testkube_generate.md) - Generate resources commands * [testkube get](testkube_get.md) - Get resources * [testkube init](testkube_init.md) - Install Helm chart registry in current kubectl context and update dependencies +* [testkube login](testkube_login.md) - Login to Testkube Cloud * [testkube migrate](testkube_migrate.md) - manual migrate command * [testkube purge](testkube_purge.md) - Uninstall Helm chart registry from current kubectl context * [testkube run](testkube_run.md) - Runs tests or test suites diff --git a/docs/docs/cli/testkube_cloud.md b/docs/docs/cli/testkube_cloud.md new file mode 100644 index 00000000000..d6aa6eaba56 --- /dev/null +++ b/docs/docs/cli/testkube_cloud.md @@ -0,0 +1,32 @@ +## testkube cloud + +Testkube Cloud commands + +``` +testkube cloud [flags] +``` + +### Options + +``` + -h, --help help for cloud +``` + +### 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](testkube.md) - Testkube entrypoint for kubectl plugin +* [testkube cloud connect](testkube_cloud_connect.md) - Testkube Cloud connect +* [testkube cloud disconnect](testkube_cloud_disconnect.md) - Switch back to Testkube OSS mode, based on active .kube/config file +* [testkube cloud init](testkube_cloud_init.md) - Install Helm chart registry in current kubectl context and update dependencies +* [testkube cloud login](testkube_cloud_login.md) - Login to Testkube Cloud + diff --git a/docs/docs/cli/testkube_cloud_connect.md b/docs/docs/cli/testkube_cloud_connect.md new file mode 100644 index 00000000000..074c2411b9f --- /dev/null +++ b/docs/docs/cli/testkube_cloud_connect.md @@ -0,0 +1,43 @@ +## testkube cloud connect + +Testkube Cloud connect + +``` +testkube cloud connect [flags] +``` + +### Options + +``` + --agent-token string Testkube Cloud agent key [required for cloud mode] + --chart string chart name (usually you don't need to change it) (default "kubeshop/testkube") + --cloud-root-domain string defaults to testkube.io, usually don't need to be changed [required for cloud mode] (default "testkube.io") + --dashboard-replicas int Dashboard replicas + --dry-run dry run mode - only print commands that would be executed + --env-id string Testkube Cloud environment id [required for cloud mode] + -h, --help help for connect + --minio-replicas int MinIO replicas + --mongo-replicas int MongoDB replicas + --name string installation name (usually you don't need to change it) (default "testkube") + --namespace string namespace where to install (default "testkube") + --no-confirm don't ask for confirmation - unatended installation mode + --no-dashboard don't install dashboard + --no-minio don't install MinIO + --no-mongo don't install MongoDB + --org-id string Testkube Cloud organization id [required for cloud mode] + --values string path to Helm values file +``` + +### 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") + --oauth-enabled enable oauth + --verbose show additional debug messages +``` + +### SEE ALSO + +* [testkube cloud](testkube_cloud.md) - Testkube Cloud commands + diff --git a/docs/docs/cli/testkube_cloud_disconnect.md b/docs/docs/cli/testkube_cloud_disconnect.md new file mode 100644 index 00000000000..4dd5f888747 --- /dev/null +++ b/docs/docs/cli/testkube_cloud_disconnect.md @@ -0,0 +1,39 @@ +## testkube cloud disconnect + +Switch back to Testkube OSS mode, based on active .kube/config file + +``` +testkube cloud disconnect [flags] +``` + +### Options + +``` + --chart string chart name (usually you don't need to change it) (default "kubeshop/testkube") + --dashboard-replicas int Dashboard replicas (default 1) + --dry-run dry run mode - only print commands that would be executed + -h, --help help for disconnect + --minio-replicas int MinIO replicas (default 1) + --mongo-replicas int MongoDB replicas (default 1) + --name string installation name (usually you don't need to change it) (default "testkube") + --namespace string namespace where to install (default "testkube") + --no-confirm don't ask for confirmation - unatended installation mode + --no-dashboard don't install dashboard + --no-minio don't install MinIO + --no-mongo don't install MongoDB + --values string path to Helm values file +``` + +### 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") + --oauth-enabled enable oauth + --verbose show additional debug messages +``` + +### SEE ALSO + +* [testkube cloud](testkube_cloud.md) - Testkube Cloud commands + diff --git a/docs/docs/cli/testkube_cloud_init.md b/docs/docs/cli/testkube_cloud_init.md new file mode 100644 index 00000000000..e814ab12e38 --- /dev/null +++ b/docs/docs/cli/testkube_cloud_init.md @@ -0,0 +1,37 @@ +## testkube cloud init + +Install Helm chart registry in current kubectl context and update dependencies + +``` +testkube cloud init [flags] +``` + +### Options + +``` + --agent-token string Testkube Cloud agent key + --chart string chart name (usually you don't need to change it) (default "kubeshop/testkube") + --cloud-root-domain string defaults to testkube.io, usually don't need to be changed [required for cloud mode] (default "testkube.io") + --dry-run dry run mode - only print commands that would be executed + --env-id string Testkube Cloud environment id + -h, --help help for init + --name string installation name (usually you don't need to change it) (default "testkube") + --namespace string namespace where to install (default "testkube") + --no-confirm don't ask for confirmation - unatended installation mode + --org-id string Testkube Cloud organization id + --values string path to Helm values file +``` + +### 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") + --oauth-enabled enable oauth + --verbose show additional debug messages +``` + +### SEE ALSO + +* [testkube cloud](testkube_cloud.md) - Testkube Cloud commands + diff --git a/docs/docs/cli/testkube_cloud_login.md b/docs/docs/cli/testkube_cloud_login.md new file mode 100644 index 00000000000..fe6df5bab90 --- /dev/null +++ b/docs/docs/cli/testkube_cloud_login.md @@ -0,0 +1,31 @@ +## testkube cloud login + +Login to Testkube Cloud + +``` +testkube cloud login [flags] +``` + +### Options + +``` + --cloud-root-domain string defaults to testkube.io, usually don't need to be changed [required for cloud mode] (default "testkube.io") + --env-id string Testkube Cloud environment id + -h, --help help for login + --org-id string Testkube Cloud organization id +``` + +### 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 cloud](testkube_cloud.md) - Testkube Cloud commands + diff --git a/docs/docs/cli/testkube_create_test.md b/docs/docs/cli/testkube_create_test.md index ea207818958..87687442b07 100644 --- a/docs/docs/cli/testkube_create_test.md +++ b/docs/docs/cli/testkube_create_test.md @@ -46,6 +46,7 @@ testkube create test [flags] --mount-secret stringToString secret value pair for mounting it to executor pod: --mount-secret secret_name=secret_mountpath (default []) -n, --name string unique test name - mandatory --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 --schedule string test schedule in a cron job form: * * * * * --scraper-template string scraper template file path for extensions to scraper template diff --git a/docs/docs/cli/testkube_create_webhook.md b/docs/docs/cli/testkube_create_webhook.md index 6951e9f8ed4..a97b3a4f577 100644 --- a/docs/docs/cli/testkube_create_webhook.md +++ b/docs/docs/cli/testkube_create_webhook.md @@ -13,13 +13,15 @@ testkube create webhook [flags] ### Options ``` - -e, --events stringArray event types handled by executor e.g. start-test|end-test - -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 - --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 executor 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 ``` ### Options inherited from parent commands diff --git a/docs/docs/cli/testkube_generate_tests-crds.md b/docs/docs/cli/testkube_generate_tests-crds.md index 7097a4ff008..31ec11fb7a6 100644 --- a/docs/docs/cli/testkube_generate_tests-crds.md +++ b/docs/docs/cli/testkube_generate_tests-crds.md @@ -13,7 +13,8 @@ testkube generate tests-crds [flags] ### Options ``` - --artifact-dir stringArray artifact dirs for container executor + --args-mode string usage mode for arguments. one of append|override (default "append") + --artifact-dir stringArray artifact dirs for scraping --artifact-storage-class-name string artifact storage class name for container executor --artifact-volume-mount-path string artifact volume mount path for container executor --command stringArray command passed to image in executor @@ -32,6 +33,7 @@ testkube generate tests-crds [flags] --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 --schedule string test schedule in a cron job form: * * * * * --scraper-template string scraper template file path for extensions to scraper template diff --git a/docs/docs/cli/testkube_init.md b/docs/docs/cli/testkube_init.md index 065c4d4a5e6..60c5b419bb8 100644 --- a/docs/docs/cli/testkube_init.md +++ b/docs/docs/cli/testkube_init.md @@ -9,17 +9,21 @@ testkube init [flags] ### Options ``` - --agent-key string Testkube Cloud agent key [required for cloud mode] - --agent-uri string Testkube Cloud agent URI [required for cloud mode] - --chart string chart name (default "kubeshop/testkube") - -h, --help help for init - --name string installation name (default "testkube") - --namespace string namespace where to install (default "testkube") - --no-confirm don't ask for confirmation - unatended installation mode - --no-dashboard don't install dashboard - --no-minio don't install MinIO - --no-mongo don't install MongoDB - --values string path to Helm values file + --agent-token string Testkube Cloud agent key [required for cloud mode] + --agent-uri string Testkube Cloud agent URI [required for cloud mode] + --chart string chart name (usually you don't need to change it) (default "kubeshop/testkube") + --cloud-root-domain string defaults to testkube.io, usually don't need to be changed [required for cloud mode] (default "testkube.io") + --dry-run dry run mode - only print commands that would be executed + --env-id string Testkube Cloud environment id [required for cloud mode] + -h, --help help for init + --name string installation name (usually you don't need to change it) (default "testkube") + --namespace string namespace where to install (default "testkube") + --no-confirm don't ask for confirmation - unatended installation mode + --no-dashboard don't install dashboard + --no-minio don't install MinIO + --no-mongo don't install MongoDB + --org-id string Testkube Cloud organization id [required for cloud mode] + --values string path to Helm values file ``` ### Options inherited from parent commands diff --git a/docs/docs/cli/testkube_login.md b/docs/docs/cli/testkube_login.md new file mode 100644 index 00000000000..906cd008d15 --- /dev/null +++ b/docs/docs/cli/testkube_login.md @@ -0,0 +1,31 @@ +## testkube login + +Login to Testkube Cloud + +``` +testkube login [flags] +``` + +### Options + +``` + --cloud-root-domain string defaults to testkube.io, usually don't need to be changed [required for cloud mode] (default "testkube.io") + --env-id string Testkube Cloud environment id + -h, --help help for login + --org-id string Testkube Cloud organization id +``` + +### 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](testkube.md) - Testkube entrypoint for kubectl plugin + diff --git a/docs/docs/cli/testkube_run_test.md b/docs/docs/cli/testkube_run_test.md index 924b62a2b4e..a18c814fe32 100644 --- a/docs/docs/cli/testkube_run_test.md +++ b/docs/docs/cli/testkube_run_test.md @@ -42,6 +42,7 @@ testkube run test [flags] --mount-secret stringToString secret value pair for mounting it to executor pod: --mount-secret secret_name=secret_mountpath (default []) -n, --name string execution name, if empty will be autogenerated --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 --scraper-template string scraper template file path for extensions to scraper template -s, --secret-variable stringToString execution secret variable passed to executor (default []) diff --git a/docs/docs/cli/testkube_set_context.md b/docs/docs/cli/testkube_set_context.md index 82c3435b01b..0debbf90b2f 100644 --- a/docs/docs/cli/testkube_set_context.md +++ b/docs/docs/cli/testkube_set_context.md @@ -9,13 +9,13 @@ testkube set context [flags] ### Options ``` - -k, --api-key string API Key for Testkube Cloud - --cloud-api-uri string Testkube Cloud API URI - defaults to api.testksube.io (default "https://api.testkube.io") - -e, --env string Testkube Cloud Environment ID - -h, --help help for context - --kubeconfig reset context mode for CLI to default kubeconfig based - -n, --namespace string Testkube namespace to use for CLI commands - -o, --org string Testkube Cloud Organization ID + -k, --api-key string API Key for Testkube Cloud + --cloud-root-domain string defaults to testkube.io, usually you don't need to change it (default "testkube.io") + -e, --env string Testkube Cloud Environment ID + -h, --help help for context + --kubeconfig reset context mode for CLI to default kubeconfig based + -n, --namespace string Testkube namespace to use for CLI commands + -o, --org string Testkube Cloud Organization ID ``` ### Options inherited from parent commands diff --git a/docs/docs/cli/testkube_update_test.md b/docs/docs/cli/testkube_update_test.md index 4fb6b9955a0..fd51cb86d55 100644 --- a/docs/docs/cli/testkube_update_test.md +++ b/docs/docs/cli/testkube_update_test.md @@ -45,6 +45,7 @@ testkube update test [flags] --mount-secret stringToString secret value pair for mounting it to executor pod: --mount-secret secret_name=secret_mountpath (default []) -n, --name string unique test name - mandatory --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 --schedule string test schedule in a cron job form: * * * * * --scraper-template string scraper template file path for extensions to scraper template diff --git a/docs/docs/cli/testkube_upgrade.md b/docs/docs/cli/testkube_upgrade.md index 9e0bd72b058..294d3ca174e 100644 --- a/docs/docs/cli/testkube_upgrade.md +++ b/docs/docs/cli/testkube_upgrade.md @@ -9,17 +9,21 @@ testkube upgrade [flags] ### Options ``` - --agent-key string Testkube Cloud agent key [required for cloud mode] - --agent-uri string Testkube Cloud agent URI [required for cloud mode] - --chart string chart name (default "kubeshop/testkube") - -h, --help help for upgrade - --name string installation name (default "testkube") - --namespace string namespace where to install (default "testkube") - --no-confirm don't ask for confirmation - unatended installation mode - --no-dashboard don't install dashboard - --no-minio don't install MinIO - --no-mongo don't install MongoDB - --values string path to Helm values file + --agent-token string Testkube Cloud agent key [required for cloud mode] + --agent-uri string Testkube Cloud agent URI [required for cloud mode] + --chart string chart name (usually you don't need to change it) (default "kubeshop/testkube") + --cloud-root-domain string defaults to testkube.io, usually don't need to be changed [required for cloud mode] (default "testkube.io") + --dry-run dry run mode - only print commands that would be executed + --env-id string Testkube Cloud environment id [required for cloud mode] + -h, --help help for upgrade + --name string installation name (usually you don't need to change it) (default "testkube") + --namespace string namespace where to install (default "testkube") + --no-confirm don't ask for confirmation - unatended installation mode + --no-dashboard don't install dashboard + --no-minio don't install MinIO + --no-mongo don't install MongoDB + --org-id string Testkube Cloud organization id [required for cloud mode] + --values string path to Helm values file ``` ### Options inherited from parent commands diff --git a/docs/docs/img/add-testkube-bot-to-workspace.gif b/docs/docs/img/add-testkube-bot-to-workspace.gif deleted file mode 100644 index b47c11646f8..00000000000 Binary files a/docs/docs/img/add-testkube-bot-to-workspace.gif and /dev/null differ diff --git a/docs/docs/img/create-first-environment.png b/docs/docs/img/create-first-environment.png new file mode 100644 index 00000000000..06a17f4d1c7 Binary files /dev/null and b/docs/docs/img/create-first-environment.png differ diff --git a/docs/docs/img/sign-in.png b/docs/docs/img/sign-in.png new file mode 100644 index 00000000000..e96c6284960 Binary files /dev/null and b/docs/docs/img/sign-in.png differ diff --git a/docs/docs/img/slack-running-example.png b/docs/docs/img/slack-running-example.png new file mode 100644 index 00000000000..73d806a3e93 Binary files /dev/null and b/docs/docs/img/slack-running-example.png differ diff --git a/docs/docs/index.mdx b/docs/docs/index.mdx index 98fb267dfbe..8b6fb63cceb 100644 --- a/docs/docs/index.mdx +++ b/docs/docs/index.mdx @@ -127,5 +127,5 @@ With Testkube, tests are part of a cluster's state and can be executed as needed - ✨Automatically on deployment of annotated/labeled Kubernetes objects (services, pods, etc) - ⏲️ On a schedule -- 🧑‍💻 Manually via Testkube's CLI or Open Source Dashboard +- 🧑‍💻 Manually via Testkube's CLI or via Testkube's Dashboard - ⚡ Externally triggered via API diff --git a/docs/docs/test-types/executor-zap.md b/docs/docs/test-types/executor-zap.md index 0e7f1b4fc40..96c9905e89c 100644 --- a/docs/docs/test-types/executor-zap.md +++ b/docs/docs/test-types/executor-zap.md @@ -129,9 +129,11 @@ For additional files that should be passed in as parameter to the executor, use For tests running on production, you should use a Git repository to keep track of the changes the test went through. When running these tests, Testkube will clone the repository every time. An example test creation command would look something like this: -```bash -$ testkube create test --git-uri https://github.com/kubeshop/testkube.git --type "zap/api" --name git-zap-api-test --executor-args "zap-api.yaml" --git-branch main --git-path contrib/executor/zap/examples -kubectl testkube run test --watch git-zap-api-test +```sh +testkube create test --git-uri https://github.com/kubeshop/testkube.git --type "zap/api" --name git-zap-api-test --executor-args "zap-api.yaml" --git-branch main --git-path contrib/executor/zap/examples +``` + +```sh title="Output:" Test created testkube / git-zap-api-test 🥇 ``` diff --git a/docs/package-lock.json b/docs/package-lock.json index e3b716fd9e2..39ffa61ae48 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,10 +8,10 @@ "name": "testkube-documentation", "version": "0.0.1", "dependencies": { - "@docusaurus/core": "^2.4.0", - "@docusaurus/plugin-client-redirects": "^2.4.0", - "@docusaurus/preset-classic": "^2.4.0", - "@docusaurus/theme-search-algolia": "^2.4.0", + "@docusaurus/core": "^2.4.1", + "@docusaurus/plugin-client-redirects": "^2.4.1", + "@docusaurus/preset-classic": "^2.4.1", + "@docusaurus/theme-search-algolia": "^2.4.1", "@mdx-js/react": "^1.6.22", "algoliasearch": "^4.14.3", "clsx": "^1.2.1", @@ -23,7 +23,7 @@ "redocusaurus": "^1.3.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^2.4.0", + "@docusaurus/module-type-aliases": "^2.4.1", "@tsconfig/docusaurus": "^1.0.5", "typescript": "^4.7.4" }, @@ -32,19 +32,31 @@ } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.7.4.tgz", - "integrity": "sha512-daoLpQ3ps/VTMRZDEBfU8ixXd+amZcNJ4QSP3IERGyzqnL5Ch8uSRFt/4G8pUvW9c3o6GA4vtVv4I4lmnkdXyg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.2.tgz", + "integrity": "sha512-hkG80c9kx9ClVAEcUJbTd2ziVC713x9Bji9Ty4XJfKXlxlsx3iXsoNhAwfeR4ulzIUg7OE5gez0UU1zVDdG7kg==", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.2", + "@algolia/autocomplete-shared": "1.9.2" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.2.tgz", + "integrity": "sha512-2LVsf4W66hVHQ3Ua/8k15oPlxjELCztbAkQm/hP42Sw+GLkHAdY1vaVRYziaWq64+Oljfg6FKkZHCdgXH+CGIA==", "dependencies": { - "@algolia/autocomplete-shared": "1.7.4" + "@algolia/autocomplete-shared": "1.9.2" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.4.tgz", - "integrity": "sha512-s37hrvLEIfcmKY8VU9LsAXgm2yfmkdHT3DnA3SgHaY93yjZ2qL57wzb5QweVkYuEBZkT2PIREvRoLXC2sxTbpQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.2.tgz", + "integrity": "sha512-pqgIm2GNqtCT59Y1ICctIPrYTi34+wNPiNWEclD/yDzp5uDUUsyGe5XrUjCNyQRTKonAlmYxoaEHOn8FWgmBHA==", "dependencies": { - "@algolia/autocomplete-shared": "1.7.4" + "@algolia/autocomplete-shared": "1.9.2" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -52,9 +64,13 @@ } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.4.tgz", - "integrity": "sha512-2VGCk7I9tA9Ge73Km99+Qg87w0wzW4tgUruvWAn/gfey1ZXgmxZtyIRBebk35R1O8TbK77wujVtCnpsGpRy1kg==" + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.2.tgz", + "integrity": "sha512-XxX6YDn+7LG+SmdpXEOnj7fc3TjiVpQ0CbGhjLwrd2tYr6LVY2D4Iiu/iuYJ4shvVDWWnpwArSk0uIWC/8OPUA==", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } }, "node_modules/@algolia/cache-browser-local-storage": { "version": "4.14.3", @@ -2112,18 +2128,18 @@ } }, "node_modules/@docsearch/css": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.3.3.tgz", - "integrity": "sha512-6SCwI7P8ao+se1TUsdZ7B4XzL+gqeQZnBc+2EONZlcVa0dVrk0NjETxozFKgMv0eEGH8QzP1fkN+A1rH61l4eg==" + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.0.tgz", + "integrity": "sha512-Ob5FQLubplcBNihAVtriR59FRBeP8u69F6mu4L4yIr60KfsPc10bOV0DoPErJw0zF9IBN2cNLW9qdmt8zWPxyg==" }, "node_modules/@docsearch/react": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.3.3.tgz", - "integrity": "sha512-pLa0cxnl+G0FuIDuYlW+EBK6Rw2jwLw9B1RHIeS4N4s2VhsfJ/wzeCi3CWcs5yVfxLd5ZK50t//TMA5e79YT7Q==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.0.tgz", + "integrity": "sha512-3IG8mmSMzSHNGy2S1VuPyYU9tFCxFpj5Ov8SYwsSHM4yMvFsaO9oFxXocA5lSenliIELhuOuS5+BdxHa/Qlf2A==", "dependencies": { - "@algolia/autocomplete-core": "1.7.4", - "@algolia/autocomplete-preset-algolia": "1.7.4", - "@docsearch/css": "3.3.3", + "@algolia/autocomplete-core": "1.9.2", + "@algolia/autocomplete-preset-algolia": "1.9.2", + "@docsearch/css": "3.5.0", "algoliasearch": "^4.0.0" }, "peerDependencies": { @@ -2144,9 +2160,9 @@ } }, "node_modules/@docusaurus/core": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.4.0.tgz", - "integrity": "sha512-J55/WEoIpRcLf3afO5POHPguVZosKmJEQWKBL+K7TAnfuE7i+Y0NPLlkKtnWCehagGsgTqClfQEexH/UT4kELA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.4.1.tgz", + "integrity": "sha512-SNsY7PshK3Ri7vtsLXVeAJGS50nJN3RgF836zkyUfAD01Fq+sAk5EwWgLw+nnm5KVNGDu7PRR2kRGDsWvqpo0g==", "dependencies": { "@babel/core": "^7.18.6", "@babel/generator": "^7.18.7", @@ -2158,13 +2174,13 @@ "@babel/runtime": "^7.18.6", "@babel/runtime-corejs3": "^7.18.6", "@babel/traverse": "^7.18.8", - "@docusaurus/cssnano-preset": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/mdx-loader": "2.4.0", + "@docusaurus/cssnano-preset": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/mdx-loader": "2.4.1", "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "@slorber/static-site-generator-webpack-plugin": "^4.0.7", "@svgr/webpack": "^6.2.1", "autoprefixer": "^10.4.7", @@ -2247,9 +2263,9 @@ } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.0.tgz", - "integrity": "sha512-RmdiA3IpsLgZGXRzqnmTbGv43W4OD44PCo+6Q/aYjEM2V57vKCVqNzuafE94jv0z/PjHoXUrjr69SaRymBKYYw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.1.tgz", + "integrity": "sha512-ka+vqXwtcW1NbXxWsh6yA1Ckii1klY9E53cJ4O9J09nkMBgrNX3iEFED1fWdv8wf4mJjvGi5RLZ2p9hJNjsLyQ==", "dependencies": { "cssnano-preset-advanced": "^5.3.8", "postcss": "^8.4.14", @@ -2261,9 +2277,9 @@ } }, "node_modules/@docusaurus/logger": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.4.0.tgz", - "integrity": "sha512-T8+qR4APN+MjcC9yL2Es+xPJ2923S9hpzDmMtdsOcUGLqpCGBbU1vp3AAqDwXtVgFkq+NsEk7sHdVsfLWR/AXw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.4.1.tgz", + "integrity": "sha512-5h5ysIIWYIDHyTVd8BjheZmQZmEgWDR54aQ1BX9pjFfpyzFo5puKXKYrYJXbjEHGyVhEzmB9UXwbxGfaZhOjcg==", "dependencies": { "chalk": "^4.1.2", "tslib": "^2.4.0" @@ -2273,14 +2289,14 @@ } }, "node_modules/@docusaurus/mdx-loader": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.4.0.tgz", - "integrity": "sha512-GWoH4izZKOmFoC+gbI2/y8deH/xKLvzz/T5BsEexBye8EHQlwsA7FMrVa48N063bJBH4FUOiRRXxk5rq9cC36g==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.4.1.tgz", + "integrity": "sha512-4KhUhEavteIAmbBj7LVFnrVYDiU51H5YWW1zY6SmBSte/YLhDutztLTBE0PQl1Grux1jzUJeaSvAzHpTn6JJDQ==", "dependencies": { "@babel/parser": "^7.18.8", "@babel/traverse": "^7.18.8", - "@docusaurus/logger": "2.4.0", - "@docusaurus/utils": "2.4.0", + "@docusaurus/logger": "2.4.1", + "@docusaurus/utils": "2.4.1", "@mdx-js/mdx": "^1.6.22", "escape-html": "^1.0.3", "file-loader": "^6.2.0", @@ -2321,12 +2337,12 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.0.tgz", - "integrity": "sha512-YEQO2D3UXs72qCn8Cr+RlycSQXVGN9iEUyuHwTuK4/uL/HFomB2FHSU0vSDM23oLd+X/KibQ3Ez6nGjQLqXcHg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.1.tgz", + "integrity": "sha512-gLBuIFM8Dp2XOCWffUDSjtxY7jQgKvYujt7Mx5s4FCTfoL5dN1EVbnrn+O2Wvh8b0a77D57qoIDY7ghgmatR1A==", "dependencies": { "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/types": "2.4.0", + "@docusaurus/types": "2.4.1", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2340,15 +2356,15 @@ } }, "node_modules/@docusaurus/plugin-client-redirects": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.4.0.tgz", - "integrity": "sha512-HsS+Dc2ZLWhfpjYJ5LIrOB/XfXZcElcC7o1iA4yIVtiFz+LHhwP863fhqbwSJ1c6tNDOYBH3HwbskHrc/PIn7Q==", - "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.4.1.tgz", + "integrity": "sha512-tp0j16gaLIJ4p+IR0P6KDOFsTOGGMY54MNPnmM61Vaqqt5omLqsuKUO8UlCGU1oW/4EIQOhXYy99XYY5MjE+7A==", + "dependencies": { + "@docusaurus/core": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "eta": "^2.0.0", "fs-extra": "^10.1.0", "lodash": "^4.17.21", @@ -2363,17 +2379,17 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.0.tgz", - "integrity": "sha512-YwkAkVUxtxoBAIj/MCb4ohN0SCtHBs4AS75jMhPpf67qf3j+U/4n33cELq7567hwyZ6fMz2GPJcVmctzlGGThQ==", - "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/mdx-loader": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.1.tgz", + "integrity": "sha512-E2i7Knz5YIbE1XELI6RlTnZnGgS52cUO4BlCiCUCvQHbR+s1xeIWz4C6BtaVnlug0Ccz7nFSksfwDpVlkujg5Q==", + "dependencies": { + "@docusaurus/core": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/mdx-loader": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "cheerio": "^1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^10.1.0", @@ -2393,17 +2409,17 @@ } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.0.tgz", - "integrity": "sha512-ic/Z/ZN5Rk/RQo+Io6rUGpToOtNbtPloMR2JcGwC1xT2riMu6zzfSwmBi9tHJgdXH6CB5jG+0dOZZO8QS5tmDg==", - "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/mdx-loader": "2.4.0", - "@docusaurus/module-type-aliases": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.1.tgz", + "integrity": "sha512-Lo7lSIcpswa2Kv4HEeUcGYqaasMUQNpjTXpV0N8G6jXgZaQurqp7E8NGYeGbDXnb48czmHWbzDL4S3+BbK0VzA==", + "dependencies": { + "@docusaurus/core": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/mdx-loader": "2.4.1", + "@docusaurus/module-type-aliases": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "@types/react-router-config": "^5.0.6", "combine-promises": "^1.1.0", "fs-extra": "^10.1.0", @@ -2423,15 +2439,15 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.0.tgz", - "integrity": "sha512-Pk2pOeOxk8MeU3mrTU0XLIgP9NZixbdcJmJ7RUFrZp1Aj42nd0RhIT14BGvXXyqb8yTQlk4DmYGAzqOfBsFyGw==", - "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/mdx-loader": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.1.tgz", + "integrity": "sha512-/UjuH/76KLaUlL+o1OvyORynv6FURzjurSjvn2lbWTFc4tpYY2qLYTlKpTCBVPhlLUQsfyFnshEJDLmPneq2oA==", + "dependencies": { + "@docusaurus/core": "2.4.1", + "@docusaurus/mdx-loader": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "fs-extra": "^10.1.0", "tslib": "^2.4.0", "webpack": "^5.73.0" @@ -2445,13 +2461,13 @@ } }, "node_modules/@docusaurus/plugin-debug": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.4.0.tgz", - "integrity": "sha512-KC56DdYjYT7Txyux71vXHXGYZuP6yYtqwClvYpjKreWIHWus5Zt6VNi23rMZv3/QKhOCrN64zplUbdfQMvddBQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.4.1.tgz", + "integrity": "sha512-7Yu9UPzRShlrH/G8btOpR0e6INFZr0EegWplMjOqelIwAcx3PKyR8mgPTxGTxcqiYj6hxSCRN0D8R7YrzImwNA==", "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", + "@docusaurus/core": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", "fs-extra": "^10.1.0", "react-json-view": "^1.21.3", "tslib": "^2.4.0" @@ -2465,13 +2481,13 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.0.tgz", - "integrity": "sha512-uGUzX67DOAIglygdNrmMOvEp8qG03X20jMWadeqVQktS6nADvozpSLGx4J0xbkblhJkUzN21WiilsP9iVP+zkw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.1.tgz", + "integrity": "sha512-dyZJdJiCoL+rcfnm0RPkLt/o732HvLiEwmtoNzOoz9MSZz117UH2J6U2vUDtzUzwtFLIf32KkeyzisbwUCgcaQ==", "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "@docusaurus/core": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "tslib": "^2.4.0" }, "engines": { @@ -2483,13 +2499,13 @@ } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.0.tgz", - "integrity": "sha512-adj/70DANaQs2+TF/nRdMezDXFAV/O/pjAbUgmKBlyOTq5qoMe0Tk4muvQIwWUmiUQxFJe+sKlZGM771ownyOg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.1.tgz", + "integrity": "sha512-mKIefK+2kGTQBYvloNEKtDmnRD7bxHLsBcxgnbt4oZwzi2nxCGjPX6+9SQO2KCN5HZbNrYmGo5GJfMgoRvy6uA==", "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "@docusaurus/core": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "tslib": "^2.4.0" }, "engines": { @@ -2501,13 +2517,13 @@ } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-2.4.0.tgz", - "integrity": "sha512-E66uGcYs4l7yitmp/8kMEVQftFPwV9iC62ORh47Veqzs6ExwnhzBkJmwDnwIysHBF1vlxnzET0Fl2LfL5fRR3A==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-2.4.1.tgz", + "integrity": "sha512-Zg4Ii9CMOLfpeV2nG74lVTWNtisFaH9QNtEw48R5QE1KIwDBdTVaiSA18G1EujZjrzJJzXN79VhINSbOJO/r3g==", "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "@docusaurus/core": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "tslib": "^2.4.0" }, "engines": { @@ -2519,16 +2535,16 @@ } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.4.0.tgz", - "integrity": "sha512-pZxh+ygfnI657sN8a/FkYVIAmVv0CGk71QMKqJBOfMmDHNN1FeDeFkBjWP49ejBqpqAhjufkv5UWq3UOu2soCw==", - "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.4.1.tgz", + "integrity": "sha512-lZx+ijt/+atQ3FVE8FOHV/+X3kuok688OydDXrqKRJyXBJZKgGjA2Qa8RjQ4f27V2woaXhtnyrdPop/+OjVMRg==", + "dependencies": { + "@docusaurus/core": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "fs-extra": "^10.1.0", "sitemap": "^7.1.1", "tslib": "^2.4.0" @@ -2542,23 +2558,23 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.4.0.tgz", - "integrity": "sha512-/5z5o/9bc6+P5ool2y01PbJhoGddEGsC0ej1MF6mCoazk8A+kW4feoUd68l7Bnv01rCnG3xy7kHUQP97Y0grUA==", - "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/plugin-content-blog": "2.4.0", - "@docusaurus/plugin-content-docs": "2.4.0", - "@docusaurus/plugin-content-pages": "2.4.0", - "@docusaurus/plugin-debug": "2.4.0", - "@docusaurus/plugin-google-analytics": "2.4.0", - "@docusaurus/plugin-google-gtag": "2.4.0", - "@docusaurus/plugin-google-tag-manager": "2.4.0", - "@docusaurus/plugin-sitemap": "2.4.0", - "@docusaurus/theme-classic": "2.4.0", - "@docusaurus/theme-common": "2.4.0", - "@docusaurus/theme-search-algolia": "2.4.0", - "@docusaurus/types": "2.4.0" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.4.1.tgz", + "integrity": "sha512-P4//+I4zDqQJ+UDgoFrjIFaQ1MeS9UD1cvxVQaI6O7iBmiHQm0MGROP1TbE7HlxlDPXFJjZUK3x3cAoK63smGQ==", + "dependencies": { + "@docusaurus/core": "2.4.1", + "@docusaurus/plugin-content-blog": "2.4.1", + "@docusaurus/plugin-content-docs": "2.4.1", + "@docusaurus/plugin-content-pages": "2.4.1", + "@docusaurus/plugin-debug": "2.4.1", + "@docusaurus/plugin-google-analytics": "2.4.1", + "@docusaurus/plugin-google-gtag": "2.4.1", + "@docusaurus/plugin-google-tag-manager": "2.4.1", + "@docusaurus/plugin-sitemap": "2.4.1", + "@docusaurus/theme-classic": "2.4.1", + "@docusaurus/theme-common": "2.4.1", + "@docusaurus/theme-search-algolia": "2.4.1", + "@docusaurus/types": "2.4.1" }, "engines": { "node": ">=16.14" @@ -2582,22 +2598,22 @@ } }, "node_modules/@docusaurus/theme-classic": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.4.0.tgz", - "integrity": "sha512-GMDX5WU6Z0OC65eQFgl3iNNEbI9IMJz9f6KnOyuMxNUR6q0qVLsKCNopFUDfFNJ55UU50o7P7o21yVhkwpfJ9w==", - "dependencies": { - "@docusaurus/core": "2.4.0", - "@docusaurus/mdx-loader": "2.4.0", - "@docusaurus/module-type-aliases": "2.4.0", - "@docusaurus/plugin-content-blog": "2.4.0", - "@docusaurus/plugin-content-docs": "2.4.0", - "@docusaurus/plugin-content-pages": "2.4.0", - "@docusaurus/theme-common": "2.4.0", - "@docusaurus/theme-translations": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.4.1.tgz", + "integrity": "sha512-Rz0wKUa+LTW1PLXmwnf8mn85EBzaGSt6qamqtmnh9Hflkc+EqiYMhtUJeLdV+wsgYq4aG0ANc+bpUDpsUhdnwg==", + "dependencies": { + "@docusaurus/core": "2.4.1", + "@docusaurus/mdx-loader": "2.4.1", + "@docusaurus/module-type-aliases": "2.4.1", + "@docusaurus/plugin-content-blog": "2.4.1", + "@docusaurus/plugin-content-docs": "2.4.1", + "@docusaurus/plugin-content-pages": "2.4.1", + "@docusaurus/theme-common": "2.4.1", + "@docusaurus/theme-translations": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", "copy-text-to-clipboard": "^3.0.1", @@ -2621,17 +2637,17 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.4.0.tgz", - "integrity": "sha512-IkG/l5f/FLY6cBIxtPmFnxpuPzc5TupuqlOx+XDN+035MdQcAh8wHXXZJAkTeYDeZ3anIUSUIvWa7/nRKoQEfg==", - "dependencies": { - "@docusaurus/mdx-loader": "2.4.0", - "@docusaurus/module-type-aliases": "2.4.0", - "@docusaurus/plugin-content-blog": "2.4.0", - "@docusaurus/plugin-content-docs": "2.4.0", - "@docusaurus/plugin-content-pages": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.4.1.tgz", + "integrity": "sha512-G7Zau1W5rQTaFFB3x3soQoZpkgMbl/SYNG8PfMFIjKa3M3q8n0m/GRf5/H/e5BqOvt8c+ZWIXGCiz+kUCSHovA==", + "dependencies": { + "@docusaurus/mdx-loader": "2.4.1", + "@docusaurus/module-type-aliases": "2.4.1", + "@docusaurus/plugin-content-blog": "2.4.1", + "@docusaurus/plugin-content-docs": "2.4.1", + "@docusaurus/plugin-content-pages": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2651,18 +2667,18 @@ } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.0.tgz", - "integrity": "sha512-pPCJSCL1Qt4pu/Z0uxBAuke0yEBbxh0s4fOvimna7TEcBLPq0x06/K78AaABXrTVQM6S0vdocFl9EoNgU17hqA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.1.tgz", + "integrity": "sha512-6BcqW2lnLhZCXuMAvPRezFs1DpmEKzXFKlYjruuas+Xy3AQeFzDJKTJFIm49N77WFCTyxff8d3E4Q9pi/+5McQ==", "dependencies": { "@docsearch/react": "^3.1.1", - "@docusaurus/core": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/plugin-content-docs": "2.4.0", - "@docusaurus/theme-common": "2.4.0", - "@docusaurus/theme-translations": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "@docusaurus/core": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/plugin-content-docs": "2.4.1", + "@docusaurus/theme-common": "2.4.1", + "@docusaurus/theme-translations": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "algoliasearch": "^4.13.1", "algoliasearch-helper": "^3.10.0", "clsx": "^1.2.1", @@ -2681,9 +2697,9 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.4.0.tgz", - "integrity": "sha512-kEoITnPXzDPUMBHk3+fzEzbopxLD3fR5sDoayNH0vXkpUukA88/aDL1bqkhxWZHA3LOfJ3f0vJbOwmnXW5v85Q==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.4.1.tgz", + "integrity": "sha512-T1RAGP+f86CA1kfE8ejZ3T3pUU3XcyvrGMfC/zxCtc2BsnoexuNI9Vk2CmuKCb+Tacvhxjv5unhxXce0+NKyvA==", "dependencies": { "fs-extra": "^10.1.0", "tslib": "^2.4.0" @@ -2693,9 +2709,9 @@ } }, "node_modules/@docusaurus/types": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.4.0.tgz", - "integrity": "sha512-xaBXr+KIPDkIaef06c+i2HeTqVNixB7yFut5fBXPGI2f1rrmEV2vLMznNGsFwvZ5XmA3Quuefd4OGRkdo97Dhw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.4.1.tgz", + "integrity": "sha512-0R+cbhpMkhbRXX138UOc/2XZFF8hiZa6ooZAEEJFp5scytzCw4tC1gChMFXrpa3d2tYE6AX8IrOEpSonLmfQuQ==", "dependencies": { "@types/history": "^4.7.11", "@types/react": "*", @@ -2712,11 +2728,11 @@ } }, "node_modules/@docusaurus/utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.4.0.tgz", - "integrity": "sha512-89hLYkvtRX92j+C+ERYTuSUK6nF9bGM32QThcHPg2EDDHVw6FzYQXmX6/p+pU5SDyyx5nBlE4qXR92RxCAOqfg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.4.1.tgz", + "integrity": "sha512-1lvEZdAQhKNht9aPXPoh69eeKnV0/62ROhQeFKKxmzd0zkcuE/Oc5Gpnt00y/f5bIsmOsYMY7Pqfm/5rteT5GA==", "dependencies": { - "@docusaurus/logger": "2.4.0", + "@docusaurus/logger": "2.4.1", "@svgr/webpack": "^6.2.1", "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", @@ -2746,9 +2762,9 @@ } }, "node_modules/@docusaurus/utils-common": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.4.0.tgz", - "integrity": "sha512-zIMf10xuKxddYfLg5cS19x44zud/E9I7lj3+0bv8UIs0aahpErfNrGhijEfJpAfikhQ8tL3m35nH3hJ3sOG82A==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.4.1.tgz", + "integrity": "sha512-bCVGdZU+z/qVcIiEQdyx0K13OC5mYwxhSuDUR95oFbKVuXYRrTVrwZIqQljuo1fyJvFTKHiL9L9skQOPokuFNQ==", "dependencies": { "tslib": "^2.4.0" }, @@ -2765,12 +2781,12 @@ } }, "node_modules/@docusaurus/utils-validation": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.4.0.tgz", - "integrity": "sha512-IrBsBbbAp6y7mZdJx4S4pIA7dUyWSA0GNosPk6ZJ0fX3uYIEQgcQSGIgTeSC+8xPEx3c16o03en1jSDpgQgz/w==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.4.1.tgz", + "integrity": "sha512-unII3hlJlDwZ3w8U+pMO3Lx3RhI4YEbY3YNsQj4yzrkZzlpqZOLuAiZK2JyULnD+TKbceKU0WyWkQXtYbLNDFA==", "dependencies": { - "@docusaurus/logger": "2.4.0", - "@docusaurus/utils": "2.4.0", + "@docusaurus/logger": "2.4.1", + "@docusaurus/utils": "2.4.1", "joi": "^17.6.0", "js-yaml": "^4.1.0", "tslib": "^2.4.0" @@ -4027,9 +4043,9 @@ } }, "node_modules/algoliasearch-helper": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.12.0.tgz", - "integrity": "sha512-/j1U3PEwdan0n6P/QqSnSpNSLC5+cEMvyljd5CnmNmUjDlGrys+vFEOwjVEnqELIiAGMHEA/Nl3CiKVFBUYqyQ==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.13.2.tgz", + "integrity": "sha512-1bZjtHuqCBYw7Eu3Qh0Jfq4s63UcbOs6VvLPdt7kxn5+zMgs46xiXgc65YhZBNM3hDGrudhAX9hDhE9OP+rKUw==", "dependencies": { "@algolia/events": "^4.0.1" }, @@ -5357,11 +5373,11 @@ } }, "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dependencies": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.11" } }, "node_modules/cross-spawn": { @@ -6510,9 +6526,9 @@ } }, "node_modules/fbjs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.4.tgz", - "integrity": "sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", "dependencies": { "cross-fetch": "^3.1.5", "fbjs-css-vars": "^1.0.0", @@ -6520,7 +6536,7 @@ "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.30" + "ua-parser-js": "^1.0.35" } }, "node_modules/fbjs-css-vars": { @@ -8834,10 +8850,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "license": "MIT", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -10146,9 +10161,9 @@ } }, "node_modules/postcss-sort-media-queries": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-4.3.0.tgz", - "integrity": "sha512-jAl8gJM2DvuIJiI9sL1CuiHtKM4s5aEIomkU8G3LFvbP+p8i7Sz8VV63uieTgoewGqKbi+hxBTiOKJlB35upCg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-4.4.1.tgz", + "integrity": "sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw==", "dependencies": { "sort-css-media-queries": "2.1.0" }, @@ -11585,6 +11600,15 @@ "ajv": "^8.8.2" } }, + "node_modules/search-insights": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.6.0.tgz", + "integrity": "sha512-vU2/fJ+h/Mkm/DJOe+EaM5cafJv/1rRTZpGJTuFPf/Q5LjzgMDsqPdSaZsAe+GAWHHsfsu+rQSAn6c8IGtBEVw==", + "peer": true, + "engines": { + "node": ">=8.16.0" + } + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -12708,9 +12732,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.35", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", - "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", + "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==", "funding": [ { "type": "opencollective", @@ -13878,25 +13902,35 @@ }, "dependencies": { "@algolia/autocomplete-core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.7.4.tgz", - "integrity": "sha512-daoLpQ3ps/VTMRZDEBfU8ixXd+amZcNJ4QSP3IERGyzqnL5Ch8uSRFt/4G8pUvW9c3o6GA4vtVv4I4lmnkdXyg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.2.tgz", + "integrity": "sha512-hkG80c9kx9ClVAEcUJbTd2ziVC713x9Bji9Ty4XJfKXlxlsx3iXsoNhAwfeR4ulzIUg7OE5gez0UU1zVDdG7kg==", + "requires": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.2", + "@algolia/autocomplete-shared": "1.9.2" + } + }, + "@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.2.tgz", + "integrity": "sha512-2LVsf4W66hVHQ3Ua/8k15oPlxjELCztbAkQm/hP42Sw+GLkHAdY1vaVRYziaWq64+Oljfg6FKkZHCdgXH+CGIA==", "requires": { - "@algolia/autocomplete-shared": "1.7.4" + "@algolia/autocomplete-shared": "1.9.2" } }, "@algolia/autocomplete-preset-algolia": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.4.tgz", - "integrity": "sha512-s37hrvLEIfcmKY8VU9LsAXgm2yfmkdHT3DnA3SgHaY93yjZ2qL57wzb5QweVkYuEBZkT2PIREvRoLXC2sxTbpQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.2.tgz", + "integrity": "sha512-pqgIm2GNqtCT59Y1ICctIPrYTi34+wNPiNWEclD/yDzp5uDUUsyGe5XrUjCNyQRTKonAlmYxoaEHOn8FWgmBHA==", "requires": { - "@algolia/autocomplete-shared": "1.7.4" + "@algolia/autocomplete-shared": "1.9.2" } }, "@algolia/autocomplete-shared": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.4.tgz", - "integrity": "sha512-2VGCk7I9tA9Ge73Km99+Qg87w0wzW4tgUruvWAn/gfey1ZXgmxZtyIRBebk35R1O8TbK77wujVtCnpsGpRy1kg==" + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.2.tgz", + "integrity": "sha512-XxX6YDn+7LG+SmdpXEOnj7fc3TjiVpQ0CbGhjLwrd2tYr6LVY2D4Iiu/iuYJ4shvVDWWnpwArSk0uIWC/8OPUA==", + "requires": {} }, "@algolia/cache-browser-local-storage": { "version": "4.14.3", @@ -15241,25 +15275,25 @@ "optional": true }, "@docsearch/css": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.3.3.tgz", - "integrity": "sha512-6SCwI7P8ao+se1TUsdZ7B4XzL+gqeQZnBc+2EONZlcVa0dVrk0NjETxozFKgMv0eEGH8QzP1fkN+A1rH61l4eg==" + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.0.tgz", + "integrity": "sha512-Ob5FQLubplcBNihAVtriR59FRBeP8u69F6mu4L4yIr60KfsPc10bOV0DoPErJw0zF9IBN2cNLW9qdmt8zWPxyg==" }, "@docsearch/react": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.3.3.tgz", - "integrity": "sha512-pLa0cxnl+G0FuIDuYlW+EBK6Rw2jwLw9B1RHIeS4N4s2VhsfJ/wzeCi3CWcs5yVfxLd5ZK50t//TMA5e79YT7Q==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.0.tgz", + "integrity": "sha512-3IG8mmSMzSHNGy2S1VuPyYU9tFCxFpj5Ov8SYwsSHM4yMvFsaO9oFxXocA5lSenliIELhuOuS5+BdxHa/Qlf2A==", "requires": { - "@algolia/autocomplete-core": "1.7.4", - "@algolia/autocomplete-preset-algolia": "1.7.4", - "@docsearch/css": "3.3.3", + "@algolia/autocomplete-core": "1.9.2", + "@algolia/autocomplete-preset-algolia": "1.9.2", + "@docsearch/css": "3.5.0", "algoliasearch": "^4.0.0" } }, "@docusaurus/core": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.4.0.tgz", - "integrity": "sha512-J55/WEoIpRcLf3afO5POHPguVZosKmJEQWKBL+K7TAnfuE7i+Y0NPLlkKtnWCehagGsgTqClfQEexH/UT4kELA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.4.1.tgz", + "integrity": "sha512-SNsY7PshK3Ri7vtsLXVeAJGS50nJN3RgF836zkyUfAD01Fq+sAk5EwWgLw+nnm5KVNGDu7PRR2kRGDsWvqpo0g==", "requires": { "@babel/core": "^7.18.6", "@babel/generator": "^7.18.7", @@ -15271,13 +15305,13 @@ "@babel/runtime": "^7.18.6", "@babel/runtime-corejs3": "^7.18.6", "@babel/traverse": "^7.18.8", - "@docusaurus/cssnano-preset": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/mdx-loader": "2.4.0", + "@docusaurus/cssnano-preset": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/mdx-loader": "2.4.1", "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "@slorber/static-site-generator-webpack-plugin": "^4.0.7", "@svgr/webpack": "^6.2.1", "autoprefixer": "^10.4.7", @@ -15345,9 +15379,9 @@ } }, "@docusaurus/cssnano-preset": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.0.tgz", - "integrity": "sha512-RmdiA3IpsLgZGXRzqnmTbGv43W4OD44PCo+6Q/aYjEM2V57vKCVqNzuafE94jv0z/PjHoXUrjr69SaRymBKYYw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.1.tgz", + "integrity": "sha512-ka+vqXwtcW1NbXxWsh6yA1Ckii1klY9E53cJ4O9J09nkMBgrNX3iEFED1fWdv8wf4mJjvGi5RLZ2p9hJNjsLyQ==", "requires": { "cssnano-preset-advanced": "^5.3.8", "postcss": "^8.4.14", @@ -15356,23 +15390,23 @@ } }, "@docusaurus/logger": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.4.0.tgz", - "integrity": "sha512-T8+qR4APN+MjcC9yL2Es+xPJ2923S9hpzDmMtdsOcUGLqpCGBbU1vp3AAqDwXtVgFkq+NsEk7sHdVsfLWR/AXw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.4.1.tgz", + "integrity": "sha512-5h5ysIIWYIDHyTVd8BjheZmQZmEgWDR54aQ1BX9pjFfpyzFo5puKXKYrYJXbjEHGyVhEzmB9UXwbxGfaZhOjcg==", "requires": { "chalk": "^4.1.2", "tslib": "^2.4.0" } }, "@docusaurus/mdx-loader": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.4.0.tgz", - "integrity": "sha512-GWoH4izZKOmFoC+gbI2/y8deH/xKLvzz/T5BsEexBye8EHQlwsA7FMrVa48N063bJBH4FUOiRRXxk5rq9cC36g==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.4.1.tgz", + "integrity": "sha512-4KhUhEavteIAmbBj7LVFnrVYDiU51H5YWW1zY6SmBSte/YLhDutztLTBE0PQl1Grux1jzUJeaSvAzHpTn6JJDQ==", "requires": { "@babel/parser": "^7.18.8", "@babel/traverse": "^7.18.8", - "@docusaurus/logger": "2.4.0", - "@docusaurus/utils": "2.4.0", + "@docusaurus/logger": "2.4.1", + "@docusaurus/utils": "2.4.1", "@mdx-js/mdx": "^1.6.22", "escape-html": "^1.0.3", "file-loader": "^6.2.0", @@ -15404,12 +15438,12 @@ } }, "@docusaurus/module-type-aliases": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.0.tgz", - "integrity": "sha512-YEQO2D3UXs72qCn8Cr+RlycSQXVGN9iEUyuHwTuK4/uL/HFomB2FHSU0vSDM23oLd+X/KibQ3Ez6nGjQLqXcHg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.1.tgz", + "integrity": "sha512-gLBuIFM8Dp2XOCWffUDSjtxY7jQgKvYujt7Mx5s4FCTfoL5dN1EVbnrn+O2Wvh8b0a77D57qoIDY7ghgmatR1A==", "requires": { "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/types": "2.4.0", + "@docusaurus/types": "2.4.1", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -15419,15 +15453,15 @@ } }, "@docusaurus/plugin-client-redirects": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.4.0.tgz", - "integrity": "sha512-HsS+Dc2ZLWhfpjYJ5LIrOB/XfXZcElcC7o1iA4yIVtiFz+LHhwP863fhqbwSJ1c6tNDOYBH3HwbskHrc/PIn7Q==", - "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.4.1.tgz", + "integrity": "sha512-tp0j16gaLIJ4p+IR0P6KDOFsTOGGMY54MNPnmM61Vaqqt5omLqsuKUO8UlCGU1oW/4EIQOhXYy99XYY5MjE+7A==", + "requires": { + "@docusaurus/core": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "eta": "^2.0.0", "fs-extra": "^10.1.0", "lodash": "^4.17.21", @@ -15435,17 +15469,17 @@ } }, "@docusaurus/plugin-content-blog": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.0.tgz", - "integrity": "sha512-YwkAkVUxtxoBAIj/MCb4ohN0SCtHBs4AS75jMhPpf67qf3j+U/4n33cELq7567hwyZ6fMz2GPJcVmctzlGGThQ==", - "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/mdx-loader": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.1.tgz", + "integrity": "sha512-E2i7Knz5YIbE1XELI6RlTnZnGgS52cUO4BlCiCUCvQHbR+s1xeIWz4C6BtaVnlug0Ccz7nFSksfwDpVlkujg5Q==", + "requires": { + "@docusaurus/core": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/mdx-loader": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "cheerio": "^1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^10.1.0", @@ -15458,17 +15492,17 @@ } }, "@docusaurus/plugin-content-docs": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.0.tgz", - "integrity": "sha512-ic/Z/ZN5Rk/RQo+Io6rUGpToOtNbtPloMR2JcGwC1xT2riMu6zzfSwmBi9tHJgdXH6CB5jG+0dOZZO8QS5tmDg==", - "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/mdx-loader": "2.4.0", - "@docusaurus/module-type-aliases": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.1.tgz", + "integrity": "sha512-Lo7lSIcpswa2Kv4HEeUcGYqaasMUQNpjTXpV0N8G6jXgZaQurqp7E8NGYeGbDXnb48czmHWbzDL4S3+BbK0VzA==", + "requires": { + "@docusaurus/core": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/mdx-loader": "2.4.1", + "@docusaurus/module-type-aliases": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "@types/react-router-config": "^5.0.6", "combine-promises": "^1.1.0", "fs-extra": "^10.1.0", @@ -15481,100 +15515,100 @@ } }, "@docusaurus/plugin-content-pages": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.0.tgz", - "integrity": "sha512-Pk2pOeOxk8MeU3mrTU0XLIgP9NZixbdcJmJ7RUFrZp1Aj42nd0RhIT14BGvXXyqb8yTQlk4DmYGAzqOfBsFyGw==", - "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/mdx-loader": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.1.tgz", + "integrity": "sha512-/UjuH/76KLaUlL+o1OvyORynv6FURzjurSjvn2lbWTFc4tpYY2qLYTlKpTCBVPhlLUQsfyFnshEJDLmPneq2oA==", + "requires": { + "@docusaurus/core": "2.4.1", + "@docusaurus/mdx-loader": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "fs-extra": "^10.1.0", "tslib": "^2.4.0", "webpack": "^5.73.0" } }, "@docusaurus/plugin-debug": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.4.0.tgz", - "integrity": "sha512-KC56DdYjYT7Txyux71vXHXGYZuP6yYtqwClvYpjKreWIHWus5Zt6VNi23rMZv3/QKhOCrN64zplUbdfQMvddBQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.4.1.tgz", + "integrity": "sha512-7Yu9UPzRShlrH/G8btOpR0e6INFZr0EegWplMjOqelIwAcx3PKyR8mgPTxGTxcqiYj6hxSCRN0D8R7YrzImwNA==", "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", + "@docusaurus/core": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", "fs-extra": "^10.1.0", "react-json-view": "^1.21.3", "tslib": "^2.4.0" } }, "@docusaurus/plugin-google-analytics": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.0.tgz", - "integrity": "sha512-uGUzX67DOAIglygdNrmMOvEp8qG03X20jMWadeqVQktS6nADvozpSLGx4J0xbkblhJkUzN21WiilsP9iVP+zkw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.1.tgz", + "integrity": "sha512-dyZJdJiCoL+rcfnm0RPkLt/o732HvLiEwmtoNzOoz9MSZz117UH2J6U2vUDtzUzwtFLIf32KkeyzisbwUCgcaQ==", "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "@docusaurus/core": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "tslib": "^2.4.0" } }, "@docusaurus/plugin-google-gtag": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.0.tgz", - "integrity": "sha512-adj/70DANaQs2+TF/nRdMezDXFAV/O/pjAbUgmKBlyOTq5qoMe0Tk4muvQIwWUmiUQxFJe+sKlZGM771ownyOg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.1.tgz", + "integrity": "sha512-mKIefK+2kGTQBYvloNEKtDmnRD7bxHLsBcxgnbt4oZwzi2nxCGjPX6+9SQO2KCN5HZbNrYmGo5GJfMgoRvy6uA==", "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "@docusaurus/core": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "tslib": "^2.4.0" } }, "@docusaurus/plugin-google-tag-manager": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-2.4.0.tgz", - "integrity": "sha512-E66uGcYs4l7yitmp/8kMEVQftFPwV9iC62ORh47Veqzs6ExwnhzBkJmwDnwIysHBF1vlxnzET0Fl2LfL5fRR3A==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-2.4.1.tgz", + "integrity": "sha512-Zg4Ii9CMOLfpeV2nG74lVTWNtisFaH9QNtEw48R5QE1KIwDBdTVaiSA18G1EujZjrzJJzXN79VhINSbOJO/r3g==", "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "@docusaurus/core": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "tslib": "^2.4.0" } }, "@docusaurus/plugin-sitemap": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.4.0.tgz", - "integrity": "sha512-pZxh+ygfnI657sN8a/FkYVIAmVv0CGk71QMKqJBOfMmDHNN1FeDeFkBjWP49ejBqpqAhjufkv5UWq3UOu2soCw==", - "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.4.1.tgz", + "integrity": "sha512-lZx+ijt/+atQ3FVE8FOHV/+X3kuok688OydDXrqKRJyXBJZKgGjA2Qa8RjQ4f27V2woaXhtnyrdPop/+OjVMRg==", + "requires": { + "@docusaurus/core": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "fs-extra": "^10.1.0", "sitemap": "^7.1.1", "tslib": "^2.4.0" } }, "@docusaurus/preset-classic": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.4.0.tgz", - "integrity": "sha512-/5z5o/9bc6+P5ool2y01PbJhoGddEGsC0ej1MF6mCoazk8A+kW4feoUd68l7Bnv01rCnG3xy7kHUQP97Y0grUA==", - "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/plugin-content-blog": "2.4.0", - "@docusaurus/plugin-content-docs": "2.4.0", - "@docusaurus/plugin-content-pages": "2.4.0", - "@docusaurus/plugin-debug": "2.4.0", - "@docusaurus/plugin-google-analytics": "2.4.0", - "@docusaurus/plugin-google-gtag": "2.4.0", - "@docusaurus/plugin-google-tag-manager": "2.4.0", - "@docusaurus/plugin-sitemap": "2.4.0", - "@docusaurus/theme-classic": "2.4.0", - "@docusaurus/theme-common": "2.4.0", - "@docusaurus/theme-search-algolia": "2.4.0", - "@docusaurus/types": "2.4.0" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.4.1.tgz", + "integrity": "sha512-P4//+I4zDqQJ+UDgoFrjIFaQ1MeS9UD1cvxVQaI6O7iBmiHQm0MGROP1TbE7HlxlDPXFJjZUK3x3cAoK63smGQ==", + "requires": { + "@docusaurus/core": "2.4.1", + "@docusaurus/plugin-content-blog": "2.4.1", + "@docusaurus/plugin-content-docs": "2.4.1", + "@docusaurus/plugin-content-pages": "2.4.1", + "@docusaurus/plugin-debug": "2.4.1", + "@docusaurus/plugin-google-analytics": "2.4.1", + "@docusaurus/plugin-google-gtag": "2.4.1", + "@docusaurus/plugin-google-tag-manager": "2.4.1", + "@docusaurus/plugin-sitemap": "2.4.1", + "@docusaurus/theme-classic": "2.4.1", + "@docusaurus/theme-common": "2.4.1", + "@docusaurus/theme-search-algolia": "2.4.1", + "@docusaurus/types": "2.4.1" } }, "@docusaurus/react-loadable": { @@ -15587,22 +15621,22 @@ } }, "@docusaurus/theme-classic": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.4.0.tgz", - "integrity": "sha512-GMDX5WU6Z0OC65eQFgl3iNNEbI9IMJz9f6KnOyuMxNUR6q0qVLsKCNopFUDfFNJ55UU50o7P7o21yVhkwpfJ9w==", - "requires": { - "@docusaurus/core": "2.4.0", - "@docusaurus/mdx-loader": "2.4.0", - "@docusaurus/module-type-aliases": "2.4.0", - "@docusaurus/plugin-content-blog": "2.4.0", - "@docusaurus/plugin-content-docs": "2.4.0", - "@docusaurus/plugin-content-pages": "2.4.0", - "@docusaurus/theme-common": "2.4.0", - "@docusaurus/theme-translations": "2.4.0", - "@docusaurus/types": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.4.1.tgz", + "integrity": "sha512-Rz0wKUa+LTW1PLXmwnf8mn85EBzaGSt6qamqtmnh9Hflkc+EqiYMhtUJeLdV+wsgYq4aG0ANc+bpUDpsUhdnwg==", + "requires": { + "@docusaurus/core": "2.4.1", + "@docusaurus/mdx-loader": "2.4.1", + "@docusaurus/module-type-aliases": "2.4.1", + "@docusaurus/plugin-content-blog": "2.4.1", + "@docusaurus/plugin-content-docs": "2.4.1", + "@docusaurus/plugin-content-pages": "2.4.1", + "@docusaurus/theme-common": "2.4.1", + "@docusaurus/theme-translations": "2.4.1", + "@docusaurus/types": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", "copy-text-to-clipboard": "^3.0.1", @@ -15619,17 +15653,17 @@ } }, "@docusaurus/theme-common": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.4.0.tgz", - "integrity": "sha512-IkG/l5f/FLY6cBIxtPmFnxpuPzc5TupuqlOx+XDN+035MdQcAh8wHXXZJAkTeYDeZ3anIUSUIvWa7/nRKoQEfg==", - "requires": { - "@docusaurus/mdx-loader": "2.4.0", - "@docusaurus/module-type-aliases": "2.4.0", - "@docusaurus/plugin-content-blog": "2.4.0", - "@docusaurus/plugin-content-docs": "2.4.0", - "@docusaurus/plugin-content-pages": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-common": "2.4.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.4.1.tgz", + "integrity": "sha512-G7Zau1W5rQTaFFB3x3soQoZpkgMbl/SYNG8PfMFIjKa3M3q8n0m/GRf5/H/e5BqOvt8c+ZWIXGCiz+kUCSHovA==", + "requires": { + "@docusaurus/mdx-loader": "2.4.1", + "@docusaurus/module-type-aliases": "2.4.1", + "@docusaurus/plugin-content-blog": "2.4.1", + "@docusaurus/plugin-content-docs": "2.4.1", + "@docusaurus/plugin-content-pages": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-common": "2.4.1", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -15642,18 +15676,18 @@ } }, "@docusaurus/theme-search-algolia": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.0.tgz", - "integrity": "sha512-pPCJSCL1Qt4pu/Z0uxBAuke0yEBbxh0s4fOvimna7TEcBLPq0x06/K78AaABXrTVQM6S0vdocFl9EoNgU17hqA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.1.tgz", + "integrity": "sha512-6BcqW2lnLhZCXuMAvPRezFs1DpmEKzXFKlYjruuas+Xy3AQeFzDJKTJFIm49N77WFCTyxff8d3E4Q9pi/+5McQ==", "requires": { "@docsearch/react": "^3.1.1", - "@docusaurus/core": "2.4.0", - "@docusaurus/logger": "2.4.0", - "@docusaurus/plugin-content-docs": "2.4.0", - "@docusaurus/theme-common": "2.4.0", - "@docusaurus/theme-translations": "2.4.0", - "@docusaurus/utils": "2.4.0", - "@docusaurus/utils-validation": "2.4.0", + "@docusaurus/core": "2.4.1", + "@docusaurus/logger": "2.4.1", + "@docusaurus/plugin-content-docs": "2.4.1", + "@docusaurus/theme-common": "2.4.1", + "@docusaurus/theme-translations": "2.4.1", + "@docusaurus/utils": "2.4.1", + "@docusaurus/utils-validation": "2.4.1", "algoliasearch": "^4.13.1", "algoliasearch-helper": "^3.10.0", "clsx": "^1.2.1", @@ -15665,18 +15699,18 @@ } }, "@docusaurus/theme-translations": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.4.0.tgz", - "integrity": "sha512-kEoITnPXzDPUMBHk3+fzEzbopxLD3fR5sDoayNH0vXkpUukA88/aDL1bqkhxWZHA3LOfJ3f0vJbOwmnXW5v85Q==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.4.1.tgz", + "integrity": "sha512-T1RAGP+f86CA1kfE8ejZ3T3pUU3XcyvrGMfC/zxCtc2BsnoexuNI9Vk2CmuKCb+Tacvhxjv5unhxXce0+NKyvA==", "requires": { "fs-extra": "^10.1.0", "tslib": "^2.4.0" } }, "@docusaurus/types": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.4.0.tgz", - "integrity": "sha512-xaBXr+KIPDkIaef06c+i2HeTqVNixB7yFut5fBXPGI2f1rrmEV2vLMznNGsFwvZ5XmA3Quuefd4OGRkdo97Dhw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.4.1.tgz", + "integrity": "sha512-0R+cbhpMkhbRXX138UOc/2XZFF8hiZa6ooZAEEJFp5scytzCw4tC1gChMFXrpa3d2tYE6AX8IrOEpSonLmfQuQ==", "requires": { "@types/history": "^4.7.11", "@types/react": "*", @@ -15689,11 +15723,11 @@ } }, "@docusaurus/utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.4.0.tgz", - "integrity": "sha512-89hLYkvtRX92j+C+ERYTuSUK6nF9bGM32QThcHPg2EDDHVw6FzYQXmX6/p+pU5SDyyx5nBlE4qXR92RxCAOqfg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.4.1.tgz", + "integrity": "sha512-1lvEZdAQhKNht9aPXPoh69eeKnV0/62ROhQeFKKxmzd0zkcuE/Oc5Gpnt00y/f5bIsmOsYMY7Pqfm/5rteT5GA==", "requires": { - "@docusaurus/logger": "2.4.0", + "@docusaurus/logger": "2.4.1", "@svgr/webpack": "^6.2.1", "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", @@ -15712,20 +15746,20 @@ } }, "@docusaurus/utils-common": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.4.0.tgz", - "integrity": "sha512-zIMf10xuKxddYfLg5cS19x44zud/E9I7lj3+0bv8UIs0aahpErfNrGhijEfJpAfikhQ8tL3m35nH3hJ3sOG82A==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.4.1.tgz", + "integrity": "sha512-bCVGdZU+z/qVcIiEQdyx0K13OC5mYwxhSuDUR95oFbKVuXYRrTVrwZIqQljuo1fyJvFTKHiL9L9skQOPokuFNQ==", "requires": { "tslib": "^2.4.0" } }, "@docusaurus/utils-validation": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.4.0.tgz", - "integrity": "sha512-IrBsBbbAp6y7mZdJx4S4pIA7dUyWSA0GNosPk6ZJ0fX3uYIEQgcQSGIgTeSC+8xPEx3c16o03en1jSDpgQgz/w==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.4.1.tgz", + "integrity": "sha512-unII3hlJlDwZ3w8U+pMO3Lx3RhI4YEbY3YNsQj4yzrkZzlpqZOLuAiZK2JyULnD+TKbceKU0WyWkQXtYbLNDFA==", "requires": { - "@docusaurus/logger": "2.4.0", - "@docusaurus/utils": "2.4.0", + "@docusaurus/logger": "2.4.1", + "@docusaurus/utils": "2.4.1", "joi": "^17.6.0", "js-yaml": "^4.1.0", "tslib": "^2.4.0" @@ -16641,9 +16675,9 @@ } }, "algoliasearch-helper": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.12.0.tgz", - "integrity": "sha512-/j1U3PEwdan0n6P/QqSnSpNSLC5+cEMvyljd5CnmNmUjDlGrys+vFEOwjVEnqELIiAGMHEA/Nl3CiKVFBUYqyQ==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.13.2.tgz", + "integrity": "sha512-1bZjtHuqCBYw7Eu3Qh0Jfq4s63UcbOs6VvLPdt7kxn5+zMgs46xiXgc65YhZBNM3hDGrudhAX9hDhE9OP+rKUw==", "requires": { "@algolia/events": "^4.0.1" } @@ -17520,11 +17554,11 @@ } }, "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "requires": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.11" } }, "cross-spawn": { @@ -18295,9 +18329,9 @@ } }, "fbjs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.4.tgz", - "integrity": "sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", "requires": { "cross-fetch": "^3.1.5", "fbjs-css-vars": "^1.0.0", @@ -18305,7 +18339,7 @@ "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.30" + "ua-parser-js": "^1.0.35" } }, "fbjs-css-vars": { @@ -19776,9 +19810,9 @@ } }, "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "requires": { "whatwg-url": "^5.0.0" } @@ -20594,9 +20628,9 @@ } }, "postcss-sort-media-queries": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-4.3.0.tgz", - "integrity": "sha512-jAl8gJM2DvuIJiI9sL1CuiHtKM4s5aEIomkU8G3LFvbP+p8i7Sz8VV63uieTgoewGqKbi+hxBTiOKJlB35upCg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-4.4.1.tgz", + "integrity": "sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw==", "requires": { "sort-css-media-queries": "2.1.0" } @@ -21604,6 +21638,12 @@ } } }, + "search-insights": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.6.0.tgz", + "integrity": "sha512-vU2/fJ+h/Mkm/DJOe+EaM5cafJv/1rRTZpGJTuFPf/Q5LjzgMDsqPdSaZsAe+GAWHHsfsu+rQSAn6c8IGtBEVw==", + "peer": true + }, "section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -22385,9 +22425,9 @@ "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==" }, "ua-parser-js": { - "version": "0.7.35", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", - "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==" + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", + "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==" }, "unherit": { "version": "1.1.3", diff --git a/docs/package.json b/docs/package.json index f287b353034..a195553f5c0 100644 --- a/docs/package.json +++ b/docs/package.json @@ -15,10 +15,10 @@ "typecheck": "tsc" }, "dependencies": { - "@docusaurus/core": "^2.4.0", - "@docusaurus/plugin-client-redirects": "^2.4.0", - "@docusaurus/preset-classic": "^2.4.0", - "@docusaurus/theme-search-algolia": "^2.4.0", + "@docusaurus/core": "^2.4.1", + "@docusaurus/plugin-client-redirects": "^2.4.1", + "@docusaurus/preset-classic": "^2.4.1", + "@docusaurus/theme-search-algolia": "^2.4.1", "@mdx-js/react": "^1.6.22", "algoliasearch": "^4.14.3", "clsx": "^1.2.1", @@ -30,7 +30,7 @@ "redocusaurus": "^1.3.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^2.4.0", + "@docusaurus/module-type-aliases": "^2.4.1", "@tsconfig/docusaurus": "^1.0.5", "typescript": "^4.7.4" }, diff --git a/docs/redirects.js b/docs/redirects.js index a38007c45ea..abb7a8d1353 100644 --- a/docs/redirects.js +++ b/docs/redirects.js @@ -76,10 +76,6 @@ const redirects = [ from: "/telemetry", to: "/articles/telemetry", }, - { - from: "/installing", - to: "/articles/getting-started-overview", - }, { from: "/guides/test-suites/testsuites-getting-results", to: "/articles/getting-test-suites-results", @@ -114,10 +110,6 @@ const redirects = [ from: "/integrations/slack-integration", to: "/articles/slack-integration", }, - { - from: "/integrations", - to: "/articles/getting-started-overview", - }, { from: "/overview/supported-tests", to: "/articles/supported-tests", @@ -127,7 +119,13 @@ const redirects = [ to: "/articles/testkube-benefits", }, { - from: ["/getting-started/index", "/getting-started/installation"], + from: [ + "/getting-started/index", + "/getting-started/installation", + "/getting-started", + "/integrations", + "/installing", + ], to: "/articles/getting-started-overview", }, { @@ -335,8 +333,8 @@ const redirects = [ to: "/articles/crds", }, { - from: "/getting-started", - to: "/articles/getting-started-overview", + from: "/articles/operator-api-reference", + to: "/articles/crds-reference", }, ]; diff --git a/docs/sidebars.js b/docs/sidebars.js index 78e701fa991..aa02af7091f 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -49,7 +49,7 @@ const sidebars = { "articles/running-tests", "articles/getting-tests-results", "articles/adding-tests-variables", - "articles/adding-timeout" + "articles/adding-timeout", ], }, { @@ -76,11 +76,8 @@ const sidebars = { }, "articles/adding-tests-secrets", "articles/scheduling-tests", - "articles/artifacts-storage", - "articles/metrics", "articles/test-triggers", - "articles/testkube-dependencies", - "articles/common-issues", + "articles/webhooks", "articles/test-sources", ], }, @@ -92,20 +89,12 @@ const sidebars = { type: "category", label: "Getting to Production", items: [ - { - type: "category", - label: "Exposing Testkube Dashboard", - link: { - type: "doc", - id: "articles/exposing-testkube", - }, - items: ["articles/exposing-testkube-with-ingress-nginx"], - }, { type: "category", label: "Authentication", items: ["articles/oauth-cli", "articles/oauth-dashboard"], }, + "articles/exposing-testkube-with-ingress-nginx", "articles/deploying-in-aws", ], }, @@ -118,6 +107,8 @@ const sidebars = { }, items: [ "articles/github-actions", + "articles/run-tests-with-github-actions", + "articles/testkube-cli-docker", { type: "category", label: "GitOps", @@ -140,7 +131,7 @@ const sidebars = { }, ], }, - "articles/webhooks", + "articles/cd-events", "articles/slack-integration", "articles/generate-test-crds", "articles/logging", @@ -194,6 +185,12 @@ const sidebars = { type: "category", label: "Reference", items: [ + { + type: "doc", + id: "articles/helm-chart", + label: "Helm Chart", + }, + "articles/crds-reference", { type: "category", label: "CLI", @@ -204,16 +201,15 @@ const sidebars = { }, ], }, - { - type: "doc", - id: "articles/helm-chart", - label: "Helm Chart", - }, "openapi", + "articles/metrics", + "articles/artifacts-storage", + "articles/testkube-dependencies", "articles/architecture", "articles/telemetry", ], }, + "articles/common-issues", { type: "category", label: "Contributing", diff --git a/go.mod b/go.mod index 56b60b5c687..403b4a04037 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/99designs/gqlgen v0.17.27 github.com/Masterminds/semver v1.5.0 - github.com/adhocore/gronx v1.1.2 + github.com/adhocore/gronx v1.6.3 github.com/cdevents/sdk-go v0.3.0 github.com/cli/cli/v2 v2.20.2 github.com/cloudevents/sdk-go/v2 v2.14.0 @@ -24,25 +24,25 @@ 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.12.2 + github.com/kubeshop/testkube-operator v1.13.0 github.com/minio/minio-go/v7 v7.0.47 github.com/montanaflynn/stats v0.6.6 github.com/moogar0880/problems v0.1.1 github.com/nats-io/nats.go v1.22.1 github.com/olekukonko/tablewriter v0.0.5 - github.com/onsi/ginkgo/v2 v2.9.2 - github.com/onsi/gomega v1.27.6 - github.com/otiai10/copy v1.9.0 + github.com/onsi/ginkgo/v2 v2.1.6 + github.com/onsi/gomega v1.20.2 + github.com/otiai10/copy v1.11.0 github.com/prometheus/client_golang v1.14.0 - github.com/pterm/pterm v0.12.59 + github.com/pterm/pterm v0.12.62 github.com/rikatz/kubepug v1.4.0 github.com/segmentio/analytics-go/v3 v3.2.1 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/slack-go/slack v0.11.4 - github.com/spf13/cobra v1.7.0 + github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.2 github.com/valyala/fasthttp v1.44.0 - github.com/vektah/gqlparser/v2 v2.5.1 + github.com/vektah/gqlparser/v2 v2.5.2-0.20230422221642-25e09f9d292d go.mongodb.org/mongo-driver v1.11.0 go.uber.org/zap v1.24.0 google.golang.org/grpc v1.51.0 @@ -56,6 +56,7 @@ require ( require ( atomicgo.dev/cursor v0.1.1 // indirect atomicgo.dev/keyboard v0.2.9 // indirect + atomicgo.dev/schedule v0.0.2 // indirect cloud.google.com/go/compute v1.12.1 // indirect cloud.google.com/go/compute/metadata v0.2.1 // indirect github.com/AlecAivazis/survey/v2 v2.3.6 // indirect @@ -83,10 +84,8 @@ require ( github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.1 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/google/gnostic v0.6.9 // indirect - github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect @@ -96,13 +95,12 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/cpuid/v2 v2.2.3 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/lithammer/fuzzysearch v1.1.5 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/microcosm-cc/bluemonday v1.0.21 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -114,7 +112,7 @@ require ( github.com/nats-io/nkeys v0.3.0 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/package-url/packageurl-go v0.1.0 // indirect - github.com/pquerna/cachecontrol v0.1.0 // indirect + github.com/pquerna/cachecontrol v0.2.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect @@ -129,7 +127,7 @@ require ( github.com/yuin/goldmark-emoji v1.0.1 // indirect golang.org/x/exp v0.0.0-20230118134722-a68e582fa157 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect @@ -150,16 +148,16 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/go-cmp v0.5.9 github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 github.com/imdario/mergo v0.3.13 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect @@ -191,8 +189,8 @@ require ( golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.1.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.7.0 // indirect - golang.org/x/term v0.6.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect diff --git a/go.sum b/go.sum index 512f29a1605..410986e1ee1 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4= atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.0.2 h1:2e/4KY6t3wokja01Cyty6qgkQM8MotJzjtqCH70oX2Q= +atomicgo.dev/schedule v0.0.2/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -76,8 +78,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/adhocore/gronx v1.1.2 h1:Hgm+d8SyGtn+rCoDkxZq3nLNFLLkzRGR7L2ziRRD1w8= -github.com/adhocore/gronx v1.1.2/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/adhocore/gronx v1.6.3 h1:bnm5vieTrY3QQPpsfB0hrAaeaHDpuZTUC2LLCVMLe9c= +github.com/adhocore/gronx v1.6.3/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= @@ -205,8 +207,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -223,8 +225,6 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gofiber/adaptor/v2 v2.1.29 h1:JnYd6fbqVM9D4zPchk+kg89PfxyuKqZKhBWGQDHfKH4= @@ -270,9 +270,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -305,8 +304,6 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk= -github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -337,8 +334,8 @@ github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4Dvx github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/itchyny/gojq v0.12.9 h1:biKpbKwMxVYhCU1d6mR7qMr3f0Hn9F5k5YykCVb3gmM= github.com/itchyny/gojq v0.12.9/go.mod h1:T4Ip7AETUXeGpD+436m+UEl3m3tokRgajd5pRfsR5oE= github.com/itchyny/timefmt-go v0.1.4 h1:hFEfWVdwsEi+CY8xY2FtgWHGQaBaC3JeHd+cve0ynVM= @@ -383,19 +380,18 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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.12.2 h1:wKIZ9XvV9qGsPWdl6peADz8GHGaEXpPjO3N3Ia3VUA0= -github.com/kubeshop/testkube-operator v1.12.2/go.mod h1:6Rs8MugOzaMcthGzobf6GBlRzbOFiK/GJiuYN6MCfEw= +github.com/kubeshop/testkube-operator v1.13.0 h1:JE+PeZ8LuIsvQvhuo9ffvxEZhx62vfiYMKb51tRZgPE= +github.com/kubeshop/testkube-operator v1.13.0/go.mod h1:6Rs8MugOzaMcthGzobf6GBlRzbOFiK/GJiuYN6MCfEw= 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.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= -github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -406,9 +402,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -468,17 +463,13 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= -github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= -github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4= -github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4= -github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI= +github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= +github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc= +github.com/otiai10/copy v1.11.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= +github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/package-url/packageurl-go v0.1.0 h1:efWBc98O/dBZRg1pw2xiDzovnlMjCa9NPnfaiBduh8I= github.com/package-url/packageurl-go v0.1.0/go.mod h1:C/ApiuWpmbpni4DIOECf6WCjFUZV7O1Fx7VAzrZHgBw= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -488,8 +479,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= -github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= +github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= +github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -523,8 +514,8 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= -github.com/pterm/pterm v0.12.59 h1:VBStvXiZL+6L+nNYjlXsD/035RJF5crqOvgqAm/Rvns= -github.com/pterm/pterm v0.12.59/go.mod h1:Lt90KhnId704siiQtMZiLS7UfoC7TRUM1HufzdM0kjk= +github.com/pterm/pterm v0.12.62 h1:Xjj5Wl6UR4Il9xOiDUOZRwReRTdO75if/JdWsn9I59s= +github.com/pterm/pterm v0.12.62/go.mod h1:+c3ujjE7N5qmNx6eKAa7YVSC6m/gCorJJKhzwYTbL90= github.com/rikatz/kubepug v1.4.0 h1:xfYljEOCsEWUjJC8jIMiNF22jwhyArqKeQw9jUu3FRw= github.com/rikatz/kubepug v1.4.0/go.mod h1:ZwpUsmmVxehGdBTcP6NnOn2zT+BmuNqHRazJe97igzA= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -536,9 +527,8 @@ github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfm github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -569,8 +559,8 @@ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:s github.com/slack-go/slack v0.11.4 h1:ojSa7KlPm3PqY2AomX4VTxEsK5eci5JaxCjlzGV5zoM= github.com/slack-go/slack v0.11.4/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -604,8 +594,8 @@ github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/vektah/gqlparser/v2 v2.5.1 h1:ZGu+bquAY23jsxDRcYpWjttRZrUz07LbiY77gUOHcr4= -github.com/vektah/gqlparser/v2 v2.5.1/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs= +github.com/vektah/gqlparser/v2 v2.5.2-0.20230422221642-25e09f9d292d h1:ibuD+jp4yLoOY4w8+5+2fDq0ufJ/noPn/cPntJMWB1E= +github.com/vektah/gqlparser/v2 v2.5.2-0.20230422221642-25e09f9d292d/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= @@ -631,6 +621,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= @@ -700,6 +691,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -741,8 +734,10 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -765,6 +760,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -819,22 +815,25 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -843,6 +842,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -894,8 +894,9 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/app/api/v1/executions.go b/internal/app/api/v1/executions.go index 5757d2ace59..7541aaf067a 100644 --- a/internal/app/api/v1/executions.go +++ b/internal/app/api/v1/executions.go @@ -22,13 +22,12 @@ import ( "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/executor/client" "github.com/kubeshop/testkube/pkg/executor/output" + "github.com/kubeshop/testkube/pkg/scheduler" "github.com/kubeshop/testkube/pkg/types" "github.com/kubeshop/testkube/pkg/workerpool" ) const ( - // DefaultConcurrencyLevel is a default concurrency level for worker pool - DefaultConcurrencyLevel = "10" // latestExecutionNo defines the number of relevant latest executions latestExecutions = 5 @@ -82,7 +81,7 @@ func (s *TestkubeAPI) ExecuteTestsHandler() fiber.Handler { } var results []testkube.Execution if len(tests) != 0 { - concurrencyLevel, err := strconv.Atoi(c.Query("concurrency", DefaultConcurrencyLevel)) + concurrencyLevel, err := strconv.Atoi(c.Query("concurrency", strconv.Itoa(scheduler.DefaultConcurrencyLevel))) if err != nil { return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: can't detect concurrency level: %w", errPrefix, err)) } 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/server.go b/internal/app/api/v1/server.go index f7dacae3dbf..9243c5e89a5 100644 --- a/internal/app/api/v1/server.go +++ b/internal/app/api/v1/server.go @@ -32,7 +32,7 @@ import ( executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors/v1" testsclientv3 "github.com/kubeshop/testkube-operator/client/tests/v3" testsourcesclientv1 "github.com/kubeshop/testkube-operator/client/testsources/v1" - testsuitesclientv2 "github.com/kubeshop/testkube-operator/client/testsuites/v2" + 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" @@ -61,7 +61,7 @@ func NewTestkubeAPI( testsuiteExecutionsResults testresult.Repository, testsClient *testsclientv3.TestsClient, executorsClient *executorsclientv1.ExecutorsClient, - testsuitesClient *testsuitesclientv2.TestSuitesClient, + testsuitesClient *testsuitesclientv3.TestSuitesClient, secretClient *secret.Client, webhookClient *executorsclientv1.WebhooksClient, clientset kubernetes.Interface, @@ -150,7 +150,7 @@ type TestkubeAPI struct { TestExecutionResults testresult.Repository Executor client.Executor ContainerExecutor client.Executor - TestsSuitesClient *testsuitesclientv2.TestSuitesClient + TestsSuitesClient *testsuitesclientv3.TestSuitesClient TestsClient *testsclientv3.TestsClient ExecutorsClient *executorsclientv1.ExecutorsClient SecretClient *secret.Client diff --git a/internal/app/api/v1/tests.go b/internal/app/api/v1/tests.go index 4b6a4abecc2..0af82e79ca3 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,9 +12,11 @@ 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" + testsclientv3 "github.com/kubeshop/testkube-operator/client/tests/v3" "github.com/kubeshop/testkube-operator/pkg/secret" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/crd" @@ -368,41 +371,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 +439,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)) } @@ -505,12 +533,16 @@ func (s TestkubeAPI) DeleteTestHandler() fiber.Handler { return s.Warn(c, http.StatusNotFound, fmt.Errorf("%s: client could not find test: %w", errPrefix, err)) } - return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not delete test: %w", errPrefix, err)) + if _, ok := err.(*testsclientv3.DeleteDependenciesError); ok { + return s.Warn(c, http.StatusInternalServerError, fmt.Errorf("client deleted test %s but deleting test dependencies(secrets) returned errors: %w", name, err)) + } + + return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: client could not delete test: %w", errPrefix, err)) } // delete executions for test if err = s.ExecutionResults.DeleteByTest(c.Context(), name); err != nil { - return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: could not delete the executions of the test: %w", errPrefix, err)) + return s.Warn(c, http.StatusInternalServerError, fmt.Errorf("test %s was deleted but deleting test executions returned error: %w", name, err)) } return c.SendStatus(http.StatusNoContent) 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 18d109f476d..aa59845bcf6 100644 --- a/internal/app/api/v1/testsuites.go +++ b/internal/app/api/v1/testsuites.go @@ -1,26 +1,29 @@ package v1 import ( + "bytes" "context" + "encoding/json" "fmt" "net/http" "sort" "strconv" "strings" - "github.com/kubeshop/testkube/pkg/datefilter" - "github.com/kubeshop/testkube/pkg/repository/testresult" - "github.com/gofiber/fiber/v2" "go.mongodb.org/mongo-driver/mongo" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/yaml" - testsuitesv2 "github.com/kubeshop/testkube-operator/apis/testsuite/v2" + testsuitesv3 "github.com/kubeshop/testkube-operator/apis/testsuite/v3" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/crd" + "github.com/kubeshop/testkube/pkg/datefilter" testsmapper "github.com/kubeshop/testkube/pkg/mapper/tests" testsuiteexecutionsmapper "github.com/kubeshop/testkube/pkg/mapper/testsuiteexecutions" testsuitesmapper "github.com/kubeshop/testkube/pkg/mapper/testsuites" + "github.com/kubeshop/testkube/pkg/repository/testresult" + "github.com/kubeshop/testkube/pkg/scheduler" "github.com/kubeshop/testkube/pkg/types" "github.com/kubeshop/testkube/pkg/workerpool" ) @@ -29,21 +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 - err := c.BodyParser(&request) - if err != nil { - return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse request: %w", errPrefix, err)) - } - errPrefix = errPrefix + " " + request.Name + 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 c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { - request.QuoteTestSuiteTextFields() - data, err := crd.GenerateYAML(crd.TemplateTestSuite, []testkube.TestSuiteUpsertRequest{request}) - return s.getCRDs(c, data, err) - } + 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) + } + + err := json.Unmarshal(data, &request) + if err != nil { + s.Log.Warnw("could not parse json request", "error", err) + } + errPrefix = errPrefix + " " + request.Name + + emptyBatch := true + for _, step := range request.Steps { + if len(step.Execute) != 0 { + emptyBatch = false + break + } + } + + if emptyBatch { + var requestV2 testkube.TestSuiteUpsertRequestV2 + if err := json.Unmarshal(data, &requestV2); err != nil { + return s.Error(c, http.StatusBadRequest, err) + } + + request = *requestV2.ToTestSuiteUpsertRequest() + } + + if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + request.QuoteTestSuiteTextFields() + data, err := crd.GenerateYAML(crd.TemplateTestSuite, []testkube.TestSuiteUpsertRequest{request}) + return s.getCRDs(c, data, err) + } - testSuite := testsuitesmapper.MapTestSuiteUpsertRequestToTestCRD(request) - 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) @@ -64,11 +104,45 @@ 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 - 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 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)) + } + + request = testsuitesmapper.MapTestSuiteTestCRDToUpdateRequest(&testSuite) + } else { + data := c.Body() + if string(c.Request().Header.ContentType()) != mediaTypeJSON { + return s.Error(c, http.StatusBadRequest, fiber.ErrUnprocessableEntity) + } + + err := json.Unmarshal(data, &request) + if err != nil { + s.Log.Warnw("could not parse json request", "error", err) + } + + if request.Steps != nil { + emptyBatch := true + for _, step := range *request.Steps { + if len(step.Execute) != 0 { + emptyBatch = false + break + } + } + + if emptyBatch { + var requestV2 testkube.TestSuiteUpdateRequestV2 + if err := json.Unmarshal(data, &requestV2); err != nil { + return s.Error(c, http.StatusBadRequest, err) + } + + request = *requestV2.ToTestSuiteUpdateRequest() + } + } } var name string @@ -88,7 +162,10 @@ func (s TestkubeAPI) UpdateTestSuiteHandler() fiber.Handler { } // map TestSuite but load spec only to not override metadata.ResourceVersion - testSuiteSpec := testsuitesmapper.MapTestSuiteUpdateRequestToTestCRD(request, testSuite) + testSuiteSpec, err := testsuitesmapper.MapTestSuiteUpdateRequestToTestCRD(request, testSuite) + if err != nil { + return s.Error(c, http.StatusBadRequest, err) + } updatedTestSuite, err := s.TestsSuitesClient.Update(testSuiteSpec) @@ -219,7 +296,7 @@ func (s TestkubeAPI) DeleteTestSuitesHandler() fiber.Handler { if selector == "" { err = s.TestsSuitesClient.DeleteAll() } else { - var testSuiteList *testsuitesv2.TestSuiteList + var testSuiteList *testsuitesv3.TestSuiteList testSuiteList, err = s.TestsSuitesClient.List(selector) if err != nil { if !errors.IsNotFound(err) { @@ -268,7 +345,7 @@ func (s TestkubeAPI) DeleteTestSuitesHandler() fiber.Handler { } } -func (s TestkubeAPI) getFilteredTestSuitesList(c *fiber.Ctx) (*testsuitesv2.TestSuiteList, error) { +func (s TestkubeAPI) getFilteredTestSuitesList(c *fiber.Ctx) (*testsuitesv3.TestSuiteList, error) { crTestSuites, err := s.TestsSuitesClient.List(c.Query("selector")) if err != nil { return nil, err @@ -530,7 +607,7 @@ func (s TestkubeAPI) ExecuteTestSuitesHandler() fiber.Handler { selector := c.Query("selector") s.Log.Debugw("getting test suite", "name", name, "selector", selector) - var testSuites []testsuitesv2.TestSuite + var testSuites []testsuitesv3.TestSuite if name != "" { errPrefix = errPrefix + " " + name testSuite, err := s.TestsSuitesClient.Get(name) @@ -554,7 +631,7 @@ func (s TestkubeAPI) ExecuteTestSuitesHandler() fiber.Handler { var results []testkube.TestSuiteExecution if len(testSuites) != 0 { - concurrencyLevel, err := strconv.Atoi(c.Query("concurrency", DefaultConcurrencyLevel)) + concurrencyLevel, err := strconv.Atoi(c.Query("concurrency", strconv.Itoa(scheduler.DefaultConcurrencyLevel))) if err != nil { return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: can't detect concurrency level: %w", errPrefix, err)) } diff --git a/internal/app/api/v1/testtriggers.go b/internal/app/api/v1/testtriggers.go index eeb7f80dd33..a236e836cfa 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(&testTrigger); 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 0a6639c2c37..9a9135b9930 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,20 +18,32 @@ 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(&webhook); 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 { - 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 { @@ -56,6 +71,12 @@ func (s TestkubeAPI) ListWebhooksHandler() fiber.Handler { } if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + for i := range results { + if results[i].PayloadTemplate != "" { + results[i].PayloadTemplate = fmt.Sprintf("%q", results[i].PayloadTemplate) + } + } + data, err := crd.GenerateYAML(crd.TemplateWebhook, results) return s.getCRDs(c, data, err) } @@ -79,6 +100,10 @@ func (s TestkubeAPI) GetWebhookHandler() fiber.Handler { result := webhooksmapper.MapCRDToAPI(*item) if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { + if result.PayloadTemplate != "" { + result.PayloadTemplate = fmt.Sprintf("%q", result.PayloadTemplate) + } + data, err := crd.GenerateYAML(crd.TemplateWebhook, []testkube.Webhook{result}) return s.getCRDs(c, data, err) } diff --git a/internal/config/config.go b/internal/config/config.go index a133df341a0..9c64dcb2ccc 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -60,6 +60,8 @@ type Config struct { TestkubePodStartTimeout time.Duration `envconfig:"TESTKUBE_POD_START_TIMEOUT" default:"30m"` CDEventsTarget string `envconfig:"CDEVENTS_TARGET" default:""` TestkubeDashboardURI string `envconfig:"TESTKUBE_DASHBOARD_URI" default:""` + DisableReconciler bool `envconfig:"DISABLE_RECONCILER" default:"false"` + TestkubeClusterName string `envconfig:"TESTKUBE_CLUSTER_NAME" default:""` } func Get() (*Config, error) { diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 8c65884695c..2f5f04fc868 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -66,7 +66,8 @@ type Agent struct { receiveTimeout time.Duration healthcheckInterval time.Duration - clusterID string + clusterID string + clusterName string } func NewAgent(logger *zap.SugaredLogger, @@ -77,6 +78,7 @@ func NewAgent(logger *zap.SugaredLogger, logStreamWorkerCount int, logStreamFunc func(ctx context.Context, executionID string) (chan output.Output, error), clusterID string, + clusterName string, ) (*Agent, error) { return &Agent{ handler: handler, diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go index 01597a0d8df..14fff844a89 100644 --- a/pkg/agent/agent_test.go +++ b/pkg/agent/agent_test.go @@ -56,7 +56,7 @@ func TestCommandExecution(t *testing.T) { var logStreamFunc func(ctx context.Context, executionID string) (chan output.Output, error) logger, _ := zap.NewDevelopment() - agent, err := agent.NewAgent(logger.Sugar(), m, "api-key", grpcClient, 5, 5, logStreamFunc, "") + agent, err := agent.NewAgent(logger.Sugar(), m, "api-key", grpcClient, 5, 5, logStreamFunc, "", "") if err != nil { t.Fatal(err) } diff --git a/pkg/agent/events.go b/pkg/agent/events.go index b62ddb7bf27..7e7a7354eba 100644 --- a/pkg/agent/events.go +++ b/pkg/agent/events.go @@ -48,6 +48,7 @@ func (ag *Agent) Metadata() map[string]string { } func (ag *Agent) Notify(event testkube.Event) (result testkube.EventResult) { + event.ClusterName = ag.clusterName // Non blocking send select { case ag.events <- event: diff --git a/pkg/agent/events_test.go b/pkg/agent/events_test.go index d42f7855669..ae15f59bc49 100644 --- a/pkg/agent/events_test.go +++ b/pkg/agent/events_test.go @@ -52,7 +52,7 @@ func TestEventLoop(t *testing.T) { grpcClient := cloud.NewTestKubeCloudAPIClient(grpcConn) var logStreamFunc func(ctx context.Context, executionID string) (chan output.Output, error) - agent, err := agent.NewAgent(logger.Sugar(), nil, "api-key", grpcClient, 5, 5, logStreamFunc, "") + agent, err := agent.NewAgent(logger.Sugar(), nil, "api-key", grpcClient, 5, 5, logStreamFunc, "", "") assert.NoError(t, err) go func() { l, err := agent.Load() diff --git a/pkg/agent/logs_test.go b/pkg/agent/logs_test.go index f371d10f131..660dd4f4833 100644 --- a/pkg/agent/logs_test.go +++ b/pkg/agent/logs_test.go @@ -63,7 +63,7 @@ func TestLogStream(t *testing.T) { } logger, _ := zap.NewDevelopment() - agent, err := agent.NewAgent(logger.Sugar(), m, "api-key", grpcClient, 5, 5, logStreamFunc, "") + agent, err := agent.NewAgent(logger.Sugar(), m, "api-key", grpcClient, 5, 5, logStreamFunc, "", "") if err != nil { t.Fatal(err) } diff --git a/pkg/api/v1/client/interface.go b/pkg/api/v1/client/interface.go index 7a0932f6dbc..9a318af2a65 100644 --- a/pkg/api/v1/client/interface.go +++ b/pkg/api/v1/client/interface.go @@ -171,6 +171,7 @@ type ExecuteTestOptions struct { JobTemplate string ContentRequest *testkube.TestContentRequest PreRunScriptContent string + PostRunScriptContent string ScraperTemplate string NegativeTest bool IsNegativeTestChangedOnRun bool @@ -187,6 +188,7 @@ type ExecuteTestSuiteOptions struct { ExecutionLabels map[string]string ContentRequest *testkube.TestContentRequest RunningContext *testkube.RunningContext + ConcurrencyLevel int32 } // Gettable is an interface of gettable objects diff --git a/pkg/api/v1/client/test.go b/pkg/api/v1/client/test.go index 666f45bb70b..717c0207810 100644 --- a/pkg/api/v1/client/test.go +++ b/pkg/api/v1/client/test.go @@ -150,6 +150,7 @@ func (c TestClient) ExecuteTest(id, executionName string, options ExecuteTestOpt JobTemplate: options.JobTemplate, ContentRequest: options.ContentRequest, PreRunScript: options.PreRunScriptContent, + PostRunScript: options.PostRunScriptContent, ScraperTemplate: options.ScraperTemplate, NegativeTest: options.NegativeTest, IsNegativeTestChangedOnRun: options.IsNegativeTestChangedOnRun, @@ -187,6 +188,7 @@ func (c TestClient) ExecuteTests(selector string, concurrencyLevel int, options JobTemplate: options.JobTemplate, ContentRequest: options.ContentRequest, PreRunScript: options.PreRunScriptContent, + PostRunScript: options.PostRunScriptContent, ScraperTemplate: options.ScraperTemplate, NegativeTest: options.NegativeTest, IsNegativeTestChangedOnRun: options.IsNegativeTestChangedOnRun, diff --git a/pkg/api/v1/client/testsuite.go b/pkg/api/v1/client/testsuite.go index 284b453226d..e3ecd8b3b60 100644 --- a/pkg/api/v1/client/testsuite.go +++ b/pkg/api/v1/client/testsuite.go @@ -143,13 +143,14 @@ 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, + Name: executionName, + Variables: options.ExecutionVariables, + HttpProxy: options.HTTPProxy, + HttpsProxy: options.HTTPSProxy, + ExecutionLabels: options.ExecutionLabels, + ContentRequest: options.ContentRequest, + RunningContext: options.RunningContext, + ConcurrencyLevel: options.ConcurrencyLevel, } body, err := json.Marshal(executionRequest) @@ -165,12 +166,13 @@ 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, + Variables: options.ExecutionVariables, + HttpProxy: options.HTTPProxy, + HttpsProxy: options.HTTPSProxy, + ExecutionLabels: options.ExecutionLabels, + ContentRequest: options.ContentRequest, + RunningContext: options.RunningContext, + ConcurrencyLevel: options.ConcurrencyLevel, } body, err := json.Marshal(executionRequest) diff --git a/pkg/api/v1/testkube/model_event.go b/pkg/api/v1/testkube/model_event.go index 880b287be65..8e762deed32 100644 --- a/pkg/api/v1/testkube/model_event.go +++ b/pkg/api/v1/testkube/model_event.go @@ -9,7 +9,7 @@ */ package testkube -// CRD based executor data +// Event data type Event struct { // UUID of event Id string `json:"id"` @@ -19,4 +19,6 @@ type Event struct { Type_ *EventType `json:"type"` TestExecution *Execution `json:"testExecution,omitempty"` TestSuiteExecution *TestSuiteExecution `json:"testSuiteExecution,omitempty"` + // cluster name of event + ClusterName string `json:"clusterName,omitempty"` } diff --git a/pkg/api/v1/testkube/model_execution.go b/pkg/api/v1/testkube/model_execution.go index a21cae86b0d..0297f3f0efc 100644 --- a/pkg/api/v1/testkube/model_execution.go +++ b/pkg/api/v1/testkube/model_execution.go @@ -64,7 +64,9 @@ type Execution struct { // minio bucket name to get uploads from BucketName string `json:"bucketName,omitempty"` ArtifactRequest *ArtifactRequest `json:"artifactRequest,omitempty"` - // script to run before test execution - PreRunScript string `json:"preRunScript,omitempty"` + // script to run before test execution (not supported for container executors) + PreRunScript string `json:"preRunScript,omitempty"` + // script to run after test execution (not supported for container executors) + PostRunScript string `json:"postRunScript,omitempty"` RunningContext *RunningContext `json:"runningContext,omitempty"` } diff --git a/pkg/api/v1/testkube/model_execution_extended.go b/pkg/api/v1/testkube/model_execution_extended.go index 1148d5f255d..9ba8b4bea9b 100644 --- a/pkg/api/v1/testkube/model_execution_extended.go +++ b/pkg/api/v1/testkube/model_execution_extended.go @@ -21,12 +21,16 @@ func NewExecutionWithID(id, testType, testName string) *Execution { } } -func NewExecution(testNamespace, testName, testSuiteName, executionName, testType string, +func NewExecution(id, testNamespace, testName, testSuiteName, executionName, testType string, executionNumber int, content *TestContent, result ExecutionResult, variables map[string]Variable, testSecretUUID, testSuiteSecretUUID string, labels map[string]string) Execution { + if id == "" { + id = primitive.NewObjectID().Hex() + } + return Execution{ - Id: primitive.NewObjectID().Hex(), + Id: id, TestName: testName, TestSuiteName: testSuiteName, TestNamespace: testNamespace, @@ -103,11 +107,12 @@ func (e *Execution) Err(err error) Execution { e.ExecutionResult.Err(err) return *e } -func (e *Execution) Errw(msg string, err error) Execution { +func (e *Execution) Errw(id, msg string, err error) Execution { if e.ExecutionResult == nil { e.ExecutionResult = &ExecutionResult{} } + e.Id = id e.ExecutionResult.Err(fmt.Errorf(msg, err)) return *e } diff --git a/pkg/api/v1/testkube/model_execution_request.go b/pkg/api/v1/testkube/model_execution_request.go index 9699f8827a0..23c5e1f223c 100644 --- a/pkg/api/v1/testkube/model_execution_request.go +++ b/pkg/api/v1/testkube/model_execution_request.go @@ -11,6 +11,8 @@ package testkube // test execution request body type ExecutionRequest struct { + // execution id + Id string `json:"id,omitempty"` // test execution custom name Name string `json:"name,omitempty"` // unique test suite name (CRD Test suite name), if it's run as a part of test suite @@ -68,8 +70,10 @@ type ExecutionRequest struct { // cron job template extensions CronJobTemplate string `json:"cronJobTemplate,omitempty"` ContentRequest *TestContentRequest `json:"contentRequest,omitempty"` - // script to run before test execution + // script to run before test execution (not supported for container executors) PreRunScript string `json:"preRunScript,omitempty"` + // script to run after test execution (not supported for container executors) + PostRunScript string `json:"postRunScript,omitempty"` // scraper template extensions ScraperTemplate string `json:"scraperTemplate,omitempty"` // config map references diff --git a/pkg/api/v1/testkube/model_execution_update_request.go b/pkg/api/v1/testkube/model_execution_update_request.go index 97b5ba65bca..48089be07ce 100644 --- a/pkg/api/v1/testkube/model_execution_update_request.go +++ b/pkg/api/v1/testkube/model_execution_update_request.go @@ -11,6 +11,8 @@ package testkube // test execution request update body type ExecutionUpdateRequest struct { + // execution id + Id *string `json:"id,omitempty"` // test execution custom name Name *string `json:"name,omitempty"` // unique test suite name (CRD Test suite name), if it's run as a part of test suite @@ -68,8 +70,10 @@ type ExecutionUpdateRequest struct { // cron job template extensions CronJobTemplate *string `json:"cronJobTemplate,omitempty"` ContentRequest **TestContentUpdateRequest `json:"contentRequest,omitempty"` - // script to run before test execution + // script to run before test execution (not supported for container executors) PreRunScript *string `json:"preRunScript,omitempty"` + // script to run after test execution (not supported for container executors) + PostRunScript *string `json:"postRunScript,omitempty"` // scraper template extensions ScraperTemplate *string `json:"scraperTemplate,omitempty"` // config *map references diff --git a/pkg/api/v1/testkube/model_test_base_extended.go b/pkg/api/v1/testkube/model_test_base_extended.go index de1bd908a18..a895504a190 100644 --- a/pkg/api/v1/testkube/model_test_base_extended.go +++ b/pkg/api/v1/testkube/model_test_base_extended.go @@ -78,6 +78,7 @@ func (test *Test) QuoteTestTextFields() { &test.ExecutionRequest.JobTemplate, &test.ExecutionRequest.CronJobTemplate, &test.ExecutionRequest.PreRunScript, + &test.ExecutionRequest.PostRunScript, &test.ExecutionRequest.ScraperTemplate, } diff --git a/pkg/api/v1/testkube/model_test_content.go b/pkg/api/v1/testkube/model_test_content.go index 1fccdc804dc..7369c925a64 100644 --- a/pkg/api/v1/testkube/model_test_content.go +++ b/pkg/api/v1/testkube/model_test_content.go @@ -10,7 +10,7 @@ package testkube type TestContent struct { - // test type + // type of sources a runner can get data from. string: String content (e.g. Postman JSON file). file-uri: content stored on the webserver. git-file: the file stored in the Git repo in the given repository.path field (Deprecated: use git instead). git-dir: the entire git repo or git subdirectory depending on the repository.path field (Testkube does a shadow clone and sparse checkout to limit IOs in the case of monorepos). (Deprecated: use git instead). git: automatically provisions either a file, directory or whole git repository depending on the repository.path field. Type_ string `json:"type,omitempty"` Repository *Repository `json:"repository,omitempty"` // test content data as string diff --git a/pkg/api/v1/testkube/model_test_content_update.go b/pkg/api/v1/testkube/model_test_content_update.go index 56df514449b..aa7cb0745d7 100644 --- a/pkg/api/v1/testkube/model_test_content_update.go +++ b/pkg/api/v1/testkube/model_test_content_update.go @@ -11,7 +11,7 @@ package testkube // test content update body type TestContentUpdate struct { - // test type + // type of sources a runner can get data from. *string: String content (e.g. Postman JSON file). file-uri: content stored on the webserver. git-file: the file stored in the Git repo in the given repository.path field (Deprecated: use git instead). git-dir: the entire git repo or git subdirectory depending on the repository.path field (Testkube does a shadow clone and sparse checkout to limit IOs in the case of monorepos). (Deprecated: use git instead). git: automatically provisions either a file, directory or whole git repository depending on the repository.path field. Type_ *string `json:"type,omitempty"` Repository **RepositoryUpdate `json:"repository,omitempty"` // test content data as *string diff --git a/pkg/api/v1/testkube/model_test_source.go b/pkg/api/v1/testkube/model_test_source.go index ff83fc0ce39..efcf6ca5b81 100644 --- a/pkg/api/v1/testkube/model_test_source.go +++ b/pkg/api/v1/testkube/model_test_source.go @@ -11,7 +11,7 @@ package testkube // Test source resource for shared test content type TestSource struct { - // test type + // type of sources a runner can get data from. string: String content (e.g. Postman JSON file). file-uri: content stored on the webserver. git-file: the file stored in the Git repo in the given repository.path field (Deprecated: use git instead). git-dir: the entire git repo or git subdirectory depending on the repository.path field (Testkube does a shadow clone and sparse checkout to limit IOs in the case of monorepos). (Deprecated: use git instead). git: automatically provisions either a file, directory or whole git repository depending on the repository.path field. Type_ string `json:"type,omitempty"` Repository *Repository `json:"repository,omitempty"` // test content data as string diff --git a/pkg/api/v1/testkube/model_test_source_update.go b/pkg/api/v1/testkube/model_test_source_update.go index 0b76da5e138..d8e7601c7ec 100644 --- a/pkg/api/v1/testkube/model_test_source_update.go +++ b/pkg/api/v1/testkube/model_test_source_update.go @@ -11,7 +11,7 @@ package testkube // Test source resource update for shared test content type TestSourceUpdate struct { - // test type + // type of sources a runner can get data from. *string: String content (e.g. Postman JSON file). file-uri: content stored on the webserver. git-file: the file stored in the Git repo in the given repository.path field (Deprecated: use git instead). git-dir: the entire git repo or git subdirectory depending on the repository.path field (Testkube does a shadow clone and sparse checkout to limit IOs in the case of monorepos). (Deprecated: use git instead). git: automatically provisions either a file, directory or whole git repository depending on the repository.path field. Type_ *string `json:"type,omitempty"` Repository **RepositoryUpdate `json:"repository,omitempty"` // test content data as *string diff --git a/pkg/api/v1/testkube/model_test_source_update_request.go b/pkg/api/v1/testkube/model_test_source_update_request.go index e89c45508f4..875435a7bcb 100644 --- a/pkg/api/v1/testkube/model_test_source_update_request.go +++ b/pkg/api/v1/testkube/model_test_source_update_request.go @@ -11,7 +11,7 @@ package testkube // test source update request body type TestSourceUpdateRequest struct { - // test type + // type of sources a runner can get data from. *string: String content (e.g. Postman JSON file). file-uri: content stored on the webserver. git-file: the file stored in the Git repo in the given repository.path field (Deprecated: use git instead). git-dir: the entire git repo or git subdirectory depending on the repository.path field (Testkube does a shadow clone and sparse checkout to limit IOs in the case of monorepos). (Deprecated: use git instead). git: automatically provisions either a file, directory or whole git repository depending on the repository.path field. Type_ *string `json:"type,omitempty"` Repository **RepositoryUpdate `json:"repository,omitempty"` // test content data as *string diff --git a/pkg/api/v1/testkube/model_test_source_upsert_request.go b/pkg/api/v1/testkube/model_test_source_upsert_request.go index a8b7b2ef394..f585664202c 100644 --- a/pkg/api/v1/testkube/model_test_source_upsert_request.go +++ b/pkg/api/v1/testkube/model_test_source_upsert_request.go @@ -11,7 +11,7 @@ package testkube // test source create request body type TestSourceUpsertRequest struct { - // test type + // type of sources a runner can get data from. string: String content (e.g. Postman JSON file). file-uri: content stored on the webserver. git-file: the file stored in the Git repo in the given repository.path field (Deprecated: use git instead). git-dir: the entire git repo or git subdirectory depending on the repository.path field (Testkube does a shadow clone and sparse checkout to limit IOs in the case of monorepos). (Deprecated: use git instead). git: automatically provisions either a file, directory or whole git repository depending on the repository.path field. Type_ string `json:"type,omitempty"` Repository *Repository `json:"repository,omitempty"` // test content data as string diff --git a/pkg/api/v1/testkube/model_test_suite.go b/pkg/api/v1/testkube/model_test_suite.go index 83d06fd04c2..ff5158d6bd6 100644 --- a/pkg/api/v1/testkube/model_test_suite.go +++ b/pkg/api/v1/testkube/model_test_suite.go @@ -17,12 +17,12 @@ type TestSuite struct { Name string `json:"name"` Namespace string `json:"namespace,omitempty"` Description string `json:"description,omitempty"` - // Run this step before whole suite - Before []TestSuiteStep `json:"before,omitempty"` - // Steps to run - Steps []TestSuiteStep `json:"steps"` - // Run this step after whole suite - After []TestSuiteStep `json:"after,omitempty"` + // Run these batch steps before whole suite + Before []TestSuiteBatchStep `json:"before,omitempty"` + // Batch steps to run + Steps []TestSuiteBatchStep `json:"steps,omitempty"` + // Run these batch steps after whole suite + After []TestSuiteBatchStep `json:"after,omitempty"` // test suite labels Labels map[string]string `json:"labels,omitempty"` // schedule to run test suite 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 217462ad2c1..a7431993af3 100644 --- a/pkg/api/v1/testkube/model_test_suite_base_extended.go +++ b/pkg/api/v1/testkube/model_test_suite_base_extended.go @@ -33,14 +33,16 @@ func (t TestSuite) GetObjectRef() *ObjectRef { // GetTestNames return test names for TestSuite func (t TestSuite) GetTestNames() []string { var names []string - var steps []TestSuiteStep + var batches []TestSuiteBatchStep - steps = append(steps, t.Before...) - steps = append(steps, t.Steps...) - steps = append(steps, t.After...) - for _, step := range steps { - if step.Execute != nil { - names = append(names, step.Execute.Name) + batches = append(batches, t.Before...) + batches = append(batches, t.Steps...) + batches = append(batches, t.After...) + for _, batch := range batches { + for _, step := range batch.Execute { + if step.Test != "" { + names = append(names, step.Test) + } } } diff --git a/pkg/api/v1/testkube/model_test_suite_batch_step.go b/pkg/api/v1/testkube/model_test_suite_batch_step.go new file mode 100644 index 00000000000..5be92db1a0e --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_batch_step.go @@ -0,0 +1,16 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +// set of steps run in parallel +type TestSuiteBatchStep struct { + StopOnFailure bool `json:"stopOnFailure"` + Execute []TestSuiteStep `json:"execute,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_test_suite_batch_step_execution_result.go b/pkg/api/v1/testkube/model_test_suite_batch_step_execution_result.go new file mode 100644 index 00000000000..d72e0ccc2fb --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_batch_step_execution_result.go @@ -0,0 +1,16 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +// execution result returned from executor +type TestSuiteBatchStepExecutionResult struct { + Step *TestSuiteBatchStep `json:"step,omitempty"` + Execute []TestSuiteStepExecutionResult `json:"execute,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_test_suite_batch_step_execution_summary.go b/pkg/api/v1/testkube/model_test_suite_batch_step_execution_summary.go new file mode 100644 index 00000000000..66eacb548e5 --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_batch_step_execution_summary.go @@ -0,0 +1,15 @@ +/* + * 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 + +// Test suite batch execution summary +type TestSuiteBatchStepExecutionSummary struct { + Execute []TestSuiteStepExecutionSummary `json:"execute,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_test_suite_execution.go b/pkg/api/v1/testkube/model_test_suite_execution.go index c89329fdce8..acf097d9216 100644 --- a/pkg/api/v1/testkube/model_test_suite_execution.go +++ b/pkg/api/v1/testkube/model_test_suite_execution.go @@ -19,7 +19,7 @@ type TestSuiteExecution struct { Id string `json:"id"` // execution name Name string `json:"name"` - TestSuite *ObjectRef `json:"testSuite,omitempty"` + TestSuite *ObjectRef `json:"testSuite"` Status *TestSuiteExecutionStatus `json:"status,omitempty"` // Environment variables passed to executor. // Deprecated: use Basic Variables instead @@ -36,7 +36,9 @@ type TestSuiteExecution struct { // test duration in ms DurationMs int32 `json:"durationMs,omitempty"` // steps execution results - StepResults []TestSuiteStepExecutionResult `json:"stepResults,omitempty"` + StepResults []TestSuiteStepExecutionResultV2 `json:"stepResults,omitempty"` + // batch steps execution results + ExecuteStepResults []TestSuiteBatchStepExecutionResult `json:"executeStepResults,omitempty"` // test suite labels Labels map[string]string `json:"labels,omitempty"` RunningContext *RunningContext `json:"runningContext,omitempty"` diff --git a/pkg/api/v1/testkube/model_test_suite_execution_extended.go b/pkg/api/v1/testkube/model_test_suite_execution_extended.go index 944bdd93875..a17bb2c69ce 100644 --- a/pkg/api/v1/testkube/model_test_suite_execution_extended.go +++ b/pkg/api/v1/testkube/model_test_suite_execution_extended.go @@ -1,6 +1,8 @@ package testkube import ( + "fmt" + "strings" "time" "go.mongodb.org/mongo-driver/bson/primitive" @@ -43,11 +45,19 @@ func NewStartedTestSuiteExecution(testSuite TestSuite, request TestSuiteExecutio } // add queued execution steps - steps := append(testSuite.Before, testSuite.Steps...) - steps = append(steps, testSuite.After...) + batches := append(testSuite.Before, testSuite.Steps...) + batches = append(batches, testSuite.After...) - for i := range steps { - testExecution.StepResults = append(testExecution.StepResults, NewTestStepQueuedResult(&steps[i])) + for i := range batches { + var stepResults []TestSuiteStepExecutionResult + for j := range batches[i].Execute { + stepResults = append(stepResults, NewTestStepQueuedResult(&batches[i].Execute[j])) + } + + testExecution.ExecuteStepResults = append(testExecution.ExecuteStepResults, TestSuiteBatchStepExecutionResult{ + Step: &batches[i], + Execute: stepResults, + }) } return testExecution @@ -55,14 +65,28 @@ func NewStartedTestSuiteExecution(testSuite TestSuite, request TestSuiteExecutio func (e TestSuiteExecution) FailedStepsCount() (count int) { for _, stepResult := range e.StepResults { - if stepResult.Execution.IsFailed() { + if stepResult.Execution != nil && stepResult.Execution.IsFailed() { count++ } } + + for _, batchStepResult := range e.ExecuteStepResults { + for _, stepResult := range batchStepResult.Execute { + if stepResult.Execution != nil && stepResult.Execution.IsFailed() { + count++ + break + } + } + } + return } func (e TestSuiteExecution) IsCompleted() bool { + if e.Status == nil { + return false + } + return *e.Status == *TestSuiteExecutionStatusFailed || *e.Status == *TestSuiteExecutionStatusPassed || *e.Status == *TestSuiteExecutionStatusAborted || @@ -77,7 +101,6 @@ func (e *TestSuiteExecution) Stop() { } func (e *TestSuiteExecution) CalculateDuration() time.Duration { - end := e.EndTime start := e.StartTime @@ -93,26 +116,73 @@ func (e *TestSuiteExecution) CalculateDuration() time.Duration { } func (e TestSuiteExecution) Table() (header []string, output [][]string) { - header = []string{"Status", "Step", "ID", "Error"} - output = make([][]string, 0) + if len(e.StepResults) != 0 { + header = []string{"Status", "Step", "ID", "Error"} + output = make([][]string, 0) + + for _, sr := range e.StepResults { + status := "no-execution-result" + if sr.Execution != nil && sr.Execution.ExecutionResult != nil && sr.Execution.ExecutionResult.Status != nil { + status = string(*sr.Execution.ExecutionResult.Status) + } + + if sr.Step == nil { + continue + } - for _, sr := range e.StepResults { - status := "no-execution-result" - if sr.Execution != nil && sr.Execution.ExecutionResult != nil && sr.Execution.ExecutionResult.Status != nil { - status = string(*sr.Execution.ExecutionResult.Status) + switch sr.Step.Type() { + case TestSuiteStepTypeExecuteTest: + var id, errorMessage string + if sr.Execution != nil && sr.Execution.ExecutionResult != nil { + errorMessage = sr.Execution.ExecutionResult.ErrorMessage + id = sr.Execution.Id + } + row := []string{status, sr.Step.FullName(), id, errorMessage} + output = append(output, row) + case TestSuiteStepTypeDelay: + row := []string{status, sr.Step.FullName(), "", ""} + output = append(output, row) + } } + } - switch sr.Step.Type() { - case TestSuiteStepTypeExecuteTest: - var id, errorMessage string - if sr.Execution != nil && sr.Execution.ExecutionResult != nil { - errorMessage = sr.Execution.ExecutionResult.ErrorMessage - id = sr.Execution.Id + if len(e.ExecuteStepResults) != 0 { + header = []string{"Statuses", "Step", "IDs", "Errors"} + output = make([][]string, 0) + + for _, bs := range e.ExecuteStepResults { + var statuses, names, ids, errorMessages []string + + for _, sr := range bs.Execute { + status := "no-execution-result" + if sr.Execution != nil && sr.Execution.ExecutionResult != nil && sr.Execution.ExecutionResult.Status != nil { + status = string(*sr.Execution.ExecutionResult.Status) + } + + statuses = append(statuses, status) + if sr.Step == nil { + continue + } + + switch sr.Step.Type() { + case TestSuiteStepTypeExecuteTest: + var id, errorMessage string + if sr.Execution != nil && sr.Execution.ExecutionResult != nil { + errorMessage = sr.Execution.ExecutionResult.ErrorMessage + id = sr.Execution.Id + } + + names = append(names, sr.Step.FullName()) + ids = append(ids, id) + errorMessages = append(errorMessages, fmt.Sprintf("%q", errorMessage)) + case TestSuiteStepTypeDelay: + names = append(names, sr.Step.FullName()) + ids = append(ids, "\"\"") + errorMessages = append(errorMessages, "\"\"") + } } - row := []string{status, sr.Step.FullName(), id, errorMessage} - output = append(output, row) - case TestSuiteStepTypeDelay: - row := []string{status, sr.Step.FullName(), "", ""} + + row := []string{strings.Join(statuses, ", "), strings.Join(names, ", "), strings.Join(ids, ", "), strings.Join(errorMessages, ", ")} output = append(output, row) } } @@ -121,19 +191,19 @@ func (e TestSuiteExecution) Table() (header []string, output [][]string) { } func (e *TestSuiteExecution) IsRunning() bool { - return *e.Status == RUNNING_TestSuiteExecutionStatus + return e.Status != nil && *e.Status == RUNNING_TestSuiteExecutionStatus } func (e *TestSuiteExecution) IsQueued() bool { - return *e.Status == QUEUED_TestSuiteExecutionStatus + return e.Status != nil && *e.Status == QUEUED_TestSuiteExecutionStatus } func (e *TestSuiteExecution) IsPassed() bool { - return *e.Status == PASSED_TestSuiteExecutionStatus + return e.Status != nil && *e.Status == PASSED_TestSuiteExecutionStatus } func (e *TestSuiteExecution) IsFailed() bool { - return *e.Status == FAILED_TestSuiteExecutionStatus + return e.Status != nil && *e.Status == FAILED_TestSuiteExecutionStatus } func (e *TestSuiteExecution) IsAborted() bool { 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 4926eafa979..ff81002ee93 100644 --- a/pkg/api/v1/testkube/model_test_suite_execution_request.go +++ b/pkg/api/v1/testkube/model_test_suite_execution_request.go @@ -36,4 +36,6 @@ type TestSuiteExecutionRequest struct { RunningContext *RunningContext `json:"runningContext,omitempty"` // cron job template extensions CronJobTemplate string `json:"cronJobTemplate,omitempty"` + // number of tests run in parallel + ConcurrencyLevel int32 `json:"concurrencyLevel,omitempty"` } diff --git a/pkg/api/v1/testkube/model_test_suite_execution_summary.go b/pkg/api/v1/testkube/model_test_suite_execution_summary.go index 9750db6e3a5..830833166c3 100644 --- a/pkg/api/v1/testkube/model_test_suite_execution_summary.go +++ b/pkg/api/v1/testkube/model_test_suite_execution_summary.go @@ -29,8 +29,8 @@ type TestSuiteExecutionSummary struct { // test suite execution duration Duration string `json:"duration,omitempty"` // test suite execution duration in ms - DurationMs int32 `json:"durationMs,omitempty"` - Execution []TestSuiteStepExecutionSummary `json:"execution,omitempty"` + DurationMs int32 `json:"durationMs,omitempty"` + Execution []TestSuiteBatchStepExecutionSummary `json:"execution,omitempty"` // test suite and execution labels Labels map[string]string `json:"labels,omitempty"` } 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 e8b6afd8d39..a731bb07ede 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 @@ -36,4 +36,6 @@ type TestSuiteExecutionUpdateRequest struct { RunningContext *RunningContext `json:"runningContext,omitempty"` // cron job template extensions CronJobTemplate *string `json:"cronJobTemplate,omitempty"` + // number of tests run in parallel + ConcurrencyLevel *int32 `json:"concurrencyLevel,omitempty"` } diff --git a/pkg/api/v1/testkube/model_test_suite_step.go b/pkg/api/v1/testkube/model_test_suite_step.go index 4c7e46b1ba3..1e1020dce2f 100644 --- a/pkg/api/v1/testkube/model_test_suite_step.go +++ b/pkg/api/v1/testkube/model_test_suite_step.go @@ -10,7 +10,8 @@ package testkube type TestSuiteStep struct { - StopTestOnFailure bool `json:"stopTestOnFailure"` - Execute *TestSuiteStepExecuteTest `json:"execute,omitempty"` - Delay *TestSuiteStepDelay `json:"delay,omitempty"` + // object name + Test string `json:"test,omitempty"` + // delay duration in time units + Delay string `json:"delay,omitempty"` } diff --git a/pkg/api/v1/testkube/model_test_suite_step_delay.go b/pkg/api/v1/testkube/model_test_suite_step_delay_v2.go similarity index 90% rename from pkg/api/v1/testkube/model_test_suite_step_delay.go rename to pkg/api/v1/testkube/model_test_suite_step_delay_v2.go index e3bae35cb64..1b8d845d527 100644 --- a/pkg/api/v1/testkube/model_test_suite_step_delay.go +++ b/pkg/api/v1/testkube/model_test_suite_step_delay_v2.go @@ -9,7 +9,7 @@ */ package testkube -type TestSuiteStepDelay struct { +type TestSuiteStepDelayV2 struct { // delay duration in milliseconds Duration int32 `json:"duration"` } diff --git a/pkg/api/v1/testkube/model_test_suite_step_delay_extended.go b/pkg/api/v1/testkube/model_test_suite_step_delay_v2_extended.go similarity index 61% rename from pkg/api/v1/testkube/model_test_suite_step_delay_extended.go rename to pkg/api/v1/testkube/model_test_suite_step_delay_v2_extended.go index 111d0e4eb55..eada4293d36 100644 --- a/pkg/api/v1/testkube/model_test_suite_step_delay_extended.go +++ b/pkg/api/v1/testkube/model_test_suite_step_delay_v2_extended.go @@ -2,6 +2,6 @@ package testkube import "fmt" -func (s TestSuiteStepDelay) FullName() string { +func (s TestSuiteStepDelayV2) FullName() string { return fmt.Sprintf("delay %dms", s.Duration) } diff --git a/pkg/api/v1/testkube/model_test_suite_step_execute_test_extended.go b/pkg/api/v1/testkube/model_test_suite_step_execute_test_extended.go deleted file mode 100644 index 807e0f192fc..00000000000 --- a/pkg/api/v1/testkube/model_test_suite_step_execute_test_extended.go +++ /dev/null @@ -1,14 +0,0 @@ -package testkube - -import "fmt" - -func (s TestSuiteStepExecuteTest) FullName() string { - return fmt.Sprintf("run:%s/%s", s.Namespace, s.Name) -} - -func (s TestSuiteStepExecuteTest) GetObjectRef() *ObjectRef { - return &ObjectRef{ - Name: s.Name, - Namespace: s.Namespace, - } -} diff --git a/pkg/api/v1/testkube/model_test_suite_step_execute_test_base.go b/pkg/api/v1/testkube/model_test_suite_step_execute_test_v2.go similarity index 90% rename from pkg/api/v1/testkube/model_test_suite_step_execute_test_base.go rename to pkg/api/v1/testkube/model_test_suite_step_execute_test_v2.go index f7dc102d94c..12b1d10359f 100644 --- a/pkg/api/v1/testkube/model_test_suite_step_execute_test_base.go +++ b/pkg/api/v1/testkube/model_test_suite_step_execute_test_v2.go @@ -9,7 +9,7 @@ */ package testkube -type TestSuiteStepExecuteTest struct { +type TestSuiteStepExecuteTestV2 struct { // object kubernetes namespace Namespace string `json:"namespace,omitempty"` // object name diff --git a/pkg/api/v1/testkube/model_test_suite_step_execute_test_v2_extended.go b/pkg/api/v1/testkube/model_test_suite_step_execute_test_v2_extended.go new file mode 100644 index 00000000000..bbf16ca2573 --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_step_execute_test_v2_extended.go @@ -0,0 +1,13 @@ +package testkube + +import "fmt" + +func (s TestSuiteStepExecuteTestV2) FullName() string { + return fmt.Sprintf("run:%s", s.Name) +} + +func (s TestSuiteStepExecuteTestV2) GetObjectRef() *ObjectRef { + return &ObjectRef{ + Name: s.Name, + } +} diff --git a/pkg/api/v1/testkube/model_test_suite_step_execution_result_extended.go b/pkg/api/v1/testkube/model_test_suite_step_execution_result_extended.go index 6ebc6e488fb..e50a0f3f165 100644 --- a/pkg/api/v1/testkube/model_test_suite_step_execution_result_extended.go +++ b/pkg/api/v1/testkube/model_test_suite_step_execution_result_extended.go @@ -2,7 +2,7 @@ package testkube func NewTestStepQueuedResult(step *TestSuiteStep) (result TestSuiteStepExecutionResult) { result.Step = step - result.Execution = NewQueuedExecution() + result.Execution = NewQueuedExecution().WithID() return } diff --git a/pkg/api/v1/testkube/model_test_suite_step_execution_result_v2.go b/pkg/api/v1/testkube/model_test_suite_step_execution_result_v2.go new file mode 100644 index 00000000000..d0a27182f7b --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_step_execution_result_v2.go @@ -0,0 +1,17 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +// execution result returned from executor +type TestSuiteStepExecutionResultV2 struct { + Step *TestSuiteStepV2 `json:"step,omitempty"` + Test *ObjectRef `json:"test,omitempty"` + Execution *Execution `json:"execution,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_test_suite_step_extended.go b/pkg/api/v1/testkube/model_test_suite_step_extended.go index 5b4d30f4413..79bccf03769 100644 --- a/pkg/api/v1/testkube/model_test_suite_step_extended.go +++ b/pkg/api/v1/testkube/model_test_suite_step_extended.go @@ -1,10 +1,10 @@ package testkube func (s TestSuiteStep) Type() *TestSuiteStepType { - if s.Execute != nil { + if s.Test != "" { return TestSuiteStepTypeExecuteTest } - if s.Delay != nil { + if s.Delay != "" { return TestSuiteStepTypeDelay } return nil @@ -13,9 +13,9 @@ func (s TestSuiteStep) Type() *TestSuiteStepType { func (s TestSuiteStep) FullName() string { switch s.Type() { case TestSuiteStepTypeDelay: - return s.Delay.FullName() + return s.Delay case TestSuiteStepTypeExecuteTest: - return s.Execute.FullName() + return s.Test default: return "unknown" } diff --git a/pkg/api/v1/testkube/model_test_suite_step_v2.go b/pkg/api/v1/testkube/model_test_suite_step_v2.go new file mode 100644 index 00000000000..ae65e0dec00 --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_step_v2.go @@ -0,0 +1,16 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +type TestSuiteStepV2 struct { + StopTestOnFailure bool `json:"stopTestOnFailure"` + Execute *TestSuiteStepExecuteTestV2 `json:"execute,omitempty"` + Delay *TestSuiteStepDelayV2 `json:"delay,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_test_suite_step_v2_extended.go b/pkg/api/v1/testkube/model_test_suite_step_v2_extended.go new file mode 100644 index 00000000000..333bdacc803 --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_step_v2_extended.go @@ -0,0 +1,22 @@ +package testkube + +func (s TestSuiteStepV2) Type() *TestSuiteStepType { + if s.Execute != nil { + return TestSuiteStepTypeExecuteTest + } + if s.Delay != nil { + return TestSuiteStepTypeDelay + } + return nil +} + +func (s TestSuiteStepV2) FullName() string { + switch s.Type() { + case TestSuiteStepTypeDelay: + return s.Delay.FullName() + case TestSuiteStepTypeExecuteTest: + return s.Execute.FullName() + default: + return "unknown" + } +} diff --git a/pkg/api/v1/testkube/model_test_suite_update_request.go b/pkg/api/v1/testkube/model_test_suite_update_request.go index 14b3742280b..988026468e2 100644 --- a/pkg/api/v1/testkube/model_test_suite_update_request.go +++ b/pkg/api/v1/testkube/model_test_suite_update_request.go @@ -20,12 +20,12 @@ type TestSuiteUpdateRequest struct { // object name Name *string `json:"name"` Description *string `json:"description,omitempty"` - // Run this step before whole suite - Before *[]TestSuiteStep `json:"before,omitempty"` - // Steps to run - Steps *[]TestSuiteStep `json:"steps"` - // Run this step after whole suite - After *[]TestSuiteStep `json:"after,omitempty"` + // Run these batch steps before whole suite + Before *[]TestSuiteBatchStep `json:"before,omitempty"` + // Batch steps to run + Steps *[]TestSuiteBatchStep `json:"steps,omitempty"` + // Run these batch steps after whole suite + After *[]TestSuiteBatchStep `json:"after,omitempty"` // test suite labels Labels *map[string]string `json:"labels,omitempty"` // schedule to run test suite diff --git a/pkg/api/v1/testkube/model_test_suite_update_request_v2.go b/pkg/api/v1/testkube/model_test_suite_update_request_v2.go new file mode 100644 index 00000000000..50f6d4ba030 --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_update_request_v2.go @@ -0,0 +1,37 @@ +/* + * 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 + +import ( + "time" +) + +// test suite update body +type TestSuiteUpdateRequestV2 struct { + // object kubernetes namespace + Namespace *string `json:"namespace,omitempty"` + // object name + Name *string `json:"name"` + Description *string `json:"description,omitempty"` + // Run this step before whole suite + Before *[]TestSuiteStepV2 `json:"before,omitempty"` + // Steps to run + Steps *[]TestSuiteStepV2 `json:"steps,omitempty"` + // Run this step after whole suite + After *[]TestSuiteStepV2 `json:"after,omitempty"` + // test suite labels + Labels *map[string]string `json:"labels,omitempty"` + // schedule to run test suite + Schedule *string `json:"schedule,omitempty"` + Repeats *int32 `json:"repeats,omitempty"` + Created time.Time `json:"created,omitempty"` + ExecutionRequest **TestSuiteExecutionUpdateRequest `json:"executionRequest,omitempty"` + Status *TestSuiteStatus `json:"status"` +} diff --git a/pkg/api/v1/testkube/model_test_suite_update_request_v2_extended.go b/pkg/api/v1/testkube/model_test_suite_update_request_v2_extended.go new file mode 100644 index 00000000000..844be8e65fc --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_update_request_v2_extended.go @@ -0,0 +1,42 @@ +package testkube + +// ToTestSuiteUpdateRequest converts to TestSuiteUpdateRequest model +func (t *TestSuiteUpdateRequestV2) ToTestSuiteUpdateRequest() *TestSuiteUpdateRequest { + var before, steps, after *[]TestSuiteBatchStep + + if t.Before != nil { + before = &[]TestSuiteBatchStep{} + for _, step := range *t.Before { + *before = append(*before, *step.ToTestSuiteBatchStep()) + } + } + + if t.Steps != nil { + steps = &[]TestSuiteBatchStep{} + for _, step := range *t.Steps { + *steps = append(*steps, *step.ToTestSuiteBatchStep()) + } + } + + if t.After != nil { + after = &[]TestSuiteBatchStep{} + for _, step := range *t.After { + *after = append(*after, *step.ToTestSuiteBatchStep()) + } + } + + return &TestSuiteUpdateRequest{ + Name: t.Name, + Namespace: t.Namespace, + Description: t.Description, + Before: before, + Steps: steps, + After: after, + Labels: t.Labels, + Schedule: t.Schedule, + Repeats: t.Repeats, + Created: t.Created, + ExecutionRequest: t.ExecutionRequest, + Status: t.Status, + } +} diff --git a/pkg/api/v1/testkube/model_test_suite_upsert_request.go b/pkg/api/v1/testkube/model_test_suite_upsert_request.go index f483610bde0..a6f512f4c3b 100644 --- a/pkg/api/v1/testkube/model_test_suite_upsert_request.go +++ b/pkg/api/v1/testkube/model_test_suite_upsert_request.go @@ -20,12 +20,12 @@ type TestSuiteUpsertRequest struct { // object name Name string `json:"name"` Description string `json:"description,omitempty"` - // Run this step before whole suite - Before []TestSuiteStep `json:"before,omitempty"` - // Steps to run - Steps []TestSuiteStep `json:"steps"` - // Run this step after whole suite - After []TestSuiteStep `json:"after,omitempty"` + // Run these batch steps before whole suite + Before []TestSuiteBatchStep `json:"before,omitempty"` + // Batch steps to run + Steps []TestSuiteBatchStep `json:"steps,omitempty"` + // Run these batch steps after whole suite + After []TestSuiteBatchStep `json:"after,omitempty"` // test suite labels Labels map[string]string `json:"labels,omitempty"` // schedule to run test suite diff --git a/pkg/api/v1/testkube/model_test_suite_upsert_request_v2.go b/pkg/api/v1/testkube/model_test_suite_upsert_request_v2.go new file mode 100644 index 00000000000..368650a4199 --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_upsert_request_v2.go @@ -0,0 +1,37 @@ +/* + * 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 + +import ( + "time" +) + +// test suite create request body +type TestSuiteUpsertRequestV2 struct { + // object kubernetes namespace + Namespace string `json:"namespace"` + // object name + Name string `json:"name"` + Description string `json:"description,omitempty"` + // Run this step before whole suite + Before []TestSuiteStepV2 `json:"before,omitempty"` + // Steps to run + Steps []TestSuiteStepV2 `json:"steps,omitempty"` + // Run this step after whole suite + After []TestSuiteStepV2 `json:"after,omitempty"` + // test suite labels + Labels map[string]string `json:"labels,omitempty"` + // schedule to run test suite + Schedule string `json:"schedule,omitempty"` + Repeats int32 `json:"repeats,omitempty"` + Created time.Time `json:"created,omitempty"` + ExecutionRequest *TestSuiteExecutionRequest `json:"executionRequest,omitempty"` + Status *TestSuiteStatus `json:"status"` +} diff --git a/pkg/api/v1/testkube/model_test_suite_upsert_request_v2_exetended.go b/pkg/api/v1/testkube/model_test_suite_upsert_request_v2_exetended.go new file mode 100644 index 00000000000..c51ace003be --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_upsert_request_v2_exetended.go @@ -0,0 +1,57 @@ +package testkube + +import "time" + +// ToTestSuiteUpsertRequest converts to TestSuiteUpsertRequest model +func (t *TestSuiteUpsertRequestV2) ToTestSuiteUpsertRequest() *TestSuiteUpsertRequest { + var before, steps, after []TestSuiteBatchStep + for _, step := range t.Before { + before = append(before, *step.ToTestSuiteBatchStep()) + } + + for _, step := range t.Steps { + steps = append(steps, *step.ToTestSuiteBatchStep()) + } + + for _, step := range t.After { + after = append(after, *step.ToTestSuiteBatchStep()) + } + + return &TestSuiteUpsertRequest{ + Name: t.Name, + Namespace: t.Namespace, + Description: t.Description, + Before: before, + Steps: steps, + After: after, + Labels: t.Labels, + Schedule: t.Schedule, + Repeats: t.Repeats, + Created: t.Created, + ExecutionRequest: t.ExecutionRequest, + Status: t.Status, + } +} + +// ToTestSuiteBatchStep converts to ToTestSuiteBatchStep model +func (s *TestSuiteStepV2) ToTestSuiteBatchStep() *TestSuiteBatchStep { + var test string + if s.Execute != nil { + test = s.Execute.Name + } + + var delay string + if s.Delay != nil && s.Delay.Duration != 0 { + delay = time.Duration(s.Delay.Duration * int32(time.Millisecond)).String() + } + + return &TestSuiteBatchStep{ + StopOnFailure: s.StopTestOnFailure, + Execute: []TestSuiteStep{ + { + Test: test, + Delay: delay, + }, + }, + } +} diff --git a/pkg/api/v1/testkube/model_test_suite_v2.go b/pkg/api/v1/testkube/model_test_suite_v2.go new file mode 100644 index 00000000000..4758a7630e2 --- /dev/null +++ b/pkg/api/v1/testkube/model_test_suite_v2.go @@ -0,0 +1,34 @@ +/* + * 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 + +import ( + "time" +) + +type TestSuiteV2 struct { + Name string `json:"name"` + Namespace string `json:"namespace,omitempty"` + Description string `json:"description,omitempty"` + // Run this step before whole suite + Before []TestSuiteStepV2 `json:"before,omitempty"` + // Steps to run + Steps []TestSuiteStepV2 `json:"steps,omitempty"` + // Run this step after whole suite + After []TestSuiteStepV2 `json:"after,omitempty"` + // test suite labels + Labels map[string]string `json:"labels,omitempty"` + // schedule to run test suite + Schedule string `json:"schedule,omitempty"` + Repeats int32 `json:"repeats,omitempty"` + Created time.Time `json:"created,omitempty"` + ExecutionRequest *TestSuiteExecutionRequest `json:"executionRequest,omitempty"` + Status *TestSuiteStatus `json:"status"` +} diff --git a/pkg/api/v1/testkube/model_test_trigger_key_map.go b/pkg/api/v1/testkube/model_test_trigger_key_map.go index a63fbfe2988..9eb52cdbc2a 100644 --- a/pkg/api/v1/testkube/model_test_trigger_key_map.go +++ b/pkg/api/v1/testkube/model_test_trigger_key_map.go @@ -18,4 +18,6 @@ type TestTriggerKeyMap struct { Executions []string `json:"executions"` // mapping between resources and supported events Events map[string][]string `json:"events"` + // list of supported values for conditions + Conditions []string `json:"conditions,omitempty"` } 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 fb87195cfeb..742efad5fa4 100644 --- a/pkg/api/v1/testkube/model_test_upsert_request_extended.go +++ b/pkg/api/v1/testkube/model_test_upsert_request_extended.go @@ -22,6 +22,7 @@ func (test *TestUpsertRequest) QuoteTestTextFields() { &test.ExecutionRequest.JobTemplate, &test.ExecutionRequest.CronJobTemplate, &test.ExecutionRequest.PreRunScript, + &test.ExecutionRequest.PostRunScript, &test.ExecutionRequest.ScraperTemplate, } diff --git a/pkg/api/v1/testkube/model_webhook.go b/pkg/api/v1/testkube/model_webhook.go index 93f4e41ba26..87472bcde21 100644 --- a/pkg/api/v1/testkube/model_webhook.go +++ b/pkg/api/v1/testkube/model_webhook.go @@ -19,6 +19,10 @@ type Webhook struct { Selector string `json:"selector,omitempty"` // will load the generated payload for notification inside the object PayloadObjectField string `json:"payloadObjectField,omitempty"` + // golang based template for notification payload + PayloadTemplate string `json:"payloadTemplate,omitempty"` + // webhook headers + 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 21429b74a32..cad406d497d 100644 --- a/pkg/api/v1/testkube/model_webhook_create_request.go +++ b/pkg/api/v1/testkube/model_webhook_create_request.go @@ -19,6 +19,10 @@ type WebhookCreateRequest struct { Selector string `json:"selector,omitempty"` // will load the generated payload for notification inside the object PayloadObjectField string `json:"payloadObjectField,omitempty"` + // golang based template for notification payload + PayloadTemplate string `json:"payloadTemplate,omitempty"` + // webhook headers + Headers map[string]string `json:"headers,omitempty"` // webhook labels Labels map[string]string `json:"labels,omitempty"` } diff --git a/pkg/cloudlogin/login.go b/pkg/cloudlogin/login.go index 258b20ffa82..ca26a2668f8 100644 --- a/pkg/cloudlogin/login.go +++ b/pkg/cloudlogin/login.go @@ -15,7 +15,12 @@ const ( redirectURL = "http://127.0.0.1:8090/callback" ) -func CloudLogin(ctx context.Context, providerURL string) (string, chan string, error) { +type Tokens struct { + IDToken string + RefreshToken string +} + +func CloudLogin(ctx context.Context, providerURL string) (string, chan Tokens, error) { provider, err := oidc.NewProvider(ctx, providerURL) if err != nil { return "", nil, err @@ -25,7 +30,7 @@ func CloudLogin(ctx context.Context, providerURL string) (string, chan string, e ClientID: clientID, Endpoint: provider.Endpoint(), RedirectURL: redirectURL, - Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, + Scopes: []string{oidc.ScopeOpenID, "profile", "email", "offline_access"}, } // Start a local server to handle the callback from the OIDC provider. @@ -44,7 +49,7 @@ func CloudLogin(ctx context.Context, providerURL string) (string, chan string, e // Redirect the user to the OIDC provider's login page. authURL := oauth2Config.AuthCodeURL("state", oauth2.AccessTypeOffline) - respCh := make(chan string) + respCh := make(chan Tokens) go func() { // Wait for the user to authorize the client and retrieve the authorization code. @@ -54,30 +59,53 @@ func CloudLogin(ctx context.Context, providerURL string) (string, chan string, e token, err := oauth2Config.Exchange(ctx, code) if err != nil { fmt.Fprintf(os.Stderr, "Failed to retrieve token: %v\n", err) - respCh <- "" + respCh <- Tokens{} return } // Initialize the OIDC verifier with the provider's public keys. verifier := provider.Verifier(&oidc.Config{ClientID: clientID}) - // Verify the ID token. - rawIDToken, ok := token.Extra("id_token").(string) - if !ok { - fmt.Fprintf(os.Stderr, "No ID token found in OAuth2 token.\n") - respCh <- "" - return - } - - _, err = verifier.Verify(ctx, rawIDToken) + _, err = verifier.Verify(ctx, token.AccessToken) if err != nil { fmt.Fprintf(os.Stderr, "Failed to verify ID token: %v\n", err) - respCh <- "" + respCh <- Tokens{} return } - respCh <- rawIDToken + respCh <- Tokens{ + IDToken: token.AccessToken, + RefreshToken: token.RefreshToken, + } }() return authURL, respCh, nil } + +func CheckAndRefreshToken(ctx context.Context, providerURL, rawIDToken, refreshToken string) (string, string, error) { + provider, err := oidc.NewProvider(context.Background(), providerURL) + if err != nil { + return "", "", err + } + verifier := provider.Verifier(&oidc.Config{ClientID: clientID}) + _, err = verifier.Verify(ctx, rawIDToken) + if err != nil { + // Token is expired. Refresh it. + oauth2Config := oauth2.Config{ + ClientID: clientID, + Endpoint: provider.Endpoint(), + Scopes: []string{oidc.ScopeOpenID, "profile", "email", "offline_access"}, + } + + tokenSource := oauth2Config.TokenSource(ctx, &oauth2.Token{ + RefreshToken: refreshToken, + }) + token, err := tokenSource.Token() + if err != nil { + return "", "", err + } + + return token.AccessToken, token.RefreshToken, nil + } + return rawIDToken, refreshToken, nil +} diff --git a/pkg/crd/crd_test.go b/pkg/crd/crd_test.go index eab294f737c..93f0eab4fcf 100644 --- a/pkg/crd/crd_test.go +++ b/pkg/crd/crd_test.go @@ -12,7 +12,7 @@ func TestGenerateYAML(t *testing.T) { t.Run("generate single CRD yaml", func(t *testing.T) { // given - expected := "apiVersion: executor.testkube.io/v1\nkind: Webhook\nmetadata:\n name: name1\n namespace: namespace1\n labels:\n key1: value1\nspec:\n events:\n - start-test\n uri: http://localhost\n selector: app=backend\n payloadObjectField: text\n" + 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" webhooks := []testkube.Webhook{ { Name: "name1", @@ -22,6 +22,8 @@ func TestGenerateYAML(t *testing.T) { Selector: "app=backend", Labels: map[string]string{"key1": "value1"}, PayloadObjectField: "text", + PayloadTemplate: "{{ .Id }}", + Headers: map[string]string{"Content-Type": "appication/xml"}, }, } @@ -35,7 +37,7 @@ func TestGenerateYAML(t *testing.T) { t.Run("generate multiple CRDs yaml", func(t *testing.T) { // given - expected := "apiVersion: executor.testkube.io/v1\nkind: Webhook\nmetadata:\n name: name1\n namespace: namespace1\n labels:\n key1: value1\nspec:\n events:\n - start-test\n uri: http://localhost\n selector: app=backend\n payloadObjectField: text\n\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" + 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" webhooks := []testkube.Webhook{ { Name: "name1", @@ -45,6 +47,8 @@ func TestGenerateYAML(t *testing.T) { Selector: "app=backend", Labels: map[string]string{"key1": "value1"}, PayloadObjectField: "text", + PayloadTemplate: "{{ .Id }}", + Headers: map[string]string{"Content-Type": "appication/xml"}, }, { Name: "name2", @@ -54,6 +58,8 @@ func TestGenerateYAML(t *testing.T) { Selector: "app=backend", Labels: map[string]string{"key2": "value2"}, PayloadObjectField: "text", + PayloadTemplate: "{{ .Id }}", + Headers: map[string]string{"Content-Type": "appication/xml"}, }, } diff --git a/pkg/crd/templates/test.tmpl b/pkg/crd/templates/test.tmpl index 70d95e7faea..225ebfb0178 100644 --- a/pkg/crd/templates/test.tmpl +++ b/pkg/crd/templates/test.tmpl @@ -79,7 +79,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.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.CronJobTemplate) (.ExecutionRequest.PreRunScript) (.ExecutionRequest.PostRunScript) (.ExecutionRequest.ScraperTemplate) (ne (len .ExecutionRequest.EnvConfigMaps) 0) (ne (len .ExecutionRequest.EnvSecrets) 0) }} executionRequest: {{- if .ExecutionRequest.Name }} name: {{ .ExecutionRequest.Name }} @@ -195,6 +195,9 @@ spec: {{- if .ExecutionRequest.PreRunScript }} preRunScript: {{ .ExecutionRequest.PreRunScript }} {{- end }} + {{- if .ExecutionRequest.PostRunScript }} + postRunScript: {{ .ExecutionRequest.PostRunScript }} + {{- end }} {{- if .ExecutionRequest.ScraperTemplate }} scraperTemplate: {{ .ExecutionRequest.ScraperTemplate }} {{- end }} diff --git a/pkg/crd/templates/testsuite.tmpl b/pkg/crd/templates/testsuite.tmpl index 46aabdc80c1..bb48c772cec 100644 --- a/pkg/crd/templates/testsuite.tmpl +++ b/pkg/crd/templates/testsuite.tmpl @@ -1,4 +1,4 @@ -apiVersion: tests.testkube.io/v2 +apiVersion: tests.testkube.io/v3 kind: TestSuite metadata: name: {{ .Name }} @@ -16,63 +16,51 @@ spec: {{- if ne (len .Before) 0 }} before: {{- range .Before }} - {{- if .Execute }} - - execute: - stopOnFailure: {{ .StopTestOnFailure }} - {{- if .Execute.Namespace }} - namespace: {{ .Execute.Namespace }} + - stopOnFailure: {{ .StopOnFailure }} + {{- if ne (len .Execute) 0 }} + execute: + {{- range .Execute }} + {{- if .Test }} + - test: {{ .Test }} {{- end }} - {{- if .Execute.Name}} - name: {{ .Execute.Name }} + {{- if .Delay }} + - delay: {{ .Delay }} {{- end }} {{- end }} - {{- if .Delay }} - - delay: - {{- if .Delay.Duration }} - duration: {{ .Delay.Duration }} - {{- end }} {{- end }} {{- end }} {{- end }} {{- if ne (len .Steps) 0 }} steps: {{- range .Steps }} - {{- if .Execute }} - - execute: - stopOnFailure: {{ .StopTestOnFailure }} - {{- if .Execute.Namespace }} - namespace: {{ .Execute.Namespace }} + - stopOnFailure: {{ .StopOnFailure }} + {{- if ne (len .Execute) 0 }} + execute: + {{- range .Execute }} + {{- if .Test }} + - test: {{ .Test }} {{- end }} - {{- if .Execute.Name }} - name: {{ .Execute.Name }} + {{- if .Delay }} + - delay: {{ .Delay }} {{- end }} {{- end }} - {{- if .Delay }} - - delay: - {{- if .Delay.Duration }} - duration: {{ .Delay.Duration }} - {{- end}} {{- end }} {{- end }} {{- end }} {{- if ne (len .After) 0 }} after: {{- range .After }} - {{- if .Execute }} - - execute: - stopOnFailure: {{ .StopTestOnFailure }} - {{- if .Execute.Namespace }} - namespace: {{ .Execute.Namespace }} + - stopOnFailure: {{ .StopOnFailure }} + {{- if ne (len .Execute) 0 }} + execute: + {{- range .Execute }} + {{- if .Test }} + - test: {{ .Test }} {{- end }} - {{- if .Execute.Name }} - name: {{ .Execute.Name }} + {{- if .Delay }} + - delay: {{ .Delay }} {{- end }} {{- end }} - {{- if .Delay }} - - delay: - {{- if .Delay.Duration }} - duration: {{ .Delay.Duration }} - {{- end }} {{- end }} {{- end }} {{- end }} diff --git a/pkg/crd/templates/webhook.tmpl b/pkg/crd/templates/webhook.tmpl index c4d5c3976ec..8302e9a7523 100644 --- a/pkg/crd/templates/webhook.tmpl +++ b/pkg/crd/templates/webhook.tmpl @@ -25,3 +25,12 @@ spec: {{- if .PayloadObjectField }} payloadObjectField: {{ .PayloadObjectField }} {{- end }} + {{- if .PayloadTemplate }} + payloadTemplate: {{ .PayloadTemplate }} + {{- end }} + {{- if ne (len .Headers) 0 }} + headers: + {{- range $key, $value := .Headers }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} diff --git a/pkg/event/emitter.go b/pkg/event/emitter.go index 5cdb7bcf6b3..8d8f6856e32 100644 --- a/pkg/event/emitter.go +++ b/pkg/event/emitter.go @@ -20,24 +20,26 @@ const ( ) // NewEmitter returns new emitter instance -func NewEmitter(eventBus bus.Bus) *Emitter { +func NewEmitter(eventBus bus.Bus, clusterName string) *Emitter { return &Emitter{ - Results: make(chan testkube.EventResult, eventsBuffer), - Log: log.DefaultLogger, - Loader: NewLoader(), - Bus: eventBus, - Listeners: make(common.Listeners, 0), + Results: make(chan testkube.EventResult, eventsBuffer), + Log: log.DefaultLogger, + Loader: NewLoader(), + Bus: eventBus, + Listeners: make(common.Listeners, 0), + ClusterName: clusterName, } } // Emitter handles events emitting for webhooks type Emitter struct { - Results chan testkube.EventResult - Listeners common.Listeners - Loader *Loader - Log *zap.SugaredLogger - mutex sync.Mutex - Bus bus.Bus + Results chan testkube.EventResult + Listeners common.Listeners + Loader *Loader + Log *zap.SugaredLogger + mutex sync.Mutex + Bus bus.Bus + ClusterName string } // Register adds new listener @@ -124,6 +126,7 @@ func (e *Emitter) UpdateListeners(listeners common.Listeners) { // Notify notifies emitter with webhook func (e *Emitter) Notify(event testkube.Event) { + event.ClusterName = e.ClusterName err := e.Bus.PublishTopic(event.Topic(), event) e.Log.Infow("event published", append(event.Log(), "error", err)...) } diff --git a/pkg/event/emitter_integration_test.go b/pkg/event/emitter_integration_test.go index de133faf264..c1750c624a1 100644 --- a/pkg/event/emitter_integration_test.go +++ b/pkg/event/emitter_integration_test.go @@ -24,7 +24,7 @@ func GetTestNATSEmitter() *Emitter { if err != nil { panic(err) } - return NewEmitter(bus.NewNATSBus(nc)) + return NewEmitter(bus.NewNATSBus(nc), "") } func TestEmitter_NATS_Register_Integration(t *testing.T) { diff --git a/pkg/event/emitter_test.go b/pkg/event/emitter_test.go index a82a93966f5..62882b40e92 100644 --- a/pkg/event/emitter_test.go +++ b/pkg/event/emitter_test.go @@ -25,7 +25,7 @@ func TestEmitter_Register(t *testing.T) { t.Parallel() // given eventBus := bus.NewEventBusMock() - emitter := NewEmitter(eventBus) + emitter := NewEmitter(eventBus, "") // when emitter.Register(&dummy.DummyListener{Id: "l1"}) @@ -43,7 +43,7 @@ func TestEmitter_Listen(t *testing.T) { t.Parallel() // given eventBus := bus.NewEventBusMock() - emitter := NewEmitter(eventBus) + emitter := NewEmitter(eventBus, "") // given listener with matching selector listener1 := &dummy.DummyListener{Id: "l1", SelectorString: "type=listener1"} // and listener with second matic selector @@ -97,7 +97,7 @@ func TestEmitter_Notify(t *testing.T) { t.Parallel() // given eventBus := bus.NewEventBusMock() - emitter := NewEmitter(eventBus) + emitter := NewEmitter(eventBus, "") // and 2 listeners subscribed to the same queue // * first on pod1 listener1 := &dummy.DummyListener{Id: "l3"} @@ -131,7 +131,7 @@ func TestEmitter_Reconcile(t *testing.T) { t.Parallel() // given first reconciler loop was done eventBus := bus.NewEventBusMock() - emitter := NewEmitter(eventBus) + emitter := NewEmitter(eventBus, "") emitter.Loader.Register(&dummy.DummyLoader{IdPrefix: "dummy1"}) emitter.Loader.Register(&dummy.DummyLoader{IdPrefix: "dummy2"}) @@ -185,7 +185,7 @@ func TestEmitter_UpdateListeners(t *testing.T) { t.Parallel() // given eventBus := bus.NewEventBusMock() - emitter := NewEmitter(eventBus) + emitter := NewEmitter(eventBus, "") // given listener with matching selector listener1 := &dummy.DummyListener{Id: "l1", SelectorString: "type=listener1"} // and listener with second matching selector diff --git a/pkg/event/kind/slack/loader.go b/pkg/event/kind/slack/loader.go index 70d87b99d96..1944a81a546 100644 --- a/pkg/event/kind/slack/loader.go +++ b/pkg/event/kind/slack/loader.go @@ -13,12 +13,12 @@ import ( var _ common.ListenerLoader = (*SlackLoader)(nil) -func NewSlackLoader(messageTemplate, configString string, events []testkube.EventType) *SlackLoader { +func NewSlackLoader(messageTemplate, configString, clusterName string, events []testkube.EventType) *SlackLoader { var config []slack.NotificationsConfig if err := json.Unmarshal([]byte(configString), &config); err != nil { log.DefaultLogger.Errorw("error unmarshalling slack config", "error", err) } - slackNotifier := slack.NewNotifier(messageTemplate, config) + slackNotifier := slack.NewNotifier(messageTemplate, clusterName, config) return &SlackLoader{ Log: log.DefaultLogger, events: events, diff --git a/pkg/event/kind/slack/loader_test.go b/pkg/event/kind/slack/loader_test.go index 477a85c39f3..5afb3bfd419 100644 --- a/pkg/event/kind/slack/loader_test.go +++ b/pkg/event/kind/slack/loader_test.go @@ -14,7 +14,7 @@ func TestSlackLoader_Load(t *testing.T) { t.Parallel() // given // default slack notifier is not ready by default - l := NewSlackLoader("", "", testkube.AllEventTypes) + l := NewSlackLoader("", "", "", testkube.AllEventTypes) // when listeners, err := l.Load() diff --git a/pkg/event/kind/webhook/listener.go b/pkg/event/kind/webhook/listener.go index dd3bda20049..30f5faef644 100644 --- a/pkg/event/kind/webhook/listener.go +++ b/pkg/event/kind/webhook/listener.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "text/template" "github.com/pkg/errors" "go.uber.org/zap" @@ -18,7 +19,8 @@ import ( var _ common.Listener = (*WebhookListener)(nil) -func NewWebhookListener(name, uri, selector string, events []testkube.EventType, payloadObjectField string) *WebhookListener { +func NewWebhookListener(name, uri, selector string, events []testkube.EventType, + payloadObjectField, payloadTemplate string, headers map[string]string) *WebhookListener { return &WebhookListener{ name: name, Uri: uri, @@ -27,6 +29,8 @@ func NewWebhookListener(name, uri, selector string, events []testkube.EventType, selector: selector, events: events, payloadObjectField: payloadObjectField, + payloadTemplate: payloadTemplate, + headers: headers, } } @@ -38,6 +42,8 @@ type WebhookListener struct { events []testkube.EventType selector string payloadObjectField string + payloadTemplate string + headers map[string]string } func (l *WebhookListener) Name() string { @@ -58,6 +64,8 @@ func (l *WebhookListener) Metadata() map[string]string { "selector": l.selector, "events": fmt.Sprintf("%v", l.events), "payloadObjectField": l.payloadObjectField, + "payloadTemplate": l.payloadTemplate, + "headers": fmt.Sprintf("%v", l.headers), } } @@ -65,17 +73,43 @@ func (l *WebhookListener) PayloadObjectField() string { return l.payloadObjectField } +func (l *WebhookListener) PayloadTemplate() string { + return l.payloadTemplate +} + +func (l *WebhookListener) Headers() map[string]string { + return l.headers +} + func (l *WebhookListener) Notify(event testkube.Event) (result testkube.EventResult) { body := bytes.NewBuffer([]byte{}) - err := json.NewEncoder(body).Encode(event) - if err == nil && l.payloadObjectField != "" { - data := map[string]string{l.payloadObjectField: string(body.Bytes())} - body.Reset() - err = json.NewEncoder(body).Encode(data) - } - log := l.Log.With(event.Log()...) + var err error + if l.payloadTemplate != "" { + var tmpl *template.Template + tmpl, err = template.New("webhook").Parse(l.payloadTemplate) + 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()) + } else { + err = json.NewEncoder(body).Encode(event) + if err == nil && l.payloadObjectField != "" { + data := map[string]string{l.payloadObjectField: string(body.Bytes())} + body.Reset() + err = json.NewEncoder(body).Encode(data) + } + } + if err != nil { err = errors.Wrap(err, "webhook send json encode error") log.Errorw("webhook send json encode error", "error", err) @@ -89,6 +123,10 @@ func (l *WebhookListener) Notify(event testkube.Event) (result testkube.EventRes } request.Header.Set("Content-Type", "application/json") + for key, value := range l.headers { + request.Header.Set(key, value) + } + resp, err := l.HttpClient.Do(request) if err != nil { log.Errorw("webhook send error", "error", err) diff --git a/pkg/event/kind/webhook/listener_test.go b/pkg/event/kind/webhook/listener_test.go index 83fbce81e73..974fa6aa4d2 100644 --- a/pkg/event/kind/webhook/listener_test.go +++ b/pkg/event/kind/webhook/listener_test.go @@ -3,6 +3,7 @@ package webhook import ( "bytes" "encoding/json" + "io" "net/http" "net/http/httptest" "testing" @@ -32,7 +33,7 @@ func TestWebhookListener_Notify(t *testing.T) { svr := httptest.NewServer(testHandler) defer svr.Close() - l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "") + l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "", nil) // when r := l.Notify(testkube.Event{ @@ -54,7 +55,7 @@ func TestWebhookListener_Notify(t *testing.T) { svr := httptest.NewServer(testHandler) defer svr.Close() - l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "") + l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "", nil) // when r := l.Notify(testkube.Event{ @@ -71,7 +72,7 @@ func TestWebhookListener_Notify(t *testing.T) { t.Parallel() // given - s := NewWebhookListener("l1", "http://baduri.badbadbad", "", testEventTypes, "") + s := NewWebhookListener("l1", "http://baduri.badbadbad", "", testEventTypes, "", "", nil) // when r := s.Notify(testkube.Event{ @@ -104,7 +105,7 @@ func TestWebhookListener_Notify(t *testing.T) { svr := httptest.NewServer(testHandler) defer svr.Close() - l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "field") + l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "field", "", nil) // when r := l.Notify(testkube.Event{ @@ -115,6 +116,33 @@ func TestWebhookListener_Notify(t *testing.T) { assert.Equal(t, "", r.Error()) }) + + t.Run("send event success response using payload template", func(t *testing.T) { + t.Parallel() + // given + testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) + + // then + assert.Equal(t, "{\"id\": \"12345\"}", string(body)) + }) + + svr := httptest.NewServer(testHandler) + defer svr.Close() + + l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "{\"id\": \"{{ .Id }}\"}", map[string]string{"Content-Type": "application/json"}) + + // when + r := l.Notify(testkube.Event{ + Id: "12345", + Type_: testkube.EventStartTest, + TestExecution: exampleExecution(), + }) + + assert.Equal(t, "", r.Error()) + + }) } func exampleExecution() *testkube.Execution { diff --git a/pkg/event/kind/webhook/loader.go b/pkg/event/kind/webhook/loader.go index 89fc4b8320d..a47d708590f 100644 --- a/pkg/event/kind/webhook/loader.go +++ b/pkg/event/kind/webhook/loader.go @@ -40,7 +40,7 @@ func (r WebhooksLoader) Load() (listeners common.Listeners, err error) { for _, webhook := range webhookList.Items { 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)) + listeners = append(listeners, NewWebhookListener(name, webhook.Spec.Uri, webhook.Spec.Selector, types, webhook.Spec.PayloadObjectField, webhook.Spec.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 ebbecb69417..9c137b148eb 100644 --- a/pkg/event/kind/webhook/loader_test.go +++ b/pkg/event/kind/webhook/loader_test.go @@ -14,7 +14,7 @@ type DummyLoader struct { func (l DummyLoader) List(selector string) (*executorsv1.WebhookList, error) { return &executorsv1.WebhookList{ Items: []executorsv1.Webhook{ - {Spec: executorsv1.WebhookSpec{Uri: "http://localhost:3333", Events: []executorsv1.EventType{"start-test"}, PayloadObjectField: "text"}}, + {Spec: executorsv1.WebhookSpec{Uri: "http://localhost:3333", Events: []executorsv1.EventType{"start-test"}, PayloadObjectField: "text", PayloadTemplate: "{{ .Id }}", Headers: map[string]string{"Content-Type": "application/xml"}}}, }, }, nil } diff --git a/pkg/executor/agent/agent.go b/pkg/executor/agent/agent.go index f2f160467a0..b5d1a68b32b 100644 --- a/pkg/executor/agent/agent.go +++ b/pkg/executor/agent/agent.go @@ -56,16 +56,26 @@ func Run(ctx context.Context, r runner.Runner, args []string) { } if r.GetType().IsMain() && e.PreRunScript != "" { - output.PrintEvent("running script", e.Id) + output.PrintEvent("running prerun script", e.Id) - if err = runScript(e.PreRunScript); err != nil { - output.PrintError(os.Stderr, err) + if serr := runScript(e.PreRunScript); serr != nil { + output.PrintError(os.Stderr, serr) os.Exit(1) } } output.PrintEvent("running test", e.Id) result, err := r.Run(ctx, e) + + if r.GetType().IsMain() && e.PostRunScript != "" { + output.PrintEvent("running postrun script", e.Id) + + if serr := runScript(e.PostRunScript); serr != nil { + output.PrintError(os.Stderr, serr) + os.Exit(1) + } + } + if err != nil { output.PrintError(os.Stderr, err) os.Exit(1) @@ -75,7 +85,7 @@ func Run(ctx context.Context, r runner.Runner, args []string) { } func runScript(body string) error { - scriptFile, err := os.CreateTemp("", "prerun*.sh") + scriptFile, err := os.CreateTemp("", "runscript*.sh") if err != nil { return err } diff --git a/pkg/keymap/triggers/triggers.go b/pkg/keymap/triggers/triggers.go index 40c50933e39..49db9299119 100644 --- a/pkg/keymap/triggers/triggers.go +++ b/pkg/keymap/triggers/triggers.go @@ -7,6 +7,7 @@ type KeyMap struct { Actions []string `json:"actions"` Executions []string `json:"executions"` Events map[string][]string `json:"events"` + Conditions []string `json:"conditions"` } func NewKeyMap() *KeyMap { @@ -15,6 +16,7 @@ func NewKeyMap() *KeyMap { Actions: testtrigger.GetSupportedActions(), Executions: testtrigger.GetSupportedExecutions(), Events: getSupportedEvents(), + Conditions: testtrigger.GetSupportedConditions(), } } diff --git a/pkg/mapper/cdevents/mapper.go b/pkg/mapper/cdevents/mapper.go index f2a472de0a8..1245a9d3197 100644 --- a/pkg/mapper/cdevents/mapper.go +++ b/pkg/mapper/cdevents/mapper.go @@ -320,6 +320,14 @@ func MapTestkubeEventFinishTestSuiteToCDEvent(event testkube.Event, clusterID, d } } + for _, batch := range event.TestSuiteExecution.ExecuteStepResults { + for _, result := range batch.Execute { + if result.Execution != nil && result.Execution.ExecutionResult != nil { + errs = append(errs, result.Execution.ExecutionResult.ErrorMessage) + } + } + } + ev.SetSubjectReason(strings.Join(errs, ",")) } @@ -332,6 +340,14 @@ func MapTestkubeEventFinishTestSuiteToCDEvent(event testkube.Event, clusterID, d } } + for _, batch := range event.TestSuiteExecution.ExecuteStepResults { + for _, result := range batch.Execute { + if result.Execution != nil && result.Execution.ExecutionResult != nil { + errs = append(errs, result.Execution.ExecutionResult.ErrorMessage) + } + } + } + ev.SetSubjectReason(strings.Join(errs, ",")) } diff --git a/pkg/mapper/cdevents/mapper_test.go b/pkg/mapper/cdevents/mapper_test.go index 39572340c7a..31ede996876 100644 --- a/pkg/mapper/cdevents/mapper_test.go +++ b/pkg/mapper/cdevents/mapper_test.go @@ -415,9 +415,13 @@ func TestMapTestkubeEventFinishTestSuiteToCDEvent(t *testing.T) { Type_: "scheduler", }, Status: testkube.TestSuiteExecutionStatusFailed, - StepResults: []testkube.TestSuiteStepExecutionResult{ + ExecuteStepResults: []testkube.TestSuiteBatchStepExecutionResult{ { - Execution: &execution, + Execute: []testkube.TestSuiteStepExecutionResult{ + { + Execution: &execution, + }, + }, }, }, }, diff --git a/pkg/mapper/executions/summary_test.go b/pkg/mapper/executions/summary_test.go index 705ef03e708..2befcaa223e 100644 --- a/pkg/mapper/executions/summary_test.go +++ b/pkg/mapper/executions/summary_test.go @@ -35,6 +35,7 @@ func getExecutions() testkube.Executions { ex1 := new(testkube.ExecutionResult) execution1 := testkube.NewExecution( + "", "testkube", "script1", "testsuite1", @@ -53,6 +54,7 @@ func getExecutions() testkube.Executions { ex2 := new(testkube.ExecutionResult) execution2 := testkube.NewExecution( + "", "testkube", "script1", "testsuite1", 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/keymap/triggers/kube_openapi.go b/pkg/mapper/keymap/triggers/kube_openapi.go index 6aed0341df0..cfb286b4520 100644 --- a/pkg/mapper/keymap/triggers/kube_openapi.go +++ b/pkg/mapper/keymap/triggers/kube_openapi.go @@ -11,5 +11,6 @@ func MapTestTriggerKeyMapToAPI(km *triggers.KeyMap) *testkube.TestTriggerKeyMap Actions: km.Actions, Executions: km.Executions, Events: km.Events, + Conditions: km.Conditions, } } diff --git a/pkg/mapper/tests/kube_openapi.go b/pkg/mapper/tests/kube_openapi.go index 4570442294c..edcead54a6c 100644 --- a/pkg/mapper/tests/kube_openapi.go +++ b/pkg/mapper/tests/kube_openapi.go @@ -150,6 +150,7 @@ func MapExecutionRequestFromSpec(specExecutionRequest *testsv3.ExecutionRequest) JobTemplate: specExecutionRequest.JobTemplate, CronJobTemplate: specExecutionRequest.CronJobTemplate, PreRunScript: specExecutionRequest.PreRunScript, + PostRunScript: specExecutionRequest.PostRunScript, ScraperTemplate: specExecutionRequest.ScraperTemplate, NegativeTest: specExecutionRequest.NegativeTest, EnvConfigMaps: MapEnvReferences(specExecutionRequest.EnvConfigMaps), @@ -206,3 +207,266 @@ 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.PostRunScript, + &executionRequest.PostRunScript, + }, + { + &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/tests/openapi_kube.go b/pkg/mapper/tests/openapi_kube.go index 75bfeaa614d..e09c5539fe3 100644 --- a/pkg/mapper/tests/openapi_kube.go +++ b/pkg/mapper/tests/openapi_kube.go @@ -162,6 +162,7 @@ func MapExecutionRequestToSpecExecutionRequest(executionRequest *testkube.Execut JobTemplate: executionRequest.JobTemplate, CronJobTemplate: executionRequest.CronJobTemplate, PreRunScript: executionRequest.PreRunScript, + PostRunScript: executionRequest.PostRunScript, ScraperTemplate: executionRequest.ScraperTemplate, NegativeTest: executionRequest.NegativeTest, EnvConfigMaps: mapEnvReferences(executionRequest.EnvConfigMaps), @@ -460,6 +461,10 @@ func MapExecutionUpdateRequestToSpecExecutionRequest(executionRequest *testkube. executionRequest.PreRunScript, &request.PreRunScript, }, + { + executionRequest.PostRunScript, + &request.PostRunScript, + }, { executionRequest.CronJobTemplate, &request.CronJobTemplate, @@ -636,3 +641,25 @@ func MapExecutionToTestStatus(execution *testkube.Execution) (specStatus testsv3 return specStatus } + +// MapTestSuiteExecutionStatusToExecutionStatus maps test suite execution status to execution status +func MapTestSuiteExecutionStatusToExecutionStatus(testSuiteStatus *testkube.TestSuiteExecutionStatus) ( + testStatus *testkube.ExecutionStatus) { + switch testSuiteStatus { + case testkube.TestSuiteExecutionStatusAborted: + testStatus = testkube.ExecutionStatusAborted + case testkube.TestSuiteExecutionStatusTimeout: + testStatus = testkube.ExecutionStatusTimeout + case testkube.TestSuiteExecutionStatusRunning: + testStatus = testkube.ExecutionStatusRunning + case testkube.TestSuiteExecutionStatusQueued: + testStatus = testkube.ExecutionStatusQueued + case testkube.TestSuiteExecutionStatusFailed: + testStatus = testkube.ExecutionStatusFailed + case testkube.TestSuiteExecutionStatusPassed: + testStatus = testkube.ExecutionStatusPassed + + } + + return testStatus +} 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/testsuiteexecutions/summary.go b/pkg/mapper/testsuiteexecutions/summary.go index fe46cb46fc8..a768b30ebf5 100644 --- a/pkg/mapper/testsuiteexecutions/summary.go +++ b/pkg/mapper/testsuiteexecutions/summary.go @@ -9,38 +9,24 @@ func MapToSummary(execution *testkube.TestSuiteExecution) *testkube.TestSuiteExe testSuiteName = execution.TestSuite.Name } - var summary []testkube.TestSuiteStepExecutionSummary - for _, step := range execution.StepResults { - var id, name, testName string - var status *testkube.ExecutionStatus - var tp *testkube.TestSuiteStepType - if step.Execution != nil { - id = step.Execution.Id - name = step.Execution.Name - testName = step.Execution.TestName - - if step.Execution.ExecutionResult != nil { - status = step.Execution.ExecutionResult.Status - } - } + var summary []testkube.TestSuiteBatchStepExecutionSummary - if step.Step != nil { - if step.Step.Execute != nil { - tp = testkube.TestSuiteStepTypeExecuteTest - } - - if step.Step.Delay != nil { - tp = testkube.TestSuiteStepTypeDelay + if len(execution.StepResults) != 0 { + summary = make([]testkube.TestSuiteBatchStepExecutionSummary, len(execution.StepResults)) + for i, step := range execution.StepResults { + summary[i] = testkube.TestSuiteBatchStepExecutionSummary{ + Execute: []testkube.TestSuiteStepExecutionSummary{ + mapStepExecutionResultV2ToExecutionSummary(step), + }, } } + } - summary = append(summary, testkube.TestSuiteStepExecutionSummary{ - Id: id, - Name: name, - TestName: testName, - Status: status, - Type_: tp, - }) + if len(execution.ExecuteStepResults) != 0 { + summary = make([]testkube.TestSuiteBatchStepExecutionSummary, len(execution.ExecuteStepResults)) + for i, step := range execution.ExecuteStepResults { + summary[i] = mapBatchStepExecutionResultToExecutionSummary(step) + } } return &testkube.TestSuiteExecutionSummary{ @@ -56,3 +42,80 @@ func MapToSummary(execution *testkube.TestSuiteExecution) *testkube.TestSuiteExe Labels: execution.Labels, } } + +func mapStepExecutionResultV2ToExecutionSummary(step testkube.TestSuiteStepExecutionResultV2) testkube.TestSuiteStepExecutionSummary { + var id, name, testName string + var status *testkube.ExecutionStatus + var tp *testkube.TestSuiteStepType + if step.Execution != nil { + id = step.Execution.Id + name = step.Execution.Name + testName = step.Execution.TestName + + if step.Execution.ExecutionResult != nil { + status = step.Execution.ExecutionResult.Status + } + } + + if step.Step != nil { + if step.Step.Execute != nil { + tp = testkube.TestSuiteStepTypeExecuteTest + } + + if step.Step.Delay != nil { + tp = testkube.TestSuiteStepTypeDelay + } + } + + return testkube.TestSuiteStepExecutionSummary{ + Id: id, + Name: name, + TestName: testName, + Status: status, + Type_: tp, + } +} + +func mapBatchStepExecutionResultToExecutionSummary(step testkube.TestSuiteBatchStepExecutionResult) testkube.TestSuiteBatchStepExecutionSummary { + batch := make([]testkube.TestSuiteStepExecutionSummary, len(step.Execute)) + for i, step := range step.Execute { + batch[i] = mapStepExecutionResultToExecutionSummary(step) + } + + return testkube.TestSuiteBatchStepExecutionSummary{ + Execute: batch, + } +} + +func mapStepExecutionResultToExecutionSummary(step testkube.TestSuiteStepExecutionResult) testkube.TestSuiteStepExecutionSummary { + var id, name, testName string + var status *testkube.ExecutionStatus + var tp *testkube.TestSuiteStepType + if step.Execution != nil { + id = step.Execution.Id + name = step.Execution.Name + testName = step.Execution.TestName + + if step.Execution.ExecutionResult != nil { + status = step.Execution.ExecutionResult.Status + } + } + + if step.Step != nil { + if step.Step.Test != "" { + tp = testkube.TestSuiteStepTypeExecuteTest + } + + if step.Step.Delay != "" { + tp = testkube.TestSuiteStepTypeDelay + } + } + + return testkube.TestSuiteStepExecutionSummary{ + Id: id, + Name: name, + TestName: testName, + Status: status, + Type_: tp, + } +} diff --git a/pkg/mapper/testsuiteexecutions/summary_test.go b/pkg/mapper/testsuiteexecutions/summary_test.go index 168ae1753f1..134151d0a1f 100644 --- a/pkg/mapper/testsuiteexecutions/summary_test.go +++ b/pkg/mapper/testsuiteexecutions/summary_test.go @@ -30,10 +30,10 @@ func TestSuiteMapToSummary(t *testing.T) { assert.Equal(t, result[i].Duration, executions[i].Duration) assert.Equal(t, result[i].DurationMs, executions[i].DurationMs) for j := range result[i].Execution { - assert.Equal(t, result[i].Execution[j].Id, executions[i].StepResults[j].Execution.Id) - assert.Equal(t, result[i].Execution[j].Name, executions[i].StepResults[j].Execution.Name) - assert.Equal(t, result[i].Execution[j].TestName, executions[i].StepResults[j].Execution.TestName) - assert.Equal(t, result[i].Execution[j].Status, executions[i].StepResults[j].Execution.ExecutionResult.Status) + assert.Equal(t, result[i].Execution[j].Execute[0].Id, executions[i].StepResults[j].Execution.Id) + assert.Equal(t, result[i].Execution[j].Execute[0].Name, executions[i].StepResults[j].Execution.Name) + assert.Equal(t, result[i].Execution[j].Execute[0].TestName, executions[i].StepResults[j].Execution.TestName) + assert.Equal(t, result[i].Execution[j].Execute[0].Status, executions[i].StepResults[j].Execution.ExecutionResult.Status) var tp *testkube.TestSuiteStepType if executions[i].StepResults[j].Step.Execute != nil { tp = testkube.TestSuiteStepTypeExecuteTest @@ -43,15 +43,54 @@ func TestSuiteMapToSummary(t *testing.T) { tp = testkube.TestSuiteStepTypeDelay } - assert.Equal(t, result[i].Execution[j].Type_, tp) + assert.Equal(t, result[i].Execution[j].Execute[0].Type_, tp) + } + } +} + +func TestSuiteMapBatchToSummary(t *testing.T) { + // given + executions := getBatchExecutions() + + // when + var result []*testkube.TestSuiteExecutionSummary + for i := range executions { + result = append(result, MapToSummary(&executions[i])) + } + + // then - test mappings + for i := 0; i < len(executions); i++ { + assert.Equal(t, result[i].Id, executions[i].Id) + assert.Equal(t, result[i].Name, executions[i].Name) + assert.Equal(t, result[i].TestSuiteName, executions[i].TestSuite.Name) + assert.Equal(t, result[i].Status, executions[i].Status) + assert.Equal(t, result[i].StartTime, executions[i].StartTime) + assert.Equal(t, result[i].EndTime, executions[i].EndTime) + assert.Equal(t, result[i].Duration, executions[i].Duration) + assert.Equal(t, result[i].DurationMs, executions[i].DurationMs) + for j := range result[i].Execution { + assert.Equal(t, result[i].Execution[j].Execute[0].Id, executions[i].ExecuteStepResults[j].Execute[0].Execution.Id) + assert.Equal(t, result[i].Execution[j].Execute[0].Name, executions[i].ExecuteStepResults[j].Execute[0].Execution.Name) + assert.Equal(t, result[i].Execution[j].Execute[0].TestName, executions[i].ExecuteStepResults[j].Execute[0].Execution.TestName) + assert.Equal(t, result[i].Execution[j].Execute[0].Status, executions[i].ExecuteStepResults[j].Execute[0].Execution.ExecutionResult.Status) + var tp *testkube.TestSuiteStepType + if executions[i].ExecuteStepResults[j].Execute[0].Step.Test != "" { + tp = testkube.TestSuiteStepTypeExecuteTest + } + + if executions[i].ExecuteStepResults[j].Execute[0].Step.Delay != "" { + tp = testkube.TestSuiteStepTypeDelay + } + + assert.Equal(t, result[i].Execution[j].Execute[0].Type_, tp) } } } func getExecutions() []testkube.TestSuiteExecution { - stepResults1 := []testkube.TestSuiteStepExecutionResult{ + stepResults1 := []testkube.TestSuiteStepExecutionResultV2{ { - Step: &testkube.TestSuiteStep{Execute: &testkube.TestSuiteStepExecuteTest{}}, + Step: &testkube.TestSuiteStepV2{Execute: &testkube.TestSuiteStepExecuteTestV2{Name: "testname1"}}, Execution: &testkube.Execution{ Id: "id1", Name: "name1", @@ -81,9 +120,9 @@ func getExecutions() []testkube.TestSuiteExecution { } execution1.Stop() - stepResults2 := []testkube.TestSuiteStepExecutionResult{ + stepResults2 := []testkube.TestSuiteStepExecutionResultV2{ { - Step: &testkube.TestSuiteStep{Execute: &testkube.TestSuiteStepExecuteTest{}}, + Step: &testkube.TestSuiteStepV2{Execute: &testkube.TestSuiteStepExecuteTestV2{Name: "testname2"}}, Execution: &testkube.Execution{ Id: "id2", Name: "name2", @@ -120,3 +159,84 @@ func getExecutions() []testkube.TestSuiteExecution { } } + +func getBatchExecutions() []testkube.TestSuiteExecution { + stepResults1 := []testkube.TestSuiteBatchStepExecutionResult{ + { + Execute: []testkube.TestSuiteStepExecutionResult{ + { + Step: &testkube.TestSuiteStep{Test: "testname1"}, + Execution: &testkube.Execution{ + Id: "id1", + Name: "name1", + TestName: "testname1", + ExecutionResult: &testkube.ExecutionResult{ + Status: testkube.ExecutionStatusPassed, + }, + }, + }, + }, + }, + } + + execution1 := testkube.TestSuiteExecution{ + Id: "tid1", + Name: "script1", + TestSuite: &testkube.ObjectRef{ + Namespace: "testkube", + Name: "testsuite1", + }, + Status: testkube.TestSuiteExecutionStatusFailed, + Envs: map[string]string{"var": "key"}, + Variables: map[string]testkube.Variable{"p": testkube.NewBasicVariable("p", "v1")}, + SecretUUID: "secret-uuid", + StartTime: time.Now(), + EndTime: time.Now(), + ExecuteStepResults: stepResults1, + Labels: map[string]string{"label": "value"}, + } + + execution1.Stop() + stepResults2 := []testkube.TestSuiteBatchStepExecutionResult{ + { + Execute: []testkube.TestSuiteStepExecutionResult{ + { + Step: &testkube.TestSuiteStep{Test: "testname2"}, + Execution: &testkube.Execution{ + Id: "id2", + Name: "name2", + TestName: "testname2", + ExecutionResult: &testkube.ExecutionResult{ + Status: testkube.ExecutionStatusFailed, + }, + }, + }, + }, + }, + } + + execution2 := testkube.TestSuiteExecution{ + Id: "tid2", + Name: "script2", + TestSuite: &testkube.ObjectRef{ + Namespace: "testkube", + Name: "testsuite2", + }, + Status: testkube.TestSuiteExecutionStatusPassed, + Envs: map[string]string{"var": "key"}, + Variables: map[string]testkube.Variable{"p": testkube.NewBasicVariable("p", "v2")}, + SecretUUID: "secret-uuid", + StartTime: time.Now(), + EndTime: time.Now(), + ExecuteStepResults: stepResults2, + Labels: map[string]string{"label": "value"}, + } + + execution2.Stop() + + return []testkube.TestSuiteExecution{ + execution1, + execution2, + } + +} diff --git a/pkg/mapper/testsuites/kube_openapi.go b/pkg/mapper/testsuites/kube_openapi.go index 3fd1b37e563..1d2244bed67 100644 --- a/pkg/mapper/testsuites/kube_openapi.go +++ b/pkg/mapper/testsuites/kube_openapi.go @@ -4,12 +4,12 @@ import ( corev1 "k8s.io/api/core/v1" commonv1 "github.com/kubeshop/testkube-operator/apis/common/v1" - testsuitesv2 "github.com/kubeshop/testkube-operator/apis/testsuite/v2" + testsuitesv3 "github.com/kubeshop/testkube-operator/apis/testsuite/v3" "github.com/kubeshop/testkube/pkg/api/v1/testkube" ) // MapTestSuiteListKubeToAPI maps TestSuiteList CRD to list of OpenAPI spec TestSuite -func MapTestSuiteListKubeToAPI(cr testsuitesv2.TestSuiteList) (tests []testkube.TestSuite) { +func MapTestSuiteListKubeToAPI(cr testsuitesv3.TestSuiteList) (tests []testkube.TestSuite) { tests = make([]testkube.TestSuite, len(cr.Items)) for i, item := range cr.Items { tests[i] = MapCRToAPI(item) @@ -19,18 +19,39 @@ func MapTestSuiteListKubeToAPI(cr testsuitesv2.TestSuiteList) (tests []testkube. } // MapCRToAPI maps TestSuite CRD to OpenAPI spec TestSuite -func MapCRToAPI(cr testsuitesv2.TestSuite) (test testkube.TestSuite) { +func MapCRToAPI(cr testsuitesv3.TestSuite) (test testkube.TestSuite) { test.Name = cr.Name test.Namespace = cr.Namespace - - for _, s := range cr.Spec.Before { - test.Before = append(test.Before, mapCRStepToAPI(s)) - } - for _, s := range cr.Spec.Steps { - test.Steps = append(test.Steps, mapCRStepToAPI(s)) + var batches = []struct { + source *[]testsuitesv3.TestSuiteBatchStep + dest *[]testkube.TestSuiteBatchStep + }{ + { + source: &cr.Spec.Before, + dest: &test.Before, + }, + { + source: &cr.Spec.Steps, + dest: &test.Steps, + }, + { + source: &cr.Spec.After, + dest: &test.After, + }, } - for _, s := range cr.Spec.After { - test.After = append(test.After, mapCRStepToAPI(s)) + + for i := range batches { + for _, b := range *batches[i].source { + steps := make([]testkube.TestSuiteStep, len(b.Execute)) + for j := range b.Execute { + steps[j] = mapCRStepToAPI(b.Execute[j]) + } + + *batches[i].dest = append(*batches[i].dest, testkube.TestSuiteBatchStep{ + StopOnFailure: b.StopOnFailure, + Execute: steps, + }) + } } test.Description = cr.Spec.Description @@ -44,23 +65,17 @@ func MapCRToAPI(cr testsuitesv2.TestSuite) (test testkube.TestSuite) { } // mapCRStepToAPI maps CRD TestSuiteStepSpec to OpenAPI spec TestSuiteStep -func mapCRStepToAPI(crstep testsuitesv2.TestSuiteStepSpec) (teststep testkube.TestSuiteStep) { +func mapCRStepToAPI(crstep testsuitesv3.TestSuiteStepSpec) (teststep testkube.TestSuiteStep) { switch true { - case crstep.Execute != nil: + case crstep.Test != "": teststep = testkube.TestSuiteStep{ - StopTestOnFailure: crstep.Execute.StopOnFailure, - Execute: &testkube.TestSuiteStepExecuteTest{ - Name: crstep.Execute.Name, - Namespace: crstep.Execute.Namespace, - }, + Test: crstep.Test, } - case crstep.Delay != nil: + case crstep.Delay.Duration != 0: teststep = testkube.TestSuiteStep{ - Delay: &testkube.TestSuiteStepDelay{ - Duration: crstep.Delay.Duration, - }, + Delay: crstep.Delay.Duration.String(), } } @@ -79,10 +94,10 @@ func MapDepratcatedParams(in map[string]testkube.Variable) map[string]string { // MapCRDVariables maps variables between API and operator CRDs // TODO if we could merge operator into testkube repository we would get rid of those mappings -func MapCRDVariables(in map[string]testkube.Variable) map[string]testsuitesv2.Variable { - out := map[string]testsuitesv2.Variable{} +func MapCRDVariables(in map[string]testkube.Variable) map[string]testsuitesv3.Variable { + out := map[string]testsuitesv3.Variable{} for k, v := range in { - variable := testsuitesv2.Variable{ + variable := testsuitesv3.Variable{ Name: v.Name, Type_: string(*v.Type_), Value: v.Value, @@ -115,7 +130,7 @@ func MapCRDVariables(in map[string]testkube.Variable) map[string]testsuitesv2.Va return out } -func MergeVariablesAndParams(variables map[string]testsuitesv2.Variable, params map[string]string) map[string]testkube.Variable { +func MergeVariablesAndParams(variables map[string]testsuitesv3.Variable, params map[string]string) map[string]testkube.Variable { out := map[string]testkube.Variable{} for k, v := range params { out[k] = testkube.NewBasicVariable(k, v) @@ -142,7 +157,7 @@ func MergeVariablesAndParams(variables map[string]testsuitesv2.Variable, params } // MapExecutionRequestFromSpec maps CRD to OpenAPI spec ExecutionRequest -func MapExecutionRequestFromSpec(specExecutionRequest *testsuitesv2.TestSuiteExecutionRequest) *testkube.TestSuiteExecutionRequest { +func MapExecutionRequestFromSpec(specExecutionRequest *testsuitesv3.TestSuiteExecutionRequest) *testkube.TestSuiteExecutionRequest { if specExecutionRequest == nil { return nil } @@ -163,7 +178,7 @@ func MapExecutionRequestFromSpec(specExecutionRequest *testsuitesv2.TestSuiteExe } // MapStatusFromSpec maps CRD to OpenAPI spec TestSuiteStatus -func MapStatusFromSpec(specStatus testsuitesv2.TestSuiteStatus) *testkube.TestSuiteStatus { +func MapStatusFromSpec(specStatus testsuitesv3.TestSuiteStatus) *testkube.TestSuiteStatus { if specStatus.LatestExecution == nil { return nil } @@ -177,3 +192,121 @@ func MapStatusFromSpec(specStatus testsuitesv2.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.Steps = &steps + + after := mapCRDToTestBatchSteps(testSuite.Spec.After) + request.After = &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/testsuites/kube_openapi_test.go b/pkg/mapper/testsuites/kube_openapi_test.go index 6e055e1fbed..79704f9b13f 100644 --- a/pkg/mapper/testsuites/kube_openapi_test.go +++ b/pkg/mapper/testsuites/kube_openapi_test.go @@ -2,38 +2,45 @@ package testsuites import ( "testing" + "time" "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - testsuitesv2 "github.com/kubeshop/testkube-operator/apis/testsuite/v2" + testsuitesv3 "github.com/kubeshop/testkube-operator/apis/testsuite/v3" ) func TestMapTestSuiteListKubeToAPI(t *testing.T) { openAPITest := MapCRToAPI( - testsuitesv2.TestSuite{ - Spec: testsuitesv2.TestSuiteSpec{ - Before: []testsuitesv2.TestSuiteStepSpec{ + testsuitesv3.TestSuite{ + Spec: testsuitesv3.TestSuiteSpec{ + Before: []testsuitesv3.TestSuiteBatchStep{ { - Delay: &testsuitesv2.TestSuiteStepDelay{ - Duration: 1000, + Execute: []testsuitesv3.TestSuiteStepSpec{ + { + Delay: metav1.Duration{Duration: 2 * time.Second}, + }, }, }, }, - Steps: []testsuitesv2.TestSuiteStepSpec{ + Steps: []testsuitesv3.TestSuiteBatchStep{ { - Execute: &testsuitesv2.TestSuiteStepExecute{ - Namespace: "testkube", - Name: "some-test-name", + Execute: []testsuitesv3.TestSuiteStepSpec{ + { + Test: "some-test-name", + }, }, }, }, - After: []testsuitesv2.TestSuiteStepSpec{ + After: []testsuitesv3.TestSuiteBatchStep{ { - Delay: &testsuitesv2.TestSuiteStepDelay{ - Duration: 1000, + Execute: []testsuitesv3.TestSuiteStepSpec{ + { + Delay: metav1.Duration{Duration: time.Second}, + }, }, }, }, @@ -43,7 +50,58 @@ func TestMapTestSuiteListKubeToAPI(t *testing.T) { }, ) - assert.Equal(t, 1, len(openAPITest.Steps)) assert.Equal(t, 1, len(openAPITest.Before)) + assert.Equal(t, "2s", openAPITest.Before[0].Execute[0].Delay) + assert.Equal(t, 1, len(openAPITest.Steps)) + assert.Equal(t, "some-test-name", openAPITest.Steps[0].Execute[0].Test) assert.Equal(t, 1, len(openAPITest.After)) + assert.Equal(t, "1s", openAPITest.After[0].Execute[0].Delay) +} + +func TestMapTestSuiteTestCRDToUpdateRequest(t *testing.T) { + + openAPITest := MapTestSuiteTestCRDToUpdateRequest( + &testsuitesv3.TestSuite{ + Spec: testsuitesv3.TestSuiteSpec{ + Before: []testsuitesv3.TestSuiteBatchStep{ + { + Execute: []testsuitesv3.TestSuiteStepSpec{ + { + Delay: metav1.Duration{Duration: 2 * time.Second}, + }, + }, + }, + }, + + Steps: []testsuitesv3.TestSuiteBatchStep{ + { + Execute: []testsuitesv3.TestSuiteStepSpec{ + { + Test: "some-test-name", + }, + }, + }, + }, + + After: []testsuitesv3.TestSuiteBatchStep{ + { + Execute: []testsuitesv3.TestSuiteStepSpec{ + { + Delay: metav1.Duration{Duration: time.Second}, + }, + }, + }, + }, + + Repeats: 2, + }, + }, + ) + + assert.Equal(t, 1, len(*openAPITest.Before)) + assert.Equal(t, "2s", (*openAPITest.Before)[0].Execute[0].Delay) + assert.Equal(t, 1, len(*openAPITest.Steps)) + assert.Equal(t, "some-test-name", (*openAPITest.Steps)[0].Execute[0].Test) + assert.Equal(t, 1, len(*openAPITest.After)) + assert.Equal(t, "1s", (*openAPITest.After)[0].Execute[0].Delay) } diff --git a/pkg/mapper/testsuites/openapi_kube.go b/pkg/mapper/testsuites/openapi_kube.go index 9eae98cb396..94e34e85294 100644 --- a/pkg/mapper/testsuites/openapi_kube.go +++ b/pkg/mapper/testsuites/openapi_kube.go @@ -1,9 +1,11 @@ package testsuites import ( + "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - testsuitesv2 "github.com/kubeshop/testkube-operator/apis/testsuite/v2" + testsuitesv3 "github.com/kubeshop/testkube-operator/apis/testsuite/v3" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/types" ) @@ -13,9 +15,22 @@ func MapToTestExecutionSummary(executions []testkube.TestSuiteExecution) []testk result := make([]testkube.TestSuiteExecutionSummary, len(executions)) for i, execution := range executions { - executionsSummary := make([]testkube.TestSuiteStepExecutionSummary, len(execution.StepResults)) - for j, stepResult := range execution.StepResults { - executionsSummary[j] = mapStepResultToExecutionSummary(stepResult) + var executionsSummary []testkube.TestSuiteBatchStepExecutionSummary + + if len(execution.StepResults) != 0 { + executionsSummary = make([]testkube.TestSuiteBatchStepExecutionSummary, len(execution.StepResults)) + for j, stepResult := range execution.StepResults { + executionsSummary[j] = testkube.TestSuiteBatchStepExecutionSummary{ + Execute: []testkube.TestSuiteStepExecutionSummary{mapStepResultV2ToStepExecutionSummary(stepResult)}, + } + } + } + + if len(execution.ExecuteStepResults) != 0 { + executionsSummary = make([]testkube.TestSuiteBatchStepExecutionSummary, len(execution.ExecuteStepResults)) + for j, stepResult := range execution.ExecuteStepResults { + executionsSummary[j] = mapBatchStepResultToExecutionSummary(stepResult) + } } result[i] = testkube.TestSuiteExecutionSummary{ @@ -35,7 +50,48 @@ func MapToTestExecutionSummary(executions []testkube.TestSuiteExecution) []testk return result } -func mapStepResultToExecutionSummary(r testkube.TestSuiteStepExecutionResult) testkube.TestSuiteStepExecutionSummary { +func mapStepResultV2ToStepExecutionSummary(r testkube.TestSuiteStepExecutionResultV2) testkube.TestSuiteStepExecutionSummary { + var id, testName, name string + var status *testkube.ExecutionStatus = testkube.ExecutionStatusPassed + var stepType *testkube.TestSuiteStepType + + if r.Test != nil { + testName = r.Test.Name + } + + if r.Execution != nil { + id = r.Execution.Id + if r.Execution.ExecutionResult != nil { + status = r.Execution.ExecutionResult.Status + } + } + + if r.Step != nil { + stepType = r.Step.Type() + name = r.Step.FullName() + } + + return testkube.TestSuiteStepExecutionSummary{ + Id: id, + Name: name, + TestName: testName, + Status: status, + Type_: stepType, + } +} + +func mapBatchStepResultToExecutionSummary(r testkube.TestSuiteBatchStepExecutionResult) testkube.TestSuiteBatchStepExecutionSummary { + batch := make([]testkube.TestSuiteStepExecutionSummary, len(r.Execute)) + for i := range r.Execute { + batch[i] = mapStepResultToStepExecutionSummary(r.Execute[i]) + } + + return testkube.TestSuiteBatchStepExecutionSummary{ + Execute: batch, + } +} + +func mapStepResultToStepExecutionSummary(r testkube.TestSuiteStepExecutionResult) testkube.TestSuiteStepExecutionSummary { var id, testName, name string var status *testkube.ExecutionStatus = testkube.ExecutionStatusPassed var stepType *testkube.TestSuiteStepType @@ -65,61 +121,85 @@ func mapStepResultToExecutionSummary(r testkube.TestSuiteStepExecutionResult) te } } -func MapTestSuiteUpsertRequestToTestCRD(request testkube.TestSuiteUpsertRequest) testsuitesv2.TestSuite { - return testsuitesv2.TestSuite{ +func MapTestSuiteUpsertRequestToTestCRD(request testkube.TestSuiteUpsertRequest) (testsuite testsuitesv3.TestSuite, err error) { + before, err := mapTestBatchStepsToCRD(request.Before) + if err != nil { + return testsuite, err + } + + steps, err := mapTestBatchStepsToCRD(request.Steps) + if err != nil { + return testsuite, err + } + + after, err := mapTestBatchStepsToCRD(request.After) + if err != nil { + return testsuite, err + } + + return testsuitesv3.TestSuite{ ObjectMeta: metav1.ObjectMeta{ Name: request.Name, Namespace: request.Namespace, Labels: request.Labels, }, - Spec: testsuitesv2.TestSuiteSpec{ + Spec: testsuitesv3.TestSuiteSpec{ Repeats: int(request.Repeats), Description: request.Description, - Before: mapTestStepsToCRD(request.Before), - Steps: mapTestStepsToCRD(request.Steps), - After: mapTestStepsToCRD(request.After), + Before: before, + Steps: steps, + After: after, Schedule: request.Schedule, ExecutionRequest: MapExecutionRequestToSpecExecutionRequest(request.ExecutionRequest), }, - } + }, nil } -func mapTestStepsToCRD(steps []testkube.TestSuiteStep) (out []testsuitesv2.TestSuiteStepSpec) { - for _, step := range steps { - out = append(out, mapTestStepToCRD(step)) +func mapTestBatchStepsToCRD(batches []testkube.TestSuiteBatchStep) (out []testsuitesv3.TestSuiteBatchStep, err error) { + for _, batch := range batches { + steps := make([]testsuitesv3.TestSuiteStepSpec, len(batch.Execute)) + for i := range batch.Execute { + steps[i], err = mapTestStepToCRD(batch.Execute[i]) + if err != nil { + return nil, err + } + } + + out = append(out, testsuitesv3.TestSuiteBatchStep{ + StopOnFailure: batch.StopOnFailure, + Execute: steps, + }) } - return out + return out, nil } -func mapTestStepToCRD(step testkube.TestSuiteStep) (stepSpec testsuitesv2.TestSuiteStepSpec) { +func mapTestStepToCRD(step testkube.TestSuiteStep) (stepSpec testsuitesv3.TestSuiteStepSpec, err error) { switch step.Type() { case testkube.TestSuiteStepTypeDelay: - stepSpec.Delay = &testsuitesv2.TestSuiteStepDelay{ - Duration: step.Delay.Duration, - } + if step.Delay != "" { + duration, err := time.ParseDuration(step.Delay) + if err != nil { + return stepSpec, err + } - case testkube.TestSuiteStepTypeExecuteTest: - s := step.Execute - stepSpec.Execute = &testsuitesv2.TestSuiteStepExecute{ - Namespace: s.Namespace, - Name: s.Name, - // TODO move StopOnFailure level up in operator model to mimic this one - StopOnFailure: step.StopTestOnFailure, + stepSpec.Delay = metav1.Duration{Duration: duration} } + case testkube.TestSuiteStepTypeExecuteTest: + stepSpec.Test = step.Test } - return + return stepSpec, nil } // MapExecutionRequestToSpecExecutionRequest maps ExecutionRequest OpenAPI spec to ExecutionRequest CRD spec -func MapExecutionRequestToSpecExecutionRequest(executionRequest *testkube.TestSuiteExecutionRequest) *testsuitesv2.TestSuiteExecutionRequest { +func MapExecutionRequestToSpecExecutionRequest(executionRequest *testkube.TestSuiteExecutionRequest) *testsuitesv3.TestSuiteExecutionRequest { if executionRequest == nil { return nil } - return &testsuitesv2.TestSuiteExecutionRequest{ + return &testsuitesv3.TestSuiteExecutionRequest{ Name: executionRequest.Name, Labels: executionRequest.Labels, ExecutionLabels: executionRequest.ExecutionLabels, @@ -135,7 +215,8 @@ func MapExecutionRequestToSpecExecutionRequest(executionRequest *testkube.TestSu } // MapTestSuiteUpsertRequestToTestCRD maps TestSuiteUpdateRequest OpenAPI spec to TestSuite CRD spec -func MapTestSuiteUpdateRequestToTestCRD(request testkube.TestSuiteUpdateRequest, testSuite *testsuitesv2.TestSuite) *testsuitesv2.TestSuite { +func MapTestSuiteUpdateRequestToTestCRD(request testkube.TestSuiteUpdateRequest, + testSuite *testsuitesv3.TestSuite) (*testsuitesv3.TestSuite, error) { var fields = []struct { source *string destination *string @@ -164,16 +245,26 @@ func MapTestSuiteUpdateRequestToTestCRD(request testkube.TestSuiteUpdateRequest, } } + var err error if request.Before != nil { - testSuite.Spec.Before = mapTestStepsToCRD(*request.Before) + testSuite.Spec.Before, err = mapTestBatchStepsToCRD(*request.Before) + if err != nil { + return nil, err + } } if request.Steps != nil { - testSuite.Spec.Steps = mapTestStepsToCRD(*request.Steps) + testSuite.Spec.Steps, err = mapTestBatchStepsToCRD(*request.Steps) + if err != nil { + return nil, err + } } if request.After != nil { - testSuite.Spec.After = mapTestStepsToCRD(*request.After) + testSuite.Spec.After, err = mapTestBatchStepsToCRD(*request.After) + if err != nil { + return nil, err + } } if request.Labels != nil { @@ -188,18 +279,18 @@ func MapTestSuiteUpdateRequestToTestCRD(request testkube.TestSuiteUpdateRequest, testSuite.Spec.ExecutionRequest = MapExecutionUpdateRequestToSpecExecutionRequest(*request.ExecutionRequest, testSuite.Spec.ExecutionRequest) } - return testSuite + return testSuite, nil } // MapExecutionUpdateRequestToSpecExecutionRequest maps ExecutionUpdateRequest OpenAPI spec to ExecutionRequest CRD spec func MapExecutionUpdateRequestToSpecExecutionRequest(executionRequest *testkube.TestSuiteExecutionUpdateRequest, - request *testsuitesv2.TestSuiteExecutionRequest) *testsuitesv2.TestSuiteExecutionRequest { + request *testsuitesv3.TestSuiteExecutionRequest) *testsuitesv3.TestSuiteExecutionRequest { if executionRequest == nil { return nil } if request == nil { - request = &testsuitesv2.TestSuiteExecutionRequest{} + request = &testsuitesv3.TestSuiteExecutionRequest{} } empty := true @@ -273,14 +364,14 @@ func MapExecutionUpdateRequestToSpecExecutionRequest(executionRequest *testkube. } // MapStatusToSpec maps OpenAPI spec TestSuiteStatus to CRD -func MapStatusToSpec(testSuiteStatus *testkube.TestSuiteStatus) (specStatus testsuitesv2.TestSuiteStatus) { +func MapStatusToSpec(testSuiteStatus *testkube.TestSuiteStatus) (specStatus testsuitesv3.TestSuiteStatus) { if testSuiteStatus == nil || testSuiteStatus.LatestExecution == nil { return specStatus } - specStatus.LatestExecution = &testsuitesv2.TestSuiteExecutionCore{ + specStatus.LatestExecution = &testsuitesv3.TestSuiteExecutionCore{ Id: testSuiteStatus.LatestExecution.Id, - Status: (*testsuitesv2.TestSuiteExecutionStatus)(testSuiteStatus.LatestExecution.Status), + Status: (*testsuitesv3.TestSuiteExecutionStatus)(testSuiteStatus.LatestExecution.Status), } specStatus.LatestExecution.StartTime.Time = testSuiteStatus.LatestExecution.StartTime @@ -290,10 +381,10 @@ func MapStatusToSpec(testSuiteStatus *testkube.TestSuiteStatus) (specStatus test } // MapExecutionToTestSuiteStatus maps OpenAPI Execution to TestSuiteStatus CRD -func MapExecutionToTestSuiteStatus(execution *testkube.TestSuiteExecution) (specStatus testsuitesv2.TestSuiteStatus) { - specStatus.LatestExecution = &testsuitesv2.TestSuiteExecutionCore{ +func MapExecutionToTestSuiteStatus(execution *testkube.TestSuiteExecution) (specStatus testsuitesv3.TestSuiteStatus) { + specStatus.LatestExecution = &testsuitesv3.TestSuiteExecutionCore{ Id: execution.Id, - Status: (*testsuitesv2.TestSuiteExecutionStatus)(execution.Status), + Status: (*testsuitesv3.TestSuiteExecutionStatus)(execution.Status), } specStatus.LatestExecution.StartTime.Time = execution.StartTime 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), + } +} diff --git a/pkg/mapper/webhooks/mapper.go b/pkg/mapper/webhooks/mapper.go index 52b00cdd51c..dad2489e065 100644 --- a/pkg/mapper/webhooks/mapper.go +++ b/pkg/mapper/webhooks/mapper.go @@ -17,6 +17,8 @@ func MapCRDToAPI(item executorv1.Webhook) testkube.Webhook { Selector: item.Spec.Selector, Labels: item.Labels, PayloadObjectField: item.Spec.PayloadObjectField, + PayloadTemplate: item.Spec.PayloadTemplate, + Headers: item.Spec.Headers, } } @@ -49,6 +51,8 @@ func MapAPIToCRD(request testkube.WebhookCreateRequest) executorv1.Webhook { Events: MapEventTypesToStringArray(request.Events), Selector: request.Selector, PayloadObjectField: request.PayloadObjectField, + PayloadTemplate: request.PayloadTemplate, + Headers: request.Headers, }, } } diff --git a/pkg/reconciler/reconciler.go b/pkg/reconciler/reconciler.go new file mode 100644 index 00000000000..15d6ff8b400 --- /dev/null +++ b/pkg/reconciler/reconciler.go @@ -0,0 +1,183 @@ +package reconciler + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/mongo" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/executor" + "github.com/kubeshop/testkube/pkg/mapper/tests" + "github.com/kubeshop/testkube/pkg/repository/result" + "github.com/kubeshop/testkube/pkg/repository/testresult" +) + +const ( + reconciliationInterval = 5 * time.Minute +) + +var ( + errTestkubeAPICrahsed = errors.New("testkube api server crashed") +) + +type Client struct { + k8sclient kubernetes.Interface + resultRepository result.Repository + testResultRepository testresult.Repository + logger *zap.SugaredLogger + namespace string +} + +func NewClient(k8sclient kubernetes.Interface, resultRepository result.Repository, testResultRepository testresult.Repository, + logger *zap.SugaredLogger, namespace string) *Client { + return &Client{ + k8sclient: k8sclient, + resultRepository: resultRepository, + testResultRepository: testResultRepository, + logger: logger, + namespace: namespace, + } +} + +func (client *Client) Run(ctx context.Context) error { + client.logger.Debugw("reconciliation started") + + timer := time.NewTimer(reconciliationInterval) + + defer func() { + timer.Stop() + }() + + for { + select { + case <-timer.C: + if err := client.ProcessTests(ctx); err != nil { + client.logger.Errorw("error processing tests statuses %w", err) + } + + if err := client.ProcessTestSuites(ctx); err != nil { + client.logger.Errorw("error processing test suites statuses %w", err) + } + case <-ctx.Done(): + client.logger.Debugw("reconciliation finished") + return ctx.Err() + } + } +} + +func (client *Client) ProcessTests(ctx context.Context) error { + executions, err := client.resultRepository.GetExecutions(ctx, + result.NewExecutionsFilter().WithStatus(string(*testkube.ExecutionStatusRunning))) + if err != nil { + return err + } + +OuterLoop: + for _, execution := range executions { + select { + case <-ctx.Done(): + return ctx.Err() + default: + errMessage := errTestkubeAPICrahsed.Error() + pods, err := executor.GetJobPods(ctx, client.k8sclient.CoreV1().Pods(client.namespace), execution.Id, 1, 1) + if err == nil { + InnerLoop: + for _, pod := range pods.Items { + if pod.Labels["job-name"] == execution.Id { + switch pod.Status.Phase { + case corev1.PodFailed: + errMessage = pod.Status.Message + break InnerLoop + default: + continue OuterLoop + } + } + } + } + + execution.ExecutionResult = &testkube.ExecutionResult{ + Status: testkube.ExecutionStatusFailed, + ErrorMessage: errMessage, + } + if err = client.resultRepository.Update(ctx, execution); err != nil { + return err + } + } + } + + return nil +} + +func (client *Client) ProcessTestSuites(ctx context.Context) error { + executions, err := client.testResultRepository.GetExecutions(ctx, + testresult.NewExecutionsFilter().WithStatus(string(*testkube.TestSuiteExecutionStatusRunning))) + if err != nil { + return err + } + +OuterLoop: + for _, execution := range executions { + select { + case <-ctx.Done(): + return ctx.Err() + default: + status := testkube.TestSuiteExecutionStatusPassed + InnerLoop: + for _, step := range execution.ExecuteStepResults { + for _, execute := range step.Execute { + if execute.Step != nil && execute.Step.Type() == testkube.TestSuiteStepTypeExecuteTest && execute.Execution != nil { + exec, err := client.resultRepository.Get(ctx, execute.Execution.Id) + if err != nil && err != mongo.ErrNoDocuments { + return err + } + + if exec.ExecutionResult == nil { + continue OuterLoop + } + + if exec.ExecutionResult.IsRunning() { + continue OuterLoop + } + + if exec.ExecutionResult.IsFailed() { + status = testkube.TestSuiteExecutionStatusFailed + } + + if exec.ExecutionResult.IsAborted() { + status = testkube.TestSuiteExecutionStatusAborted + break InnerLoop + } + + if exec.ExecutionResult.IsTimeout() { + status = testkube.TestSuiteExecutionStatusTimeout + break InnerLoop + } + } + } + } + + execution.Status = status + for i := range execution.ExecuteStepResults { + for j := range execution.ExecuteStepResults[i].Execute { + if execution.ExecuteStepResults[i].Execute[j].Execution != nil && + execution.ExecuteStepResults[i].Execute[j].Execution.IsRunning() { + execution.ExecuteStepResults[i].Execute[j].Execution.ExecutionResult = &testkube.ExecutionResult{ + Status: tests.MapTestSuiteExecutionStatusToExecutionStatus(status), + ErrorMessage: errTestkubeAPICrahsed.Error(), + } + } + } + } + if err = client.testResultRepository.Update(ctx, execution); err != nil { + return err + } + } + } + + return nil +} diff --git a/pkg/scheduler/service.go b/pkg/scheduler/service.go index b4f79742f50..7d0408ac5a2 100644 --- a/pkg/scheduler/service.go +++ b/pkg/scheduler/service.go @@ -8,7 +8,7 @@ import ( executorsv1 "github.com/kubeshop/testkube-operator/client/executors/v1" testsv3 "github.com/kubeshop/testkube-operator/client/tests/v3" testsourcesv1 "github.com/kubeshop/testkube-operator/client/testsources/v1" - testsuitesv2 "github.com/kubeshop/testkube-operator/client/testsuites/v2" + testsuitesv3 "github.com/kubeshop/testkube-operator/client/testsuites/v3" v1 "github.com/kubeshop/testkube/internal/app/api/metrics" "github.com/kubeshop/testkube/pkg/configmap" "github.com/kubeshop/testkube/pkg/event" @@ -26,7 +26,7 @@ type Scheduler struct { testExecutionResults testresult.Repository executorsClient executorsv1.Interface testsClient testsv3.Interface - testSuitesClient testsuitesv2.Interface + testSuitesClient testsuitesv3.Interface testSourcesClient testsourcesv1.Interface secretClient secret.Interface events *event.Emitter @@ -43,7 +43,7 @@ func NewScheduler( testExecutionResults testresult.Repository, executorsClient executorsv1.Interface, testsClient testsv3.Interface, - testSuitesClient testsuitesv2.Interface, + testSuitesClient testsuitesv3.Interface, testSourcesClient testsourcesv1.Interface, secretClient secret.Interface, events *event.Emitter, diff --git a/pkg/scheduler/test_scheduler.go b/pkg/scheduler/test_scheduler.go index 9b5ff165e96..95b33532248 100644 --- a/pkg/scheduler/test_scheduler.go +++ b/pkg/scheduler/test_scheduler.go @@ -59,14 +59,14 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request secretUUID, err := s.testsClient.GetCurrentSecretUUID(test.Name) if err != nil { - return execution.Errw("can't get current secret uuid: %w", err), nil + return execution.Errw(request.Id, "can't get current secret uuid: %w", err), nil } request.TestSecretUUID = secretUUID // merge available data into execution options test spec, executor spec, request, test id options, err := s.getExecuteOptions(test.Namespace, test.Name, request) if err != nil { - return execution.Errw("can't create valid execution options: %w", err), nil + return execution.Errw(request.Id, "can't create valid execution options: %w", err), nil } // store execution in storage, can be fetched from API now @@ -74,12 +74,12 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request options.ID = execution.Id if err := s.createSecretsReferences(&execution); err != nil { - return execution.Errw("can't create secret variables `Secret` references: %w", err), nil + return execution.Errw(execution.Id, "can't create secret variables `Secret` references: %w", err), nil } err = s.executionResults.Insert(ctx, execution) if err != nil { - return execution.Errw("can't create new test execution, can't insert into storage: %w", err), nil + return execution.Errw(execution.Id, "can't create new test execution, can't insert into storage: %w", err), nil } s.logger.Infow("calling executor with options", "options", options.Request) @@ -92,7 +92,7 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request err = s.executionResults.StartExecution(ctx, execution.Id, execution.StartTime) if err != nil { s.events.Notify(testkube.NewEventEndTestFailed(&execution)) - return execution.Errw("can't execute test, can't insert into storage error: %w", err), nil + return execution.Errw(execution.Id, "can't execute test, can't insert into storage error: %w", err), nil } // sync/async test execution @@ -104,12 +104,12 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request // update storage with current execution status if uerr := s.executionResults.UpdateResult(ctx, execution.Id, execution); uerr != nil { s.events.Notify(testkube.NewEventEndTestFailed(&execution)) - return execution.Errw("update execution error: %w", uerr), nil + return execution.Errw(execution.Id, "update execution error: %w", uerr), nil } if err != nil { s.events.Notify(testkube.NewEventEndTestFailed(&execution)) - return execution.Errw("test execution failed: %w", err), nil + return execution.Errw(execution.Id, "test execution failed: %w", err), nil } s.logger.Infow("test started", "executionId", execution.Id, "status", execution.ExecutionResult.Status) @@ -203,6 +203,7 @@ func (s *Scheduler) createSecretsReferences(execution *testkube.Execution) (err func newExecutionFromExecutionOptions(options client.ExecuteOptions) testkube.Execution { execution := testkube.NewExecution( + options.Request.Id, options.Namespace, options.TestName, options.Request.TestSuiteName, @@ -226,6 +227,7 @@ func newExecutionFromExecutionOptions(options client.ExecuteOptions) testkube.Ex execution.BucketName = options.Request.BucketName execution.ArtifactRequest = options.Request.ArtifactRequest execution.PreRunScript = options.Request.PreRunScript + execution.PostRunScript = options.Request.PostRunScript execution.RunningContext = options.Request.RunningContext return execution @@ -291,6 +293,10 @@ func (s *Scheduler) getExecuteOptions(namespace, id string, request testkube.Exe test.ExecutionRequest.PreRunScript, &request.PreRunScript, }, + { + test.ExecutionRequest.PostRunScript, + &request.PostRunScript, + }, { test.ExecutionRequest.ScraperTemplate, &request.ScraperTemplate, diff --git a/pkg/scheduler/test_scheduler_test.go b/pkg/scheduler/test_scheduler_test.go index c038a0f75f4..03f235c3c07 100644 --- a/pkg/scheduler/test_scheduler_test.go +++ b/pkg/scheduler/test_scheduler_test.go @@ -133,6 +133,7 @@ func TestGetExecuteOptions(t *testing.T) { JobTemplate: "", CronJobTemplate: "", PreRunScript: "", + PostRunScript: "", ScraperTemplate: "", EnvConfigMaps: []testkube.EnvReference{ { diff --git a/pkg/scheduler/testsuite_scheduler.go b/pkg/scheduler/testsuite_scheduler.go index fd9e4d55d5e..00a4f13459e 100644 --- a/pkg/scheduler/testsuite_scheduler.go +++ b/pkg/scheduler/testsuite_scheduler.go @@ -7,22 +7,28 @@ import ( "sync" "time" - "github.com/kubeshop/testkube/pkg/version" - "github.com/pkg/errors" - testsuitesv2 "github.com/kubeshop/testkube-operator/apis/testsuite/v2" + testsuitesv3 "github.com/kubeshop/testkube-operator/apis/testsuite/v3" "github.com/kubeshop/testkube/pkg/api/v1/testkube" testsuitesmapper "github.com/kubeshop/testkube/pkg/mapper/testsuites" "github.com/kubeshop/testkube/pkg/telemetry" + "github.com/kubeshop/testkube/pkg/version" "github.com/kubeshop/testkube/pkg/workerpool" ) const ( abortionPollingInterval = 100 * time.Millisecond + // DefaultConcurrencyLevel is a default concurrency level for worker pool + DefaultConcurrencyLevel = 10 ) -func (s *Scheduler) PrepareTestSuiteRequests(work []testsuitesv2.TestSuite, request testkube.TestSuiteExecutionRequest) []workerpool.Request[ +type testTuple struct { + test testkube.Test + executionID string +} + +func (s *Scheduler) PrepareTestSuiteRequests(work []testsuitesv3.TestSuite, request testkube.TestSuiteExecutionRequest) []workerpool.Request[ testkube.TestSuite, testkube.TestSuiteExecutionRequest, testkube.TestSuiteExecution, @@ -100,42 +106,64 @@ func (s *Scheduler) runSteps(ctx context.Context, wg *sync.WaitGroup, testsuiteE hasFailedSteps := false cancelSteps := false - var stepResult *testkube.TestSuiteStepExecutionResult + var batchStepResult *testkube.TestSuiteBatchStepExecutionResult var abortionStatus *testkube.TestSuiteExecutionStatus abortChan := make(chan *testkube.TestSuiteExecutionStatus) go s.abortionCheck(ctx, testsuiteExecution, request.Timeout, abortChan) - for i := range testsuiteExecution.StepResults { - stepResult = &testsuiteExecution.StepResults[i] + 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) cancelSteps = true - stepResult.Execution.ExecutionResult.Abort() + for j := range batchStepResult.Execute { + if batchStepResult.Execute[j].Execution != nil && batchStepResult.Execute[j].Execution.ExecutionResult != nil { + batchStepResult.Execute[j].Execution.ExecutionResult.Abort() + } + } + testsuiteExecution.Status = testkube.TestSuiteExecutionStatusAborting default: - s.logger.Debugw("Running step", "step", testsuiteExecution.StepResults[i].Step, "i", i) + s.logger.Debugw("Running batch step", "step", batchStepResult.Execute, "i", i) if cancelSteps { - s.logger.Debugw("Aborting step", "step", testsuiteExecution.StepResults[i].Step, "i", i) + 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() + } + } - stepResult.Execution.ExecutionResult.Abort() continue } // start execution of given step - stepResult.Execution.ExecutionResult.InProgress() + 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, stepResult) + 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) + } + } - s.logger.Debugw("Step execution result", "step", testsuiteExecution.StepResults[i].Step, "result", stepResult.Execution.ExecutionResult) + s.logger.Debugw("Batch step execution result", "step", batchStepResult.Execute, "results", results) err = s.testExecutionResults.Update(ctx, *testsuiteExecution) if err != nil { @@ -145,17 +173,19 @@ func (s *Scheduler) runSteps(ctx context.Context, wg *sync.WaitGroup, testsuiteE continue } - if stepResult.IsFailed() { - hasFailedSteps = true - if stepResult.Step.StopTestOnFailure { - cancelSteps = true - continue + for j := range batchStepResult.Execute { + if batchStepResult.Execute[j].IsFailed() { + hasFailedSteps = true + if batchStepResult.Step != nil && batchStepResult.Step.StopOnFailure { + cancelSteps = true + break + } } } } } - if *testsuiteExecution.Status == testkube.ABORTING_TestSuiteExecutionStatus { + if testsuiteExecution.Status != nil && *testsuiteExecution.Status == testkube.ABORTING_TestSuiteExecutionStatus { if abortionStatus != nil && *abortionStatus == testkube.TIMEOUT_TestSuiteExecutionStatus { s.events.Notify(testkube.NewEventEndTestSuiteTimeout(testsuiteExecution)) testsuiteExecution.Status = testkube.TestSuiteExecutionStatusTimeout @@ -286,28 +316,76 @@ func (s *Scheduler) wasTestSuiteAborted(ctx context.Context, id string) bool { s.logger.Debugw("Checking if test suite execution was aborted", "id", id, "status", execution.Status) - return *execution.Status == testkube.ABORTING_TestSuiteExecutionStatus + return execution.Status != nil && *execution.Status == testkube.ABORTING_TestSuiteExecutionStatus } func (s *Scheduler) executeTestStep(ctx context.Context, testsuiteExecution testkube.TestSuiteExecution, - request testkube.TestSuiteExecutionRequest, result *testkube.TestSuiteStepExecutionResult) { + request testkube.TestSuiteExecutionRequest, result *testkube.TestSuiteBatchStepExecutionResult) { var testSuiteName string if testsuiteExecution.TestSuite != nil { testSuiteName = testsuiteExecution.TestSuite.Name } - step := result.Step + var testTuples []testTuple + var duration time.Duration + for i := range result.Execute { + step := result.Execute[i].Step + if step == nil { + continue + } + + l := s.logger.With("type", step.Type(), "testSuiteName", testSuiteName, "name", step.FullName()) + + switch step.Type() { + case testkube.TestSuiteStepTypeExecuteTest: + executeTestStep := step.Test + if executeTestStep == "" { + continue + } + + execution := result.Execute[i].Execution + if execution == nil { + continue + } + + l.Info("executing test", "variables", testsuiteExecution.Variables, "request", request) + + testTuples = append(testTuples, testTuple{ + test: testkube.Test{Name: executeTestStep}, + executionID: execution.Id, + }) + case testkube.TestSuiteStepTypeDelay: + if step.Delay == "" { + continue + } + + l.Infow("delaying execution", "step", step.FullName(), "delay", step.Delay) + + delay, err := time.ParseDuration(step.Delay) + if err != nil { + result.Execute[i].Err(err) + continue + } + + if delay > duration { + duration = delay + } + default: + result.Execute[i].Err(errors.Errorf("can't find handler for execution step type: '%v'", step.Type())) + } + } + + concurrencyLevel := DefaultConcurrencyLevel + if request.ConcurrencyLevel != 0 { + concurrencyLevel = int(request.ConcurrencyLevel) + } - l := s.logger.With("type", step.Type(), "testSuiteName", testSuiteName, "name", step.FullName()) + workerpoolService := workerpool.New[testkube.Test, testkube.ExecutionRequest, testkube.Execution](concurrencyLevel) - switch step.Type() { - case testkube.TestSuiteStepTypeExecuteTest: - executeTestStep := step.Execute - request := testkube.ExecutionRequest{ - Name: fmt.Sprintf("%s-%s", testSuiteName, executeTestStep.Name), + if len(testTuples) != 0 { + req := testkube.ExecutionRequest{ TestSuiteName: testSuiteName, - Namespace: executeTestStep.Namespace, Variables: testsuiteExecution.Variables, TestSuiteSecretUUID: request.SecretUUID, Sync: true, @@ -322,26 +400,50 @@ func (s *Scheduler) executeTestStep(ctx context.Context, testsuiteExecution test }, } - l.Info("executing test", "variables", testsuiteExecution.Variables, "request", request) - - execution, err := s.executeTest(ctx, testkube.Test{Name: executeTestStep.Name}, request) - if err != nil { - result.Err(err) - return + requests := make([]workerpool.Request[testkube.Test, testkube.ExecutionRequest, testkube.Execution], len(testTuples)) + for i := range testTuples { + req.Name = fmt.Sprintf("%s-%s", testSuiteName, testTuples[i].test.Name) + req.Id = testTuples[i].executionID + requests[i] = workerpool.Request[testkube.Test, testkube.ExecutionRequest, testkube.Execution]{ + Object: testTuples[i].test, + Options: req, + ExecFn: s.executeTest, + } } - result.Execution = &execution - case testkube.TestSuiteStepTypeDelay: - l.Infow("delaying execution", "step", step.FullName(), "delay", step.Delay.Duration) + go workerpoolService.SendRequests(requests) + go workerpoolService.Run(ctx) + } - duration := time.Millisecond * time.Duration(step.Delay.Duration) + if duration != 0 { s.delayWithAbortionCheck(duration, testsuiteExecution.Id, result) - default: - result.Err(errors.Errorf("can't find handler for execution step type: '%v'", step.Type())) + } + + results := make(map[string]testkube.Execution, len(testTuples)) + if len(testTuples) != 0 { + for r := range workerpoolService.GetResponses() { + results[r.Result.Id] = r.Result + status := "" + if r.Result.ExecutionResult != nil && r.Result.ExecutionResult.Status != nil { + status = string(*r.Result.ExecutionResult.Status) + } + + s.logger.Infow("execution result", "id", r.Result.Id, "status", status) + } + + for i := range result.Execute { + if result.Execute[i].Execution == nil { + continue + } + + if value, ok := results[result.Execute[i].Execution.Id]; ok { + result.Execute[i].Execution = &value + } + } } } -func (s *Scheduler) delayWithAbortionCheck(duration time.Duration, testSuiteId string, result *testkube.TestSuiteStepExecutionResult) { +func (s *Scheduler) delayWithAbortionCheck(duration time.Duration, testSuiteId string, result *testkube.TestSuiteBatchStepExecutionResult) { timer := time.NewTimer(duration) ticker := time.NewTicker(abortionPollingInterval) @@ -355,13 +457,36 @@ func (s *Scheduler) delayWithAbortionCheck(duration time.Duration, testSuiteId s case <-timer.C: s.logger.Infow("delay finished", "testSuiteId", testSuiteId, "duration", duration) - result.Execution.ExecutionResult.Success() + 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 { + result.Execute[i].Execution.ExecutionResult.Success() + } + } + return case <-ticker.C: if s.wasTestSuiteAborted(context.Background(), testSuiteId) { s.logger.Infow("delay aborted", "testSuiteId", testSuiteId, "duration", duration) - result.Execution.ExecutionResult.Abort() + 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() + } + } + return } } diff --git a/pkg/slack/slack.go b/pkg/slack/slack.go index 7be743c8115..ffc24551ca8 100644 --- a/pkg/slack/slack.go +++ b/pkg/slack/slack.go @@ -26,6 +26,7 @@ type MessageArgs struct { StartTime string EndTime string Duration string + ClusterName string } type Notifier struct { @@ -33,11 +34,12 @@ type Notifier struct { timestamps map[string]string Ready bool messageTemplate string + clusterName string config *Config } -func NewNotifier(template string, config []NotificationsConfig) *Notifier { - notifier := Notifier{messageTemplate: template, config: NewConfig(config)} +func NewNotifier(template, clusterName string, config []NotificationsConfig) *Notifier { + notifier := Notifier{messageTemplate: template, clusterName: clusterName, config: NewConfig(config)} notifier.timestamps = make(map[string]string) if token, ok := os.LookupEnv("SLACK_TOKEN"); ok { log.DefaultLogger.Infow("initializing slack client", "SLACK_TOKEN", token) @@ -180,8 +182,9 @@ func (s *Notifier) composeTestsuiteMessage(execution *testkube.TestSuiteExecutio StartTime: execution.StartTime.String(), EndTime: execution.EndTime.String(), Duration: execution.Duration, - TotalSteps: len(execution.StepResults), + TotalSteps: len(execution.ExecuteStepResults), FailedSteps: execution.FailedStepsCount(), + ClusterName: s.clusterName, } log.DefaultLogger.Infow("Execution changed", "status", execution.Status) @@ -216,6 +219,7 @@ func (s *Notifier) composeTestMessage(execution *testkube.Execution, eventType t Duration: execution.Duration, TotalSteps: len(execution.ExecutionResult.Steps), FailedSteps: execution.ExecutionResult.FailedStepsCount(), + ClusterName: s.clusterName, } log.DefaultLogger.Infow("Execution changed", "status", execution.ExecutionResult.Status) diff --git a/pkg/triggers/executor.go b/pkg/triggers/executor.go index 17e0473f610..a7d9611f406 100644 --- a/pkg/triggers/executor.go +++ b/pkg/triggers/executor.go @@ -2,18 +2,16 @@ package triggers import ( "context" - "strconv" "time" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" testsv3 "github.com/kubeshop/testkube-operator/apis/tests/v3" - testsuitesv2 "github.com/kubeshop/testkube-operator/apis/testsuite/v2" + testsuitesv3 "github.com/kubeshop/testkube-operator/apis/testsuite/v3" testtriggersv1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" - v1 "github.com/kubeshop/testkube/internal/app/api/v1" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/scheduler" "github.com/kubeshop/testkube/pkg/workerpool" ) @@ -29,10 +27,7 @@ type ExecutorF func(context.Context, *testtriggersv1.TestTrigger) error func (s *Service) execute(ctx context.Context, t *testtriggersv1.TestTrigger) error { status := s.getStatusForTrigger(t) - concurrencyLevel, err := strconv.Atoi(v1.DefaultConcurrencyLevel) - if err != nil { - return errors.Wrap(err, "error parsing default concurrency level") - } + concurrencyLevel := scheduler.DefaultConcurrencyLevel switch t.Spec.Execution { case ExecutionTest: @@ -139,10 +134,10 @@ func (s *Service) getTests(t *testtriggersv1.TestTrigger) ([]testsv3.Test, error return tests, nil } -func (s *Service) getTestSuites(t *testtriggersv1.TestTrigger) ([]testsuitesv2.TestSuite, error) { - var testSuites []testsuitesv2.TestSuite +func (s *Service) getTestSuites(t *testtriggersv1.TestTrigger) ([]testsuitesv3.TestSuite, error) { + var testSuites []testsuitesv3.TestSuite if t.Spec.TestSelector.Name != "" { - s.logger.Debugf("trigger service: executor component: fetching testsuitesv2.TestSuite with name %s", t.Spec.TestSelector.Name) + s.logger.Debugf("trigger service: executor component: fetching testsuitesv3.TestSuite with name %s", t.Spec.TestSelector.Name) testSuite, err := s.testSuitesClient.Get(t.Spec.TestSelector.Name) if err != nil { return nil, err @@ -155,7 +150,7 @@ func (s *Service) getTestSuites(t *testtriggersv1.TestTrigger) ([]testsuitesv2.T return nil, errors.WithMessagef(err, "error creating selector from test resource label selector") } stringifiedSelector := selector.String() - s.logger.Debugf("trigger service: executor component: fetching testsuitesv2.TestSuite with label %s", stringifiedSelector) + s.logger.Debugf("trigger service: executor component: fetching testsuitesv3.TestSuite with label %s", stringifiedSelector) testSuitesList, err := s.testSuitesClient.List(stringifiedSelector) if err != nil { return nil, err diff --git a/pkg/triggers/executor_test.go b/pkg/triggers/executor_test.go index d9303c63ff1..be84fc9cb71 100644 --- a/pkg/triggers/executor_test.go +++ b/pkg/triggers/executor_test.go @@ -4,13 +4,6 @@ import ( "context" "testing" - "github.com/kubeshop/testkube/pkg/repository/config" - - "github.com/kubeshop/testkube/pkg/repository/result" - "github.com/kubeshop/testkube/pkg/repository/testresult" - - testsourcesv1 "github.com/kubeshop/testkube-operator/client/testsources/v1" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -20,7 +13,8 @@ import ( testtriggersv1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors/v1" testsclientv3 "github.com/kubeshop/testkube-operator/client/tests/v3" - testsuitesv2 "github.com/kubeshop/testkube-operator/client/testsuites/v2" + testsourcesv1 "github.com/kubeshop/testkube-operator/client/testsources/v1" + testsuitesv3 "github.com/kubeshop/testkube-operator/client/testsuites/v3" "github.com/kubeshop/testkube/internal/app/api/metrics" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/configmap" @@ -28,6 +22,9 @@ import ( "github.com/kubeshop/testkube/pkg/event/bus" "github.com/kubeshop/testkube/pkg/executor/client" "github.com/kubeshop/testkube/pkg/log" + "github.com/kubeshop/testkube/pkg/repository/config" + "github.com/kubeshop/testkube/pkg/repository/result" + "github.com/kubeshop/testkube/pkg/repository/testresult" "github.com/kubeshop/testkube/pkg/scheduler" "github.com/kubeshop/testkube/pkg/secret" ) @@ -46,7 +43,7 @@ func TestExecute(t *testing.T) { mockExecutorsClient := executorsclientv1.NewMockInterface(mockCtrl) mockTestsClient := testsclientv3.NewMockInterface(mockCtrl) - mockTestSuitesClient := testsuitesv2.NewMockInterface(mockCtrl) + mockTestSuitesClient := testsuitesv3.NewMockInterface(mockCtrl) mockTestSourcesClient := testsourcesv1.NewMockInterface(mockCtrl) mockSecretClient := secret.NewMockInterface(mockCtrl) configMapConfig := config.NewMockRepository(mockCtrl) @@ -54,7 +51,7 @@ func TestExecute(t *testing.T) { mockExecutor := client.NewMockExecutor(mockCtrl) - mockEventEmitter := event.NewEmitter(bus.NewEventBusMock()) + mockEventEmitter := event.NewEmitter(bus.NewEventBusMock(), "") mockTest := testsv3.Test{ ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "some-test"}, diff --git a/pkg/triggers/matcher.go b/pkg/triggers/matcher.go index 741ce04580f..51c20fcc5bc 100644 --- a/pkg/triggers/matcher.go +++ b/pkg/triggers/matcher.go @@ -5,7 +5,6 @@ import ( "time" "github.com/pkg/errors" - "go.uber.org/zap" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" diff --git a/pkg/triggers/scraper_test.go b/pkg/triggers/scraper_test.go index 23b2b54e290..b7971bc58b3 100644 --- a/pkg/triggers/scraper_test.go +++ b/pkg/triggers/scraper_test.go @@ -5,15 +5,14 @@ import ( "testing" "time" - "github.com/kubeshop/testkube/pkg/repository/result" - "github.com/kubeshop/testkube/pkg/repository/testresult" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "go.mongodb.org/mongo-driver/mongo" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/log" + "github.com/kubeshop/testkube/pkg/repository/result" + "github.com/kubeshop/testkube/pkg/repository/testresult" ) func TestService_runExecutionScraper(t *testing.T) { diff --git a/pkg/triggers/service.go b/pkg/triggers/service.go index abe7e472407..13211a8406a 100644 --- a/pkg/triggers/service.go +++ b/pkg/triggers/service.go @@ -7,28 +7,24 @@ import ( "strings" "time" + "go.uber.org/zap" + "k8s.io/client-go/kubernetes" + + testsv3 "github.com/kubeshop/testkube-operator/apis/tests/v3" + testsuitev3 "github.com/kubeshop/testkube-operator/apis/testsuite/v3" + testtriggersv1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" + executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors/v1" + testsclientv3 "github.com/kubeshop/testkube-operator/client/tests/v3" + testsuitesclientv3 "github.com/kubeshop/testkube-operator/client/testsuites/v3" + testkubeclientsetv1 "github.com/kubeshop/testkube-operator/pkg/clientset/versioned" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/repository/config" - - "github.com/kubeshop/testkube/pkg/version" - "github.com/kubeshop/testkube/pkg/repository/result" "github.com/kubeshop/testkube/pkg/repository/testresult" - "github.com/kubeshop/testkube/pkg/scheduler" "github.com/kubeshop/testkube/pkg/telemetry" "github.com/kubeshop/testkube/pkg/utils" - - testsv3 "github.com/kubeshop/testkube-operator/apis/tests/v3" - testsuitev2 "github.com/kubeshop/testkube-operator/apis/testsuite/v2" - testtriggersv1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" - executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors/v1" - testsclientv3 "github.com/kubeshop/testkube-operator/client/tests/v3" - testsuitesclientv2 "github.com/kubeshop/testkube-operator/client/testsuites/v2" - testkubeclientsetv1 "github.com/kubeshop/testkube-operator/pkg/clientset/versioned" - - "go.uber.org/zap" - "k8s.io/client-go/kubernetes" + "github.com/kubeshop/testkube/pkg/version" ) const ( @@ -57,7 +53,7 @@ type Service struct { scheduler *scheduler.Scheduler clientset kubernetes.Interface testKubeClientset testkubeclientsetv1.Interface - testSuitesClient testsuitesclientv2.Interface + testSuitesClient testsuitesclientv3.Interface testsClient testsclientv3.Interface resultRepository result.Repository testResultRepository testresult.Repository @@ -74,7 +70,7 @@ func NewService( scheduler *scheduler.Scheduler, clientset kubernetes.Interface, testKubeClientset testkubeclientsetv1.Interface, - testSuitesClient testsuitesclientv2.Interface, + testSuitesClient testsuitesclientv3.Interface, testsClient testsclientv3.Interface, resultRepository result.Repository, testResultRepository testresult.Repository, @@ -297,7 +293,7 @@ func (s *Service) updateTest(test *testsv3.Test) { } } -func (s *Service) addTestSuite(testSuite *testsuitev2.TestSuite) { +func (s *Service) addTestSuite(testSuite *testsuitev3.TestSuite) { ctx := context.Background() telemetryEnabled, err := s.configMap.GetTelemetryEnabled(ctx) if err != nil { diff --git a/pkg/triggers/service_test.go b/pkg/triggers/service_test.go index 0e700ca407b..512b13ea149 100644 --- a/pkg/triggers/service_test.go +++ b/pkg/triggers/service_test.go @@ -5,23 +5,20 @@ import ( "testing" "time" - "github.com/kubeshop/testkube/pkg/repository/config" - - "github.com/kubeshop/testkube/pkg/repository/result" - "github.com/kubeshop/testkube/pkg/repository/testresult" - "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" executorv1 "github.com/kubeshop/testkube-operator/apis/executor/v1" testsv3 "github.com/kubeshop/testkube-operator/apis/tests/v3" - testtriggersv1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" + v1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors/v1" testsclientv3 "github.com/kubeshop/testkube-operator/client/tests/v3" testsourcesv1 "github.com/kubeshop/testkube-operator/client/testsources/v1" - testsuitesv2 "github.com/kubeshop/testkube-operator/client/testsuites/v2" + testsuitesv3 "github.com/kubeshop/testkube-operator/client/testsuites/v3" faketestkube "github.com/kubeshop/testkube-operator/pkg/clientset/versioned/fake" "github.com/kubeshop/testkube/internal/app/api/metrics" "github.com/kubeshop/testkube/pkg/api/v1/testkube" @@ -30,12 +27,11 @@ import ( "github.com/kubeshop/testkube/pkg/event/bus" "github.com/kubeshop/testkube/pkg/executor/client" "github.com/kubeshop/testkube/pkg/log" + "github.com/kubeshop/testkube/pkg/repository/config" + "github.com/kubeshop/testkube/pkg/repository/result" + "github.com/kubeshop/testkube/pkg/repository/testresult" "github.com/kubeshop/testkube/pkg/scheduler" "github.com/kubeshop/testkube/pkg/secret" - - v1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" - "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestService_Run(t *testing.T) { @@ -53,7 +49,7 @@ func TestService_Run(t *testing.T) { mockExecutorsClient := executorsclientv1.NewMockInterface(mockCtrl) mockTestsClient := testsclientv3.NewMockInterface(mockCtrl) - mockTestSuitesClient := testsuitesv2.NewMockInterface(mockCtrl) + mockTestSuitesClient := testsuitesv3.NewMockInterface(mockCtrl) mockTestSourcesClient := testsourcesv1.NewMockInterface(mockCtrl) mockSecretClient := secret.NewMockInterface(mockCtrl) configMapConfig := config.NewMockRepository(mockCtrl) @@ -61,7 +57,7 @@ func TestService_Run(t *testing.T) { mockExecutor := client.NewMockExecutor(mockCtrl) - mockEventEmitter := event.NewEmitter(bus.NewEventBusMock()) + mockEventEmitter := event.NewEmitter(bus.NewEventBusMock(), "") mockTest := testsv3.Test{ ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "some-test"}, diff --git a/pkg/triggers/status_test.go b/pkg/triggers/status_test.go index 83e0699bc3d..b9f002c6792 100644 --- a/pkg/triggers/status_test.go +++ b/pkg/triggers/status_test.go @@ -3,9 +3,9 @@ package triggers import ( "testing" - v1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" - "github.com/stretchr/testify/assert" + + v1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" ) func TestTriggerStatus(t *testing.T) { diff --git a/pkg/triggers/watcher.go b/pkg/triggers/watcher.go index ffd1aecf779..d6d08f71bf5 100644 --- a/pkg/triggers/watcher.go +++ b/pkg/triggers/watcher.go @@ -3,31 +3,26 @@ package triggers import ( "context" + "github.com/google/go-cmp/cmp" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers" appsinformerv1 "k8s.io/client-go/informers/apps/v1" coreinformerv1 "k8s.io/client-go/informers/core/v1" networkinginformerv1 "k8s.io/client-go/informers/networking/v1" "k8s.io/client-go/kubernetes" - - "github.com/kubeshop/testkube-operator/pkg/clientset/versioned" - testkubeinformerv1 "github.com/kubeshop/testkube-operator/pkg/informers/externalversions/tests/v1" - testkubeinformerv2 "github.com/kubeshop/testkube-operator/pkg/informers/externalversions/tests/v2" - testkubeinformerv3 "github.com/kubeshop/testkube-operator/pkg/informers/externalversions/tests/v3" - - networkingv1 "k8s.io/api/networking/v1" - - "github.com/google/go-cmp/cmp" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/informers" "k8s.io/client-go/tools/cache" testsv3 "github.com/kubeshop/testkube-operator/apis/tests/v3" - testsuitev2 "github.com/kubeshop/testkube-operator/apis/testsuite/v2" + testsuitev3 "github.com/kubeshop/testkube-operator/apis/testsuite/v3" testtriggersv1 "github.com/kubeshop/testkube-operator/apis/testtriggers/v1" + "github.com/kubeshop/testkube-operator/pkg/clientset/versioned" "github.com/kubeshop/testkube-operator/pkg/informers/externalversions" + testkubeinformerv1 "github.com/kubeshop/testkube-operator/pkg/informers/externalversions/tests/v1" + testkubeinformerv3 "github.com/kubeshop/testkube-operator/pkg/informers/externalversions/tests/v3" "github.com/kubeshop/testkube-operator/pkg/validation/tests/v1/testtrigger" ) @@ -42,7 +37,7 @@ type k8sInformers struct { configMapInformers []coreinformerv1.ConfigMapInformer testTriggerInformer testkubeinformerv1.TestTriggerInformer - testSuiteInformer testkubeinformerv2.TestSuiteInformer + testSuiteInformer testkubeinformerv3.TestSuiteInformer testInformer testkubeinformerv3.TestInformer } @@ -68,7 +63,7 @@ func newK8sInformers(clientset kubernetes.Interface, testKubeClientset versioned testkubeInformerFactory := externalversions.NewSharedInformerFactoryWithOptions( testKubeClientset, 0, externalversions.WithNamespace(testkubeNamespace)) k8sInformers.testTriggerInformer = testkubeInformerFactory.Tests().V1().TestTriggers() - k8sInformers.testSuiteInformer = testkubeInformerFactory.Tests().V2().TestSuites() + k8sInformers.testSuiteInformer = testkubeInformerFactory.Tests().V3().TestSuites() k8sInformers.testInformer = testkubeInformerFactory.Tests().V3().Tests() return &k8sInformers @@ -660,14 +655,14 @@ func (s *Service) testTriggerEventHandler() cache.ResourceEventHandlerFuncs { func (s *Service) testSuiteEventHandler() cache.ResourceEventHandlerFuncs { return cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { - testSuite, ok := obj.(*testsuitev2.TestSuite) + testSuite, ok := obj.(*testsuitev3.TestSuite) if !ok { s.logger.Errorf("failed to process create testsuite event due to it being an unexpected type, received type %+v", obj) return } if inPast(testSuite.CreationTimestamp.Time, s.watchFromDate) { s.logger.Debugf( - "trigger service: watcher component: no-op create trigger: testsuite %s/%s was created in the past", + "trigger service: watcher component: no-op create test suite: test suite %s/%s was created in the past", testSuite.Namespace, testSuite.Name, ) return @@ -691,7 +686,7 @@ func (s *Service) testEventHandler() cache.ResourceEventHandlerFuncs { } if inPast(test.CreationTimestamp.Time, s.watchFromDate) { s.logger.Debugf( - "trigger service: watcher component: no-op create trigger: test %s/%s was created in the past", + "trigger service: watcher component: no-op create test: test %s/%s was created in the past", test.Namespace, test.Name, ) return 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 b212cefe0b4..0ea71f2e66c 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,5 +1,5 @@ describe('Testkube website', () => { - it('Open Testkube website', () => { + it.skip('Open Testkube website', () => { cy.visit('/') }) it(`Validate CYPRESS_CUSTOM_ENV ENV (${Cypress.env('CUSTOM_ENV')})`, () => { diff --git a/test/dashboard-e2e/crd/crd.yaml b/test/dashboard-e2e/crd/crd.yaml index 33c309b9303..6710a9b1705 100644 --- a/test/dashboard-e2e/crd/crd.yaml +++ b/test/dashboard-e2e/crd/crd.yaml @@ -25,4 +25,10 @@ spec: name: API_URL value: testkube-api-server.testkube.svc.cluster.local:8088/v1 type: basic + DASHBOARD_API_URL: + name: DASHBOARD_API_URL + value: testkube-api-server.testkube.svc.cluster.local:8088/v1 + type: basic + artifactRequest: + storageClassName: standard schedule: "15 */4 * * *" \ 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 2cb166a7c5d..1aef69cc0fc 100644 --- a/test/maven/executor-smoke/crd/crd.yaml +++ b/test/maven/executor-smoke/crd/crd.yaml @@ -49,7 +49,7 @@ spec: apiVersion: tests.testkube.io/v3 kind: Test metadata: - name: maven-executor-smoke-jdk18 # expected failure - ENVs not set + name: maven-executor-smoke-jdk18-negative # expected failure - ENVs not set namespace: testkube labels: core-tests: executors-negative diff --git a/test/suites/demo-testsuite.json b/test/suites/demo-testsuite.json index 2b351554e05..0e19873a5ae 100644 --- a/test/suites/demo-testsuite.json +++ b/test/suites/demo-testsuite.json @@ -2,12 +2,12 @@ "name": "demo-testsuite", "description": "Demo release tests", "steps": [ - {"execute": {"name": "postman-executor-smoke"}}, - {"execute": {"name": "postman-executor-smoke-testsource"}}, - {"execute": {"name": "k6-executor-smoke"}}, - {"execute": {"name": "container-executor-curl-smoke"}}, - {"execute" :{"name": "cypress-11-executor-smoke-chrome"}}, - {"execute" :{"name": "cypress-default-executor-smoke-electron-testsource"}}, - {"execute" :{"name": "dashboard-e2e-tests"}} + {"execute": [{"test": "postman-executor-smoke"}]}, + {"execute": [{"test": "postman-executor-smoke-testsource-git-file"}]}, + {"execute": [{"test": "k6-executor-smoke"}]}, + {"execute": [{"test": "container-executor-curl-smoke"}]}, + {"execute": [{"test": "cypress-11-executor-smoke-chrome"}]}, + {"execute": [{"test": "cypress-default-executor-smoke-electron-testsource"}]}, + {"execute": [{"test": "dashboard-e2e-tests"}]} ] } diff --git a/test/suites/executor-artillery-smoke-tests.json b/test/suites/executor-artillery-smoke-tests.json index 19f1cea3781..636929de44b 100644 --- a/test/suites/executor-artillery-smoke-tests.json +++ b/test/suites/executor-artillery-smoke-tests.json @@ -2,7 +2,7 @@ "name": "executor-artillery-smoke-tests", "description": "artillery executor smoke tests", "steps": [ - {"execute": {"name": "artillery-executor-smoke"}}, - {"execute": {"name": "artillery-executor-smoke-negative"}} + {"execute": [{"test": "artillery-executor-smoke"}]}, + {"execute": [{"test": "artillery-executor-smoke-negative"}]} ] } diff --git a/test/suites/executor-common-smoke-tests.json b/test/suites/executor-common-smoke-tests.json index 469e63a69be..bbd2386df49 100644 --- a/test/suites/executor-common-smoke-tests.json +++ b/test/suites/executor-common-smoke-tests.json @@ -2,10 +2,10 @@ "name": "executor-common-smoke-tests", "description": "Reduced common test suite for executors", "steps": [ - {"execute": {"name": "cypress-default-executor-smoke-electron"}}, - {"execute": {"name": "cypress-default-executor-smoke-electron-testsource-git-dir"}}, - {"execute": {"name": "postman-executor-smoke"}}, - {"execute": {"name": "postman-executor-smoke-testsource-git-file"}}, - {"execute": {"name": "k6-executor-smoke"}} + {"execute": [{"test": "cypress-default-executor-smoke-electron"}]}, + {"execute": [{"test": "cypress-default-executor-smoke-electron-testsource-git-dir"}]}, + {"execute": [{"test": "postman-executor-smoke"}]}, + {"execute": [{"test": "postman-executor-smoke-testsource-git-file"}]}, + {"execute": [{"test": "k6-executor-smoke"}]} ] } diff --git a/test/suites/executor-container-curl-smoke-tests.json b/test/suites/executor-container-curl-smoke-tests.json index 3468ec2d2ef..ed18762d5f4 100644 --- a/test/suites/executor-container-curl-smoke-tests.json +++ b/test/suites/executor-container-curl-smoke-tests.json @@ -2,7 +2,7 @@ "name": "executor-container-curl-smoke-tests", "description": "container executor curl smoke tests", "steps": [ - {"execute": {"name": "container-executor-curl-smoke"}}, - {"execute": {"name": "container-executor-curl-smoke-negative"}} + {"execute": [{"test": "container-executor-curl-smoke"}]}, + {"execute": [{"test": "container-executor-curl-smoke-negative"}]} ] } diff --git a/test/suites/executor-curl-smoke-tests.json b/test/suites/executor-curl-smoke-tests.json index ebe5d375526..60e692932bf 100644 --- a/test/suites/executor-curl-smoke-tests.json +++ b/test/suites/executor-curl-smoke-tests.json @@ -2,7 +2,7 @@ "name": "executor-curl-smoke-tests", "description": "curl executor smoke tests", "steps": [ - {"execute": {"name": "curl-executor-smoke"}}, - {"execute": {"name": "curl-executor-smoke-negative"}} + {"execute": [{"test": "curl-executor-smoke"}]}, + {"execute": [{"test": "curl-executor-smoke-negative"}]} ] } diff --git a/test/suites/executor-cypress-smoke-tests.json b/test/suites/executor-cypress-smoke-tests.json index f38d26393e7..fdac64cacf2 100644 --- a/test/suites/executor-cypress-smoke-tests.json +++ b/test/suites/executor-cypress-smoke-tests.json @@ -2,27 +2,27 @@ "name": "executor-cypress-smoke-tests", "description": "Cypress executor smoke tests", "steps": [ - {"execute": {"name": "cypress-12-executor-smoke-electron"}}, - {"execute": {"name": "cypress-12-executor-smoke-chrome"}}, - {"execute": {"name": "cypress-12-executor-smoke-firefox"}}, - {"execute": {"name": "cypress-11-executor-smoke-electron"}}, - {"execute": {"name": "cypress-11-executor-smoke-chrome"}}, - {"execute": {"name": "cypress-11-executor-smoke-firefox"}}, - {"execute": {"name": "cypress-10-executor-smoke-electron"}}, - {"execute": {"name": "cypress-10-executor-smoke-chrome"}}, - {"execute": {"name": "cypress-10-executor-smoke-firefox"}}, - {"execute": {"name": "cypress-9-executor-smoke-electron"}}, - {"execute": {"name": "cypress-9-executor-smoke-chrome"}}, - {"execute": {"name": "cypress-9-executor-smoke-firefox"}}, - {"execute": {"name": "cypress-8-executor-smoke-electron"}}, - {"execute": {"name": "cypress-8-executor-smoke-chrome"}}, - {"execute": {"name": "cypress-8-executor-smoke-firefox"}}, - {"execute": {"name": "cypress-default-executor-smoke-electron"}}, - {"execute": {"name": "cypress-default-executor-smoke-electron-git-dir"}}, - {"execute": {"name": "cypress-default-executor-smoke-electron-testsource"}}, - {"execute": {"name": "cypress-default-executor-smoke-electron-testsource-git-dir"}}, - {"execute": {"name": "cypress-default-executor-smoke-yarn"}}, - {"execute": {"name": "cypress-default-executor-smoke-video-recording-enabled"}}, - {"execute": {"name": "cypress-default-executor-smoke-electron-negative"}} + {"execute": [{"test": "cypress-12-executor-smoke-electron"}]}, + {"execute": [{"test": "cypress-12-executor-smoke-chrome"}]}, + {"execute": [{"test": "cypress-12-executor-smoke-firefox"}]}, + {"execute": [{"test": "cypress-11-executor-smoke-electron"}]}, + {"execute": [{"test": "cypress-11-executor-smoke-chrome"}]}, + {"execute": [{"test": "cypress-11-executor-smoke-firefox"}]}, + {"execute": [{"test": "cypress-10-executor-smoke-electron"}]}, + {"execute": [{"test": "cypress-10-executor-smoke-chrome"}]}, + {"execute": [{"test": "cypress-10-executor-smoke-firefox"}]}, + {"execute": [{"test": "cypress-9-executor-smoke-electron"}]}, + {"execute": [{"test": "cypress-9-executor-smoke-chrome"}]}, + {"execute": [{"test": "cypress-9-executor-smoke-firefox"}]}, + {"execute": [{"test": "cypress-8-executor-smoke-electron"}]}, + {"execute": [{"test": "cypress-8-executor-smoke-chrome"}]}, + {"execute": [{"test": "cypress-8-executor-smoke-firefox"}]}, + {"execute": [{"test": "cypress-default-executor-smoke-electron"}]}, + {"execute": [{"test": "cypress-default-executor-smoke-electron-git-dir"}]}, + {"execute": [{"test": "cypress-default-executor-smoke-electron-testsource"}]}, + {"execute": [{"test": "cypress-default-executor-smoke-electron-testsource-git-dir"}]}, + {"execute": [{"test": "cypress-default-executor-smoke-yarn"}]}, + {"execute": [{"test": "cypress-default-executor-smoke-video-recording-enabled"}]}, + {"execute": [{"test": "cypress-default-executor-smoke-electron-negative"}]} ] } diff --git a/test/suites/executor-gradle-smoke-tests.json b/test/suites/executor-gradle-smoke-tests.json index 9664d020eed..1461b93c4d9 100644 --- a/test/suites/executor-gradle-smoke-tests.json +++ b/test/suites/executor-gradle-smoke-tests.json @@ -2,10 +2,10 @@ "name": "executor-gradle-smoke-tests", "description": "gradle executor smoke tests", "steps": [ - {"execute": {"name": "gradle-executor-smoke-jdk18"}}, - {"execute": {"name": "gradle-executor-smoke-jdk17"}}, - {"execute": {"name": "gradle-executor-smoke-jdk11"}}, - {"execute": {"name": "gradle-executor-smoke-jdk8"}}, - {"execute": {"name": "gradle-executor-smoke-jdk18-negative"}} + {"execute": [{"test": "gradle-executor-smoke-jdk18"}]}, + {"execute": [{"test": "gradle-executor-smoke-jdk17"}]}, + {"execute": [{"test": "gradle-executor-smoke-jdk11"}]}, + {"execute": [{"test": "gradle-executor-smoke-jdk8"}]}, + {"execute": [{"test": "gradle-executor-smoke-jdk18-negative"}]} ] } diff --git a/test/suites/executor-jmeter-smoke-tests.json b/test/suites/executor-jmeter-smoke-tests.json index 3f2c9e8dbcc..cd6c102c1b2 100644 --- a/test/suites/executor-jmeter-smoke-tests.json +++ b/test/suites/executor-jmeter-smoke-tests.json @@ -2,9 +2,9 @@ "name": "executor-jmeter-smoke-tests", "description": "jmeter executor smoke tests", "steps": [ - {"execute": {"name": "jmeter-executor-smoke"}}, - {"execute": {"name": "jmeter-executor-smoke-directory"}}, - {"execute": {"name": "jmeter-executor-smoke-negative"}}, - {"execute": {"name": "jmeter-executor-smoke-env-and-property-values"}} + {"execute": [{"test": "jmeter-executor-smoke"}]}, + {"execute": [{"test": "jmeter-executor-smoke-directory"}]}, + {"execute": [{"test": "jmeter-executor-smoke-negative"}]}, + {"execute": [{"test": "jmeter-executor-smoke-env-and-property-values"}]} ] } diff --git a/test/suites/executor-k6-other-tests.json b/test/suites/executor-k6-other-tests.json index 4a314cb09a3..80aced98c62 100644 --- a/test/suites/executor-k6-other-tests.json +++ b/test/suites/executor-k6-other-tests.json @@ -2,6 +2,6 @@ "name": "executor-k6-other-tests", "description": "k6 executor - other tests and edge-cases", "steps": [ - {"execute": {"name": "k6-executor-smoke-directory"}} + {"execute": [{"test": "k6-executor-smoke-directory"}]} ] } diff --git a/test/suites/executor-k6-smoke-tests.json b/test/suites/executor-k6-smoke-tests.json index fc5e9d53e2d..c168799cdba 100644 --- a/test/suites/executor-k6-smoke-tests.json +++ b/test/suites/executor-k6-smoke-tests.json @@ -2,8 +2,8 @@ "name": "executor-k6-smoke-tests", "description": "k6 executor smoke tests", "steps": [ - {"execute": {"name": "k6-executor-smoke"}}, - {"execute": {"name": "k6-executor-smoke-git-file"}}, - {"execute": {"name": "k6-executor-smoke-negative"}} + {"execute": [{"test": "k6-executor-smoke"}]}, + {"execute": [{"test": "k6-executor-smoke-git-file"}]}, + {"execute": [{"test": "k6-executor-smoke-negative"}]} ] } diff --git a/test/suites/executor-kubepug-smoke-tests.json b/test/suites/executor-kubepug-smoke-tests.json index d20bd616cc2..45b45250e04 100644 --- a/test/suites/executor-kubepug-smoke-tests.json +++ b/test/suites/executor-kubepug-smoke-tests.json @@ -2,7 +2,7 @@ "name": "executor-kubepug-smoke-tests", "description": "kubepug executor smoke tests", "steps": [ - {"execute": {"name": "kubepug-executor-smoke"}}, - {"execute": {"name": "kubepug-executor-smoke-negative"}} + {"execute": [{"test": "kubepug-executor-smoke"}]}, + {"execute": [{"test": "kubepug-executor-smoke-negative"}]} ] } diff --git a/test/suites/executor-maven-smoke-tests.json b/test/suites/executor-maven-smoke-tests.json index 4fa3b881688..708196575b3 100644 --- a/test/suites/executor-maven-smoke-tests.json +++ b/test/suites/executor-maven-smoke-tests.json @@ -2,8 +2,8 @@ "name": "executor-maven-smoke-tests", "description": "maven executor smoke tests", "steps": [ - {"execute": {"name": "maven-executor-smoke-jdk18"}}, - {"execute": {"name": "maven-executor-smoke-jdk11"}}, - {"execute": {"name": "maven-executor-smoke-jdk18-negative"}} + {"execute": [{"test": "maven-executor-smoke-jdk18"}]}, + {"execute": [{"test": "maven-executor-smoke-jdk11"}]}, + {"execute": [{"test": "maven-executor-smoke-jdk18-negative"}]} ] } diff --git a/test/suites/executor-postman-smoke-tests.json b/test/suites/executor-postman-smoke-tests.json index 138b558b386..aa12e5ba27a 100644 --- a/test/suites/executor-postman-smoke-tests.json +++ b/test/suites/executor-postman-smoke-tests.json @@ -2,11 +2,11 @@ "name": "executor-postman-smoke-tests", "description": "postman executor smoke tests", "steps": [ - {"execute": {"name": "postman-executor-smoke"}}, - {"execute": {"name": "postman-executor-smoke-git-file"}}, - {"execute": {"name": "postman-executor-smoke-testsource"}}, - {"execute": {"name": "postman-executor-smoke-testsource-git-file"}}, - {"execute": {"name": "postman-executor-smoke-testsource-overwrite"}}, - {"execute": {"name": "postman-executor-smoke-negative"}} + {"execute": [{"test": "postman-executor-smoke"}]}, + {"execute": [{"test": "postman-executor-smoke-git-file"}]}, + {"execute": [{"test": "postman-executor-smoke-testsource"}]}, + {"execute": [{"test": "postman-executor-smoke-testsource-git-file"}]}, + {"execute": [{"test": "postman-executor-smoke-testsource-overwrite"}]}, + {"execute": [{"test": "postman-executor-smoke-negative"}]} ] } diff --git a/test/suites/executor-soapui-smoke-tests.json b/test/suites/executor-soapui-smoke-tests.json index 5bb1fb7d0b2..6b36e7c6c92 100644 --- a/test/suites/executor-soapui-smoke-tests.json +++ b/test/suites/executor-soapui-smoke-tests.json @@ -2,7 +2,7 @@ "name": "executor-soapui-smoke-tests", "description": "soapui executor smoke tests", "steps": [ - {"execute": {"name": "soapui-executor-smoke"}}, - {"execute": {"name": "soapui-executor-smoke-negative"}} + {"execute": [{"test": "soapui-executor-smoke"}]}, + {"execute": [{"test": "soapui-executor-smoke-negative"}]} ] } diff --git a/test/suites/staging-testsuite.json b/test/suites/staging-testsuite.json index 5c3df799739..f6b5548fe66 100644 --- a/test/suites/staging-testsuite.json +++ b/test/suites/staging-testsuite.json @@ -2,12 +2,12 @@ "name": "staging-testsuite", "description": "Staging release tests", "steps": [ - {"execute": {"name": "postman-executor-smoke"}}, - {"execute": {"name": "postman-executor-smoke-testsource"}}, - {"execute": {"name": "k6-executor-smoke"}}, - {"execute": {"name": "container-executor-curl-smoke"}}, - {"execute" :{"name": "cypress-11-executor-smoke-chrome"}}, - {"execute" :{"name": "cypress-default-executor-smoke-electron-testsource"}}, - {"execute" :{"name": "dashboard-e2e-tests"}} + {"execute": [{"test": "postman-executor-smoke"}]}, + {"execute": [{"test": "postman-executor-smoke-testsource"}]}, + {"execute": [{"test": "k6-executor-smoke"}]}, + {"execute": [{"test": "container-executor-curl-smoke"}]}, + {"execute": [{"test": "cypress-11-executor-smoke-chrome"}]}, + {"execute": [{"test": "cypress-default-executor-smoke-electron-testsource"}]}, + {"execute": [{"test": "dashboard-e2e-tests"}]} ] } diff --git a/test/suites/testsuite-example-1.json b/test/suites/testsuite-example-1.json index c2c5e248d5f..a2c7937e210 100644 --- a/test/suites/testsuite-example-1.json +++ b/test/suites/testsuite-example-1.json @@ -2,12 +2,13 @@ "name": "test-example-1", "description": "Run test several times", "steps": [ - {"execute": {"namespace": "testkube", "name": "testkube-dashboard"}}, - {"delay": {"duration": 5000}}, - {"execute": {"namespace": "testkube", "name": "testkube-todo-api"}}, - {"delay": {"duration": 5000}}, - {"execute": {"namespace": "testkube", "name": "testkube-todo-frontend"}}, - {"delay": {"duration": 5000}}, - {"execute": {"namespace": "testkube", "name": "kubeshop-site"}} + {"execute": [{"test": "testkube-dashboard"}]}, + {"execute": [{"delay": "5s"}]}, + {"execute": [{"test": "testkube-todo-api"}]}, + {"execute": [{"delay": "5s"}]}, + {"execute": [{"test": "testkube-todo-frontend"}]}, + {"execute": [{"delay": "5s"}]}, + {"execute": [{"test": "kubeshop-site"}]} ] } + diff --git a/test/suites/testsuite-example-2.json b/test/suites/testsuite-example-2.json index bfb676799eb..f05d74ca005 100644 --- a/test/suites/testsuite-example-2.json +++ b/test/suites/testsuite-example-2.json @@ -2,20 +2,20 @@ "name": "test-example-2", "description": "Tests fast first", "steps": [ - {"execute": {"namespace": "testkube", "name": "testkube-todo-api"}}, - {"execute": {"namespace": "testkube", "name": "testkube-todo-api"}}, - {"delay": {"duration": 1000}}, - {"execute": {"namespace": "testkube", "name": "testkube-todo-api"}}, - {"execute": {"namespace": "testkube", "name": "testkube-dashboard"}}, - {"delay": {"duration": 5000}}, - {"execute": {"namespace": "testkube", "name": "kubeshop-site"}}, - {"execute": {"namespace": "testkube", "name": "testkube-dashboard"}}, - {"execute": {"namespace": "testkube", "name": "kubeshop-site"}}, - {"delay": {"duration": 3000}}, - {"execute": {"namespace": "testkube", "name": "testkube-dashboard"}}, - {"execute": {"namespace": "testkube", "name": "kubeshop-site"}}, - {"delay": {"duration": 5000}}, - {"execute": {"namespace": "testkube", "name": "testkube-todo-frontend"}}, - {"delay": {"duration": 5000}} + {"execute": [{"test": "testkube-todo-api"}]}, + {"execute": [{"test": "testkube-todo-api"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-todo-api"}]}, + {"execute": [{"test": "testkube-dashboard"}]}, + {"execute": [{"delay": "5s"}]}, + {"execute": [{"test": "kubeshop-site"}]}, + {"execute": [{"test": "testkube-dashboard"}]}, + {"execute": [{"test": "kubeshop-site"}]}, + {"execute": [{"delay": "3s"}]}, + {"execute": [{"test": "testkube-dashboard"}]}, + {"execute": [{"test": "kubeshop-site"}]}, + {"execute": [{"delay": "5s"}]}, + {"execute": [{"test": "testkube-todo-frontend"}]}, + {"execute": [{"delay": "5s"}]} ] } diff --git a/test/suites/testsuite-example-3.json b/test/suites/testsuite-example-3.json index 87703998e37..c0491c9007e 100644 --- a/test/suites/testsuite-example-3.json +++ b/test/suites/testsuite-example-3.json @@ -2,8 +2,8 @@ "name": "test-example-3", "description": "Tests fast first", "steps": [ - {"execute": {"namespace": "testkube", "name": "testkube-variables-test"}}, - {"delay": {"duration": 1000}}, - {"execute": {"namespace": "testkube", "name": "testkube-variables-test"}} + {"execute": [{"test": "testkube-variables-test"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-variables-test"}]} ] } diff --git a/test/suites/testsuite-testkube-failing-sof.json b/test/suites/testsuite-testkube-failing-sof.json index 6ce00c96f3d..30e1bf6d0a1 100644 --- a/test/suites/testsuite-testkube-failing-sof.json +++ b/test/suites/testsuite-testkube-failing-sof.json @@ -2,12 +2,12 @@ "name": "testkube", "description": "Failing Testkube testsuite", "steps": [ - {"execute": {"name": "testkube-api-failing"}, "stopTestOnFailure": true}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-dashboard"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-api-performance"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-homepage-performance"}} + {"execute": [{"test": "testkube-api-failing"}], "stopOnFailure": true}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-dashboard"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-api-performance"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-homepage-performance"}]} ] } diff --git a/test/suites/testsuite-testkube-failing.json b/test/suites/testsuite-testkube-failing.json index 360b0637f61..d8ea6f02484 100644 --- a/test/suites/testsuite-testkube-failing.json +++ b/test/suites/testsuite-testkube-failing.json @@ -2,12 +2,12 @@ "name": "testkube", "description": "Failing Testkube testsuite", "steps": [ - {"execute": {"name": "testkube-dashboard"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-api-performance"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-api-failing"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-homepage-performance"}} + {"execute": [{"test": "testkube-dashboard"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-api-performance"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-api-failing"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-homepage-performance"}]} ] } diff --git a/test/suites/testsuite-testkube.json b/test/suites/testsuite-testkube.json index 9d600a9dff7..e295d062a68 100644 --- a/test/suites/testsuite-testkube.json +++ b/test/suites/testsuite-testkube.json @@ -2,12 +2,12 @@ "name": "testkube", "description": "Testkube test suite, api, dashboard and performance", "steps": [ - {"execute": {"name": "testkube-api"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-dashboard"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-api-performance"}}, - {"delay": {"duration": 1000}}, - {"execute": {"name": "testkube-homepage-performance"}} + {"execute": [{"test": "testkube-api"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-dashboard"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-api-performance"}]}, + {"execute": [{"delay": "1s"}]}, + {"execute": [{"test": "testkube-homepage-performance"}]} ] }