diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 68b2ac2a310..11b5870ecdf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,4 +4,4 @@ img/* @olensmar api/* @olensmar api/v1/* @olensmar -* @exu @nicufk @jasmingacic @fog1985 @vsukhin +* @exu @nicufk @jasmingacic @vsukhin diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f1215235ff0..20b91cc06da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,9 @@ jobs: run: go test -v ./... - name: Integration tests - run: go test --tags=integration -v ./... + run: | + sudo apt-get install -y git + go test --tags=integration -v ./... # Don't work yet as expected https://github.com/nwestfall/openapi-action/issues/3 - name: OpenAPI Lint Checks diff --git a/Makefile b/Makefile index a0c72385ca9..cc87c60fe3a 100644 --- a/Makefile +++ b/Makefile @@ -56,13 +56,13 @@ openapi-generate-model-testkube: test: - go test ./... -cover -v + go test ./... -cover -failfast test-e2e: go test --tags=e2e -v ./test/e2e test-integration: - go test --tags=integration -v ./... + go test -failfast --tags=integration -v ./... test-e2e-namespace: diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index 1b3211217c0..6abe060dcd8 100644 --- a/api/v1/testkube.yaml +++ b/api/v1/testkube.yaml @@ -1088,13 +1088,14 @@ components: name: type: string description: script name + namespace: + type: string + description: script namespace type: type: string description: script type - enum: - - postman/collection content: - type: string + $ref: "#/components/schemas/ScriptContent" description: script content created: type: string @@ -1105,6 +1106,26 @@ components: type: string description: script tags + ScriptContent: + type: object + properties: + type: + type: string + description: script type + enum: + - string + - file-uri + - git-file + - git-dir + repository: + $ref: "#/components/schemas/Repository" + data: + type: string + description: script content data as string + uri: + type: string + description: script content + Execution: type: object description: API server script execution @@ -1116,6 +1137,9 @@ components: scriptName: type: string description: unique script name (CRD Script name) + scriptNamespace: + type: string + description: script namespace scriptType: type: string description: script type e.g. postman/collection @@ -1144,11 +1168,11 @@ components: example: users: "3" prefix: "some-" - scriptContent: + paramsFile: type: string - description: script metadata content - repository: - $ref: "#/components/schemas/Repository" + description: params file content - need to be in format for particular executor (e.g. postman envs file) + content: + $ref: "#/components/schemas/ScriptContent" startTime: type: string description: "test start time" @@ -1369,19 +1393,7 @@ components: users: "3" prefix: "some-" content: - type: string - description: script content as string (content depends from executor) - inputType: - type: string - description: > - script content type can be: - - direct content - created from file, - - git repository with path, will be checked out, useful when test have more than one file or complicated directory structure, - enum: - - content - - git - repository: - $ref: "#/components/schemas/Repository" + $ref: "#/components/schemas/ScriptContent" Repository: description: repository representation for tests in git repositories @@ -1460,55 +1472,8 @@ components: ScriptUpsertRequest: description: scripts create request body - type: object - properties: - name: - type: string - description: script name - Custom Resource name - must be unique, use only lowercase numbers and dashes (-) - example: kubeshop-homepage-test - type: - type: string - description: script type - what executor type should be used during test execution - example: postman/collection - namespace: - type: string - description: kubernetes namespace (defaults to 'testkube') - example: testkube - inputType: - type: string - description: > - 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, - enum: - - content - - git - tags: - type: array - items: - type: string - repository: - $ref: "#/components/schemas/Repository" - content: - type: string - description: script content - executor specific e.g. fo postman-collections executor - example: > - { - "info": { - "_postman_id": "57ad6291-5b8f-4b2d-b24d-d2d2ce8785bb", - "name": "SimpleKubeshop", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "Homepage", - "request": { - "method": "GET", - "header": [], - "url": null - }, - "response": [] - } + allOf: + - $ref: "#/components/schemas/Script" TestUpsertRequest: description: test create request body diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go index d222ef0b216..53ede68ac6e 100644 --- a/cmd/api-server/main.go +++ b/cmd/api-server/main.go @@ -7,9 +7,9 @@ import ( "github.com/kelseyhightower/envconfig" kubeclient "github.com/kubeshop/testkube-operator/client" - executorsclient "github.com/kubeshop/testkube-operator/client/executors" - scriptsclient "github.com/kubeshop/testkube-operator/client/scripts" - testsclient "github.com/kubeshop/testkube-operator/client/tests" + executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors" + scriptsclientv2 "github.com/kubeshop/testkube-operator/client/scripts/v2" + testsclientv1 "github.com/kubeshop/testkube-operator/client/tests" apiv1 "github.com/kubeshop/testkube/internal/app/api/v1" "github.com/kubeshop/testkube/internal/pkg/api/repository/result" "github.com/kubeshop/testkube/internal/pkg/api/repository/storage" @@ -55,9 +55,9 @@ func main() { secretClient, err := secret.NewClient() ui.ExitOnError("Getting secret client", err) - scriptsClient := scriptsclient.NewClient(kubeClient) - executorsClient := executorsclient.NewClient(kubeClient) - testsClient := testsclient.NewClient(kubeClient) + scriptsClient := scriptsclientv2.NewClient(kubeClient) + executorsClient := executorsclientv1.NewClient(kubeClient) + testsClient := testsclientv1.NewClient(kubeClient) resultsRepository := result.NewMongoRespository(db) testResultsRepository := testresult.NewMongoRespository(db) diff --git a/cmd/kubectl-testkube/commands/crds/scripts_crds.go b/cmd/kubectl-testkube/commands/crds/scripts_crds.go index e14a686585b..e924ba17d3f 100644 --- a/cmd/kubectl-testkube/commands/crds/scripts_crds.go +++ b/cmd/kubectl-testkube/commands/crds/scripts_crds.go @@ -12,6 +12,7 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/validator" "github.com/kubeshop/testkube/pkg/api/v1/client" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/test/script/detector" "github.com/kubeshop/testkube/pkg/ui" "github.com/spf13/cobra" @@ -92,7 +93,7 @@ spec: // try to detect type if none passed d := detector.NewDefaultDetector() - if detectedType, ok := d.Detect(client.UpsertScriptOptions{Content: string(content)}); ok { + if detectedType, ok := d.Detect(client.UpsertScriptOptions{Content: &testkube.ScriptContent{Data: string(content)}}); ok { ui.Debug("Detected test script type", detectedType) scriptType = detectedType } else { diff --git a/cmd/kubectl-testkube/commands/executors/create.go b/cmd/kubectl-testkube/commands/executors/create.go index 52e91430421..56475467ac1 100644 --- a/cmd/kubectl-testkube/commands/executors/create.go +++ b/cmd/kubectl-testkube/commands/executors/create.go @@ -14,9 +14,10 @@ func NewCreateExecutorCmd() *cobra.Command { ) cmd := &cobra.Command{ - Use: "create", - Short: "Create new Executor", - Long: `Create new Executor Custom Resource`, + Use: "create", + Aliases: []string{"c"}, + Short: "Create new Executor", + Long: `Create new Executor Custom Resource`, Run: func(cmd *cobra.Command, args []string) { ui.Logo() diff --git a/cmd/kubectl-testkube/commands/scripts/common.go b/cmd/kubectl-testkube/commands/scripts/common.go index 6e9247263e2..48c04024b40 100644 --- a/cmd/kubectl-testkube/commands/scripts/common.go +++ b/cmd/kubectl-testkube/commands/scripts/common.go @@ -2,13 +2,17 @@ package scripts import ( "fmt" + "io/ioutil" "os" + "reflect" "time" - "github.com/kubeshop/testkube/pkg/api/v1/client" + apiclientv1 "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" - "github.com/kubeshop/testkube/pkg/runner/output" + "github.com/kubeshop/testkube/pkg/executor/output" + "github.com/kubeshop/testkube/pkg/test/script/detector" "github.com/kubeshop/testkube/pkg/ui" + "github.com/spf13/cobra" ) func printExecutionDetails(execution testkube.Execution) { @@ -20,7 +24,7 @@ func printExecutionDetails(execution testkube.Execution) { ui.NL() } -func DownloadArtifacts(id, dir string, client client.Client) { +func DownloadArtifacts(id, dir string, client apiclientv1.Client) { artifacts, err := client.GetExecutionArtifacts(id) ui.ExitOnError("getting artifacts ", err) @@ -40,7 +44,7 @@ func DownloadArtifacts(id, dir string, client client.Client) { ui.NL() } -func watchLogs(id string, client client.Client) { +func watchLogs(id string, client apiclientv1.Client) { ui.Info("Getting pod logs") logs, err := client.Logs(id) @@ -84,3 +88,117 @@ func watchLogs(id string, client client.Client) { uiShellGetExecution(id) } + +func newContentFromFlags(cmd *cobra.Command) (content *testkube.ScriptContent, err error) { + var fileContent []byte + + scriptContentType := cmd.Flag("script-content-type").Value.String() + file := cmd.Flag("file").Value.String() + uri := cmd.Flag("uri").Value.String() + gitUri := cmd.Flag("git-uri").Value.String() + gitBranch := cmd.Flag("git-branch").Value.String() + gitPath := cmd.Flag("git-path").Value.String() + gitUsername := cmd.Flag("git-username").Value.String() + gitToken := cmd.Flag("git-token").Value.String() + + // get file content + if file != "" { + fileContent, err = ioutil.ReadFile(file) + if err != nil { + return content, fmt.Errorf("reading file "+file+" error: %w", err) + } + } else if stat, _ := os.Stdin.Stat(); (stat.Mode() & os.ModeCharDevice) == 0 { + fileContent, err = ioutil.ReadAll(os.Stdin) + if err != nil { + return content, fmt.Errorf("reading stdin error: %w", err) + } + } + + // content is correct when is passed from file, by uri, ur by git repo + if len(fileContent) == 0 && uri == "" && gitUri == "" { + return content, fmt.Errorf("empty script content, please pass some script content to create script") + } + + // detect content type (git-file need to be everrided manually as we don't) + // TODO handle git-file somehow + if gitUri != "" && scriptContentType == "" { + scriptContentType = string(testkube.ScriptContentTypeGitDir) + } + + if uri != "" && scriptContentType == "" { + scriptContentType = string(testkube.ScriptContentTypeFileURI) + } + + if len(fileContent) > 0 { + scriptContentType = string(testkube.ScriptContentTypeString) + } + + var repository *testkube.Repository + if gitUri != "" && gitBranch != "" { + if scriptContentType == "" { + scriptContentType = "git-dir" + } + + repository = &testkube.Repository{ + Type_: "git", + Uri: gitUri, + Branch: gitBranch, + Path: gitPath, + Username: gitUsername, + Token: gitToken, + } + } + + content = &testkube.ScriptContent{ + Type_: scriptContentType, + Data: string(fileContent), + Repository: repository, + Uri: uri, + } + + return content, nil +} + +func NewUpsertScriptOptionsFromFlags(cmd *cobra.Command, script testkube.Script) (options apiclientv1.UpsertScriptOptions, err error) { + content, err := newContentFromFlags(cmd) + + ui.ExitOnError("creating content from passed parameters", err) + + name := cmd.Flag("name").Value.String() + executorType := cmd.Flag("type").Value.String() + namespace := cmd.Flag("script-namespace").Value.String() + tags, err := cmd.Flags().GetStringSlice("tags") + if err != nil { + return options, err + } + + options = apiclientv1.UpsertScriptOptions{ + Name: name, + Type_: executorType, + Content: content, + Namespace: namespace, + } + + // if tags are passed and are different from the existing overwrite + if len(tags) > 0 && !reflect.DeepEqual(script.Tags, tags) { + options.Tags = tags + } else { + options.Tags = script.Tags + } + + // try to detect type if none passed + if executorType == "" { + d := detector.NewDefaultDetector() + if detectedType, ok := d.Detect(options); ok { + ui.Info("Detected test script type", detectedType) + options.Type_ = detectedType + } + } + + if options.Type_ == "" { + return options, fmt.Errorf("can't detect executor type by passed file content (%s), please pass valid --type flag", executorType) + } + + return options, nil + +} diff --git a/cmd/kubectl-testkube/commands/scripts/create.go b/cmd/kubectl-testkube/commands/scripts/create.go index 1924025bcfa..a1e65d59a81 100644 --- a/cmd/kubectl-testkube/commands/scripts/create.go +++ b/cmd/kubectl-testkube/commands/scripts/create.go @@ -1,13 +1,7 @@ package scripts import ( - "io/ioutil" - "os" - "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" - apiClient "github.com/kubeshop/testkube/pkg/api/v1/client" - "github.com/kubeshop/testkube/pkg/api/v1/testkube" - "github.com/kubeshop/testkube/pkg/test/script/detector" "github.com/kubeshop/testkube/pkg/ui" "github.com/spf13/cobra" ) @@ -16,96 +10,53 @@ import ( func NewCreateScriptsCmd() *cobra.Command { var ( - name string - file string - executorType string - uri string - gitBranch string - gitPath string - gitUsername string - gitToken string - tags []string + scriptName string + scriptNamespace string + scriptContentType string + file string + executorType string + uri string + gitUri string + gitBranch string + gitPath string + gitUsername string + gitToken string + tags []string ) cmd := &cobra.Command{ - Use: "create", - Short: "Create new script", - Long: `Create new Script Custom Resource, `, + Use: "create", + Aliases: []string{"c"}, + Short: "Create new script", + Long: `Create new Script Custom Resource`, Run: func(cmd *cobra.Command, args []string) { ui.Logo() - var content []byte - var err error - namespace := cmd.Flag("namespace").Value.String() - - if file != "" { - // read script content - content, err = ioutil.ReadFile(file) - ui.ExitOnError("reading file"+file, err) - } else if stat, _ := os.Stdin.Stat(); (stat.Mode() & os.ModeCharDevice) == 0 { - content, err = ioutil.ReadAll(os.Stdin) - ui.ExitOnError("reading stdin", err) - } - client, _ := common.GetClient(cmd) - - script, _ := client.GetScript(name, namespace) - if name == script.Name { - ui.Failf("Script with name '%s' already exists in namespace %s", name, namespace) - } - - if len(content) == 0 && len(uri) == 0 { - ui.Failf("Empty script content. Please pass some script content to create script") - } - - var repository *testkube.Repository - if uri != "" && gitBranch != "" { - repository = &testkube.Repository{ - Type_: "git", - Uri: uri, - Branch: gitBranch, - Path: gitPath, - Username: gitUsername, - Token: gitToken, - } - } - - options := apiClient.UpsertScriptOptions{ - Name: name, - Type_: executorType, - Content: string(content), - Namespace: namespace, - Repository: repository, - } - - // try to detect type if none passed - if executorType == "" { - d := detector.NewDefaultDetector() - if detectedType, ok := d.Detect(options); ok { - ui.Info("Detected test script type", detectedType) - options.Type_ = detectedType - } - } - - if options.Type_ == "" { - ui.Failf("Can't detect executor type by passed file content, please pass valid --type flag") + script, _ := client.GetScript(scriptName, scriptNamespace) + if scriptName == script.Name { + ui.Failf("Script with name '%s' already exists in namespace %s", scriptName, scriptNamespace) } - options.Tags = tags + options, err := NewUpsertScriptOptionsFromFlags(cmd, script) + ui.ExitOnError("getting script options", err) script, err = client.CreateScript(options) - ui.ExitOnError("creating script "+name+" in namespace "+namespace, err) + ui.ExitOnError("creating script "+scriptName+" in namespace "+scriptNamespace, err) - ui.Success("Script created", name) + ui.Success("Script created", scriptNamespace, "/", scriptName) }, } - cmd.Flags().StringVarP(&name, "name", "n", "", "unique script name - mandatory") + cmd.Flags().StringVarP(&scriptName, "name", "n", "", "unique script name - mandatory") cmd.Flags().StringVarP(&file, "file", "f", "", "script file - will be read from stdin if not specified") + cmd.Flags().StringVarP(&scriptNamespace, "script-namespace", "", "testkube", "namespace where script will be created defaults to 'testkube' namespace") + cmd.Flags().StringVarP(&scriptContentType, "script-content-type", "", "", "content type of script one of string|file-uri|git-file|git-dir") cmd.Flags().StringVarP(&executorType, "type", "t", "", "script type (defaults to postman-collection)") - cmd.Flags().StringVarP(&uri, "uri", "", "", "if resource need to be loaded from URI") + cmd.Flags().StringVarP(&uri, "uri", "", "", "URI of resource - will be loaded by http GET") + cmd.Flags().StringVarP(&gitUri, "git-uri", "", "", "Git repository uri") cmd.Flags().StringVarP(&gitBranch, "git-branch", "", "", "if uri is git repository we can set additional branch parameter") cmd.Flags().StringVarP(&gitPath, "git-path", "", "", "if repository is big we need to define additional path to directory/file to checkout partially") cmd.Flags().StringVarP(&gitUsername, "git-username", "", "", "if git repository is private we can use username as an auth parameter") diff --git a/cmd/kubectl-testkube/commands/scripts/start.go b/cmd/kubectl-testkube/commands/scripts/start.go index ff13b7af302..564bcb79776 100644 --- a/cmd/kubectl-testkube/commands/scripts/start.go +++ b/cmd/kubectl-testkube/commands/scripts/start.go @@ -19,6 +19,7 @@ func NewStartScriptCmd() *cobra.Command { name string watchEnabled bool params map[string]string + paramsFileContent string downloadArtifactsEnabled bool downloadDir string ) @@ -37,7 +38,7 @@ func NewStartScriptCmd() *cobra.Command { client, namespace := common.GetClient(cmd) namespacedName := fmt.Sprintf("%s/%s", namespace, scriptName) - execution, err := client.ExecuteScript(scriptName, namespace, name, params) + execution, err := client.ExecuteScript(scriptName, namespace, name, params, paramsFileContent) ui.ExitOnError("starting script execution "+namespacedName, err) printExecutionDetails(execution) @@ -61,6 +62,7 @@ func NewStartScriptCmd() *cobra.Command { } cmd.Flags().StringVarP(&name, "name", "n", "", "execution name, if empty will be autogenerated") + cmd.Flags().StringVarP(¶msFileContent, "params-file", "", "", "params file path, e.g. postman env file - will be passed to executor if supported") cmd.Flags().StringToStringVarP(¶ms, "param", "p", map[string]string{}, "execution envs passed to executor") cmd.Flags().BoolVarP(&watchEnabled, "watch", "f", false, "watch for changes after start") cmd.Flags().StringVar(&downloadDir, "download-dir", "artifacts", "download dir") diff --git a/cmd/kubectl-testkube/commands/scripts/update.go b/cmd/kubectl-testkube/commands/scripts/update.go index 7d66f9f05f9..c319ea7d722 100644 --- a/cmd/kubectl-testkube/commands/scripts/update.go +++ b/cmd/kubectl-testkube/commands/scripts/update.go @@ -1,29 +1,26 @@ package scripts import ( - "io/ioutil" - "os" - "reflect" - "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" - apiClient "github.com/kubeshop/testkube/pkg/api/v1/client" - "github.com/kubeshop/testkube/pkg/api/v1/testkube" - "github.com/kubeshop/testkube/pkg/test/script/detector" "github.com/kubeshop/testkube/pkg/ui" "github.com/spf13/cobra" ) func NewUpdateScriptsCmd() *cobra.Command { + var ( - name string - file string - executorType string - uri string - gitBranch string - gitPath string - gitUsername string - gitToken string - tags []string + scriptName string + scriptNamespace string + scriptContentType string + file string + executorType string + uri string + gitUri string + gitBranch string + gitPath string + gitUsername string + gitToken string + tags []string ) cmd := &cobra.Command{ @@ -32,87 +29,38 @@ func NewUpdateScriptsCmd() *cobra.Command { Long: `Update Script Custom Resource, `, Run: func(cmd *cobra.Command, args []string) { ui.Logo() - var content []byte var err error - if file != "" { - // read script content - content, err = ioutil.ReadFile(file) - ui.ExitOnError("reading file"+file, err) - } else if stat, _ := os.Stdin.Stat(); (stat.Mode() & os.ModeCharDevice) == 0 { - content, err = ioutil.ReadAll(os.Stdin) - ui.ExitOnError("reading stdin", err) - } - - client, namespace := common.GetClient(cmd) - - script, _ := client.GetScript(name, namespace) - if name != script.Name { - ui.Failf("Script with name '%s' not exists in namespace %s", name, namespace) - } - - if len(content) == 0 && len(uri) == 0 { - ui.Failf("Empty script content. Please pass some script content to create script") - } - - var repository *testkube.Repository - if uri != "" && gitBranch != "" { - repository = &testkube.Repository{ - Type_: "git", - Uri: uri, - Branch: gitBranch, - Path: gitPath, - Username: gitUsername, - Token: gitToken, - } - } - - options := apiClient.UpsertScriptOptions{ - Name: name, - Type_: executorType, - Content: string(content), - Namespace: namespace, - Repository: repository, - } - - // if tags are passed and are different from the existing overwrite - if len(tags) > 0 && !reflect.DeepEqual(script.Tags, tags) { - options.Tags = tags - } else { - options.Tags = script.Tags - } - - // try to detect type if none passed - if executorType == "" { - d := detector.NewDefaultDetector() - if detectedType, ok := d.Detect(options); ok { - ui.Info("Detected test script type", detectedType) - options.Type_ = detectedType - } + client, _ := common.GetClient(cmd) + script, _ := client.GetScript(scriptName, scriptNamespace) + if scriptName != script.Name { + ui.Failf("Script with name '%s' not exists in namespace %s", scriptName, scriptNamespace) } - if options.Type_ == "" { - ui.Failf("Can't detect executor type by passed file content, please pass valid --type flag") - } + options, err := NewUpsertScriptOptionsFromFlags(cmd, script) + ui.ExitOnError("getting script options", err) script, err = client.UpdateScript(options) - ui.ExitOnError("updating script "+name+" in namespace "+namespace, err) + ui.ExitOnError("updating script "+scriptName+" in namespace "+scriptNamespace, err) - ui.Success("Script updated", name) + ui.Success("Script updated", scriptNamespace, "/", scriptName) }, } - cmd.Flags().StringVarP(&name, "name", "n", "", "unique script name - mandatory") - cmd.Flags().StringVarP(&file, "file", "f", "", "script file - will be read from stdin if not specified") + cmd.Flags().StringVarP(&scriptName, "name", "n", "", "unique script name - mandatory") + cmd.Flags().StringVarP(&file, "file", "f", "", "script file - will try to read content from stdin if not specified") + cmd.Flags().StringVarP(&scriptNamespace, "script-namespace", "", "testkube", "namespace where script will be created defaults to 'testkube' namespace") + cmd.Flags().StringVarP(&scriptContentType, "script-content-type", "", "", "content type of script one of string|file-uri|git-file|git-dir") cmd.Flags().StringVarP(&executorType, "type", "t", "", "script type (defaults to postman-collection)") - cmd.Flags().StringVarP(&uri, "uri", "", "", "if resource need to be loaded from URI") + cmd.Flags().StringVarP(&uri, "uri", "", "", "URI of resource - will be loaded by http GET") + cmd.Flags().StringVarP(&gitUri, "git-uri", "", "", "Git repository uri") cmd.Flags().StringVarP(&gitBranch, "git-branch", "", "", "if uri is git repository we can set additional branch parameter") cmd.Flags().StringVarP(&gitPath, "git-path", "", "", "if repository is big we need to define additional path to directory/file to checkout partially") cmd.Flags().StringVarP(&gitUsername, "git-username", "", "", "if git repository is private we can use username as an auth parameter") cmd.Flags().StringVarP(&gitToken, "git-token", "", "", "if git repository is private we can use token as an auth parameter") - cmd.Flags().StringSliceVar(&tags, "tags", nil, "comma separated list of tags: --tags tag1,tag2,tag3 - Warning: by passing tags existing tags will be overwritten") + cmd.Flags().StringSliceVar(&tags, "tags", nil, "comma separated list of tags: --tags tag1,tag2,tag3") return cmd } diff --git a/cmd/kubectl-testkube/commands/tests/create.go b/cmd/kubectl-testkube/commands/tests/create.go index 24a7c530c3c..47192569df9 100644 --- a/cmd/kubectl-testkube/commands/tests/create.go +++ b/cmd/kubectl-testkube/commands/tests/create.go @@ -21,9 +21,10 @@ func NewCreateTestsCmd() *cobra.Command { ) cmd := &cobra.Command{ - Use: "create", - Short: "Create new test", - Long: `Create new Test Custom Resource, `, + Use: "create", + Aliases: []string{"c"}, + Short: "Create new test", + Long: `Create new Test Custom Resource, `, Run: func(cmd *cobra.Command, args []string) { ui.Logo() diff --git a/cmd/kubectl-testkube/commands/version.go b/cmd/kubectl-testkube/commands/version.go index 35352222717..46f8e1973de 100644 --- a/cmd/kubectl-testkube/commands/version.go +++ b/cmd/kubectl-testkube/commands/version.go @@ -8,9 +8,10 @@ import ( func NewVersionCmd() *cobra.Command { return &cobra.Command{ - Use: "version", - Short: "Shows version and build info", - Long: `Shows version and build info`, + Use: "version", + Aliases: []string{"v"}, + Short: "Shows version and build info", + Long: `Shows version and build info`, Run: func(cmd *cobra.Command, args []string) { client, _ := common.GetClient(cmd) info, err := client.GetServerInfo() diff --git a/go.mod b/go.mod index 8ef5a4aa8cd..384962d5160 100644 --- a/go.mod +++ b/go.mod @@ -113,3 +113,5 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) + +// replace github.com/kubeshop/testkube-operator v0.9.1 => ../testkube-operator diff --git a/internal/app/api/v1/executions.go b/internal/app/api/v1/executions.go index 3c1278dd932..3ced1c342bd 100644 --- a/internal/app/api/v1/executions.go +++ b/internal/app/api/v1/executions.go @@ -16,8 +16,9 @@ import ( "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/executor/client" + "github.com/kubeshop/testkube/pkg/executor/output" + scriptsmapper "github.com/kubeshop/testkube/pkg/mapper/scripts" "github.com/kubeshop/testkube/pkg/rand" - "github.com/kubeshop/testkube/pkg/runner/output" "github.com/kubeshop/testkube/pkg/secret" ) @@ -309,14 +310,12 @@ func newExecutionFromExecutionOptions(options client.ExecuteOptions) testkube.Ex options.ScriptName, options.Request.Name, options.ScriptSpec.Type_, - options.ScriptSpec.Content, + scriptsmapper.MapScriptContentFromSpec(options.ScriptSpec.Content), testkube.NewPendingExecutionResult(), options.Request.Params, options.Request.Tags, ) - execution.Repository = (*testkube.Repository)(options.ScriptSpec.Repository) - return execution } diff --git a/internal/app/api/v1/scripts.go b/internal/app/api/v1/scripts.go index f29a7a9fa1b..00904e6abff 100644 --- a/internal/app/api/v1/scripts.go +++ b/internal/app/api/v1/scripts.go @@ -5,14 +5,12 @@ import ( "strings" "github.com/gofiber/fiber/v2" - scriptsv1 "github.com/kubeshop/testkube-operator/apis/script/v1" "github.com/kubeshop/testkube/pkg/api/v1/testkube" - scriptsMapper "github.com/kubeshop/testkube/pkg/mapper/scripts" + scriptsmapper "github.com/kubeshop/testkube/pkg/mapper/scripts" "github.com/kubeshop/testkube/pkg/secret" "github.com/kubeshop/testkube/pkg/jobs" "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // GetScriptHandler is method for getting an existing script @@ -29,7 +27,7 @@ func (s TestKubeAPI) GetScriptHandler() fiber.Handler { return s.Error(c, http.StatusBadGateway, err) } - scripts := scriptsMapper.MapScriptCRToAPI(*crScript) + scripts := scriptsmapper.MapScriptCRToAPI(*crScript) return c.JSON(scripts) } } @@ -71,7 +69,7 @@ func (s TestKubeAPI) ListScriptsHandler() fiber.Handler { } } - scripts := scriptsMapper.MapScriptListKubeToAPI(*crScripts) + scripts := scriptsmapper.MapScriptListKubeToAPI(*crScripts) return c.JSON(scripts) } @@ -89,30 +87,8 @@ func (s TestKubeAPI) CreateScriptHandler() fiber.Handler { s.Log.Infow("creating script", "request", request) - var repository *scriptsv1.Repository - - if request.Repository != nil { - repository = &scriptsv1.Repository{ - Type_: "git", - Uri: request.Repository.Uri, - Branch: request.Repository.Branch, - Path: request.Repository.Path, - } - } - - script, err := s.ScriptsClient.Create(&scriptsv1.Script{ - ObjectMeta: metav1.ObjectMeta{ - Name: request.Name, - Namespace: request.Namespace, - }, - Spec: scriptsv1.ScriptSpec{ - Type_: request.Type_, - InputType: request.InputType, - Content: request.Content, - Repository: repository, - Tags: request.Tags, - }, - }) + scriptSpec := scriptsmapper.MapScriptToScriptSpec(request) + script, err := s.ScriptsClient.Create(scriptSpec) s.Metrics.IncCreateScript(script.Spec.Type_, err) @@ -120,13 +96,7 @@ func (s TestKubeAPI) CreateScriptHandler() fiber.Handler { return s.Error(c, http.StatusBadGateway, err) } - // create secrets for script - stringData := map[string]string{jobs.GitUsernameSecretName: "", jobs.GitTokenSecretName: ""} - if request.Repository != nil { - stringData[jobs.GitUsernameSecretName] = request.Repository.Username - stringData[jobs.GitTokenSecretName] = request.Repository.Token - } - + stringData := GetSecretsStringData(request.Content) if err = s.SecretClient.Create(secret.GetMetadataName(request.Name), request.Namespace, stringData); err != nil { return s.Error(c, http.StatusBadGateway, err) } @@ -147,31 +117,15 @@ func (s TestKubeAPI) UpdateScriptHandler() fiber.Handler { s.Log.Infow("updating script", "request", request) - var repository *scriptsv1.Repository - - if request.Repository != nil { - repository = &scriptsv1.Repository{ - Type_: "git", - Uri: request.Repository.Uri, - Branch: request.Repository.Branch, - Path: request.Repository.Path, - } - } - // we need to get resource first and load its metadata.ResourceVersion script, err := s.ScriptsClient.Get(request.Namespace, request.Name) if err != nil { return s.Error(c, http.StatusBadGateway, err) } - script.Spec = scriptsv1.ScriptSpec{ - Type_: request.Type_, - InputType: request.InputType, - Content: request.Content, - Repository: repository, - Tags: request.Tags, - } - + // map script but load spec only to not override metadata.ResourceVersion + scriptSpec := scriptsmapper.MapScriptToScriptSpec(request) + script.Spec = scriptSpec.Spec script, err = s.ScriptsClient.Update(script) s.Metrics.IncUpdateScript(script.Spec.Type_, err) @@ -181,12 +135,7 @@ func (s TestKubeAPI) UpdateScriptHandler() fiber.Handler { } // update secrets for scipt - stringData := map[string]string{jobs.GitUsernameSecretName: "", jobs.GitTokenSecretName: ""} - if request.Repository != nil { - stringData[jobs.GitUsernameSecretName] = request.Repository.Username - stringData[jobs.GitTokenSecretName] = request.Repository.Token - } - + stringData := GetSecretsStringData(request.Content) if err = s.SecretClient.Apply(secret.GetMetadataName(request.Name), request.Namespace, stringData); err != nil { return s.Error(c, http.StatusBadGateway, err) } @@ -247,3 +196,14 @@ func (s TestKubeAPI) DeleteScriptsHandler() fiber.Handler { return c.SendStatus(fiber.StatusNoContent) } } + +func GetSecretsStringData(content *testkube.ScriptContent) map[string]string { + // create secrets for script + stringData := map[string]string{jobs.GitUsernameSecretName: "", jobs.GitTokenSecretName: ""} + if content != nil && content.Repository != nil { + stringData[jobs.GitUsernameSecretName] = content.Repository.Username + stringData[jobs.GitTokenSecretName] = content.Repository.Token + } + + return stringData +} diff --git a/internal/app/api/v1/server.go b/internal/app/api/v1/server.go index 71f4bf3b3d3..2fceaeed709 100644 --- a/internal/app/api/v1/server.go +++ b/internal/app/api/v1/server.go @@ -14,9 +14,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" executorv1 "github.com/kubeshop/testkube-operator/apis/executor/v1" - executorscr "github.com/kubeshop/testkube-operator/client/executors" - scriptscr "github.com/kubeshop/testkube-operator/client/scripts" - testscr "github.com/kubeshop/testkube-operator/client/tests" + + executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors" + scriptsclientv2 "github.com/kubeshop/testkube-operator/client/scripts/v2" + testsclientv1 "github.com/kubeshop/testkube-operator/client/tests" + "github.com/kubeshop/testkube/internal/pkg/api" "github.com/kubeshop/testkube/internal/pkg/api/datefilter" "github.com/kubeshop/testkube/internal/pkg/api/repository/result" @@ -32,9 +34,9 @@ import ( func NewServer( executionsResults result.Repository, testExecutionsResults testresult.Repository, - scriptsClient *scriptscr.ScriptsClient, - executorsClient *executorscr.ExecutorsClient, - testsClient *testscr.TestsClient, + scriptsClient *scriptsclientv2.ScriptsClient, + executorsClient *executorsclientv1.ExecutorsClient, + testsClient *testsclientv1.TestsClient, secretClient *secret.Client, ) TestKubeAPI { @@ -71,9 +73,9 @@ type TestKubeAPI struct { ExecutionResults result.Repository TestExecutionResults testresult.Repository Executor client.Executor - TestsClient *testscr.TestsClient - ScriptsClient *scriptscr.ScriptsClient - ExecutorsClient *executorscr.ExecutorsClient + TestsClient *testsclientv1.TestsClient + ScriptsClient *scriptsclientv2.ScriptsClient + ExecutorsClient *executorsclientv1.ExecutorsClient SecretClient *secret.Client Metrics Metrics Storage storage.Client diff --git a/pkg/api/v1/client/common.go b/pkg/api/v1/client/common.go index 8cd01002d6b..8178c0023ac 100644 --- a/pkg/api/v1/client/common.go +++ b/pkg/api/v1/client/common.go @@ -7,7 +7,7 @@ import ( "fmt" "io" - "github.com/kubeshop/testkube/pkg/runner/output" + "github.com/kubeshop/testkube/pkg/executor/output" ) const Version = "v1" diff --git a/pkg/api/v1/client/common_test.go b/pkg/api/v1/client/common_test.go index 9d8526ddb1f..0c96cba772c 100644 --- a/pkg/api/v1/client/common_test.go +++ b/pkg/api/v1/client/common_test.go @@ -7,7 +7,7 @@ import ( "fmt" "testing" - "github.com/kubeshop/testkube/pkg/runner/output" + "github.com/kubeshop/testkube/pkg/executor/output" "github.com/stretchr/testify/assert" ) diff --git a/pkg/api/v1/client/direct.go b/pkg/api/v1/client/direct.go index 0195a481be2..a4448e9c932 100644 --- a/pkg/api/v1/client/direct.go +++ b/pkg/api/v1/client/direct.go @@ -16,8 +16,8 @@ import ( "github.com/kelseyhightower/envconfig" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/executor/output" "github.com/kubeshop/testkube/pkg/problem" - "github.com/kubeshop/testkube/pkg/runner/output" ) const ( @@ -179,7 +179,7 @@ func (c DirectScriptsAPI) UpdateScript(options UpsertScriptOptions) (script test // ExecuteScript starts new external script execution, reads data and returns ID // Execution is started asynchronously client can check later for results -func (c DirectScriptsAPI) ExecuteScript(id, namespace, executionName string, executionParams map[string]string) (execution testkube.Execution, err error) { +func (c DirectScriptsAPI) ExecuteScript(id, namespace, executionName string, executionParams map[string]string, executionParamsFileContent string) (execution testkube.Execution, err error) { uri := c.getURI("/scripts/%s/executions", id) // get script to get script tags diff --git a/pkg/api/v1/client/direct_test.go b/pkg/api/v1/client/direct_test.go index 6f1e51a4f08..f54b4ead8a0 100644 --- a/pkg/api/v1/client/direct_test.go +++ b/pkg/api/v1/client/direct_test.go @@ -25,7 +25,7 @@ func TestScriptsAPI(t *testing.T) { client.URI = srv.URL // when - execution, err := client.ExecuteScript("test", "testkube", "some name", map[string]string{}) + execution, err := client.ExecuteScript("test", "testkube", "some name", map[string]string{}, "") // then assert.Equal(t, "1", execution.Id) @@ -84,7 +84,7 @@ func TestScriptsAPI(t *testing.T) { // given srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{"id":"1", "name":"t1", "content":"{}", "type":"postman/collection"}`) + fmt.Fprintf(w, `{"id":"1", "name":"t1", "content":{"data":"{}"}, "type":"postman/collection"}`) })) defer srv.Close() @@ -93,14 +93,14 @@ func TestScriptsAPI(t *testing.T) { // when response, err := client.CreateScript(UpsertScriptOptions{ - Content: "{}", + Content: testkube.NewStringScriptContent("{}"), Name: "t1", Type_: "postman/collection", }) // then assert.NoError(t, err) - assert.Equal(t, "{}", response.Content) + assert.Equal(t, "{}", response.Content.Data) assert.Equal(t, "t1", response.Name) assert.Equal(t, "postman/collection", response.Type_) }) @@ -181,7 +181,7 @@ func TestScriptsAPI(t *testing.T) { // given srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `[{"id":"1", "name":"t1", "content":"{}", "type":"postman/collection"},{"id":"2", "name":"t2", "content":"{}", "type":"cypress/project"}]`) + fmt.Fprintf(w, `[{"id":"1", "name":"t1", "content":{"data":"{}"}, "type":"postman/collection"},{"id":"2", "name":"t2", "content":{"data":"{}"}, "type":"cypress/project"}]`) })) defer srv.Close() diff --git a/pkg/api/v1/client/interface.go b/pkg/api/v1/client/interface.go index 8a11bc2bbad..c3386d0cadc 100644 --- a/pkg/api/v1/client/interface.go +++ b/pkg/api/v1/client/interface.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/kubeshop/testkube/pkg/api/v1/testkube" - "github.com/kubeshop/testkube/pkg/runner/output" + "github.com/kubeshop/testkube/pkg/executor/output" ) type HTTPClient interface { @@ -25,7 +25,7 @@ type Client interface { DeleteScript(name string, namespace string) error DeleteScripts(namespace string) error ListScripts(namespace string, tags []string) (scripts testkube.Scripts, err error) - ExecuteScript(id, namespace, executionName string, executionParams map[string]string) (execution testkube.Execution, err error) + ExecuteScript(id, namespace, executionName string, executionParams map[string]string, executionParamsFileContent string) (execution testkube.Execution, err error) Logs(id string) (logs chan output.Output, err error) CreateExecutor(options CreateExecutorOptions) (executor testkube.ExecutorDetails, err error) diff --git a/pkg/api/v1/client/proxy.go b/pkg/api/v1/client/proxy.go index aa7f332233f..67d2807237f 100644 --- a/pkg/api/v1/client/proxy.go +++ b/pkg/api/v1/client/proxy.go @@ -17,8 +17,8 @@ import ( "k8s.io/client-go/tools/clientcmd" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/executor/output" "github.com/kubeshop/testkube/pkg/problem" - "github.com/kubeshop/testkube/pkg/runner/output" ) // check in compile time if interface is implemented @@ -182,7 +182,7 @@ func (c ProxyScriptsAPI) UpdateScript(options UpsertScriptOptions) (script testk // ExecuteScript starts new external script execution, reads data and returns ID // Execution is started asynchronously client can check later for results -func (c ProxyScriptsAPI) ExecuteScript(id, namespace, executionName string, executionParams map[string]string) (execution testkube.Execution, err error) { +func (c ProxyScriptsAPI) ExecuteScript(id, namespace, executionName string, executionParams map[string]string, executionParamsFileContent string) (execution testkube.Execution, err error) { uri := c.getURI("/scripts/%s/executions", id) // get script to get script tags diff --git a/pkg/api/v1/client/proxy_test.go b/pkg/api/v1/client/proxy_test.go index 4d03b32f025..a2ff9e10a23 100644 --- a/pkg/api/v1/client/proxy_test.go +++ b/pkg/api/v1/client/proxy_test.go @@ -66,7 +66,7 @@ func TestDefaultDirectScriptsAPI(t *testing.T) { defer srv.Close() // when - execution, err := client.ExecuteScript("test", "testkube", "some name", map[string]string{}) + execution, err := client.ExecuteScript("test", "testkube", "some name", map[string]string{}, "") // then assert.Equal(t, "1", execution.Id) diff --git a/pkg/api/v1/testkube/model_execution.go b/pkg/api/v1/testkube/model_execution.go index 273e409377b..22c7e089656 100644 --- a/pkg/api/v1/testkube/model_execution.go +++ b/pkg/api/v1/testkube/model_execution.go @@ -19,6 +19,8 @@ type Execution struct { Id string `json:"id,omitempty"` // unique script name (CRD Script name) ScriptName string `json:"scriptName,omitempty"` + // script namespace + ScriptNamespace string `json:"scriptNamespace,omitempty"` // script type e.g. postman/collection ScriptType string `json:"scriptType,omitempty"` // execution name @@ -29,9 +31,9 @@ type Execution struct { Args []string `json:"args,omitempty"` // execution params passed to executor converted to vars for usage in tests Params map[string]string `json:"params,omitempty"` - // script metadata content - ScriptContent string `json:"scriptContent,omitempty"` - Repository *Repository `json:"repository,omitempty"` + // params file content - need to be in format for particular executor (e.g. postman envs file) + ParamsFile string `json:"paramsFile,omitempty"` + Content *ScriptContent `json:"content,omitempty"` // test start time StartTime time.Time `json:"startTime,omitempty"` // test end time diff --git a/pkg/api/v1/testkube/model_execution_extended.go b/pkg/api/v1/testkube/model_execution_extended.go index c3eed764505..b6c0baeb4be 100644 --- a/pkg/api/v1/testkube/model_execution_extended.go +++ b/pkg/api/v1/testkube/model_execution_extended.go @@ -16,15 +16,15 @@ func NewExecutionWithID(id, scriptType, scriptName string) Execution { } } -func NewExecution(scriptName, name, scriptType, scriptContent string, result ExecutionResult, params map[string]string, tags []string) Execution { +func NewExecution(scriptName, executionName, scriptType string, content *ScriptContent, result ExecutionResult, params map[string]string, tags []string) Execution { return Execution{ Id: primitive.NewObjectID().Hex(), ScriptName: scriptName, - Name: name, + Name: executionName, ScriptType: scriptType, ExecutionResult: &result, Params: params, - ScriptContent: scriptContent, + Content: content, Tags: tags, } } @@ -71,13 +71,8 @@ func (executions Executions) Table() (header []string, output [][]string) { return } -func (e *Execution) WithContent(content string) *Execution { - e.ScriptContent = content - return e -} - -func (e *Execution) WithRepository(repository *Repository) *Execution { - e.Repository = repository +func (e *Execution) WithContent(content *ScriptContent) *Execution { + e.Content = content return e } @@ -86,15 +81,6 @@ func (e *Execution) WithParams(params map[string]string) *Execution { return e } -func (e *Execution) WithRepositoryData(uri, branch, path string) *Execution { - e.Repository = &Repository{ - Uri: uri, - Branch: branch, - Path: path, - } - return e -} - func (e Execution) Err(err error) Execution { e.ExecutionResult.Err(err) return e diff --git a/pkg/api/v1/testkube/model_execution_result_extended.go b/pkg/api/v1/testkube/model_execution_result_extended.go index 51d8a5f1db9..b9fb3d22cb8 100644 --- a/pkg/api/v1/testkube/model_execution_result_extended.go +++ b/pkg/api/v1/testkube/model_execution_result_extended.go @@ -50,3 +50,13 @@ func (r *ExecutionResult) Err(err error) ExecutionResult { r.ErrorMessage = err.Error() return *r } + +// Errs return error result if any of passed errors is not nil +func (r *ExecutionResult) WithErrors(errors ...error) ExecutionResult { + for _, err := range errors { + if err != nil { + return r.Err(err) + } + } + return *r +} diff --git a/pkg/api/v1/testkube/model_executor_start_request.go b/pkg/api/v1/testkube/model_executor_start_request.go index de64d1c4e83..206d7fc8cff 100644 --- a/pkg/api/v1/testkube/model_executor_start_request.go +++ b/pkg/api/v1/testkube/model_executor_start_request.go @@ -18,10 +18,6 @@ type ExecutorStartRequest struct { // script execution custom name Name string `json:"name,omitempty"` // execution params passed to executor - Params map[string]string `json:"params,omitempty"` - // script content as string (content depends from executor) - Content string `json:"content,omitempty"` - // script content type can be: - direct content - created from file, - git repository with path, will be checked out, useful when test have more than one file or complicated directory structure, - InputType string `json:"inputType,omitempty"` - Repository *Repository `json:"repository,omitempty"` + Params map[string]string `json:"params,omitempty"` + Content *ScriptContent `json:"content,omitempty"` } diff --git a/pkg/api/v1/testkube/model_executor_start_request_extended.go b/pkg/api/v1/testkube/model_executor_start_request_extended.go index 5f1987a066d..d32b297d3b0 100644 --- a/pkg/api/v1/testkube/model_executor_start_request_extended.go +++ b/pkg/api/v1/testkube/model_executor_start_request_extended.go @@ -3,11 +3,10 @@ package testkube // scripts execution request body func ExecutorStartRequestToExecution(request ExecutorStartRequest) Execution { return Execution{ - Id: request.Id, - Name: request.Name, - ScriptType: request.Type_, - Params: request.Params, - Repository: request.Repository, - ScriptContent: request.Content, + Id: request.Id, + Name: request.Name, + ScriptType: request.Type_, + Params: request.Params, + Content: request.Content, } } diff --git a/pkg/api/v1/testkube/model_repository_extended.go b/pkg/api/v1/testkube/model_repository_extended.go new file mode 100644 index 00000000000..de89c524bee --- /dev/null +++ b/pkg/api/v1/testkube/model_repository_extended.go @@ -0,0 +1,24 @@ +package testkube + +func NewGitRepository(uri, branch string) *Repository { + return &Repository{ + Type_: "git", + Branch: "main", + Uri: uri, + } +} + +func NewAuthGitRepository(uri, branch, user, token string) *Repository { + return &Repository{ + Type_: "git", + Branch: branch, + Uri: uri, + Username: user, + Token: token, + } +} + +func (r *Repository) WithPath(path string) *Repository { + r.Path = path + return r +} diff --git a/pkg/api/v1/testkube/model_script.go b/pkg/api/v1/testkube/model_script.go index efc47792b99..5bfafae9cea 100644 --- a/pkg/api/v1/testkube/model_script.go +++ b/pkg/api/v1/testkube/model_script.go @@ -16,11 +16,12 @@ import ( type Script struct { // script name Name string `json:"name,omitempty"` + // script namespace + Namespace string `json:"namespace,omitempty"` // script type - Type_ string `json:"type,omitempty"` - // script content - Content string `json:"content,omitempty"` - Created time.Time `json:"created,omitempty"` + Type_ string `json:"type,omitempty"` + Content *ScriptContent `json:"content,omitempty"` + Created time.Time `json:"created,omitempty"` // script tags Tags []string `json:"tags,omitempty"` } diff --git a/pkg/api/v1/testkube/model_script_content.go b/pkg/api/v1/testkube/model_script_content.go new file mode 100644 index 00000000000..785f140de83 --- /dev/null +++ b/pkg/api/v1/testkube/model_script_content.go @@ -0,0 +1,20 @@ +/* + * 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 ScriptContent struct { + // script type + Type_ string `json:"type,omitempty"` + Repository *Repository `json:"repository,omitempty"` + // script content data as string + Data string `json:"data,omitempty"` + // script content + Uri string `json:"uri,omitempty"` +} diff --git a/pkg/api/v1/testkube/model_script_content_extended.go b/pkg/api/v1/testkube/model_script_content_extended.go new file mode 100644 index 00000000000..fab4f270b00 --- /dev/null +++ b/pkg/api/v1/testkube/model_script_content_extended.go @@ -0,0 +1,36 @@ +// content could be fetched as file or dir (many files, e.g. Cypress project) in executor +package testkube + +import "fmt" + +type ScriptContentType string + +const ( + ScriptContentTypeString ScriptContentType = "string" + ScriptContentTypeFileURI ScriptContentType = "file-uri" + ScriptContentTypeGitFile ScriptContentType = "git-file" + ScriptContentTypeGitDir ScriptContentType = "git-dir" +) + +var ErrScriptContentTypeNotFile = fmt.Errorf("unsupported content type use one of: file-uri, git-file, string") +var ErrScriptContentTypeNotDir = fmt.Errorf("unsupported content type use one of: git-dir") + +func NewStringScriptContent(str string) *ScriptContent { + return &ScriptContent{ + Type_: string(ScriptContentTypeString), + Data: str, + } +} + +// IsDir - for content fetched as dir +func (c *ScriptContent) IsDir() bool { + return ScriptContentType(c.Type_) == ScriptContentTypeGitDir + +} + +// IsFile - for content fetched as file +func (c *ScriptContent) IsFile() bool { + return ScriptContentType(c.Type_) == ScriptContentTypeGitFile || + ScriptContentType(c.Type_) == ScriptContentTypeFileURI || + ScriptContentType(c.Type_) == ScriptContentTypeString +} diff --git a/pkg/api/v1/testkube/model_script_upsert_request.go b/pkg/api/v1/testkube/model_script_upsert_request.go index 9c0bd232af5..408a4d77875 100644 --- a/pkg/api/v1/testkube/model_script_upsert_request.go +++ b/pkg/api/v1/testkube/model_script_upsert_request.go @@ -9,18 +9,20 @@ */ package testkube +import ( + "time" +) + // scripts create request body type ScriptUpsertRequest struct { - // script name - Custom Resource name - must be unique, use only lowercase numbers and dashes (-) + // script name Name string `json:"name,omitempty"` - // script type - what executor type should be used during test execution - Type_ string `json:"type,omitempty"` - // kubernetes namespace (defaults to 'testkube') + // script namespace Namespace string `json:"namespace,omitempty"` - // 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, - InputType string `json:"inputType,omitempty"` - Tags []string `json:"tags,omitempty"` - Repository *Repository `json:"repository,omitempty"` - // script content - executor specific e.g. fo postman-collections executor - Content string `json:"content,omitempty"` + // script type + Type_ string `json:"type,omitempty"` + Content *ScriptContent `json:"content,omitempty"` + Created time.Time `json:"created,omitempty"` + // script tags + Tags []string `json:"tags,omitempty"` } diff --git a/pkg/runner/agent/agent.go b/pkg/executor/agent/agent.go similarity index 91% rename from pkg/runner/agent/agent.go rename to pkg/executor/agent/agent.go index 670a20da20d..1d6a37ee480 100644 --- a/pkg/runner/agent/agent.go +++ b/pkg/executor/agent/agent.go @@ -7,8 +7,8 @@ import ( "os" "github.com/kubeshop/testkube/pkg/api/v1/testkube" - "github.com/kubeshop/testkube/pkg/runner" - "github.com/kubeshop/testkube/pkg/runner/output" + "github.com/kubeshop/testkube/pkg/executor/output" + "github.com/kubeshop/testkube/pkg/executor/runner" ) // Run starts test runner, test runner can have 3 states diff --git a/pkg/executor/client/common.go b/pkg/executor/client/common.go index c16c044f8f3..c7113c551f9 100644 --- a/pkg/executor/client/common.go +++ b/pkg/executor/client/common.go @@ -4,7 +4,7 @@ import ( "time" executorv1 "github.com/kubeshop/testkube-operator/apis/executor/v1" - scriptv1 "github.com/kubeshop/testkube-operator/apis/script/v1" + scriptv2 "github.com/kubeshop/testkube-operator/apis/script/v2" "github.com/kubeshop/testkube/pkg/api/v1/testkube" ) @@ -15,7 +15,7 @@ const ( type ExecuteOptions struct { ID string ScriptName string - ScriptSpec scriptv1.ScriptSpec + ScriptSpec scriptv2.ScriptSpec ExecutorName string ExecutorSpec executorv1.ExecutorSpec Request testkube.ExecutionRequest diff --git a/pkg/executor/client/interface.go b/pkg/executor/client/interface.go index e7cc7b9e856..f079e0d2eb8 100644 --- a/pkg/executor/client/interface.go +++ b/pkg/executor/client/interface.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/kubeshop/testkube/pkg/api/v1/testkube" - "github.com/kubeshop/testkube/pkg/runner/output" + "github.com/kubeshop/testkube/pkg/executor/output" ) // ResultEvent event passed when watching execution changes diff --git a/pkg/executor/client/job.go b/pkg/executor/client/job.go index e14a4a89bd5..d48f1fdb610 100644 --- a/pkg/executor/client/job.go +++ b/pkg/executor/client/job.go @@ -7,9 +7,9 @@ import ( "github.com/kubeshop/testkube/internal/pkg/api/repository/result" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/executor/output" "github.com/kubeshop/testkube/pkg/jobs" "github.com/kubeshop/testkube/pkg/log" - "github.com/kubeshop/testkube/pkg/runner/output" "go.uber.org/zap" ) diff --git a/pkg/executor/client/mapper.go b/pkg/executor/client/mapper.go index 66f0f64c224..0ca5db1f80a 100644 --- a/pkg/executor/client/mapper.go +++ b/pkg/executor/client/mapper.go @@ -2,28 +2,16 @@ package client import ( "github.com/kubeshop/testkube/pkg/api/v1/testkube" + scriptsmapper "github.com/kubeshop/testkube/pkg/mapper/scripts" ) func MapExecutionOptionsToStartRequest(options ExecuteOptions) testkube.ExecutorStartRequest { - // check if repository exists in cr repository - var respository *testkube.Repository - if options.ScriptSpec.Repository != nil { - respository = &testkube.Repository{ - Type_: "git", - Uri: options.ScriptSpec.Repository.Uri, - Branch: options.ScriptSpec.Repository.Branch, - Path: options.ScriptSpec.Repository.Path, - } - } - // pass options to executor client get params from script execution request request := testkube.ExecutorStartRequest{ - Id: options.ID, - Type_: options.ScriptSpec.Type_, - InputType: options.ScriptSpec.InputType, - Content: options.ScriptSpec.Content, - Repository: respository, - Params: options.Request.Params, + Id: options.ID, + Type_: options.ScriptSpec.Type_, + Content: scriptsmapper.MapScriptContentFromSpec(options.ScriptSpec.Content), + Params: options.Request.Params, } return request diff --git a/pkg/executor/content/fetcher.go b/pkg/executor/content/fetcher.go new file mode 100644 index 00000000000..7db90ef6d18 --- /dev/null +++ b/pkg/executor/content/fetcher.go @@ -0,0 +1,112 @@ +package content + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "path/filepath" + "strings" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/git" +) + +func NewFetcher() Fetcher { + return Fetcher{} +} + +type Fetcher struct { +} + +func (f Fetcher) Fetch(content *testkube.ScriptContent) (path string, err error) { + if content == nil { + return "", fmt.Errorf("fetch - empty content, make sure script content has valid data structure and is not nil") + } + switch testkube.ScriptContentType(content.Type_) { + case testkube.ScriptContentTypeFileURI: + return f.FetchURI(content.Uri) + case testkube.ScriptContentTypeString: + return f.FetchString(content.Data) + case testkube.ScriptContentTypeGitFile: + return f.FetchGitFile(content.Repository) + case testkube.ScriptContentTypeGitDir: + return f.FetchGitDir(content.Repository) + default: + return path, fmt.Errorf("unhandled content type: '%s'", content.Type_) + } +} + +// FetchString stores string content as file +func (f Fetcher) FetchString(str string) (path string, err error) { + return f.saveTempFile(strings.NewReader(str)) +} + +//FetchURI stores uri as local file +func (f Fetcher) FetchURI(uri string) (path string, err error) { + resp, err := http.Get(uri) + if err != nil { + return path, err + } + defer resp.Body.Close() + + return f.saveTempFile(resp.Body) +} + +// FetchGitDir returns path to locally checked out git repo with partial path +func (f Fetcher) FetchGitDir(repo *testkube.Repository) (path string, err error) { + uri, err := f.gitURI(repo) + if err != nil { + return path, err + } + + // if path not set make full repo checkout + if repo.Path == "" { + return git.Checkout(uri, repo.Branch) + } + + return git.PartialCheckout(uri, repo.Path, repo.Branch) +} + +// FetchGitFile returns path to git based file saved in local temp directory +func (f Fetcher) FetchGitFile(repo *testkube.Repository) (path string, err error) { + uri, err := f.gitURI(repo) + if err != nil { + return path, err + } + + repoPath, err := git.Checkout(uri, repo.Branch) + if err != nil { + return path, err + } + + // get git file + return filepath.Join(repoPath, repo.Path), nil +} + +// gitUri merge creds with git uri +func (f Fetcher) gitURI(repo *testkube.Repository) (uri string, err error) { + if repo.Username != "" && repo.Token != "" { + gitURI, err := url.Parse(repo.Uri) + if err != nil { + return uri, err + } + + gitURI.User = url.UserPassword(repo.Username, repo.Token) + return gitURI.String(), nil + } + + return repo.Uri, nil +} + +func (f Fetcher) saveTempFile(reader io.Reader) (path string, err error) { + tmpFile, err := ioutil.TempFile("", "fetcher-save-temp-file") + if err != nil { + return "", err + } + defer tmpFile.Close() + _, err = io.Copy(tmpFile, reader) + + return tmpFile.Name(), err +} diff --git a/pkg/executor/content/fetcher_test.go b/pkg/executor/content/fetcher_test.go new file mode 100644 index 00000000000..184351fe0b7 --- /dev/null +++ b/pkg/executor/content/fetcher_test.go @@ -0,0 +1,83 @@ +//go:build integration + +// this test need git to test fetchers +package content + +import ( + "io/ioutil" + "path/filepath" + "testing" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/stretchr/testify/assert" +) + +// this content is also saved in test repo +// in https://github.com/kubeshop/testkube-examples/blob/main/example.json +// file with \n on end +const fileContent = `{"some":"json","file":"with content"} +` + +func TestFetcher(t *testing.T) { + f := NewFetcher() + + t.Run("test fetch uri", func(t *testing.T) { + + content := &testkube.ScriptContent{ + Type_: string(testkube.ScriptContentTypeString), + Data: fileContent, + } + + path, err := f.Fetch(content) + assert.NoError(t, err) + assert.FileExists(t, path) + + d, err := ioutil.ReadFile(path) + assert.NoError(t, err) + assert.Equal(t, string(fileContent), string(d)) + }) + + t.Run("test fetch git repo based file", func(t *testing.T) { + content := &testkube.ScriptContent{ + Type_: string(testkube.ScriptContentTypeGitFile), + Repository: testkube.NewGitRepository("https://github.com/kubeshop/testkube-examples.git", "main"). + WithPath("example.json"), + } + + path, err := f.Fetch(content) + assert.NoError(t, err) + assert.FileExists(t, path) + + d, err := ioutil.ReadFile(path) + assert.NoError(t, err) + assert.Equal(t, string(fileContent), string(d)) + }) + + t.Run("test fetch git root dir", func(t *testing.T) { + content := &testkube.ScriptContent{ + Type_: string(testkube.ScriptContentTypeGitDir), + Repository: testkube.NewGitRepository("https://github.com/kubeshop/testkube-examples.git", "main"). + WithPath(""), + } + + path, err := f.Fetch(content) + assert.NoError(t, err) + assert.FileExists(t, filepath.Join(path, "example.json")) + assert.FileExists(t, filepath.Join(path, "README.md")) + assert.FileExists(t, filepath.Join(path, "subdir/example.json")) + + }) + + t.Run("test fetch git subdir", func(t *testing.T) { + content := &testkube.ScriptContent{ + Type_: string(testkube.ScriptContentTypeGitDir), + Repository: testkube.NewGitRepository("https://github.com/kubeshop/testkube-examples.git", "main"). + WithPath("subdir"), + } + + path, err := f.Fetch(content) + assert.NoError(t, err) + assert.FileExists(t, filepath.Join(path, "example.json")) + assert.FileExists(t, filepath.Join(path, "example2.json")) + }) +} diff --git a/pkg/executor/content/interface.go b/pkg/executor/content/interface.go new file mode 100644 index 00000000000..76098a2de2d --- /dev/null +++ b/pkg/executor/content/interface.go @@ -0,0 +1,28 @@ +package content + +import "github.com/kubeshop/testkube/pkg/api/v1/testkube" + +type ContentFetcher interface { + StringFetcher + URIFetcher + GitDirFetcher + GitFileFetcher + + Fetch(content *testkube.ScriptContent) (path string, err error) +} + +type StringFetcher interface { + FetchString(str string) (path string, err error) +} + +type URIFetcher interface { + FetchURI(uri string) (path string, err error) +} + +type GitDirFetcher interface { + FetchGitDir(repo *testkube.Repository) (path string, err error) +} + +type GitFileFetcher interface { + FetchGitFile(repo *testkube.Repository) (path string, err error) +} diff --git a/pkg/runner/output/output.go b/pkg/executor/output/output.go similarity index 100% rename from pkg/runner/output/output.go rename to pkg/executor/output/output.go diff --git a/pkg/runner/output/parser.go b/pkg/executor/output/parser.go similarity index 100% rename from pkg/runner/output/parser.go rename to pkg/executor/output/parser.go diff --git a/pkg/runner/output/parser_test.go b/pkg/executor/output/parser_test.go similarity index 100% rename from pkg/runner/output/parser_test.go rename to pkg/executor/output/parser_test.go diff --git a/pkg/runner/output/writer.go b/pkg/executor/output/writer.go similarity index 100% rename from pkg/runner/output/writer.go rename to pkg/executor/output/writer.go diff --git a/pkg/runner/output/writer_test.go b/pkg/executor/output/writer_test.go similarity index 100% rename from pkg/runner/output/writer_test.go rename to pkg/executor/output/writer_test.go diff --git a/pkg/executor/process.go b/pkg/executor/process.go index 716dbaf464d..8a98061e809 100644 --- a/pkg/executor/process.go +++ b/pkg/executor/process.go @@ -3,8 +3,8 @@ package executor import ( "os" + "github.com/kubeshop/testkube/pkg/executor/output" "github.com/kubeshop/testkube/pkg/process" - "github.com/kubeshop/testkube/pkg/runner/output" ) // Run runs executor process wrapped in json line output diff --git a/pkg/runner/interface.go b/pkg/executor/runner/interface.go similarity index 100% rename from pkg/runner/interface.go rename to pkg/executor/runner/interface.go diff --git a/pkg/executor/scrapper/scrapper.go b/pkg/executor/scrapper/scrapper.go new file mode 100644 index 00000000000..a2a4cca3c37 --- /dev/null +++ b/pkg/executor/scrapper/scrapper.go @@ -0,0 +1,35 @@ +package scrapper + +import ( + "fmt" + + "github.com/kubeshop/testkube/pkg/storage/minio" +) + +func NewScrapper(endpoint, accessKeyID, secretAccessKey, location, token string, ssl bool) *Scrapper { + + return &Scrapper{ + Endpoint: endpoint, + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + Location: location, + Token: token, + Ssl: ssl, + } + +} + +type Scrapper struct { + Endpoint, AccessKeyID, SecretAccessKey, Location, Token string + Ssl bool +} + +func (s Scrapper) Scrape(id string, directories []string) error { + client := minio.NewClient(s.Endpoint, s.AccessKeyID, s.SecretAccessKey, s.Location, s.Token, s.Ssl) // create storage client + err := client.Connect() + if err != nil { + return fmt.Errorf("error occured creating minio client: %w", err) + } + + return client.ScrapeArtefacts(id, directories...) +} diff --git a/pkg/git/checkout.go b/pkg/git/checkout.go index 1148ff2e3e2..846bd195265 100644 --- a/pkg/git/checkout.go +++ b/pkg/git/checkout.go @@ -9,10 +9,33 @@ import ( "github.com/kubeshop/testkube/pkg/process" ) +// Checkout will checkout directory from Git repository +func Checkout(uri, branch string) (outputDir string, err error) { + + tmpDir, err := ioutil.TempDir("", "git-checkout") + if err != nil { + return tmpDir, err + } + + _, err = process.ExecuteInDir( + tmpDir, + "git", + "clone", + "-b", branch, + "--depth", "1", + uri, "repo", + ) + if err != nil { + return "", err + } + + return tmpDir + "/repo/", nil +} + // Partial checkout will checkout only given directory from Git repository func PartialCheckout(uri, path, branch string) (outputDir string, err error) { - tmpDir, err := ioutil.TempDir("", "testkube-scripts") + tmpDir, err := ioutil.TempDir("", "git-sparse-checkout") if err != nil { return tmpDir, err } diff --git a/pkg/jobs/jobclient.go b/pkg/jobs/jobclient.go index 664887e0989..b49f232b105 100644 --- a/pkg/jobs/jobclient.go +++ b/pkg/jobs/jobclient.go @@ -12,9 +12,9 @@ import ( "github.com/kubeshop/testkube/internal/pkg/api/repository/result" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/executor/output" "github.com/kubeshop/testkube/pkg/k8sclient" "github.com/kubeshop/testkube/pkg/log" - "github.com/kubeshop/testkube/pkg/runner/output" "github.com/kubeshop/testkube/pkg/secret" "go.uber.org/zap" batchv1 "k8s.io/api/batch/v1" @@ -494,6 +494,10 @@ func NewJobSpec(id, namespace, image, jsn, scriptName string, hasSecrets bool) * } var envVars = []corev1.EnvVar{ + { + Name: "DEBUG", + Value: os.Getenv("DEBUG"), + }, { Name: "RUNNER_ENDPOINT", Value: os.Getenv("STORAGE_ENDPOINT"), diff --git a/pkg/mapper/executions/sumary_test.go b/pkg/mapper/executions/sumary_test.go index c30361b0a07..8cb72bd7886 100644 --- a/pkg/mapper/executions/sumary_test.go +++ b/pkg/mapper/executions/sumary_test.go @@ -33,7 +33,7 @@ func getExecutions() testkube.Executions { "script1", "execution1", "test/test", - "", + testkube.NewStringScriptContent(""), *ex1, map[string]string{"p": "v1"}, nil, @@ -46,7 +46,7 @@ func getExecutions() testkube.Executions { "script1", "execution2", "test/test", - "", + testkube.NewStringScriptContent(""), *ex2, map[string]string{"p": "v2"}, nil, diff --git a/pkg/mapper/scripts/kube_openapi.go b/pkg/mapper/scripts/kube_openapi.go index 9603953fee8..4fca2e3a0f5 100644 --- a/pkg/mapper/scripts/kube_openapi.go +++ b/pkg/mapper/scripts/kube_openapi.go @@ -1,22 +1,35 @@ package scripts import ( - scriptsV1 "github.com/kubeshop/testkube-operator/apis/script/v1" + scriptsv2 "github.com/kubeshop/testkube-operator/apis/script/v2" "github.com/kubeshop/testkube/pkg/api/v1/testkube" ) -func MapScriptListKubeToAPI(crScripts scriptsV1.ScriptList) (scripts []testkube.Script) { +func MapScriptListKubeToAPI(crScripts scriptsv2.ScriptList) (scripts []testkube.Script) { for _, item := range crScripts.Items { scripts = append(scripts, MapScriptCRToAPI(item)) } return } -func MapScriptCRToAPI(crScript scriptsV1.Script) (script testkube.Script) { +func MapScriptCRToAPI(crScript scriptsv2.Script) (script testkube.Script) { script.Name = crScript.Name - script.Content = crScript.Spec.Content + script.Content = MapScriptContentFromSpec(crScript.Spec.Content) script.Created = crScript.CreationTimestamp.Time script.Type_ = crScript.Spec.Type_ script.Tags = crScript.Spec.Tags return } + +func MapScriptContentFromSpec(specContent *scriptsv2.ScriptContent) *testkube.ScriptContent { + content := &testkube.ScriptContent{ + Type_: specContent.Type_, + // assuming same data structure - there is task about syncing them automatically + // https://github.com/kubeshop/testkube/issues/723 + Repository: (*testkube.Repository)(specContent.Repository), + Data: specContent.Data, + Uri: specContent.Uri, + } + + return content +} diff --git a/pkg/mapper/scripts/openapi_kube.go b/pkg/mapper/scripts/openapi_kube.go new file mode 100644 index 00000000000..1cc2ff2b4db --- /dev/null +++ b/pkg/mapper/scripts/openapi_kube.go @@ -0,0 +1,39 @@ +package scripts + +import ( + scriptsv2 "github.com/kubeshop/testkube-operator/apis/script/v2" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func MapScriptToScriptSpec(request testkube.ScriptUpsertRequest) *scriptsv2.Script { + + script := &scriptsv2.Script{ + ObjectMeta: metav1.ObjectMeta{ + Name: request.Name, + Namespace: request.Namespace, + }, + Spec: scriptsv2.ScriptSpec{ + Type_: request.Type_, + Content: MapScriptContentToScriptSpecContent(request.Content), + Tags: request.Tags, + }, + } + + return script + +} + +func MapScriptContentToScriptSpecContent(content *testkube.ScriptContent) (specContent *scriptsv2.ScriptContent) { + if content == nil { + return + } + + return &scriptsv2.ScriptContent{ + // assuming same data structure + Repository: (*scriptsv2.Repository)(content.Repository), + Data: content.Data, + Uri: content.Uri, + Type_: content.Type_, + } +} diff --git a/pkg/storage/minio/minio.go b/pkg/storage/minio/minio.go index 4b994061ba0..64e41ff7be1 100644 --- a/pkg/storage/minio/minio.go +++ b/pkg/storage/minio/minio.go @@ -4,14 +4,14 @@ import ( "context" "fmt" "os" - "path" "path/filepath" - "strings" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/log" "github.com/kubeshop/testkube/pkg/storage" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" + "go.uber.org/zap" ) var _ storage.Client = (*Client)(nil) @@ -24,6 +24,7 @@ type Client struct { location string token string minioclient *minio.Client + Log *zap.SugaredLogger } func NewClient(endpoint, accessKeyID, secretAccessKey, location, token string, ssl bool) *Client { @@ -34,6 +35,7 @@ func NewClient(endpoint, accessKeyID, secretAccessKey, location, token string, s token: token, ssl: ssl, Endpoint: endpoint, + Log: log.DefaultLogger, } return c @@ -104,24 +106,20 @@ func (c *Client) SaveFile(bucket, filePath string) error { } object, err := os.Open(filePath) if err != nil { - return err + return fmt.Errorf("minio saving file (%s) open error: %w", filePath, err) } defer object.Close() objectStat, err := object.Stat() if err != nil { - return err + return fmt.Errorf("minio object stat (file:%s) error: %w", filePath, err) } - var fileName string - if strings.Contains(filePath, "/") { - _, fileName = path.Split("/") - } else { - fileName = objectStat.Name() - } + fileName := objectStat.Name() + c.Log.Debugw("saving object in minio", "filePath", filePath, "fileName", fileName, "bucket", bucket, "size", objectStat.Size()) _, err = c.minioclient.PutObject(context.Background(), bucket, fileName, object, objectStat.Size(), minio.PutObjectOptions{ContentType: "application/octet-stream"}) if err != nil { - return err + return fmt.Errorf("minio saving file (%s) put object error: %w", fileName, err) } return nil @@ -129,17 +127,17 @@ func (c *Client) SaveFile(bucket, filePath string) error { func (c *Client) DownloadFile(bucket, file string) (*minio.Object, error) { if err := c.Connect(); err != nil { - return nil, err + return nil, fmt.Errorf("minio DownloadFile .Connect error: %w", err) } reader, err := c.minioclient.GetObject(context.Background(), bucket, file, minio.GetObjectOptions{}) if err != nil { - return nil, err + return nil, fmt.Errorf("minio DownloadFile GetObject error: %w", err) } _, err = reader.Stat() if err != nil { - return reader, err + return reader, fmt.Errorf("minio Download File Stat error: %w", err) } return reader, nil @@ -147,31 +145,39 @@ func (c *Client) DownloadFile(bucket, file string) (*minio.Object, error) { func (c *Client) ScrapeArtefacts(id string, directories ...string) error { if err := c.Connect(); err != nil { - return err + return fmt.Errorf("minio scrape artefacts connection error: %w", err) } err := c.CreateBucket(id) // create bucket name it by execution ID if err != nil { - return fmt.Errorf("failed to create a bucket %s: %w", id, err) + return fmt.Errorf("minio failed to create a bucket %s: %w", id, err) } for _, directory := range directories { + + if _, err := os.Stat(directory); os.IsNotExist(err) { + c.Log.Debugw("directory %s does not exists, skipping", directory) + continue + } + + // if directory exists walk through recursively err = filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { if err != nil { - return err + return fmt.Errorf("minio path (%s) walk error: %w", path, err) } if !info.IsDir() { err = c.SaveFile(id, path) //The function will detect if there is a subdirectory and store accordingly if err != nil { - return err + return fmt.Errorf("minio save file (%s) error: %w", path, err) } } return nil }) + if err != nil { - return err + return fmt.Errorf("minio walk error: %w", err) } } return nil diff --git a/pkg/test/script/detector/curltest.go b/pkg/test/script/detector/curltest.go index ac1d10477f5..2446bfe012c 100644 --- a/pkg/test/script/detector/curltest.go +++ b/pkg/test/script/detector/curltest.go @@ -12,7 +12,7 @@ type CurlTestAdapter struct { func (d CurlTestAdapter) Is(options apiClient.UpsertScriptOptions) (name string, ok bool) { var data map[string]interface{} - err := json.Unmarshal([]byte(options.Content), &data) + err := json.Unmarshal([]byte(options.Content.Data), &data) if err != nil { return } diff --git a/pkg/test/script/detector/curltest_test.go b/pkg/test/script/detector/curltest_test.go index 1e21f226305..bba9d7f078e 100644 --- a/pkg/test/script/detector/curltest_test.go +++ b/pkg/test/script/detector/curltest_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/kubeshop/testkube/pkg/api/v1/client" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/stretchr/testify/assert" ) @@ -18,7 +19,7 @@ func TestCurlTestAdapter(t *testing.T) { t.Run("Is return true when valid content", func(t *testing.T) { detector := CurlTestAdapter{} name, is := detector.Is(client.UpsertScriptOptions{ - Content: curlValidContent, + Content: testkube.NewStringScriptContent(curlValidContent), }) assert.True(t, is, "content should be of curl/test type") @@ -28,7 +29,7 @@ func TestCurlTestAdapter(t *testing.T) { t.Run("Is return false in case of invalid JSON content", func(t *testing.T) { detector := CurlTestAdapter{} name, is := detector.Is(client.UpsertScriptOptions{ - Content: curlInvalidContent, + Content: testkube.NewStringScriptContent(curlInvalidContent), }) assert.Empty(t, name) @@ -39,7 +40,7 @@ func TestCurlTestAdapter(t *testing.T) { t.Run("Is return false in case of content which is not JSON ", func(t *testing.T) { detector := CurlTestAdapter{} name, is := detector.Is(client.UpsertScriptOptions{ - Content: curlInvalidJSONContent, + Content: testkube.NewStringScriptContent(curlInvalidJSONContent), }) assert.Empty(t, name) diff --git a/pkg/test/script/detector/detector_test.go b/pkg/test/script/detector/detector_test.go index a527b42ee05..ac422351707 100644 --- a/pkg/test/script/detector/detector_test.go +++ b/pkg/test/script/detector/detector_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/kubeshop/testkube/pkg/api/v1/client" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/stretchr/testify/assert" ) @@ -16,7 +17,7 @@ func TestDetector(t *testing.T) { detector.Add(PostmanCollectionAdapter{}) name, found := detector.Detect(client.UpsertScriptOptions{ - Content: exampleValidContent, + Content: testkube.NewStringScriptContent(exampleValidContent), }) assert.True(t, found, "detector should find postman/collection") diff --git a/pkg/test/script/detector/postmancollection.go b/pkg/test/script/detector/postmancollection.go index 3cc8366bea0..dd38ce1bed4 100644 --- a/pkg/test/script/detector/postmancollection.go +++ b/pkg/test/script/detector/postmancollection.go @@ -12,7 +12,7 @@ type PostmanCollectionAdapter struct { func (d PostmanCollectionAdapter) Is(options apiClient.UpsertScriptOptions) (name string, ok bool) { var data map[string]interface{} - err := json.Unmarshal([]byte(options.Content), &data) + err := json.Unmarshal([]byte(options.Content.Data), &data) if err != nil { return } diff --git a/pkg/test/script/detector/postmancollection_test.go b/pkg/test/script/detector/postmancollection_test.go index c29d34f7991..0d5fb0c20cd 100644 --- a/pkg/test/script/detector/postmancollection_test.go +++ b/pkg/test/script/detector/postmancollection_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/kubeshop/testkube/pkg/api/v1/client" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/stretchr/testify/assert" ) @@ -18,7 +19,7 @@ func TestPostmanCollectionAdapter(t *testing.T) { t.Run("Is return true when valid content", func(t *testing.T) { detector := PostmanCollectionAdapter{} name, is := detector.Is(client.UpsertScriptOptions{ - Content: exampleValidContent, + Content: testkube.NewStringScriptContent(exampleValidContent), }) assert.Equal(t, "postman/collection", name) @@ -28,7 +29,7 @@ func TestPostmanCollectionAdapter(t *testing.T) { t.Run("Is return false in case of invalid JSON content", func(t *testing.T) { detector := PostmanCollectionAdapter{} name, is := detector.Is(client.UpsertScriptOptions{ - Content: exampleInvalidContent, + Content: testkube.NewStringScriptContent(exampleInvalidContent), }) assert.Empty(t, name) @@ -39,7 +40,7 @@ func TestPostmanCollectionAdapter(t *testing.T) { t.Run("Is return false in case of content which is not JSON ", func(t *testing.T) { detector := PostmanCollectionAdapter{} name, is := detector.Is(client.UpsertScriptOptions{ - Content: exampleInvalidJSONContent, + Content: testkube.NewStringScriptContent(exampleInvalidJSONContent), }) assert.Empty(t, name) diff --git a/test/run.sh b/test/run.sh index cb382da5919..276dfec0b5b 100755 --- a/test/run.sh +++ b/test/run.sh @@ -1,141 +1,142 @@ #!/bin/zsh - set -e +TESTKUBE=${TESTKUBE:-'kubectl testkube'} + script_execution_id() { - kubectl testkube scripts executions | grep $1 | head -n 1 | tr -s ' ' | cut -d" " -f 8 + $TESTKUBE scripts executions | grep $1 | head -n 1 | tr -s ' ' | cut -d" " -f 8 } test_execution_id() { - kubectl testkube tests executions | grep $1 | head -n 1 | tr -s ' ' | cut -d" " -f 2 + $TESTKUBE tests executions | grep $1 | head -n 1 | tr -s ' ' | cut -d" " -f 2 } test_scripts_delete() { echo "Scripts delete test" - kubectl testkube scripts - kubectl testkube scripts delete kubeshop-site1 > /dev/null || true - kubectl testkube scripts delete kubeshop-site2 > /dev/null || true - kubectl testkube scripts delete kubeshop-site3 > /dev/null || true - kubectl testkube scripts delete kubeshop-site4 > /dev/null || true - kubectl testkube scripts delete kubeshop-site5 > /dev/null || true - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site1 - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site2 - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site3 - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site4 - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site5 - kubectl testkube scripts list - kubectl testkube scripts delete kubeshop-site1 - kubectl testkube scripts list - kubectl testkube scripts delete kubeshop-site2 - kubectl testkube scripts list - kubectl testkube scripts delete kubeshop-site3 - kubectl testkube scripts list - kubectl testkube scripts delete kubeshop-site4 - kubectl testkube scripts list - kubectl testkube scripts delete kubeshop-site5 - kubectl testkube scripts list + $TESTKUBE scripts + $TESTKUBE scripts delete kubeshop-site1 > /dev/null || true + $TESTKUBE scripts delete kubeshop-site2 > /dev/null || true + $TESTKUBE scripts delete kubeshop-site3 > /dev/null || true + $TESTKUBE scripts delete kubeshop-site4 > /dev/null || true + $TESTKUBE scripts delete kubeshop-site5 > /dev/null || true + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site1 + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site2 + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site3 + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site4 + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site5 + $TESTKUBE scripts list + $TESTKUBE scripts delete kubeshop-site1 + $TESTKUBE scripts list + $TESTKUBE scripts delete kubeshop-site2 + $TESTKUBE scripts list + $TESTKUBE scripts delete kubeshop-site3 + $TESTKUBE scripts list + $TESTKUBE scripts delete kubeshop-site4 + $TESTKUBE scripts list + $TESTKUBE scripts delete kubeshop-site5 + $TESTKUBE scripts list } test_scripts_delete_all() { echo "Scripts delete all test" - kubectl testkube scripts - kubectl testkube scripts delete-all - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site1 - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site2 - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site3 - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site4 - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site5 - kubectl testkube scripts list - kubectl testkube scripts delete kubeshop-site1 - kubectl testkube scripts list - kubectl testkube scripts delete-all - kubectl testkube scripts list + $TESTKUBE scripts + $TESTKUBE scripts delete-all + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site1 + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site2 + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site3 + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site4 + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site5 + $TESTKUBE scripts list + $TESTKUBE scripts delete kubeshop-site1 + $TESTKUBE scripts list + $TESTKUBE scripts delete-all + $TESTKUBE scripts list } test_scripts_create() { echo "Scripts create test" - kubectl testkube scripts delete kubeshop-site > /dev/null || true - kubectl testkube scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site - kubectl testkube scripts delete testkube-todo-api > /dev/null || true - kubectl testkube scripts create --file test/e2e/TODO.postman_collection.json --name testkube-todo-api - kubectl testkube scripts delete testkube-todo-frontend > /dev/null || true - kubectl testkube scripts create --git-branch main --uri https://github.com/kubeshop/testkube-example-cypress-project.git --git-path "cypress" --name testkube-todo-frontend --type cypress/project - kubectl testkube scripts delete testkube-dashboard > /dev/null || true - kubectl testkube scripts create --uri https://github.com/kubeshop/testkube-dashboard.git --git-path test --git-branch main --name testkube-dashboard --type cypress/project - kubectl testkube scripts delete curl-test > /dev/null || true - cat test/e2e/curl.json | kubectl testkube scripts create --name curl-test + $TESTKUBE scripts delete kubeshop-site > /dev/null || true + $TESTKUBE scripts create --file test/e2e/Kubeshop.postman_collection.json --name kubeshop-site + $TESTKUBE scripts delete testkube-todo-api > /dev/null || true + $TESTKUBE scripts create --file test/e2e/TODO.postman_collection.json --name testkube-todo-api + $TESTKUBE scripts delete testkube-todo-frontend > /dev/null || true + $TESTKUBE scripts create --git-branch main --git-uri https://github.com/kubeshop/testkube-example-cypress-project.git --git-path "cypress" --name testkube-todo-frontend --type cypress/project + $TESTKUBE scripts delete testkube-dashboard > /dev/null || true + $TESTKUBE scripts create --git-uri https://github.com/kubeshop/testkube-dashboard.git --git-path test --git-branch main --name testkube-dashboard --type cypress/project + $TESTKUBE scripts delete curl-test > /dev/null || true + cat test/e2e/curl.json | $TESTKUBE scripts create --name curl-test } test_scripts_run() { - kubectl testkube scripts run kubeshop-site -f # postman - kubectl testkube scripts execution $(script_execution_id kubeshop-site) - kubectl testkube scripts run testkube-dashboard -f # cypress - kubectl testkube scripts execution $(script_execution_id testkube-dashboard) + $TESTKUBE scripts run kubeshop-site -f # postman + $TESTKUBE scripts execution $(script_execution_id kubeshop-site) + $TESTKUBE scripts run testkube-dashboard -f # cypress + $TESTKUBE scripts execution $(script_execution_id testkube-dashboard) # curl issue #821 - need to be without -f - kubectl testkube scripts run curl-test # curl + $TESTKUBE scripts run curl-test # curl sleep 5 - kubectl testkube scripts execution $(script_execution_id curl-test) + $TESTKUBE scripts execution $(script_execution_id curl-test) } test_tests_delete_all() { echo "Tests delete all test" - kubectl testkube tests delete-all - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app1 - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app2 - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app3 - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app4 - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app5 - - kubectl testkube tests delete todo-app1 - kubectl testkube tests list - - kubectl testkube tests delete-all - kubectl testkube tests list + $TESTKUBE tests delete-all + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app1 + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app2 + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app3 + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app4 + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app5 + + $TESTKUBE tests delete todo-app1 + $TESTKUBE tests list + + $TESTKUBE tests delete-all + $TESTKUBE tests list } test_tests_delete() { echo "Tests delete test" - kubectl testkube tests delete todo-app1 > /dev/null || true - kubectl testkube tests delete todo-app2 > /dev/null || true - kubectl testkube tests delete todo-app3 > /dev/null || true - kubectl testkube tests delete todo-app4 > /dev/null || true - kubectl testkube tests delete todo-app5 > /dev/null || true - - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app1 - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app2 - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app3 - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app4 - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app5 - - kubectl testkube tests delete todo-app1 - kubectl testkube tests list - kubectl testkube tests delete todo-app2 - kubectl testkube tests list - kubectl testkube tests delete todo-app3 - kubectl testkube tests list - kubectl testkube tests delete todo-app4 - kubectl testkube tests list - kubectl testkube tests delete todo-app5 - kubectl testkube tests list + $TESTKUBE tests delete todo-app1 > /dev/null || true + $TESTKUBE tests delete todo-app2 > /dev/null || true + $TESTKUBE tests delete todo-app3 > /dev/null || true + $TESTKUBE tests delete todo-app4 > /dev/null || true + $TESTKUBE tests delete todo-app5 > /dev/null || true + + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app1 + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app2 + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app3 + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app4 + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app5 + + $TESTKUBE tests delete todo-app1 + $TESTKUBE tests list + $TESTKUBE tests delete todo-app2 + $TESTKUBE tests list + $TESTKUBE tests delete todo-app3 + $TESTKUBE tests list + $TESTKUBE tests delete todo-app4 + $TESTKUBE tests list + $TESTKUBE tests delete todo-app5 + $TESTKUBE tests list } test_tests_create() { echo "create tests" - kubectl testkube tests delete todo-app > /dev/null || true - cat test/e2e/test-example-1.json | kubectl testkube tests create --name todo-app - kubectl testkube tests delete kubeshop > /dev/null || true - cat test/e2e/test-example-2.json | kubectl testkube tests create --name kubeshop + $TESTKUBE tests delete todo-app > /dev/null || true + cat test/e2e/test-example-1.json | $TESTKUBE tests create --name todo-app + $TESTKUBE tests delete kubeshop > /dev/null || true + cat test/e2e/test-example-2.json | $TESTKUBE tests create --name kubeshop } test_tests_run() { echo "run tests" - kubectl testkube tests run todo-app -f - kubectl testkube tests execution $(test_execution_id todo-app) - kubectl testkube tests run kubeshop -f - kubectl testkube tests execution $(test_execution_id kubeshop) + $TESTKUBE tests run todo-app -f + $TESTKUBE tests execution $(test_execution_id todo-app) + $TESTKUBE tests run kubeshop -f + $TESTKUBE tests execution $(test_execution_id kubeshop) } while test $# != 0