github.com/kubeshop/testkube@v1.17.23/cmd/kubectl-testkube/commands/tests/common.go (about)

     1  package tests
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/robfig/cron"
    14  	"github.com/spf13/cobra"
    15  
    16  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common"
    17  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/renderer"
    18  	"github.com/kubeshop/testkube/pkg/api/v1/client"
    19  	apiclientv1 "github.com/kubeshop/testkube/pkg/api/v1/client"
    20  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    21  	"github.com/kubeshop/testkube/pkg/executor/output"
    22  	"github.com/kubeshop/testkube/pkg/test/detector"
    23  	"github.com/kubeshop/testkube/pkg/ui"
    24  )
    25  
    26  const (
    27  	artifactsFormatFolder  = "folder"
    28  	artifactsFormatArchive = "archive"
    29  	maxArgSize             = int64(131072) // maximum argument size in linux-based systems is 128 KiB
    30  )
    31  
    32  func printExecutionDetails(execution testkube.Execution) {
    33  	ui.Warn("Type:             ", execution.TestType)
    34  	ui.Warn("Name:             ", execution.TestName)
    35  	if execution.Id != "" {
    36  		ui.Warn("Execution ID:     ", execution.Id)
    37  		ui.Warn("Execution name:   ", execution.Name)
    38  		if execution.Number != 0 {
    39  			ui.Warn("Execution number: ", fmt.Sprintf("%d", execution.Number))
    40  		}
    41  		if execution.ExecutionResult != nil && execution.ExecutionResult.Status != nil {
    42  			ui.Warn("Status:           ", string(*execution.ExecutionResult.Status))
    43  		}
    44  		ui.Warn("Start time:       ", execution.StartTime.String())
    45  		ui.Warn("End time:         ", execution.EndTime.String())
    46  		ui.Warn("Duration:         ", execution.Duration)
    47  	}
    48  
    49  	renderer.RenderVariables(execution.Variables)
    50  
    51  	ui.NL()
    52  	ui.NL()
    53  }
    54  
    55  func DownloadTestArtifacts(id, dir, format string, masks []string, client apiclientv1.Client) {
    56  	artifacts, err := client.GetExecutionArtifacts(id)
    57  	ui.ExitOnError("getting artifacts", err)
    58  
    59  	downloadFile := func(artifact testkube.Artifact, dir string) (string, error) {
    60  		return client.DownloadFile(id, artifact.Name, dir)
    61  	}
    62  	downloadArchive := func(dir string, masks []string) (string, error) {
    63  		return client.DownloadArchive(id, dir, masks)
    64  	}
    65  	downloadArtifacts(dir, format, masks, artifacts, downloadFile, downloadArchive)
    66  }
    67  
    68  func DownloadTestWorkflowArtifacts(id, dir, format string, masks []string, client apiclientv1.Client) {
    69  	artifacts, err := client.GetTestWorkflowExecutionArtifacts(id)
    70  	ui.ExitOnError("getting artifacts", err)
    71  
    72  	downloadFile := func(artifact testkube.Artifact, dir string) (string, error) {
    73  		return client.DownloadTestWorkflowArtifact(id, artifact.Name, dir)
    74  	}
    75  	downloadArchive := func(dir string, masks []string) (string, error) {
    76  		return client.DownloadTestWorkflowArtifactArchive(id, dir, masks)
    77  	}
    78  	downloadArtifacts(dir, format, masks, artifacts, downloadFile, downloadArchive)
    79  }
    80  
    81  func downloadArtifacts(
    82  	dir, format string,
    83  	masks []string,
    84  	artifacts testkube.Artifacts,
    85  	downloadFile func(artifact testkube.Artifact, dir string) (string, error),
    86  	downloadArchive func(dir string, masks []string) (string, error),
    87  ) {
    88  	err := os.MkdirAll(dir, os.ModePerm)
    89  	ui.ExitOnError("creating dir "+dir, err)
    90  
    91  	if len(artifacts) > 0 {
    92  		ui.Info("Getting artifacts", fmt.Sprintf("count = %d", len(artifacts)), "\n")
    93  	}
    94  
    95  	if format != artifactsFormatFolder && format != artifactsFormatArchive {
    96  		ui.Failf("invalid artifacts format: %s. use one of folder|archive", format)
    97  	}
    98  
    99  	var regexps []*regexp.Regexp
   100  	for _, mask := range masks {
   101  		values := strings.Split(mask, ",")
   102  		for _, value := range values {
   103  			re, err := regexp.Compile(value)
   104  			ui.ExitOnError("checking mask "+value, err)
   105  
   106  			regexps = append(regexps, re)
   107  		}
   108  	}
   109  
   110  	if format == artifactsFormatFolder {
   111  		for _, artifact := range artifacts {
   112  			found := len(regexps) == 0
   113  			for i := range regexps {
   114  				if found = regexps[i].MatchString(artifact.Name); found {
   115  					break
   116  				}
   117  			}
   118  
   119  			if !found {
   120  				continue
   121  			}
   122  
   123  			f, err := downloadFile(artifact, dir)
   124  			ui.ExitOnError("downloading file: "+f, err)
   125  			ui.Warn(" - downloading file ", f)
   126  		}
   127  	}
   128  
   129  	if format == artifactsFormatArchive {
   130  		const readinessCheckTimeout = time.Second
   131  		ticker := time.NewTicker(readinessCheckTimeout)
   132  		defer ticker.Stop()
   133  
   134  		ch := make(chan string)
   135  		defer close(ch)
   136  
   137  		go func() {
   138  			f, err := downloadArchive(dir, masks)
   139  			ui.ExitOnError("downloading archive: "+f, err)
   140  
   141  			ch <- f
   142  		}()
   143  
   144  		var archive string
   145  		ui.Warn(" - preparing archive ")
   146  
   147  	outloop:
   148  		for {
   149  			select {
   150  			case <-ticker.C:
   151  				ui.PrintDot()
   152  			case archive = <-ch:
   153  				ui.NL()
   154  				break outloop
   155  			}
   156  		}
   157  
   158  		ui.Warn(" - downloading archive ", archive)
   159  	}
   160  
   161  	ui.NL()
   162  	ui.NL()
   163  }
   164  
   165  func watchLogs(id string, silentMode bool, client apiclientv1.Client) error {
   166  	ui.Info("Getting logs from test job", id)
   167  
   168  	logs, err := client.Logs(id)
   169  	ui.ExitOnError("getting logs from executor", err)
   170  
   171  	var result error
   172  	for l := range logs {
   173  		switch l.Type_ {
   174  		case output.TypeError:
   175  			ui.UseStderr()
   176  			ui.Errf(l.Content)
   177  			if l.Result != nil {
   178  				ui.Errf("Error: %s", l.Result.ErrorMessage)
   179  				ui.Debug("Output: %s", l.Result.Output)
   180  			}
   181  			result = errors.New(l.Content)
   182  		case output.TypeResult:
   183  			ui.Info("Execution completed", l.Result.Output)
   184  		default:
   185  			if !silentMode {
   186  				ui.LogLine(l.String())
   187  			}
   188  		}
   189  	}
   190  
   191  	ui.NL()
   192  
   193  	// TODO Websocket research + plug into Event bus (EventEmitter)
   194  	// watch for success | error status - in case of connection error on logs watch need fix in 0.8
   195  	for range time.Tick(time.Second) {
   196  		execution, err := client.GetExecution(id)
   197  		ui.ExitOnError("get test execution details", err)
   198  
   199  		fmt.Print(".")
   200  
   201  		if execution.ExecutionResult.IsCompleted() {
   202  			return result
   203  		}
   204  	}
   205  
   206  	return result
   207  }
   208  
   209  func watchLogsV2(id string, silentMode bool, client apiclientv1.Client) error {
   210  	ui.Info("Getting logs from test job", id)
   211  
   212  	logs, err := client.LogsV2(id)
   213  	ui.ExitOnError("getting logs from executor", err)
   214  
   215  	var result error
   216  	for l := range logs {
   217  		if l.Error_ {
   218  			ui.UseStderr()
   219  			ui.Errf(l.Content)
   220  			result = errors.New(l.Content)
   221  			continue
   222  		}
   223  
   224  		if !silentMode {
   225  			ui.LogLine(l.Content)
   226  		}
   227  	}
   228  
   229  	ui.NL()
   230  
   231  	// TODO Websocket research + plug into Event bus (EventEmitter)
   232  	// watch for success | error status - in case of connection error on logs watch need fix in 0.8
   233  	for range time.Tick(time.Second) {
   234  		execution, err := client.GetExecution(id)
   235  		ui.ExitOnError("get test execution details", err)
   236  
   237  		fmt.Print(".")
   238  
   239  		if execution.ExecutionResult.IsCompleted() {
   240  			ui.Info("Execution completed")
   241  			return result
   242  		}
   243  	}
   244  
   245  	return result
   246  }
   247  
   248  func newContentFromFlags(cmd *cobra.Command) (content *testkube.TestContent, err error) {
   249  	testContentType := cmd.Flag("test-content-type").Value.String()
   250  	uri := cmd.Flag("uri").Value.String()
   251  
   252  	data, err := common.NewDataFromFlags(cmd)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	fileContent := ""
   258  	if data != nil {
   259  		fileContent = *data
   260  	}
   261  
   262  	if uri != "" && testContentType == "" {
   263  		testContentType = string(testkube.TestContentTypeFileURI)
   264  	}
   265  
   266  	if len(fileContent) > 0 && testContentType == "" {
   267  		testContentType = string(testkube.TestContentTypeString)
   268  	}
   269  	var repository *testkube.Repository
   270  	if cmd.Flag("git-uri") != nil {
   271  		repository, err = common.NewRepositoryFromFlags(cmd)
   272  		if err != nil {
   273  			return nil, err
   274  		}
   275  	}
   276  
   277  	if repository != nil && testContentType == "" {
   278  		testContentType = string(testkube.TestContentTypeGit)
   279  	}
   280  
   281  	content = &testkube.TestContent{
   282  		Type_:      testContentType,
   283  		Data:       fileContent,
   284  		Repository: repository,
   285  		Uri:        uri,
   286  	}
   287  
   288  	return content, nil
   289  }
   290  
   291  func newArtifactRequestFromFlags(cmd *cobra.Command) (request *testkube.ArtifactRequest, err error) {
   292  	artifactStorageClassName := cmd.Flag("artifact-storage-class-name").Value.String()
   293  	artifactVolumeMountPath := cmd.Flag("artifact-volume-mount-path").Value.String()
   294  	dirs, err := cmd.Flags().GetStringArray("artifact-dir")
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  
   299  	masks, err := cmd.Flags().GetStringArray("artifact-mask")
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  
   304  	artifactStorageBucket := cmd.Flag("artifact-storage-bucket").Value.String()
   305  	artifactOmitFolderPerExecution, err := cmd.Flags().GetBool("artifact-omit-folder-per-execution")
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  
   310  	artifactSharedBetweenPods, err := cmd.Flags().GetBool("artifact-shared-between-pods")
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  
   315  	artifactUseDefaultStorageClassName, err := cmd.Flags().GetBool("artifact-use-default-storage-class-name")
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  
   320  	if artifactStorageClassName != "" || artifactVolumeMountPath != "" || len(dirs) != 0 || len(masks) != 0 ||
   321  		artifactStorageBucket != "" || artifactOmitFolderPerExecution || artifactSharedBetweenPods || artifactUseDefaultStorageClassName {
   322  		request = &testkube.ArtifactRequest{
   323  			StorageClassName:           artifactStorageClassName,
   324  			VolumeMountPath:            artifactVolumeMountPath,
   325  			Dirs:                       dirs,
   326  			Masks:                      masks,
   327  			StorageBucket:              artifactStorageBucket,
   328  			OmitFolderPerExecution:     artifactOmitFolderPerExecution,
   329  			SharedBetweenPods:          artifactSharedBetweenPods,
   330  			UseDefaultStorageClassName: artifactUseDefaultStorageClassName,
   331  		}
   332  	}
   333  
   334  	return request, nil
   335  }
   336  
   337  func newSlavePodRequestFromFlags(cmd *cobra.Command) (request *testkube.PodRequest, err error) {
   338  	slavePodTemplate := cmd.Flag("slave-pod-template").Value.String()
   339  	slavePodTemplateReference := cmd.Flag("slave-pod-template-reference").Value.String()
   340  	slavePodRequestsCpu := cmd.Flag("slave-pod-requests-cpu").Value.String()
   341  	slavePodRequestsMemory := cmd.Flag("slave-pod-requests-memory").Value.String()
   342  	slavePodLimitsCpu := cmd.Flag("slave-pod-limits-cpu").Value.String()
   343  	slavePodLimitsMemory := cmd.Flag("slave-pod-limits-memory").Value.String()
   344  
   345  	if slavePodRequestsCpu != "" || slavePodRequestsMemory != "" || slavePodLimitsCpu != "" ||
   346  		slavePodLimitsMemory != "" || slavePodTemplate != "" || slavePodTemplateReference != "" {
   347  		request = &testkube.PodRequest{
   348  			PodTemplateReference: slavePodTemplateReference,
   349  		}
   350  
   351  		if slavePodTemplate != "" {
   352  			b, err := os.ReadFile(slavePodTemplate)
   353  			ui.ExitOnError("reading slave pod template", err)
   354  			request.PodTemplate = string(b)
   355  		}
   356  
   357  		if slavePodRequestsCpu != "" || slavePodRequestsMemory != "" {
   358  			if request.Resources == nil {
   359  				request.Resources = &testkube.PodResourcesRequest{}
   360  			}
   361  
   362  			request.Resources.Requests = &testkube.ResourceRequest{
   363  				Cpu:    slavePodRequestsCpu,
   364  				Memory: slavePodRequestsMemory,
   365  			}
   366  		}
   367  
   368  		if slavePodLimitsCpu != "" || slavePodLimitsMemory != "" {
   369  			if request.Resources == nil {
   370  				request.Resources = &testkube.PodResourcesRequest{}
   371  			}
   372  
   373  			request.Resources.Limits = &testkube.ResourceRequest{
   374  				Cpu:    slavePodLimitsCpu,
   375  				Memory: slavePodLimitsMemory,
   376  			}
   377  		}
   378  	}
   379  
   380  	return request, nil
   381  }
   382  
   383  func newEnvReferencesFromFlags(cmd *cobra.Command) (envConfigMaps, envSecrets []testkube.EnvReference, err error) {
   384  	mountConfigMaps, err := cmd.Flags().GetStringToString("mount-configmap")
   385  	if err != nil {
   386  		return nil, nil, err
   387  	}
   388  
   389  	variableConfigMaps, err := cmd.Flags().GetStringArray("variable-configmap")
   390  	if err != nil {
   391  		return nil, nil, err
   392  	}
   393  
   394  	mountSecrets, err := cmd.Flags().GetStringToString("mount-secret")
   395  	if err != nil {
   396  		return nil, nil, err
   397  	}
   398  
   399  	variableSecrets, err := cmd.Flags().GetStringArray("variable-secret")
   400  	if err != nil {
   401  		return nil, nil, err
   402  	}
   403  
   404  	mapConfigMaps := make(map[string]testkube.EnvReference)
   405  	for configMap, path := range mountConfigMaps {
   406  		mapConfigMaps[configMap] = testkube.EnvReference{
   407  			Reference: &testkube.LocalObjectReference{
   408  				Name: configMap,
   409  			},
   410  			Mount:     true,
   411  			MountPath: path,
   412  		}
   413  	}
   414  
   415  	for _, configMap := range variableConfigMaps {
   416  		if value, ok := mapConfigMaps[configMap]; ok {
   417  			value.MapToVariables = true
   418  			mapConfigMaps[configMap] = value
   419  		} else {
   420  			mapConfigMaps[configMap] = testkube.EnvReference{
   421  				Reference: &testkube.LocalObjectReference{
   422  					Name: configMap,
   423  				},
   424  				MapToVariables: true,
   425  			}
   426  		}
   427  	}
   428  
   429  	for _, value := range mapConfigMaps {
   430  		envConfigMaps = append(envConfigMaps, value)
   431  	}
   432  
   433  	mapSecrets := make(map[string]testkube.EnvReference)
   434  	for secret, path := range mountSecrets {
   435  		mapSecrets[secret] = testkube.EnvReference{
   436  			Reference: &testkube.LocalObjectReference{
   437  				Name: secret,
   438  			},
   439  			Mount:     true,
   440  			MountPath: path,
   441  		}
   442  	}
   443  
   444  	for _, secret := range variableSecrets {
   445  		if value, ok := mapSecrets[secret]; ok {
   446  			value.MapToVariables = true
   447  			mapSecrets[secret] = value
   448  		} else {
   449  			mapSecrets[secret] = testkube.EnvReference{
   450  				Reference: &testkube.LocalObjectReference{
   451  					Name: secret,
   452  				},
   453  				MapToVariables: true,
   454  			}
   455  		}
   456  	}
   457  
   458  	for _, value := range mapSecrets {
   459  		envSecrets = append(envSecrets, value)
   460  	}
   461  
   462  	return envConfigMaps, envSecrets, nil
   463  }
   464  
   465  func newExecutionRequestFromFlags(cmd *cobra.Command) (request *testkube.ExecutionRequest, err error) {
   466  	crdOnly, err := cmd.Flags().GetBool("crd-only")
   467  	if err != nil {
   468  		return nil, err
   469  	}
   470  
   471  	disableSecretCreation := false
   472  	if !crdOnly {
   473  		client, _, err := common.GetClient(cmd)
   474  		if err != nil {
   475  			return nil, err
   476  		}
   477  
   478  		info, err := client.GetServerInfo()
   479  		if err != nil {
   480  			return nil, err
   481  		}
   482  
   483  		disableSecretCreation = info.DisableSecretCreation
   484  	}
   485  
   486  	variables, err := common.CreateVariables(cmd, disableSecretCreation)
   487  	if err != nil {
   488  		return nil, err
   489  	}
   490  
   491  	executorArgs, err := cmd.Flags().GetStringArray("executor-args")
   492  	if err != nil {
   493  		return nil, err
   494  	}
   495  
   496  	mode := ""
   497  	if cmd.Flag("args-mode").Changed {
   498  		mode = cmd.Flag("args-mode").Value.String()
   499  	}
   500  	executionName := cmd.Flag("execution-name").Value.String()
   501  	envs, err := cmd.Flags().GetStringToString("env")
   502  	if err != nil {
   503  		return nil, err
   504  	}
   505  
   506  	secretEnvs, err := cmd.Flags().GetStringToString("secret-env")
   507  	if err != nil {
   508  		return nil, err
   509  	}
   510  
   511  	httpProxy := cmd.Flag("http-proxy").Value.String()
   512  	httpsProxy := cmd.Flag("https-proxy").Value.String()
   513  	image := cmd.Flag("image").Value.String()
   514  	command, err := cmd.Flags().GetStringArray("command")
   515  	if err != nil {
   516  		return nil, err
   517  	}
   518  
   519  	timeout, err := cmd.Flags().GetInt64("timeout")
   520  	if err != nil {
   521  		return nil, err
   522  	}
   523  
   524  	negativeTest, err := cmd.Flags().GetBool("negative-test")
   525  	if err != nil {
   526  		return nil, err
   527  	}
   528  
   529  	imagePullSecretNames, err := cmd.Flags().GetStringArray("image-pull-secrets")
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  
   534  	var imageSecrets []testkube.LocalObjectReference
   535  	for _, secretName := range imagePullSecretNames {
   536  		imageSecrets = append(imageSecrets, testkube.LocalObjectReference{Name: secretName})
   537  	}
   538  
   539  	jobTemplateReference := cmd.Flag("job-template-reference").Value.String()
   540  	cronJobTemplateReference := cmd.Flag("cronjob-template-reference").Value.String()
   541  	scraperTemplateReference := cmd.Flag("scraper-template-reference").Value.String()
   542  	pvcTemplateReference := cmd.Flag("pvc-template-reference").Value.String()
   543  	executionNamespace := cmd.Flag("execution-namespace").Value.String()
   544  	executePostRunScriptBeforeScraping, err := cmd.Flags().GetBool("execute-postrun-script-before-scraping")
   545  	if err != nil {
   546  		return nil, err
   547  	}
   548  	sourceScripts, err := cmd.Flags().GetBool("source-scripts")
   549  	if err != nil {
   550  		return nil, err
   551  	}
   552  
   553  	request = &testkube.ExecutionRequest{
   554  		Name:                               executionName,
   555  		Variables:                          variables,
   556  		Image:                              image,
   557  		Command:                            command,
   558  		Args:                               executorArgs,
   559  		ArgsMode:                           mode,
   560  		ImagePullSecrets:                   imageSecrets,
   561  		Envs:                               envs,
   562  		SecretEnvs:                         secretEnvs,
   563  		HttpProxy:                          httpProxy,
   564  		HttpsProxy:                         httpsProxy,
   565  		ActiveDeadlineSeconds:              timeout,
   566  		JobTemplateReference:               jobTemplateReference,
   567  		CronJobTemplateReference:           cronJobTemplateReference,
   568  		ScraperTemplateReference:           scraperTemplateReference,
   569  		PvcTemplateReference:               pvcTemplateReference,
   570  		NegativeTest:                       negativeTest,
   571  		ExecutePostRunScriptBeforeScraping: executePostRunScriptBeforeScraping,
   572  		SourceScripts:                      sourceScripts,
   573  		ExecutionNamespace:                 executionNamespace,
   574  	}
   575  
   576  	var fields = []struct {
   577  		source      string
   578  		destination *string
   579  	}{
   580  		{
   581  			cmd.Flag("job-template").Value.String(),
   582  			&request.JobTemplate,
   583  		},
   584  		{
   585  			cmd.Flag("cronjob-template").Value.String(),
   586  			&request.CronJobTemplate,
   587  		},
   588  		{
   589  			cmd.Flag("prerun-script").Value.String(),
   590  			&request.PreRunScript,
   591  		},
   592  		{
   593  			cmd.Flag("postrun-script").Value.String(),
   594  			&request.PostRunScript,
   595  		},
   596  		{
   597  			cmd.Flag("scraper-template").Value.String(),
   598  			&request.ScraperTemplate,
   599  		},
   600  		{
   601  			cmd.Flag("pvc-template").Value.String(),
   602  			&request.PvcTemplate,
   603  		},
   604  	}
   605  
   606  	for _, field := range fields {
   607  		if field.source != "" {
   608  			b, err := os.ReadFile(field.source)
   609  			if err != nil {
   610  				return nil, err
   611  			}
   612  
   613  			*field.destination = string(b)
   614  		}
   615  	}
   616  
   617  	request.EnvConfigMaps, request.EnvSecrets, err = newEnvReferencesFromFlags(cmd)
   618  	if err != nil {
   619  		return nil, err
   620  	}
   621  
   622  	request.ArtifactRequest, err = newArtifactRequestFromFlags(cmd)
   623  	if err != nil {
   624  		return nil, err
   625  	}
   626  
   627  	request.SlavePodRequest, err = newSlavePodRequestFromFlags(cmd)
   628  	if err != nil {
   629  		return nil, err
   630  	}
   631  
   632  	return request, nil
   633  }
   634  
   635  // NewUpsertTestOptionsFromFlags creates upsert test options from command flags
   636  func NewUpsertTestOptionsFromFlags(cmd *cobra.Command) (options apiclientv1.UpsertTestOptions, err error) {
   637  	content, err := newContentFromFlags(cmd)
   638  	if err != nil {
   639  		return options, fmt.Errorf("creating content from passed parameters %w", err)
   640  	}
   641  
   642  	name := cmd.Flag("name").Value.String()
   643  	file := cmd.Flag("file").Value.String()
   644  	executorType := cmd.Flag("type").Value.String()
   645  	namespace := cmd.Flag("namespace").Value.String()
   646  	description := cmd.Flag("description").Value.String()
   647  	labels, err := cmd.Flags().GetStringToString("label")
   648  	if err != nil {
   649  		return options, err
   650  	}
   651  
   652  	schedule := cmd.Flag("schedule").Value.String()
   653  	if err = validateSchedule(schedule); err != nil {
   654  		return options, err
   655  	}
   656  
   657  	copyFiles, err := cmd.Flags().GetStringArray("copy-files")
   658  	if err != nil {
   659  		return options, err
   660  	}
   661  
   662  	sourceName := ""
   663  	if cmd.Flag("source") != nil {
   664  		sourceName = cmd.Flag("source").Value.String()
   665  	}
   666  	options = apiclientv1.UpsertTestOptions{
   667  		Name:        name,
   668  		Description: description,
   669  		Type_:       executorType,
   670  		Content:     content,
   671  		Source:      sourceName,
   672  		Namespace:   namespace,
   673  		Schedule:    schedule,
   674  		Uploads:     copyFiles,
   675  		Labels:      labels,
   676  	}
   677  
   678  	options.ExecutionRequest, err = newExecutionRequestFromFlags(cmd)
   679  	if err != nil {
   680  		return options, err
   681  	}
   682  
   683  	// try to detect type if none passed
   684  	if executorType == "" {
   685  		d := detector.NewDefaultDetector()
   686  		if detectedType, ok := d.Detect(file, options); ok {
   687  			crdOnly, _ := strconv.ParseBool(cmd.Flag("crd-only").Value.String())
   688  			if !crdOnly {
   689  				ui.Info("Detected test type", detectedType)
   690  			}
   691  			options.Type_ = detectedType
   692  		}
   693  	}
   694  
   695  	if options.Type_ == "" {
   696  		return options, fmt.Errorf("can't detect executor type by passed file content (%s), please pass valid --type flag", executorType)
   697  	}
   698  
   699  	return options, nil
   700  
   701  }
   702  
   703  // readCopyFiles reads files
   704  func readCopyFiles(copyFiles []string) (map[string]string, error) {
   705  	files := map[string]string{}
   706  	for _, f := range copyFiles {
   707  		paths := strings.Split(f, ":")
   708  		if len(paths) != 2 {
   709  			return nil, fmt.Errorf("invalid file format, expecting sourcePath:destinationPath")
   710  		}
   711  		contents, err := os.ReadFile(paths[0])
   712  		if err != nil {
   713  			return nil, fmt.Errorf("could not read executor copy file: %w", err)
   714  		}
   715  		files[paths[1]] = string(contents)
   716  	}
   717  	return files, nil
   718  }
   719  
   720  // mergeCopyFiles merges the lists of files to be copied into the running test
   721  // the files set on execution overwrite the files set on test levels
   722  func mergeCopyFiles(testFiles []string, executionFiles []string) ([]string, error) {
   723  	if len(testFiles) == 0 {
   724  		return executionFiles, nil
   725  	}
   726  
   727  	if len(executionFiles) == 0 {
   728  		return testFiles, nil
   729  	}
   730  
   731  	files := map[string]string{}
   732  
   733  	for _, fileMapping := range testFiles {
   734  		fPair := strings.Split(fileMapping, ":")
   735  		if len(fPair) != 2 {
   736  			return []string{}, fmt.Errorf("invalid copy file mapping, expected source:destination, got: %s", fileMapping)
   737  		}
   738  		files[fPair[1]] = fPair[0]
   739  	}
   740  
   741  	for _, fileMapping := range executionFiles {
   742  		fPair := strings.Split(fileMapping, ":")
   743  		if len(fPair) != 2 {
   744  			return []string{}, fmt.Errorf("invalid copy file mapping, expected source:destination, got: %s", fileMapping)
   745  		}
   746  		files[fPair[1]] = fPair[0]
   747  	}
   748  
   749  	result := []string{}
   750  	for destination, source := range files {
   751  		result = append(result, fmt.Sprintf("%s:%s", source, destination))
   752  	}
   753  
   754  	return result, nil
   755  }
   756  
   757  func uploadFiles(client client.Client, parentName string, parentType client.TestingType, files []string, timeout time.Duration) error {
   758  	for _, f := range files {
   759  		paths := strings.Split(f, ":")
   760  		if len(paths) != 2 {
   761  			return fmt.Errorf("invalid file format, expecting sourcePath:destinationPath")
   762  		}
   763  		contents, err := os.ReadFile(paths[0])
   764  		if err != nil {
   765  			return fmt.Errorf("could not read file: %w", err)
   766  		}
   767  
   768  		err = client.UploadFile(parentName, parentType, paths[1], contents, timeout)
   769  		if err != nil {
   770  			return fmt.Errorf("could not upload file %s for %v with name %s: %w", paths[0], parentType, parentName, err)
   771  		}
   772  	}
   773  	return nil
   774  }
   775  
   776  // NewUpdateTestOptionsFromFlags creates update test options from command flags
   777  func NewUpdateTestOptionsFromFlags(cmd *cobra.Command) (options apiclientv1.UpdateTestOptions, err error) {
   778  	contentUpdate, err := newContentUpdateFromFlags(cmd)
   779  	if err != nil {
   780  		return options, fmt.Errorf("creating content from passed parameters %w", err)
   781  	}
   782  
   783  	if contentUpdate != nil {
   784  		options.Content = &contentUpdate
   785  	}
   786  
   787  	var fields = []struct {
   788  		name        string
   789  		destination **string
   790  	}{
   791  		{
   792  			"name",
   793  			&options.Name,
   794  		},
   795  		{
   796  			"type",
   797  			&options.Type_,
   798  		},
   799  		{
   800  			"namespace",
   801  			&options.Namespace,
   802  		},
   803  		{
   804  			"source",
   805  			&options.Source,
   806  		},
   807  		{
   808  			"description",
   809  			&options.Description,
   810  		},
   811  	}
   812  
   813  	for _, field := range fields {
   814  		if cmd.Flag(field.name).Changed {
   815  			value := cmd.Flag(field.name).Value.String()
   816  			*field.destination = &value
   817  		}
   818  	}
   819  
   820  	if cmd.Flag("schedule").Changed {
   821  		value := cmd.Flag("schedule").Value.String()
   822  		if err = validateSchedule(value); err != nil {
   823  			return options, err
   824  		}
   825  
   826  		options.Schedule = &value
   827  	}
   828  
   829  	if cmd.Flag("label").Changed {
   830  		labels, err := cmd.Flags().GetStringToString("label")
   831  		if err != nil {
   832  			return options, err
   833  		}
   834  
   835  		options.Labels = &labels
   836  	}
   837  
   838  	if cmd.Flag("copy-files").Changed {
   839  		copyFiles, err := cmd.Flags().GetStringArray("copy-files")
   840  		if err != nil {
   841  			return options, err
   842  		}
   843  
   844  		options.Uploads = &copyFiles
   845  	}
   846  
   847  	executionRequest, err := newExecutionUpdateRequestFromFlags(cmd)
   848  	if err != nil {
   849  		return options, err
   850  	}
   851  
   852  	if executionRequest != nil {
   853  		options.ExecutionRequest = &executionRequest
   854  	}
   855  
   856  	return options, nil
   857  }
   858  
   859  func newContentUpdateFromFlags(cmd *cobra.Command) (content *testkube.TestContentUpdate, err error) {
   860  	content = &testkube.TestContentUpdate{}
   861  
   862  	var fields = []struct {
   863  		name        string
   864  		destination **string
   865  	}{
   866  		{
   867  			"test-content-type",
   868  			&content.Type_,
   869  		},
   870  		{
   871  			"uri",
   872  			&content.Uri,
   873  		},
   874  	}
   875  
   876  	var nonEmpty bool
   877  	for _, field := range fields {
   878  		if cmd.Flag(field.name).Changed {
   879  			value := cmd.Flag(field.name).Value.String()
   880  			*field.destination = &value
   881  			nonEmpty = true
   882  		}
   883  	}
   884  
   885  	data, err := common.NewDataFromFlags(cmd)
   886  	if err != nil {
   887  		return nil, err
   888  	}
   889  
   890  	if data != nil {
   891  		content.Data = data
   892  		nonEmpty = true
   893  	}
   894  
   895  	repository, err := common.NewRepositoryUpdateFromFlags(cmd)
   896  	if err != nil {
   897  		return nil, err
   898  	}
   899  
   900  	if repository != nil {
   901  		content.Repository = &repository
   902  		nonEmpty = true
   903  	}
   904  
   905  	if nonEmpty {
   906  		var emptyValue string
   907  		var emptyRepository = &testkube.RepositoryUpdate{}
   908  		switch {
   909  		case content.Data != nil:
   910  			content.Repository = &emptyRepository
   911  			content.Uri = &emptyValue
   912  		case content.Repository != nil:
   913  			content.Data = &emptyValue
   914  			content.Uri = &emptyValue
   915  		case content.Uri != nil:
   916  			content.Data = &emptyValue
   917  			content.Repository = &emptyRepository
   918  		}
   919  
   920  		return content, nil
   921  	}
   922  
   923  	return nil, nil
   924  }
   925  
   926  func newExecutionUpdateRequestFromFlags(cmd *cobra.Command) (request *testkube.ExecutionUpdateRequest, err error) {
   927  	request = &testkube.ExecutionUpdateRequest{}
   928  
   929  	var fields = []struct {
   930  		name        string
   931  		destination **string
   932  	}{
   933  		{
   934  			"execution-name",
   935  			&request.Name,
   936  		},
   937  		{
   938  			"image",
   939  			&request.Image,
   940  		},
   941  		{
   942  			"http-proxy",
   943  			&request.HttpProxy,
   944  		},
   945  		{
   946  			"https-proxy",
   947  			&request.HttpsProxy,
   948  		},
   949  		{
   950  			"args-mode",
   951  			&request.ArgsMode,
   952  		},
   953  		{
   954  			"job-template-reference",
   955  			&request.JobTemplateReference,
   956  		},
   957  		{
   958  			"cronjob-template-reference",
   959  			&request.CronJobTemplateReference,
   960  		},
   961  		{
   962  			"scraper-template-reference",
   963  			&request.ScraperTemplateReference,
   964  		},
   965  		{
   966  			"pvc-template-reference",
   967  			&request.PvcTemplateReference,
   968  		},
   969  		{
   970  			"execution-namespace",
   971  			&request.ExecutionNamespace,
   972  		},
   973  	}
   974  
   975  	var nonEmpty bool
   976  	for _, field := range fields {
   977  		if cmd.Flag(field.name).Changed {
   978  			value := cmd.Flag(field.name).Value.String()
   979  			*field.destination = &value
   980  			nonEmpty = true
   981  		}
   982  	}
   983  
   984  	if cmd.Flag("variable").Changed || cmd.Flag("secret-variable").Changed || cmd.Flag("secret-variable-reference").Changed {
   985  		client, _, err := common.GetClient(cmd)
   986  		if err != nil {
   987  			return nil, err
   988  		}
   989  
   990  		info, err := client.GetServerInfo()
   991  		if err != nil {
   992  			return nil, err
   993  		}
   994  
   995  		variables, err := common.CreateVariables(cmd, info.DisableSecretCreation)
   996  		if err != nil {
   997  			return nil, err
   998  		}
   999  
  1000  		request.Variables = &variables
  1001  		nonEmpty = true
  1002  	}
  1003  
  1004  	if cmd.Flag("executor-args").Changed {
  1005  		executorArgs, err := cmd.Flags().GetStringArray("executor-args")
  1006  		if err != nil {
  1007  			return nil, err
  1008  		}
  1009  
  1010  		request.Args = &executorArgs
  1011  		nonEmpty = true
  1012  	}
  1013  
  1014  	var hashes = []struct {
  1015  		name        string
  1016  		destination **map[string]string
  1017  	}{
  1018  		{
  1019  			"env",
  1020  			&request.Envs,
  1021  		},
  1022  		{
  1023  			"secret-env",
  1024  			&request.SecretEnvs,
  1025  		},
  1026  	}
  1027  
  1028  	for _, hash := range hashes {
  1029  		if cmd.Flag(hash.name).Changed {
  1030  			value, err := cmd.Flags().GetStringToString(hash.name)
  1031  			if err != nil {
  1032  				return nil, err
  1033  			}
  1034  
  1035  			*hash.destination = &value
  1036  			nonEmpty = true
  1037  		}
  1038  	}
  1039  
  1040  	if cmd.Flag("variables-file").Changed {
  1041  		paramsFileContent := ""
  1042  		variablesFile := cmd.Flag("variables-file").Value.String()
  1043  		if variablesFile != "" {
  1044  			b, err := os.ReadFile(variablesFile)
  1045  			if err != nil {
  1046  				return nil, err
  1047  			}
  1048  
  1049  			paramsFileContent = string(b)
  1050  			request.VariablesFile = &paramsFileContent
  1051  			nonEmpty = true
  1052  		}
  1053  	}
  1054  
  1055  	if cmd.Flag("command").Changed {
  1056  		command, err := cmd.Flags().GetStringArray("command")
  1057  		if err != nil {
  1058  			return nil, err
  1059  		}
  1060  
  1061  		request.Command = &command
  1062  		nonEmpty = true
  1063  	}
  1064  
  1065  	if cmd.Flag("timeout").Changed {
  1066  		timeout, err := cmd.Flags().GetInt64("timeout")
  1067  		if err != nil {
  1068  			return nil, err
  1069  		}
  1070  
  1071  		request.ActiveDeadlineSeconds = &timeout
  1072  		nonEmpty = true
  1073  	}
  1074  
  1075  	if cmd.Flag("negative-test").Changed {
  1076  		negativeTest, err := cmd.Flags().GetBool("negative-test")
  1077  		if err != nil {
  1078  			return nil, err
  1079  		}
  1080  		request.NegativeTest = &negativeTest
  1081  		nonEmpty = true
  1082  	}
  1083  
  1084  	if cmd.Flag("image-pull-secrets").Changed {
  1085  		imagePullSecretNames, err := cmd.Flags().GetStringArray("image-pull-secrets")
  1086  		if err != nil {
  1087  			return nil, err
  1088  		}
  1089  
  1090  		var imageSecrets []testkube.LocalObjectReference
  1091  		for _, secretName := range imagePullSecretNames {
  1092  			imageSecrets = append(imageSecrets, testkube.LocalObjectReference{Name: secretName})
  1093  		}
  1094  
  1095  		request.ImagePullSecrets = &imageSecrets
  1096  		nonEmpty = true
  1097  	}
  1098  
  1099  	var values = []struct {
  1100  		source      string
  1101  		destination **string
  1102  	}{
  1103  		{
  1104  			"job-template",
  1105  			&request.JobTemplate,
  1106  		},
  1107  		{
  1108  			"cronjob-template",
  1109  			&request.CronJobTemplate,
  1110  		},
  1111  		{
  1112  			"prerun-script",
  1113  			&request.PreRunScript,
  1114  		},
  1115  		{
  1116  			"postrun-script",
  1117  			&request.PostRunScript,
  1118  		},
  1119  		{
  1120  			"scraper-template",
  1121  			&request.ScraperTemplate,
  1122  		},
  1123  		{
  1124  			"pvc-template",
  1125  			&request.PvcTemplate,
  1126  		},
  1127  	}
  1128  
  1129  	for _, value := range values {
  1130  		if cmd.Flag(value.source).Changed {
  1131  			data := ""
  1132  			name := cmd.Flag(value.source).Value.String()
  1133  			if name != "" {
  1134  				b, err := os.ReadFile(name)
  1135  				if err != nil {
  1136  					return nil, err
  1137  				}
  1138  
  1139  				data = string(b)
  1140  			}
  1141  
  1142  			*value.destination = &data
  1143  			nonEmpty = true
  1144  		}
  1145  	}
  1146  
  1147  	if cmd.Flag("mount-configmap").Changed || cmd.Flag("variable-configmap").Changed {
  1148  		envConfigMaps, _, err := newEnvReferencesFromFlags(cmd)
  1149  		if err != nil {
  1150  			return nil, err
  1151  		}
  1152  		request.EnvConfigMaps = &envConfigMaps
  1153  		nonEmpty = true
  1154  	}
  1155  
  1156  	if cmd.Flag("mount-secret").Changed || cmd.Flag("variable-secret").Changed {
  1157  		_, envSecrets, err := newEnvReferencesFromFlags(cmd)
  1158  		if err != nil {
  1159  			return nil, err
  1160  		}
  1161  		request.EnvSecrets = &envSecrets
  1162  		nonEmpty = true
  1163  	}
  1164  
  1165  	if cmd.Flag("execute-postrun-script-before-scraping").Changed {
  1166  		executePostRunScriptBeforeScraping, err := cmd.Flags().GetBool("execute-postrun-script-before-scraping")
  1167  		if err != nil {
  1168  			return nil, err
  1169  		}
  1170  		request.ExecutePostRunScriptBeforeScraping = &executePostRunScriptBeforeScraping
  1171  		nonEmpty = true
  1172  	}
  1173  
  1174  	if cmd.Flag("source-scripts").Changed {
  1175  		sourceScripts, err := cmd.Flags().GetBool("source-scripts")
  1176  		if err != nil {
  1177  			return nil, err
  1178  		}
  1179  		request.SourceScripts = &sourceScripts
  1180  		nonEmpty = true
  1181  	}
  1182  
  1183  	artifactRequest, err := newArtifactUpdateRequestFromFlags(cmd)
  1184  	if err != nil {
  1185  		return nil, err
  1186  	}
  1187  
  1188  	var emptyArtifactRequest = &testkube.ArtifactUpdateRequest{}
  1189  	if artifactRequest != nil {
  1190  		request.ArtifactRequest = &artifactRequest
  1191  		nonEmpty = true
  1192  	} else {
  1193  		request.ArtifactRequest = &emptyArtifactRequest
  1194  	}
  1195  
  1196  	slavePodRequest, err := newSlavePodUpdateRequestFromFlags(cmd)
  1197  	if err != nil {
  1198  		return nil, err
  1199  	}
  1200  
  1201  	var emptyPodRequest = &testkube.PodUpdateRequest{}
  1202  	if slavePodRequest != nil {
  1203  		request.SlavePodRequest = &slavePodRequest
  1204  		nonEmpty = true
  1205  	} else {
  1206  		request.SlavePodRequest = &emptyPodRequest
  1207  	}
  1208  
  1209  	if nonEmpty {
  1210  		return request, nil
  1211  	}
  1212  
  1213  	return nil, nil
  1214  }
  1215  
  1216  func newArtifactUpdateRequestFromFlags(cmd *cobra.Command) (request *testkube.ArtifactUpdateRequest, err error) {
  1217  	request = &testkube.ArtifactUpdateRequest{}
  1218  
  1219  	var fields = []struct {
  1220  		name        string
  1221  		destination **string
  1222  	}{
  1223  		{
  1224  			"artifact-storage-class-name",
  1225  			&request.StorageClassName,
  1226  		},
  1227  		{
  1228  			"artifact-volume-mount-path",
  1229  			&request.VolumeMountPath,
  1230  		},
  1231  		{
  1232  			"artifact-storage-bucket",
  1233  			&request.StorageBucket,
  1234  		},
  1235  	}
  1236  
  1237  	var nonEmpty bool
  1238  	for _, field := range fields {
  1239  		if cmd.Flag(field.name).Changed {
  1240  			value := cmd.Flag(field.name).Value.String()
  1241  			*field.destination = &value
  1242  			nonEmpty = true
  1243  		}
  1244  	}
  1245  
  1246  	if cmd.Flag("artifact-dir").Changed {
  1247  		dirs, err := cmd.Flags().GetStringArray("artifact-dir")
  1248  		if err != nil {
  1249  			return nil, err
  1250  		}
  1251  
  1252  		request.Dirs = &dirs
  1253  		nonEmpty = true
  1254  	}
  1255  
  1256  	if cmd.Flag("artifact-mask").Changed {
  1257  		masks, err := cmd.Flags().GetStringArray("artifact-mask")
  1258  		if err != nil {
  1259  			return nil, err
  1260  		}
  1261  
  1262  		request.Masks = &masks
  1263  		nonEmpty = true
  1264  	}
  1265  
  1266  	if cmd.Flag("artifact-omit-folder-per-execution").Changed {
  1267  		value, err := cmd.Flags().GetBool("artifact-omit-folder-per-execution")
  1268  		if err != nil {
  1269  			return nil, err
  1270  		}
  1271  
  1272  		request.OmitFolderPerExecution = &value
  1273  		nonEmpty = true
  1274  	}
  1275  
  1276  	if cmd.Flag("artifact-shared-between-pods").Changed {
  1277  		value, err := cmd.Flags().GetBool("artifact-shared-between-pods")
  1278  		if err != nil {
  1279  			return nil, err
  1280  		}
  1281  
  1282  		request.SharedBetweenPods = &value
  1283  		nonEmpty = true
  1284  	}
  1285  
  1286  	if cmd.Flag("artifact-use-default-storage-class-name").Changed {
  1287  		value, err := cmd.Flags().GetBool("artifact-use-default-storage-class-name")
  1288  		if err != nil {
  1289  			return nil, err
  1290  		}
  1291  
  1292  		request.UseDefaultStorageClassName = &value
  1293  		nonEmpty = true
  1294  	}
  1295  
  1296  	if nonEmpty {
  1297  		return request, nil
  1298  	}
  1299  
  1300  	return nil, nil
  1301  }
  1302  
  1303  func newSlavePodUpdateRequestFromFlags(cmd *cobra.Command) (request *testkube.PodUpdateRequest, err error) {
  1304  	var nonEmpty bool
  1305  	request = &testkube.PodUpdateRequest{}
  1306  	if cmd.Flag("slave-pod-template-reference").Changed {
  1307  		value := cmd.Flag("slave-pod-template-reference").Value.String()
  1308  		request.PodTemplateReference = &value
  1309  		nonEmpty = true
  1310  	}
  1311  
  1312  	if cmd.Flag("slave-pod-template").Changed {
  1313  		value := cmd.Flag("slave-pod-template").Value.String()
  1314  		b, err := os.ReadFile(value)
  1315  		if err != nil {
  1316  			return nil, err
  1317  		}
  1318  
  1319  		data := string(b)
  1320  		request.PodTemplate = &data
  1321  		nonEmpty = true
  1322  	}
  1323  
  1324  	if cmd.Flag("slave-pod-requests-cpu").Changed {
  1325  		value := cmd.Flag("slave-pod-requests-cpu").Value.String()
  1326  		if request.Resources == nil {
  1327  			data := &testkube.PodResourcesUpdateRequest{}
  1328  			request.Resources = &data
  1329  		}
  1330  
  1331  		if (*request.Resources).Requests == nil {
  1332  			(*request.Resources).Requests = &testkube.ResourceUpdateRequest{}
  1333  		}
  1334  
  1335  		(*(*request.Resources).Requests).Cpu = &value
  1336  		nonEmpty = true
  1337  	}
  1338  
  1339  	if cmd.Flag("slave-pod-requests-memory").Changed {
  1340  		value := cmd.Flag("slave-pod-requests-memory").Value.String()
  1341  		if request.Resources == nil {
  1342  			data := &testkube.PodResourcesUpdateRequest{}
  1343  			request.Resources = &data
  1344  		}
  1345  
  1346  		if (*request.Resources).Requests == nil {
  1347  			(*request.Resources).Requests = &testkube.ResourceUpdateRequest{}
  1348  		}
  1349  
  1350  		(*(*request.Resources).Requests).Memory = &value
  1351  		nonEmpty = true
  1352  	}
  1353  
  1354  	if cmd.Flag("slave-pod-limits-cpu").Changed {
  1355  		value := cmd.Flag("slave-pod-limits-cpu").Value.String()
  1356  		if request.Resources == nil {
  1357  			data := &testkube.PodResourcesUpdateRequest{}
  1358  			request.Resources = &data
  1359  		}
  1360  
  1361  		if (*request.Resources).Limits == nil {
  1362  			(*request.Resources).Limits = &testkube.ResourceUpdateRequest{}
  1363  		}
  1364  
  1365  		(*(*request.Resources).Limits).Cpu = &value
  1366  		nonEmpty = true
  1367  	}
  1368  
  1369  	if cmd.Flag("slave-pod-limits-memory").Changed {
  1370  		value := cmd.Flag("slave-pod-limits-memory").Value.String()
  1371  		if request.Resources == nil {
  1372  			data := &testkube.PodResourcesUpdateRequest{}
  1373  			request.Resources = &data
  1374  		}
  1375  
  1376  		if (*request.Resources).Limits == nil {
  1377  			(*request.Resources).Limits = &testkube.ResourceUpdateRequest{}
  1378  		}
  1379  
  1380  		(*(*request.Resources).Limits).Memory = &value
  1381  		nonEmpty = true
  1382  	}
  1383  
  1384  	if nonEmpty {
  1385  		return request, nil
  1386  	}
  1387  
  1388  	return nil, nil
  1389  }
  1390  
  1391  func validateSchedule(schedule string) error {
  1392  	if schedule == "" {
  1393  		return nil
  1394  	}
  1395  
  1396  	specParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
  1397  	if _, err := specParser.Parse(schedule); err != nil {
  1398  		return err
  1399  	}
  1400  
  1401  	return nil
  1402  }
  1403  
  1404  // isFileTooBigForCLI checks the file size found on path and compares it with maxArgSize
  1405  func isFileTooBigForCLI(path string) (bool, error) {
  1406  	f, err := os.Open(path)
  1407  	if err != nil {
  1408  		return false, fmt.Errorf("could not open file %s: %w", path, err)
  1409  	}
  1410  	defer func() {
  1411  		err := f.Close()
  1412  		if err != nil {
  1413  			output.PrintLog(fmt.Sprintf("%s could not close file %s: %v", ui.IconWarning, f.Name(), err))
  1414  		}
  1415  	}()
  1416  
  1417  	fileInfo, err := f.Stat()
  1418  	if err != nil {
  1419  		return false, fmt.Errorf("could not get info on file %s: %w", path, err)
  1420  	}
  1421  
  1422  	return fileInfo.Size() < maxArgSize, nil
  1423  }
  1424  
  1425  // PrepareVariablesFile reads variables file, or if the file size is too big
  1426  // it uploads them
  1427  func PrepareVariablesFile(client client.Client, parentName string, parentType client.TestingType, filePath string, timeout time.Duration) (string, bool, error) {
  1428  	isFileSmall, err := isFileTooBigForCLI(filePath)
  1429  	if err != nil {
  1430  		return "", false, fmt.Errorf("could not determine if variables file %s needs to be uploaded: %w", filePath, err)
  1431  	}
  1432  
  1433  	b, err := os.ReadFile(filePath)
  1434  	if err != nil {
  1435  		return "", false, fmt.Errorf("could not read file %s: %w", filePath, err)
  1436  	}
  1437  	if isFileSmall {
  1438  		return string(b), false, nil
  1439  	}
  1440  
  1441  	fileName := path.Base(filePath)
  1442  
  1443  	err = client.UploadFile(parentName, parentType, fileName, b, timeout)
  1444  	if err != nil {
  1445  		return "", false, fmt.Errorf("could not upload variables file for %v with name %s: %w", parentType, parentName, err)
  1446  	}
  1447  	return fileName, true, nil
  1448  }