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  }