github.com/kubeshop/testkube@v1.17.23/cmd/kubectl-testkube/commands/tests/create.go (about) 1 package tests 2 3 import ( 4 "fmt" 5 "os" 6 "strconv" 7 "time" 8 9 "github.com/spf13/cobra" 10 11 "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" 12 "github.com/kubeshop/testkube/pkg/api/v1/client" 13 apiv1 "github.com/kubeshop/testkube/pkg/api/v1/client" 14 "github.com/kubeshop/testkube/pkg/api/v1/testkube" 15 "github.com/kubeshop/testkube/pkg/crd" 16 "github.com/kubeshop/testkube/pkg/ui" 17 ) 18 19 // CreateCommonFlags are common flags for creating all test types 20 type CreateCommonFlags struct { 21 ExecutorType string 22 Labels map[string]string 23 Variables []string 24 SecretVariables []string 25 Schedule string 26 ExecutorArgs []string 27 ArgsMode string 28 ExecutionName string 29 VariablesFile string 30 Envs map[string]string 31 SecretEnvs map[string]string 32 HttpProxy, HttpsProxy string 33 SecretVariableReferences map[string]string 34 CopyFiles []string 35 Image string 36 Command []string 37 ImagePullSecretNames []string 38 Timeout int64 39 ArtifactStorageClassName string 40 ArtifactVolumeMountPath string 41 ArtifactDirs []string 42 ArtifactMasks []string 43 JobTemplate string 44 JobTemplateReference string 45 CronJobTemplate string 46 CronJobTemplateReference string 47 PreRunScript string 48 PostRunScript string 49 ExecutePostRunScriptBeforeScraping bool 50 SourceScripts bool 51 ScraperTemplate string 52 ScraperTemplateReference string 53 PvcTemplate string 54 PvcTemplateReference string 55 NegativeTest bool 56 MountConfigMaps map[string]string 57 VariableConfigMaps []string 58 MountSecrets map[string]string 59 VariableSecrets []string 60 UploadTimeout string 61 ArtifactStorageBucket string 62 ArtifactOmitFolderPerExecution bool 63 ArtifactSharedBetweenPods bool 64 ArtifactUseDefaultStorageClassName bool 65 Description string 66 SlavePodRequestsCpu string 67 SlavePodRequestsMemory string 68 SlavePodLimitsCpu string 69 SlavePodLimitsMemory string 70 SlavePodTemplate string 71 SlavePodTemplateReference string 72 ExecutionNamespace string 73 } 74 75 // NewCreateTestsCmd is a command tp create new Test Custom Resource 76 func NewCreateTestsCmd() *cobra.Command { 77 78 var ( 79 testName string 80 testContentType string 81 file string 82 uri string 83 gitUri string 84 gitBranch string 85 gitCommit string 86 gitPath string 87 gitWorkingDir string 88 gitUsername string 89 gitToken string 90 gitUsernameSecret map[string]string 91 gitTokenSecret map[string]string 92 gitCertificateSecret string 93 gitAuthType string 94 sourceName string 95 flags CreateCommonFlags 96 update bool 97 ) 98 99 cmd := &cobra.Command{ 100 Use: "test", 101 Aliases: []string{"tests", "t"}, 102 Short: "Create new Test", 103 Long: `Create new Test Custom Resource`, 104 Run: func(cmd *cobra.Command, args []string) { 105 crdOnly, err := strconv.ParseBool(cmd.Flag("crd-only").Value.String()) 106 ui.ExitOnError("parsing flag value", err) 107 108 if testName == "" { 109 ui.Failf("pass valid test name (in '--name' flag)") 110 } 111 112 namespace := cmd.Flag("namespace").Value.String() 113 var client client.Client 114 if !crdOnly { 115 client, namespace, err = common.GetClient(cmd) 116 ui.ExitOnError("getting client", err) 117 118 test, _ := client.GetTest(testName) 119 120 if testName == test.Name { 121 if cmd.Flag("update").Changed { 122 if !update { 123 ui.Failf("Test with name '%s' already exists in namespace %s, ", testName, namespace) 124 } 125 } else { 126 var ok bool 127 if stat, _ := os.Stdin.Stat(); (stat.Mode() & os.ModeCharDevice) != 0 { 128 ok = ui.Confirm(fmt.Sprintf("Test with name '%s' already exists in namespace %s, ", testName, namespace) + 129 "do you want to overwrite it?") 130 } 131 132 if !ok { 133 ui.Failf("Test creation was aborted") 134 } 135 } 136 137 options, err := NewUpdateTestOptionsFromFlags(cmd) 138 ui.ExitOnError("getting test options", err) 139 140 test, err = client.UpdateTest(options) 141 ui.ExitOnError("updating test "+testName+" in namespace "+namespace, err) 142 143 ui.SuccessAndExit("Test updated", namespace, "/", testName) 144 } 145 } 146 147 if cmd.Flag("git-uri") != nil { 148 err = common.ValidateUpsertOptions(cmd, sourceName) 149 ui.ExitOnError("validating passed flags", err) 150 } 151 152 options, err := NewUpsertTestOptionsFromFlags(cmd) 153 ui.ExitOnError("getting test options", err) 154 155 if !crdOnly { 156 executors, err := client.ListExecutors("") 157 ui.ExitOnError("getting available executors", err) 158 159 contentType := "" 160 if options.Content != nil { 161 contentType = options.Content.Type_ 162 } 163 164 err = validateExecutorTypeAndContent(options.Type_, contentType, executors) 165 ui.ExitOnError("validating executor type", err) 166 167 var timeout time.Duration 168 if flags.UploadTimeout != "" { 169 timeout, err = time.ParseDuration(flags.UploadTimeout) 170 if err != nil { 171 ui.ExitOnError("invalid upload timeout duration", err) 172 } 173 } 174 175 if len(flags.VariablesFile) > 0 { 176 options.ExecutionRequest.VariablesFile, options.ExecutionRequest.IsVariablesFileUploaded, err = PrepareVariablesFile(client, testName, apiv1.Test, flags.VariablesFile, timeout) 177 if err != nil { 178 ui.ExitOnError("could not prepare variables file", err) 179 } 180 } 181 182 if len(flags.CopyFiles) > 0 { 183 err := uploadFiles(client, testName, apiv1.Test, flags.CopyFiles, timeout) 184 ui.ExitOnError("could not upload files", err) 185 } 186 187 _, err = client.CreateTest(options) 188 ui.ExitOnError("creating test "+testName+" in namespace "+namespace, err) 189 190 ui.Success("Test created", namespace, "/", testName) 191 } else { 192 (*testkube.TestUpsertRequest)(&options).QuoteTestTextFields() 193 data, err := crd.ExecuteTemplate(crd.TemplateTest, options) 194 ui.ExitOnError("executing crd template", err) 195 196 ui.Info(data) 197 } 198 }, 199 } 200 201 cmd.Flags().StringVarP(&testName, "name", "n", "", "unique test name - mandatory") 202 cmd.Flags().StringVarP(&testContentType, "test-content-type", "", "", "content type of test one of string|file-uri|git") 203 204 // create options 205 cmd.Flags().StringVarP(&file, "file", "f", "", "test file - will be read from stdin if not specified") 206 cmd.Flags().StringVarP(&uri, "uri", "", "", "URI of resource - will be loaded by http GET") 207 cmd.Flags().StringVarP(&gitUri, "git-uri", "", "", "Git repository uri") 208 cmd.Flags().StringVarP(&gitBranch, "git-branch", "", "", "if uri is git repository we can set additional branch parameter") 209 cmd.Flags().StringVarP(&gitCommit, "git-commit", "", "", "if uri is git repository we can use commit id (sha) parameter") 210 cmd.Flags().StringVarP(&gitPath, "git-path", "", "", "if repository is big we need to define additional path to directory/file to checkout partially") 211 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") 212 cmd.Flags().StringVarP(&gitUsername, "git-username", "", "", "if git repository is private we can use username as an auth parameter") 213 cmd.Flags().StringVarP(&gitToken, "git-token", "", "", "if git repository is private we can use token as an auth parameter") 214 cmd.Flags().StringToStringVarP(&gitUsernameSecret, "git-username-secret", "", map[string]string{}, "git username secret in a form of secret_name1=secret_key1 for private repository") 215 cmd.Flags().StringToStringVarP(&gitTokenSecret, "git-token-secret", "", map[string]string{}, "git token secret in a form of secret_name1=secret_key1 for private repository") 216 cmd.Flags().StringVarP(&gitCertificateSecret, "git-certificate-secret", "", "", "if git repository is private we can use certificate as an auth parameter stored in a kubernetes secret name") 217 cmd.Flags().StringVarP(&gitAuthType, "git-auth-type", "", "basic", "auth type for git requests one of basic|header") 218 cmd.Flags().StringVarP(&sourceName, "source", "", "", "source name - will be used together with content parameters") 219 cmd.Flags().BoolVar(&update, "update", false, "update, if test already exists") 220 cmd.Flags().MarkDeprecated("env", "env is deprecated use variable instead") 221 cmd.Flags().MarkDeprecated("secret-env", "secret-env is deprecated use secret-variable instead") 222 223 AddCreateFlags(cmd, &flags) 224 225 return cmd 226 } 227 228 // AddCreateFlags adds flags to the create command that can be used by the create from file 229 func AddCreateFlags(cmd *cobra.Command, flags *CreateCommonFlags) { 230 231 cmd.Flags().StringVarP(&flags.ExecutorType, "type", "t", "", "test type") 232 233 cmd.Flags().StringToStringVarP(&flags.Labels, "label", "l", nil, "label key value pair: --label key1=value1") 234 cmd.Flags().StringArrayVarP(&flags.Variables, "variable", "v", nil, "variable key value pair: --variable key1=value1") 235 cmd.Flags().StringArrayVarP(&flags.SecretVariables, "secret-variable", "s", nil, "secret variable key value pair: --secret-variable key1=value1") 236 cmd.Flags().StringVarP(&flags.Schedule, "schedule", "", "", "test schedule in a cron job form: * * * * *") 237 cmd.Flags().StringArrayVar(&flags.Command, "command", []string{}, "command passed to image in executor") 238 cmd.Flags().StringArrayVarP(&flags.ExecutorArgs, "executor-args", "", []string{}, "executor binary additional arguments") 239 cmd.Flags().StringVarP(&flags.ArgsMode, "args-mode", "", "append", "usage mode for arguments. one of append|override|replace") 240 cmd.Flags().StringVarP(&flags.ExecutionName, "execution-name", "", "", "execution name, if empty will be autogenerated") 241 cmd.Flags().StringVarP(&flags.VariablesFile, "variables-file", "", "", "variables file path, e.g. postman env file - will be passed to executor if supported") 242 cmd.Flags().StringToStringVarP(&flags.Envs, "env", "", map[string]string{}, "envs in a form of name1=val1 passed to executor") 243 cmd.Flags().StringToStringVarP(&flags.SecretEnvs, "secret-env", "", map[string]string{}, "secret envs in a form of secret_key1=secret_name1 passed to executor") 244 cmd.Flags().StringVar(&flags.HttpProxy, "http-proxy", "", "http proxy for executor containers") 245 cmd.Flags().StringVar(&flags.HttpsProxy, "https-proxy", "", "https proxy for executor containers") 246 cmd.Flags().StringToStringVarP(&flags.SecretVariableReferences, "secret-variable-reference", "", nil, "secret variable references in a form name1=secret_name1=secret_key1") 247 cmd.Flags().StringArrayVarP(&flags.CopyFiles, "copy-files", "", []string{}, "file path mappings from host to pod of form source:destination") 248 cmd.Flags().StringVar(&flags.Image, "image", "", "override executor container image") 249 cmd.Flags().StringArrayVar(&flags.ImagePullSecretNames, "image-pull-secrets", []string{}, "secret name used to pull the image in container executor") 250 cmd.Flags().Int64Var(&flags.Timeout, "timeout", 0, "duration in seconds for test to timeout. 0 disables timeout.") 251 cmd.Flags().StringVar(&flags.ArtifactStorageClassName, "artifact-storage-class-name", "", "artifact storage class name for container executor") 252 cmd.Flags().StringVar(&flags.ArtifactVolumeMountPath, "artifact-volume-mount-path", "", "artifact volume mount path for container executor") 253 cmd.Flags().StringArrayVarP(&flags.ArtifactDirs, "artifact-dir", "", []string{}, "artifact dirs for scraping") 254 cmd.Flags().StringArrayVarP(&flags.ArtifactMasks, "artifact-mask", "", []string{}, "regexp to filter scraped artifacts, single or comma separated, like report/.* or .*\\.json,.*\\.js$") 255 cmd.Flags().StringVar(&flags.JobTemplate, "job-template", "", "job template file path for extensions to job template") 256 cmd.Flags().StringVar(&flags.JobTemplateReference, "job-template-reference", "", "reference to job template to use for the test") 257 cmd.Flags().StringVar(&flags.CronJobTemplate, "cronjob-template", "", "cron job template file path for extensions to cron job template") 258 cmd.Flags().StringVar(&flags.CronJobTemplateReference, "cronjob-template-reference", "", "reference to cron job template to use for the test") 259 cmd.Flags().StringVarP(&flags.PreRunScript, "prerun-script", "", "", "path to script to be run before test execution") 260 cmd.Flags().StringVarP(&flags.PostRunScript, "postrun-script", "", "", "path to script to be run after test execution") 261 cmd.Flags().BoolVarP(&flags.ExecutePostRunScriptBeforeScraping, "execute-postrun-script-before-scraping", "", false, "whether to execute postrun scipt before scraping or not (prebuilt executor only)") 262 cmd.Flags().BoolVarP(&flags.SourceScripts, "source-scripts", "", false, "run scripts using source command (container executor only)") 263 cmd.Flags().StringVar(&flags.ScraperTemplate, "scraper-template", "", "scraper template file path for extensions to scraper template") 264 cmd.Flags().StringVar(&flags.ScraperTemplateReference, "scraper-template-reference", "", "reference to scraper template to use for the test") 265 cmd.Flags().StringVar(&flags.PvcTemplate, "pvc-template", "", "pvc template file path for extensions to pvc template") 266 cmd.Flags().StringVar(&flags.PvcTemplateReference, "pvc-template-reference", "", "reference to pvc template to use for the test") 267 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") 268 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") 269 cmd.Flags().StringArrayVar(&flags.VariableConfigMaps, "variable-configmap", []string{}, "config map name used to map all keys to basis variables") 270 cmd.Flags().StringToStringVarP(&flags.MountSecrets, "mount-secret", "", map[string]string{}, "secret value pair for mounting it to executor pod: --mount-secret secret_name=secret_mountpath") 271 cmd.Flags().StringArrayVar(&flags.VariableSecrets, "variable-secret", []string{}, "secret name used to map all keys to secret variables") 272 cmd.Flags().StringVar(&flags.UploadTimeout, "upload-timeout", "", "timeout to use when uploading files, example: 30s") 273 cmd.Flags().StringVar(&flags.ArtifactStorageBucket, "artifact-storage-bucket", "", "artifact storage bucket") 274 cmd.Flags().BoolVarP(&flags.ArtifactOmitFolderPerExecution, "artifact-omit-folder-per-execution", "", false, "don't store artifacts in execution folder") 275 cmd.Flags().BoolVarP(&flags.ArtifactSharedBetweenPods, "artifact-shared-between-pods", "", false, "whether to share volume between pods") 276 cmd.Flags().BoolVarP(&flags.ArtifactUseDefaultStorageClassName, "artifact-use-default-storage-class-name", "", false, "whether to use default storage class name") 277 cmd.Flags().StringVarP(&flags.Description, "description", "", "", "test description") 278 cmd.Flags().StringVar(&flags.SlavePodRequestsCpu, "slave-pod-requests-cpu", "", "slave pod resource requests cpu") 279 cmd.Flags().StringVar(&flags.SlavePodRequestsMemory, "slave-pod-requests-memory", "", "slave pod resource requests memory") 280 cmd.Flags().StringVar(&flags.SlavePodLimitsCpu, "slave-pod-limits-cpu", "", "slave pod resource limits cpu") 281 cmd.Flags().StringVar(&flags.SlavePodLimitsMemory, "slave-pod-limits-memory", "", "slave pod resource limits memory") 282 cmd.Flags().StringVar(&flags.SlavePodTemplate, "slave-pod-template", "", "slave pod template file path for extensions to slave pod template") 283 cmd.Flags().StringVar(&flags.SlavePodTemplateReference, "slave-pod-template-reference", "", "reference to slave pod template to use for the test") 284 cmd.Flags().StringVar(&flags.ExecutionNamespace, "execution-namespace", "", "namespace for test execution (Pro edition only)") 285 } 286 287 func validateExecutorTypeAndContent(executorType, contentType string, executors testkube.ExecutorsDetails) error { 288 typeValid := false 289 executorTypes := []string{} 290 contentTypes := []string{} 291 292 for _, ed := range executors { 293 executorTypes = append(executorTypes, ed.Executor.Types...) 294 for _, et := range ed.Executor.Types { 295 if et == executorType { 296 typeValid = true 297 contentTypes = ed.Executor.ContentTypes 298 break 299 } 300 } 301 } 302 303 if !typeValid { 304 return fmt.Errorf("invalid executor type '%s' use one of: %v", executorType, executorTypes) 305 } 306 307 if len(contentTypes) != 0 { 308 contentValid := false 309 for _, ct := range contentTypes { 310 if ct == contentType { 311 contentValid = true 312 break 313 } 314 } 315 316 if !contentValid { 317 return fmt.Errorf("invalid content type '%s' use one of: %v", contentType, contentTypes) 318 } 319 } 320 321 return nil 322 }