github.com/kubeshop/testkube@v1.17.23/pkg/scheduler/test_scheduler.go (about) 1 package scheduler 2 3 import ( 4 "context" 5 "fmt" 6 "path/filepath" 7 "strings" 8 9 "github.com/pkg/errors" 10 v1 "k8s.io/api/core/v1" 11 12 testsv3 "github.com/kubeshop/testkube-operator/api/tests/v3" 13 testsourcev1 "github.com/kubeshop/testkube-operator/api/testsource/v1" 14 "github.com/kubeshop/testkube-operator/pkg/secret" 15 "github.com/kubeshop/testkube/internal/common" 16 "github.com/kubeshop/testkube/pkg/api/v1/testkube" 17 "github.com/kubeshop/testkube/pkg/executor" 18 "github.com/kubeshop/testkube/pkg/executor/client" 19 "github.com/kubeshop/testkube/pkg/logs/events" 20 testsmapper "github.com/kubeshop/testkube/pkg/mapper/tests" 21 "github.com/kubeshop/testkube/pkg/tcl/checktcl" 22 "github.com/kubeshop/testkube/pkg/tcl/schedulertcl" 23 "github.com/kubeshop/testkube/pkg/workerpool" 24 ) 25 26 const ( 27 containerType = "container" 28 gitCredentialPrefix = "git_credential_" 29 ) 30 31 func (s *Scheduler) PrepareTestRequests(work []testsv3.Test, request testkube.ExecutionRequest) []workerpool.Request[ 32 testkube.Test, testkube.ExecutionRequest, testkube.Execution] { 33 requests := make([]workerpool.Request[testkube.Test, testkube.ExecutionRequest, testkube.Execution], len(work)) 34 for i := range work { 35 requests[i] = workerpool.Request[testkube.Test, testkube.ExecutionRequest, testkube.Execution]{ 36 Object: testsmapper.MapTestCRToAPI(work[i]), 37 Options: request, 38 ExecFn: s.executeTest, 39 } 40 } 41 42 return requests 43 } 44 45 func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request testkube.ExecutionRequest) ( 46 execution testkube.Execution, err error) { 47 // generate random execution name in case there is no one set 48 // like for docker images 49 if request.Name == "" && test.ExecutionRequest != nil && test.ExecutionRequest.Name != "" { 50 request.Name = test.ExecutionRequest.Name 51 } 52 53 request.Number = s.getNextExecutionNumber(test.Name) 54 if request.Name == "" { 55 request.Name = fmt.Sprintf("%s-%d", test.Name, request.Number) 56 } 57 58 if request.TestSuiteName != "" { 59 request.Name = fmt.Sprintf("%s-%d", request.Name, request.Number) 60 } 61 62 // test name + test execution name should be unique 63 execution, _ = s.testResults.GetByNameAndTest(ctx, request.Name, test.Name) 64 65 if execution.Name == request.Name { 66 err := errors.Errorf("test execution with name %s already exists", request.Name) 67 return s.handleExecutionError(ctx, execution, "duplicate execution: %w", err) 68 } 69 70 secretUUID, err := s.testsClient.GetCurrentSecretUUID(test.Name) 71 if err != nil { 72 return s.handleExecutionError(ctx, execution, "can't get current secret uuid: %w", err) 73 } 74 75 request.TestSecretUUID = secretUUID 76 // merge available data into execution options test spec, executor spec, request, test id 77 options, err := s.getExecuteOptions(test.Namespace, test.Name, request) 78 if err != nil { 79 return s.handleExecutionError(ctx, execution, "can't get execute options: %w", err) 80 } 81 82 // store execution in storage, can be fetched from API now 83 execution, err = newExecutionFromExecutionOptions(&s.subscriptionChecker, options) 84 if err != nil { 85 return s.handleExecutionError(ctx, execution, "can't get new execution: %w", err) 86 } 87 88 options.ID = execution.Id 89 90 s.events.Notify(testkube.NewEventStartTest(&execution)) 91 92 if err := s.createSecretsReferences(&execution, &options); err != nil { 93 return s.handleExecutionError(ctx, execution, "can't create secret variables `Secret` references: %w", err) 94 } 95 96 err = s.testResults.Insert(ctx, execution) 97 if err != nil { 98 return s.handleExecutionError(ctx, execution, "can't create new test execution, can't insert into storage: %w", err) 99 } 100 101 s.logger.Infow("calling executor with options", "executionId", execution.Id, "options", options.Request) 102 103 execution.Start() 104 105 // update storage with current execution status 106 err = s.testResults.StartExecution(ctx, execution.Id, execution.StartTime) 107 if err != nil { 108 return s.handleExecutionError(ctx, execution, "can't execute test, can't insert into storage error: %w", err) 109 } 110 111 // sync/async test execution 112 result, err := s.startTestExecution(ctx, options, &execution) 113 114 // set execution result to one created 115 execution.ExecutionResult = result 116 117 // update storage with current execution status 118 if uerr := s.testResults.UpdateResult(ctx, execution.Id, execution); uerr != nil { 119 return s.handleExecutionError(ctx, execution, "update execution error: %w", err) 120 } 121 122 if err != nil { 123 return s.handleExecutionError(ctx, execution, "test execution failed: %w", err) 124 } 125 126 s.logger.Infow("test started", "executionId", execution.Id, "status", execution.ExecutionResult.Status) 127 128 s.handleExecutionStart(ctx, execution) 129 130 return execution, nil 131 } 132 133 func (s *Scheduler) handleExecutionStart(ctx context.Context, execution testkube.Execution) { 134 // pass here all needed execution data to the log 135 if s.featureFlags.LogsV2 { 136 137 l := events.NewLog(fmt.Sprintf("starting execution %s (%s)", execution.Name, execution.Id)). 138 WithType("execution-config"). 139 WithVersion(events.LogVersionV2). 140 WithSource("test-scheduler"). 141 WithMetadataEntry("command", strings.Join(execution.Command, " ")). 142 WithMetadataEntry("argsmode", execution.ArgsMode). 143 WithMetadataEntry("args", strings.Join(execution.Args, " ")). 144 WithMetadataEntry("pre-run", execution.PreRunScript). 145 WithMetadataEntry("post-run", execution.PostRunScript) 146 147 s.logsStream.Push(ctx, execution.Id, l) 148 } 149 } 150 151 func (s *Scheduler) handleExecutionError(ctx context.Context, execution testkube.Execution, msgTpl string, err error) (testkube.Execution, error) { 152 // push error log to the log stream if logs v2 enabled 153 if s.featureFlags.LogsV2 { 154 l := events.NewLog(fmt.Sprintf(msgTpl, err)). 155 WithType("error"). 156 WithVersion(events.LogVersionV2). 157 WithSource("test-scheduler") 158 159 s.logsStream.Push(ctx, execution.Id, l) 160 161 } 162 163 // notify events that execution failed 164 s.events.Notify(testkube.NewEventEndTestFailed(&execution)) 165 166 return execution.Errw(execution.Id, msgTpl, err), nil 167 } 168 169 func (s *Scheduler) startTestExecution(ctx context.Context, options client.ExecuteOptions, execution *testkube.Execution) (result *testkube.ExecutionResult, err error) { 170 executor := s.getExecutor(options.TestName) 171 return executor.Execute(ctx, execution, options) 172 } 173 174 func (s *Scheduler) getExecutor(testName string) client.Executor { 175 testCR, err := s.testsClient.Get(testName) 176 if err != nil { 177 s.logger.Errorw("can't get test", "test", testName, "error", err) 178 return s.executor 179 } 180 181 executorCR, err := s.executorsClient.GetByType(testCR.Spec.Type_) 182 if err != nil { 183 s.logger.Errorw("can't get executor", "test", testName, "error", err) 184 return s.executor 185 } 186 187 switch executorCR.Spec.ExecutorType { 188 case containerType: 189 return s.containerExecutor 190 default: 191 return s.executor 192 } 193 } 194 195 func (s *Scheduler) getNextExecutionNumber(testName string) int32 { 196 number, err := s.testResults.GetNextExecutionNumber(context.Background(), testName) 197 if err != nil { 198 s.logger.Errorw("retrieving latest execution", "error", err) 199 return number 200 } 201 202 return number 203 } 204 205 // createSecretsReferences strips secrets from text and store it inside model as reference to secret 206 func (s *Scheduler) createSecretsReferences(execution *testkube.Execution, options *client.ExecuteOptions) (err error) { 207 secrets := map[string]string{} 208 secretName := execution.Id + "-vars" 209 210 for k, v := range execution.Variables { 211 if v.IsSecret() { 212 obfuscated := execution.Variables[k] 213 if v.SecretRef != nil { 214 obfuscated.SecretRef = &testkube.SecretRef{ 215 Namespace: execution.TestNamespace, 216 Name: v.SecretRef.Name, 217 Key: v.SecretRef.Key, 218 } 219 } else { 220 obfuscated.Value = "" 221 obfuscated.SecretRef = &testkube.SecretRef{ 222 Namespace: execution.TestNamespace, 223 Name: secretName, 224 Key: v.Name, 225 } 226 227 secrets[v.Name] = v.Value 228 } 229 230 execution.Variables[k] = obfuscated 231 } 232 } 233 234 secretRefs := []*testkube.SecretRef{options.UsernameSecret, options.TokenSecret} 235 for _, secretRef := range secretRefs { 236 if secretRef == nil { 237 continue 238 } 239 240 if execution.TestNamespace == s.namespace || (secretRef.Name != secret.GetMetadataName(execution.TestName, client.SecretTest) && 241 secretRef.Name != secret.GetMetadataName(execution.TestName, client.SecretSource)) { 242 continue 243 } 244 245 data, err := s.secretClient.Get(secretRef.Name) 246 if err != nil { 247 return err 248 } 249 250 value, ok := data[secretRef.Key] 251 if !ok { 252 return fmt.Errorf("secret key %s not found for secret %s", secretRef.Key, secretRef.Name) 253 } 254 255 secrets[gitCredentialPrefix+secretRef.Key] = value 256 secretRef.Name = secretName 257 secretRef.Key = gitCredentialPrefix + secretRef.Key 258 } 259 260 labels := map[string]string{"executionID": execution.Id, "testName": execution.TestName} 261 262 if len(secrets) > 0 { 263 return s.secretClient.Create( 264 secretName, 265 labels, 266 secrets, 267 execution.TestNamespace, 268 ) 269 } 270 271 return nil 272 } 273 274 func newExecutionFromExecutionOptions(subscriptionChecker *checktcl.SubscriptionChecker, options client.ExecuteOptions) (testkube.Execution, error) { 275 execution := testkube.NewExecution( 276 options.Request.Id, 277 options.Namespace, 278 options.TestName, 279 options.Request.TestSuiteName, 280 options.Request.Name, 281 options.TestSpec.Type_, 282 int(options.Request.Number), 283 testsmapper.MapTestContentFromSpec(options.TestSpec.Content), 284 *testkube.NewRunningExecutionResult(), 285 options.Request.Variables, 286 options.Request.TestSecretUUID, 287 options.Request.TestSuiteSecretUUID, 288 common.MergeMaps(options.Labels, options.Request.ExecutionLabels), 289 ) 290 291 execution.Envs = options.Request.Envs 292 execution.Command = options.Request.Command 293 execution.Args = options.Request.Args 294 execution.IsVariablesFileUploaded = options.Request.IsVariablesFileUploaded 295 execution.VariablesFile = options.Request.VariablesFile 296 execution.Uploads = options.Request.Uploads 297 execution.BucketName = options.Request.BucketName 298 execution.ArtifactRequest = options.Request.ArtifactRequest 299 execution.PreRunScript = options.Request.PreRunScript 300 execution.PostRunScript = options.Request.PostRunScript 301 execution.ExecutePostRunScriptBeforeScraping = options.Request.ExecutePostRunScriptBeforeScraping 302 execution.SourceScripts = options.Request.SourceScripts 303 execution.RunningContext = options.Request.RunningContext 304 execution.TestExecutionName = options.Request.TestExecutionName 305 execution.DownloadArtifactExecutionIDs = options.Request.DownloadArtifactExecutionIDs 306 execution.DownloadArtifactTestNames = options.Request.DownloadArtifactTestNames 307 execution.SlavePodRequest = options.Request.SlavePodRequest 308 309 // Pro edition only (tcl protected code) 310 if schedulertcl.HasExecutionNamespace(&options.Request) { 311 if err := subscriptionChecker.IsActiveOrgPlanEnterpriseForFeature("execution namespace"); err != nil { 312 return execution, err 313 } 314 315 execution = schedulertcl.NewExecutionFromExecutionOptions(options.Request, execution) 316 } 317 318 return execution, nil 319 } 320 321 func (s *Scheduler) getExecuteOptions(namespace, id string, request testkube.ExecutionRequest) (options client.ExecuteOptions, err error) { 322 // get test content from kubernetes CRs 323 testCR, err := s.testsClient.Get(id) 324 if err != nil { 325 return options, errors.Errorf("can't get test custom resource %v", err) 326 } 327 328 if testCR.Spec.Source != "" { 329 testSourceCR, err := s.testSourcesClient.Get(testCR.Spec.Source) 330 if err != nil { 331 return options, errors.Errorf("cannot get test source custom resource: %v", err) 332 } 333 334 testCR.Spec = mergeContents(testCR.Spec, testSourceCR.Spec) 335 336 if testSourceCR.Spec.Type_ == "" && testSourceCR.Spec.Repository.Type_ == "git" { 337 testCR.Spec.Content.Type_ = testsv3.TestContentType(testkube.TestContentTypeGit) 338 } 339 } 340 341 if request.ContentRequest != nil { 342 testCR.Spec = adjustContent(testCR.Spec, request.ContentRequest) 343 } 344 345 test := testsmapper.MapTestCRToAPI(*testCR) 346 347 request.Namespace = namespace 348 if test.ExecutionRequest != nil { 349 // Test variables lowest priority, then test suite, then test suite execution / test execution 350 request.Variables = mergeVariables(test.ExecutionRequest.Variables, request.Variables) 351 352 request.Envs = mergeEnvs(request.Envs, test.ExecutionRequest.Envs) 353 request.SecretEnvs = mergeEnvs(request.SecretEnvs, test.ExecutionRequest.SecretEnvs) 354 request.EnvConfigMaps = mergeEnvReferences(request.EnvConfigMaps, test.ExecutionRequest.EnvConfigMaps) 355 request.EnvSecrets = mergeEnvReferences(request.EnvSecrets, test.ExecutionRequest.EnvSecrets) 356 357 if request.VariablesFile == "" && test.ExecutionRequest.VariablesFile != "" { 358 request.VariablesFile = test.ExecutionRequest.VariablesFile 359 request.IsVariablesFileUploaded = test.ExecutionRequest.IsVariablesFileUploaded 360 } 361 362 var fields = []struct { 363 source string 364 destination *string 365 }{ 366 { 367 test.ExecutionRequest.HttpProxy, 368 &request.HttpProxy, 369 }, 370 { 371 test.ExecutionRequest.HttpsProxy, 372 &request.HttpsProxy, 373 }, 374 { 375 test.ExecutionRequest.JobTemplate, 376 &request.JobTemplate, 377 }, 378 { 379 test.ExecutionRequest.JobTemplateReference, 380 &request.JobTemplateReference, 381 }, 382 { 383 test.ExecutionRequest.PreRunScript, 384 &request.PreRunScript, 385 }, 386 { 387 test.ExecutionRequest.PostRunScript, 388 &request.PostRunScript, 389 }, 390 { 391 test.ExecutionRequest.ScraperTemplate, 392 &request.ScraperTemplate, 393 }, 394 { 395 test.ExecutionRequest.ScraperTemplateReference, 396 &request.ScraperTemplateReference, 397 }, 398 { 399 test.ExecutionRequest.PvcTemplate, 400 &request.PvcTemplate, 401 }, 402 { 403 test.ExecutionRequest.PvcTemplateReference, 404 &request.PvcTemplateReference, 405 }, 406 { 407 test.ExecutionRequest.ArgsMode, 408 &request.ArgsMode, 409 }, 410 } 411 412 for _, field := range fields { 413 if *field.destination == "" && field.source != "" { 414 *field.destination = field.source 415 } 416 } 417 418 // Combine test executor args with execution args 419 if len(request.Command) == 0 { 420 request.Command = test.ExecutionRequest.Command 421 } 422 423 if len(request.Args) == 0 { 424 request.Args = test.ExecutionRequest.Args 425 } 426 427 if request.ActiveDeadlineSeconds == 0 && test.ExecutionRequest.ActiveDeadlineSeconds != 0 { 428 request.ActiveDeadlineSeconds = test.ExecutionRequest.ActiveDeadlineSeconds 429 } 430 431 if !request.ExecutePostRunScriptBeforeScraping && test.ExecutionRequest.ExecutePostRunScriptBeforeScraping { 432 request.ExecutePostRunScriptBeforeScraping = test.ExecutionRequest.ExecutePostRunScriptBeforeScraping 433 } 434 435 if !request.SourceScripts && test.ExecutionRequest.SourceScripts { 436 request.SourceScripts = test.ExecutionRequest.SourceScripts 437 } 438 439 request.ArtifactRequest = mergeArtifacts(request.ArtifactRequest, test.ExecutionRequest.ArtifactRequest) 440 if request.ArtifactRequest != nil && request.ArtifactRequest.VolumeMountPath == "" { 441 request.ArtifactRequest.VolumeMountPath = filepath.Join(executor.VolumeDir, "artifacts") 442 } 443 444 request.SlavePodRequest = mergeSlavePodRequests(request.SlavePodRequest, test.ExecutionRequest.SlavePodRequest) 445 s.logger.Infow("checking for negative test change", "test", test.Name, "negativeTest", request.NegativeTest, "isNegativeTestChangedOnRun", request.IsNegativeTestChangedOnRun) 446 if !request.IsNegativeTestChangedOnRun { 447 s.logger.Infow("setting negative test from test definition", "test", test.Name, "negativeTest", test.ExecutionRequest.NegativeTest) 448 request.NegativeTest = test.ExecutionRequest.NegativeTest 449 } 450 451 // Pro edition only (tcl protected code) 452 if schedulertcl.HasExecutionNamespace(test.ExecutionRequest) { 453 if err = s.subscriptionChecker.IsActiveOrgPlanEnterpriseForFeature("execution namespace"); err != nil { 454 return options, err 455 } 456 457 request = schedulertcl.GetExecuteOptions(test.ExecutionRequest, request) 458 } 459 } 460 461 // get executor from kubernetes CRs 462 executorCR, err := s.executorsClient.GetByType(testCR.Spec.Type_) 463 if err != nil { 464 return options, errors.Errorf("can't get executor spec: %v", err) 465 } 466 467 var usernameSecret, tokenSecret *testkube.SecretRef 468 var certificateSecret string 469 if test.Content != nil && test.Content.Repository != nil { 470 usernameSecret = test.Content.Repository.UsernameSecret 471 tokenSecret = test.Content.Repository.TokenSecret 472 certificateSecret = test.Content.Repository.CertificateSecret 473 } 474 475 var imagePullSecrets []string 476 477 if len(executorCR.Spec.ImagePullSecrets) != 0 { 478 imagePullSecrets = mapK8sImagePullSecrets(executorCR.Spec.ImagePullSecrets) 479 } 480 481 if testCR.Spec.ExecutionRequest != nil && 482 len(testCR.Spec.ExecutionRequest.ImagePullSecrets) != 0 { 483 imagePullSecrets = mapK8sImagePullSecrets(testCR.Spec.ExecutionRequest.ImagePullSecrets) 484 } 485 486 if len(request.ImagePullSecrets) != 0 { 487 imagePullSecrets = mapImagePullSecrets(request.ImagePullSecrets) 488 } 489 490 configMapVars := make(map[string]testkube.Variable, 0) 491 for _, configMap := range request.EnvConfigMaps { 492 if configMap.Reference == nil || !configMap.MapToVariables { 493 continue 494 } 495 496 data, err := s.configMapClient.Get(context.Background(), configMap.Reference.Name, request.Namespace) 497 if err != nil { 498 return options, errors.Errorf("can't get config map: %v", err) 499 } 500 501 for key := range data { 502 configMapVars[key] = testkube.NewConfigMapVariableReference(key, configMap.Reference.Name, key) 503 } 504 } 505 506 if len(configMapVars) != 0 { 507 request.Variables = mergeVariables(configMapVars, request.Variables) 508 } 509 510 secretVars := make(map[string]testkube.Variable, 0) 511 for _, secret := range request.EnvSecrets { 512 if secret.Reference == nil || !secret.MapToVariables { 513 continue 514 } 515 516 data, err := s.secretClient.Get(secret.Reference.Name, request.Namespace) 517 if err != nil { 518 return options, errors.Errorf("can't get secret: %v", err) 519 } 520 521 for key := range data { 522 secretVars[key] = testkube.NewSecretVariableReference(key, secret.Reference.Name, key) 523 } 524 } 525 526 if len(secretVars) != 0 { 527 request.Variables = mergeVariables(secretVars, request.Variables) 528 } 529 530 if len(request.Command) == 0 { 531 request.Command = executorCR.Spec.Command 532 } 533 534 if request.ArgsMode == string(testkube.ArgsModeTypeAppend) || request.ArgsMode == "" { 535 request.Args = append(executorCR.Spec.Args, request.Args...) 536 } 537 538 if executorCR.Spec.UseDataDirAsWorkingDir { 539 if testCR.Spec.Content.Repository != nil && testCR.Spec.Content.Repository.WorkingDir == "" { 540 if executorCR.Spec.ExecutorType == containerType { 541 testCR.Spec.Content.Repository.WorkingDir = filepath.Join(executor.VolumeDir, "repo") 542 } else { 543 testCR.Spec.Content.Repository.WorkingDir = "/" 544 } 545 } 546 } 547 548 return client.ExecuteOptions{ 549 TestName: id, 550 Namespace: request.Namespace, 551 TestSpec: testCR.Spec, 552 ExecutorName: executorCR.ObjectMeta.Name, 553 ExecutorSpec: executorCR.Spec, 554 Request: request, 555 Sync: request.Sync, 556 Labels: testCR.Labels, 557 UsernameSecret: usernameSecret, 558 TokenSecret: tokenSecret, 559 RunnerCustomCASecret: s.runnerCustomCASecret, 560 CertificateSecret: certificateSecret, 561 AgentAPITLSSecret: s.agentAPITLSSecret, 562 ImagePullSecretNames: imagePullSecrets, 563 Features: s.featureFlags, 564 }, nil 565 } 566 567 func mergeVariables(vars1 map[string]testkube.Variable, vars2 map[string]testkube.Variable) map[string]testkube.Variable { 568 variables := map[string]testkube.Variable{} 569 for k, v := range vars1 { 570 variables[k] = v 571 } 572 573 for k, v := range vars2 { 574 variables[k] = v 575 } 576 577 return variables 578 } 579 580 func mergeEnvs(envs1 map[string]string, envs2 map[string]string) map[string]string { 581 envs := map[string]string{} 582 for k, v := range envs1 { 583 envs[k] = v 584 } 585 586 for k, v := range envs2 { 587 envs[k] = v 588 } 589 590 return envs 591 } 592 593 func mergeContents(test testsv3.TestSpec, testSource testsourcev1.TestSourceSpec) testsv3.TestSpec { 594 if test.Content == nil { 595 test.Content = &testsv3.TestContent{} 596 } 597 598 if test.Content.Type_ == "" { 599 test.Content.Type_ = testsv3.TestContentType(testSource.Type_) 600 } 601 602 if test.Content.Data == "" { 603 test.Content.Data = testSource.Data 604 } 605 606 if test.Content.Uri == "" { 607 test.Content.Uri = testSource.Uri 608 } 609 610 if testSource.Repository != nil { 611 if test.Content.Repository == nil { 612 test.Content.Repository = &testsv3.Repository{} 613 } 614 615 if test.Content.Repository.UsernameSecret == nil && testSource.Repository.UsernameSecret != nil { 616 test.Content.Repository.UsernameSecret = &testsv3.SecretRef{ 617 Name: testSource.Repository.UsernameSecret.Name, 618 Key: testSource.Repository.UsernameSecret.Key, 619 } 620 } 621 622 if test.Content.Repository.TokenSecret == nil && testSource.Repository.TokenSecret != nil { 623 test.Content.Repository.TokenSecret = &testsv3.SecretRef{ 624 Name: testSource.Repository.TokenSecret.Name, 625 Key: testSource.Repository.TokenSecret.Key, 626 } 627 } 628 629 if test.Content.Repository.AuthType == "" { 630 test.Content.Repository.AuthType = testsv3.GitAuthType(testSource.Repository.AuthType) 631 } 632 633 var fields = []struct { 634 source string 635 destination *string 636 }{ 637 { 638 testSource.Repository.Type_, 639 &test.Content.Repository.Type_, 640 }, 641 { 642 testSource.Repository.Uri, 643 &test.Content.Repository.Uri, 644 }, 645 { 646 testSource.Repository.Branch, 647 &test.Content.Repository.Branch, 648 }, 649 { 650 testSource.Repository.Commit, 651 &test.Content.Repository.Commit, 652 }, 653 { 654 testSource.Repository.Path, 655 &test.Content.Repository.Path, 656 }, 657 { 658 testSource.Repository.WorkingDir, 659 &test.Content.Repository.WorkingDir, 660 }, 661 { 662 testSource.Repository.CertificateSecret, 663 &test.Content.Repository.CertificateSecret, 664 }, 665 } 666 667 for _, field := range fields { 668 if *field.destination == "" { 669 *field.destination = field.source 670 } 671 } 672 } 673 674 return test 675 } 676 677 // TODO: generics 678 func mapImagePullSecrets(secrets []testkube.LocalObjectReference) []string { 679 var res []string 680 for _, secret := range secrets { 681 res = append(res, secret.Name) 682 } 683 684 return res 685 } 686 687 func mapK8sImagePullSecrets(secrets []v1.LocalObjectReference) []string { 688 var res []string 689 for _, secret := range secrets { 690 res = append(res, secret.Name) 691 } 692 693 return res 694 } 695 696 func mergeArtifacts(artifactBase *testkube.ArtifactRequest, artifactAdjust *testkube.ArtifactRequest) *testkube.ArtifactRequest { 697 switch { 698 case artifactBase == nil && artifactAdjust == nil: 699 return nil 700 case artifactBase == nil && artifactAdjust != nil: 701 return artifactAdjust 702 case artifactBase != nil && artifactAdjust == nil: 703 return artifactBase 704 default: 705 artifactBase.Dirs = append(artifactBase.Dirs, artifactAdjust.Dirs...) 706 artifactBase.Masks = append(artifactBase.Masks, artifactAdjust.Masks...) 707 708 if !artifactBase.OmitFolderPerExecution && artifactAdjust.OmitFolderPerExecution { 709 artifactBase.OmitFolderPerExecution = artifactAdjust.OmitFolderPerExecution 710 } 711 712 if !artifactBase.SharedBetweenPods && artifactAdjust.SharedBetweenPods { 713 artifactBase.SharedBetweenPods = artifactAdjust.SharedBetweenPods 714 } 715 716 if !artifactBase.UseDefaultStorageClassName && artifactAdjust.UseDefaultStorageClassName { 717 artifactBase.UseDefaultStorageClassName = artifactAdjust.UseDefaultStorageClassName 718 } 719 720 var fields = []struct { 721 source string 722 destination *string 723 }{ 724 { 725 artifactAdjust.StorageClassName, 726 &artifactBase.StorageClassName, 727 }, 728 { 729 artifactAdjust.VolumeMountPath, 730 &artifactBase.VolumeMountPath, 731 }, 732 { 733 artifactAdjust.StorageBucket, 734 &artifactBase.StorageBucket, 735 }, 736 } 737 738 for _, field := range fields { 739 if *field.destination == "" && field.source != "" { 740 *field.destination = field.source 741 } 742 } 743 } 744 745 return artifactBase 746 } 747 748 func adjustContent(test testsv3.TestSpec, content *testkube.TestContentRequest) testsv3.TestSpec { 749 if test.Content == nil { 750 return test 751 } 752 753 switch testkube.TestContentType(test.Content.Type_) { 754 case testkube.TestContentTypeGitFile, testkube.TestContentTypeGitDir, testkube.TestContentTypeGit: 755 if test.Content.Repository == nil { 756 return test 757 } 758 759 if content.Repository != nil { 760 var fields = []struct { 761 source string 762 destination *string 763 }{ 764 { 765 content.Repository.Branch, 766 &test.Content.Repository.Branch, 767 }, 768 { 769 content.Repository.Commit, 770 &test.Content.Repository.Commit, 771 }, 772 { 773 content.Repository.Path, 774 &test.Content.Repository.Path, 775 }, 776 { 777 content.Repository.WorkingDir, 778 &test.Content.Repository.WorkingDir, 779 }, 780 } 781 782 for _, field := range fields { 783 if field.source != "" { 784 *field.destination = field.source 785 } 786 } 787 } 788 } 789 790 return test 791 } 792 793 func mergeEnvReferences(envs1 []testkube.EnvReference, envs2 []testkube.EnvReference) []testkube.EnvReference { 794 envs := make(map[string]testkube.EnvReference, 0) 795 for i := range envs1 { 796 if envs1[i].Reference == nil { 797 continue 798 } 799 800 envs[envs1[i].Reference.Name] = envs1[i] 801 } 802 803 for i := range envs2 { 804 if envs2[i].Reference == nil { 805 continue 806 } 807 808 if value, ok := envs[envs2[i].Reference.Name]; !ok { 809 envs[envs2[i].Reference.Name] = envs2[i] 810 } else { 811 if !value.Mount { 812 value.Mount = envs2[i].Mount 813 } 814 815 if value.MountPath == "" { 816 value.MountPath = envs2[i].MountPath 817 } 818 819 if !value.MapToVariables { 820 value.MapToVariables = envs2[i].MapToVariables 821 } 822 823 envs[envs2[i].Reference.Name] = value 824 } 825 } 826 827 res := make([]testkube.EnvReference, 0) 828 for key := range envs { 829 res = append(res, envs[key]) 830 } 831 832 return res 833 } 834 835 func mergeSlavePodRequests(podBase *testkube.PodRequest, podAdjust *testkube.PodRequest) *testkube.PodRequest { 836 switch { 837 case podBase == nil && podAdjust == nil: 838 return nil 839 case podBase == nil && podAdjust != nil: 840 return podAdjust 841 case podBase != nil && podAdjust == nil: 842 return podBase 843 default: 844 var fields = []struct { 845 source string 846 destination *string 847 }{ 848 { 849 podAdjust.PodTemplate, 850 &podBase.PodTemplate, 851 }, 852 { 853 podAdjust.PodTemplateReference, 854 &podBase.PodTemplateReference, 855 }, 856 } 857 858 for _, field := range fields { 859 if *field.destination == "" && field.source != "" { 860 *field.destination = field.source 861 } 862 } 863 864 if podBase.Resources == nil && podAdjust.Resources != nil { 865 podBase.Resources = podAdjust.Resources 866 return podBase 867 } 868 869 if podBase.Resources != nil && podAdjust.Resources != nil { 870 if podBase.Resources.Requests == nil && podAdjust.Resources.Requests != nil { 871 podBase.Resources.Requests = podAdjust.Resources.Requests 872 } else if podBase.Resources.Requests != nil && podAdjust.Resources.Requests != nil { 873 if podBase.Resources.Requests.Cpu == "" && podAdjust.Resources.Requests.Cpu != "" { 874 podBase.Resources.Requests.Cpu = podAdjust.Resources.Requests.Cpu 875 } 876 877 if podBase.Resources.Requests.Memory == "" && podAdjust.Resources.Requests.Memory != "" { 878 podBase.Resources.Requests.Memory = podAdjust.Resources.Requests.Memory 879 } 880 } 881 882 if podBase.Resources.Limits == nil && podAdjust.Resources.Limits != nil { 883 podBase.Resources.Limits = podAdjust.Resources.Limits 884 } else if podBase.Resources.Limits != nil && podAdjust.Resources.Limits != nil { 885 if podBase.Resources.Limits.Cpu == "" && podAdjust.Resources.Limits.Cpu != "" { 886 podBase.Resources.Limits.Cpu = podAdjust.Resources.Limits.Cpu 887 } 888 889 if podBase.Resources.Limits.Memory == "" && podAdjust.Resources.Limits.Memory != "" { 890 podBase.Resources.Limits.Memory = podAdjust.Resources.Limits.Memory 891 } 892 } 893 } 894 895 } 896 897 return podBase 898 }