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 = ©Files 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 = ¶msFileContent 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 }