github.com/secure-build/gitlab-runner@v12.5.0+incompatible/common/build.go (about) 1 package common 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/url" 8 "os" 9 "path" 10 "path/filepath" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/sirupsen/logrus" 16 17 "gitlab.com/gitlab-org/gitlab-runner/helpers" 18 "gitlab.com/gitlab-org/gitlab-runner/helpers/featureflags" 19 "gitlab.com/gitlab-org/gitlab-runner/helpers/tls" 20 "gitlab.com/gitlab-org/gitlab-runner/session" 21 "gitlab.com/gitlab-org/gitlab-runner/session/proxy" 22 "gitlab.com/gitlab-org/gitlab-runner/session/terminal" 23 ) 24 25 type GitStrategy int 26 27 const ( 28 GitClone GitStrategy = iota 29 GitFetch 30 GitNone 31 ) 32 33 const ( 34 gitCleanFlagsDefault = "-ffdx" 35 gitCleanFlagsNone = "none" 36 ) 37 38 type SubmoduleStrategy int 39 40 const ( 41 SubmoduleInvalid SubmoduleStrategy = iota 42 SubmoduleNone 43 SubmoduleNormal 44 SubmoduleRecursive 45 ) 46 47 type BuildRuntimeState string 48 49 const ( 50 BuildRunStatePending BuildRuntimeState = "pending" 51 BuildRunRuntimeRunning BuildRuntimeState = "running" 52 BuildRunRuntimeFinished BuildRuntimeState = "finished" 53 BuildRunRuntimeCanceled BuildRuntimeState = "canceled" 54 BuildRunRuntimeTerminated BuildRuntimeState = "terminated" 55 BuildRunRuntimeTimedout BuildRuntimeState = "timedout" 56 ) 57 58 type BuildStage string 59 60 const ( 61 BuildStagePrepareExecutor BuildStage = "prepare_executor" 62 BuildStagePrepare BuildStage = "prepare_script" 63 BuildStageGetSources BuildStage = "get_sources" 64 BuildStageRestoreCache BuildStage = "restore_cache" 65 BuildStageDownloadArtifacts BuildStage = "download_artifacts" 66 BuildStageUserScript BuildStage = "build_script" 67 BuildStageAfterScript BuildStage = "after_script" 68 BuildStageArchiveCache BuildStage = "archive_cache" 69 BuildStageUploadOnSuccessArtifacts BuildStage = "upload_artifacts_on_success" 70 BuildStageUploadOnFailureArtifacts BuildStage = "upload_artifacts_on_failure" 71 ) 72 73 type Build struct { 74 JobResponse `yaml:",inline"` 75 76 SystemInterrupt chan os.Signal `json:"-" yaml:"-"` 77 RootDir string `json:"-" yaml:"-"` 78 BuildDir string `json:"-" yaml:"-"` 79 CacheDir string `json:"-" yaml:"-"` 80 Hostname string `json:"-" yaml:"-"` 81 Runner *RunnerConfig `json:"runner"` 82 ExecutorData ExecutorData 83 ExecutorFeatures FeaturesInfo `json:"-" yaml:"-"` 84 85 // Unique ID for all running builds on this runner 86 RunnerID int `json:"runner_id"` 87 88 // Unique ID for all running builds on this runner and this project 89 ProjectRunnerID int `json:"project_runner_id"` 90 91 CurrentStage BuildStage 92 CurrentState BuildRuntimeState 93 94 Session *session.Session 95 96 executorStageResolver func() ExecutorStage 97 logger BuildLogger 98 allVariables JobVariables 99 100 createdAt time.Time 101 } 102 103 func (b *Build) Log() *logrus.Entry { 104 return b.Runner.Log().WithField("job", b.ID).WithField("project", b.JobInfo.ProjectID) 105 } 106 107 func (b *Build) ProjectUniqueName() string { 108 return fmt.Sprintf("runner-%s-project-%d-concurrent-%d", 109 b.Runner.ShortDescription(), b.JobInfo.ProjectID, b.ProjectRunnerID) 110 } 111 112 func (b *Build) ProjectSlug() (string, error) { 113 url, err := url.Parse(b.GitInfo.RepoURL) 114 if err != nil { 115 return "", err 116 } 117 if url.Host == "" { 118 return "", errors.New("only URI reference supported") 119 } 120 121 slug := url.Path 122 slug = strings.TrimSuffix(slug, ".git") 123 slug = path.Clean(slug) 124 if slug == "." { 125 return "", errors.New("invalid path") 126 } 127 if strings.Contains(slug, "..") { 128 return "", errors.New("it doesn't look like a valid path") 129 } 130 return slug, nil 131 } 132 133 func (b *Build) ProjectUniqueDir(sharedDir bool) string { 134 dir, err := b.ProjectSlug() 135 if err != nil { 136 dir = fmt.Sprintf("project-%d", b.JobInfo.ProjectID) 137 } 138 139 // for shared dirs path is constructed like this: 140 // <some-path>/runner-short-id/concurrent-id/group-name/project-name/ 141 // ex.<some-path>/01234567/0/group/repo/ 142 if sharedDir { 143 dir = path.Join( 144 fmt.Sprintf("%s", b.Runner.ShortDescription()), 145 fmt.Sprintf("%d", b.ProjectRunnerID), 146 dir, 147 ) 148 } 149 return dir 150 } 151 152 func (b *Build) FullProjectDir() string { 153 return helpers.ToSlash(b.BuildDir) 154 } 155 156 func (b *Build) TmpProjectDir() string { 157 return helpers.ToSlash(b.BuildDir) + ".tmp" 158 } 159 160 func (b *Build) getCustomBuildDir(rootDir, overrideKey string, customBuildDirEnabled, sharedDir bool) (string, error) { 161 dir := b.GetAllVariables().Get(overrideKey) 162 if dir == "" { 163 return path.Join(rootDir, b.ProjectUniqueDir(sharedDir)), nil 164 } 165 166 if !customBuildDirEnabled { 167 return "", MakeBuildError("setting %s is not allowed, enable `custom_build_dir` feature", overrideKey) 168 } 169 170 if !strings.HasPrefix(dir, rootDir) { 171 return "", MakeBuildError("the %s=%q has to be within %q", 172 overrideKey, dir, rootDir) 173 } 174 175 return dir, nil 176 } 177 178 func (b *Build) StartBuild(rootDir, cacheDir string, customBuildDirEnabled, sharedDir bool) error { 179 if rootDir == "" { 180 return MakeBuildError("the builds_dir is not configured") 181 } 182 183 if cacheDir == "" { 184 return MakeBuildError("the cache_dir is not configured") 185 } 186 187 // We set RootDir and invalidate variables 188 // to be able to use CI_BUILDS_DIR 189 b.RootDir = rootDir 190 b.CacheDir = path.Join(cacheDir, b.ProjectUniqueDir(false)) 191 b.refreshAllVariables() 192 193 var err error 194 b.BuildDir, err = b.getCustomBuildDir(b.RootDir, "GIT_CLONE_PATH", customBuildDirEnabled, sharedDir) 195 if err != nil { 196 return err 197 } 198 199 // We invalidate variables to be able to use 200 // CI_CACHE_DIR and CI_PROJECT_DIR 201 b.refreshAllVariables() 202 return nil 203 } 204 205 func (b *Build) executeStage(ctx context.Context, buildStage BuildStage, executor Executor) error { 206 b.CurrentStage = buildStage 207 208 b.Log().WithField("build_stage", buildStage).Debug("Executing build stage") 209 210 shell := executor.Shell() 211 if shell == nil { 212 return errors.New("No shell defined") 213 } 214 215 script, err := GenerateShellScript(buildStage, *shell) 216 if err != nil { 217 return err 218 } 219 220 // Nothing to execute 221 if script == "" { 222 return nil 223 } 224 225 cmd := ExecutorCommand{ 226 Context: ctx, 227 Script: script, 228 Stage: buildStage, 229 } 230 231 switch buildStage { 232 case BuildStageUserScript, BuildStageAfterScript: // use custom build environment 233 cmd.Predefined = false 234 default: // all other stages use a predefined build environment 235 cmd.Predefined = true 236 } 237 238 section := helpers.BuildSection{ 239 Name: string(buildStage), 240 SkipMetrics: !b.JobResponse.Features.TraceSections, 241 Run: func() error { return executor.Run(cmd) }, 242 } 243 return section.Execute(&b.logger) 244 } 245 246 func (b *Build) executeUploadArtifacts(ctx context.Context, state error, executor Executor) (err error) { 247 if state == nil { 248 return b.executeStage(ctx, BuildStageUploadOnSuccessArtifacts, executor) 249 } 250 251 return b.executeStage(ctx, BuildStageUploadOnFailureArtifacts, executor) 252 } 253 254 func (b *Build) executeScript(ctx context.Context, executor Executor) error { 255 // Prepare stage 256 err := b.executeStage(ctx, BuildStagePrepare, executor) 257 258 if err == nil { 259 err = b.attemptExecuteStage(ctx, BuildStageGetSources, executor, b.GetGetSourcesAttempts()) 260 } 261 if err == nil { 262 err = b.attemptExecuteStage(ctx, BuildStageRestoreCache, executor, b.GetRestoreCacheAttempts()) 263 } 264 if err == nil { 265 err = b.attemptExecuteStage(ctx, BuildStageDownloadArtifacts, executor, b.GetDownloadArtifactsAttempts()) 266 } 267 268 if err == nil { 269 // Execute user build script (before_script + script) 270 err = b.executeStage(ctx, BuildStageUserScript, executor) 271 272 // Execute after script (after_script) 273 timeoutContext, timeoutCancel := context.WithTimeout(ctx, AfterScriptTimeout) 274 defer timeoutCancel() 275 276 b.executeStage(timeoutContext, BuildStageAfterScript, executor) 277 } 278 279 // Execute post script (cache store, artifacts upload) 280 if err == nil { 281 err = b.executeStage(ctx, BuildStageArchiveCache, executor) 282 } 283 284 uploadError := b.executeUploadArtifacts(ctx, err, executor) 285 286 // Use job's error as most important 287 if err != nil { 288 return err 289 } 290 291 // Otherwise, use uploadError 292 return uploadError 293 } 294 295 func (b *Build) attemptExecuteStage(ctx context.Context, buildStage BuildStage, executor Executor, attempts int) (err error) { 296 if attempts < 1 || attempts > 10 { 297 return fmt.Errorf("Number of attempts out of the range [1, 10] for stage: %s", buildStage) 298 } 299 for attempt := 0; attempt < attempts; attempt++ { 300 if err = b.executeStage(ctx, buildStage, executor); err == nil { 301 return 302 } 303 } 304 return 305 } 306 307 func (b *Build) GetBuildTimeout() time.Duration { 308 buildTimeout := b.RunnerInfo.Timeout 309 if buildTimeout <= 0 { 310 buildTimeout = DefaultTimeout 311 } 312 return time.Duration(buildTimeout) * time.Second 313 } 314 315 func (b *Build) handleError(err error) error { 316 switch err { 317 case context.Canceled: 318 b.CurrentState = BuildRunRuntimeCanceled 319 return &BuildError{Inner: errors.New("canceled")} 320 321 case context.DeadlineExceeded: 322 b.CurrentState = BuildRunRuntimeTimedout 323 return &BuildError{ 324 Inner: fmt.Errorf("execution took longer than %v seconds", b.GetBuildTimeout()), 325 FailureReason: JobExecutionTimeout, 326 } 327 328 default: 329 b.CurrentState = BuildRunRuntimeFinished 330 return err 331 } 332 } 333 334 func (b *Build) run(ctx context.Context, executor Executor) (err error) { 335 b.CurrentState = BuildRunRuntimeRunning 336 337 buildFinish := make(chan error, 1) 338 339 runContext, runCancel := context.WithCancel(context.Background()) 340 defer runCancel() 341 342 if term, ok := executor.(terminal.InteractiveTerminal); b.Session != nil && ok { 343 b.Session.SetInteractiveTerminal(term) 344 } 345 346 if proxyPooler, ok := executor.(proxy.Pooler); b.Session != nil && ok { 347 b.Session.SetProxyPool(proxyPooler) 348 } 349 350 // Run build script 351 go func() { 352 buildFinish <- b.executeScript(runContext, executor) 353 }() 354 355 // Wait for signals: cancel, timeout, abort or finish 356 b.Log().Debugln("Waiting for signals...") 357 select { 358 case <-ctx.Done(): 359 err = b.handleError(ctx.Err()) 360 361 case signal := <-b.SystemInterrupt: 362 err = fmt.Errorf("aborted: %v", signal) 363 b.CurrentState = BuildRunRuntimeTerminated 364 365 case err = <-buildFinish: 366 b.CurrentState = BuildRunRuntimeFinished 367 return err 368 } 369 370 b.Log().WithError(err).Debugln("Waiting for build to finish...") 371 372 // Wait till we receive that build did finish 373 runCancel() 374 b.waitForBuildFinish(buildFinish, WaitForBuildFinishTimeout) 375 376 return err 377 } 378 379 // waitForBuildFinish will wait for the build to finish or timeout, whichever 380 // comes first. This is to prevent issues where something in the build can't be 381 // killed or processed and results into the Job running until the GitLab Runner 382 // process exists. 383 func (b *Build) waitForBuildFinish(buildFinish <-chan error, timeout time.Duration) { 384 select { 385 case <-buildFinish: 386 return 387 case <-time.After(timeout): 388 b.logger.Warningln("Timed out waiting for the build to finish") 389 return 390 } 391 } 392 393 func (b *Build) retryCreateExecutor(options ExecutorPrepareOptions, provider ExecutorProvider, logger BuildLogger) (executor Executor, err error) { 394 for tries := 0; tries < PreparationRetries; tries++ { 395 executor = provider.Create() 396 if executor == nil { 397 err = errors.New("failed to create executor") 398 return 399 } 400 401 b.executorStageResolver = executor.GetCurrentStage 402 403 err = executor.Prepare(options) 404 if err == nil { 405 break 406 } 407 executor.Cleanup() 408 executor = nil 409 if _, ok := err.(*BuildError); ok { 410 break 411 } else if options.Context.Err() != nil { 412 return nil, b.handleError(options.Context.Err()) 413 } 414 415 logger.SoftErrorln("Preparation failed:", err) 416 logger.Infoln("Will be retried in", PreparationRetryInterval, "...") 417 time.Sleep(PreparationRetryInterval) 418 } 419 return 420 } 421 422 func (b *Build) waitForTerminal(ctx context.Context, timeout time.Duration) error { 423 if b.Session == nil || !b.Session.Connected() { 424 return nil 425 } 426 427 timeout = b.getTerminalTimeout(ctx, timeout) 428 429 b.logger.Infoln( 430 fmt.Sprintf( 431 "Terminal is connected, will time out in %s...", 432 // TODO: switch to timeout.Round(time.Second) after upgrading to Go 1.9+ 433 roundDuration(timeout, time.Second), 434 ), 435 ) 436 437 select { 438 case <-ctx.Done(): 439 err := b.Session.Kill() 440 if err != nil { 441 b.Log().WithError(err).Warn("Failed to kill session") 442 } 443 return errors.New("build cancelled, killing session") 444 case <-time.After(timeout): 445 err := fmt.Errorf( 446 "Terminal session timed out (maximum time allowed - %s)", 447 // TODO: switch to timeout.Round(time.Second) after upgrading to Go 1.9+ 448 roundDuration(timeout, time.Second), 449 ) 450 b.logger.Infoln(err.Error()) 451 b.Session.TimeoutCh <- err 452 return err 453 case err := <-b.Session.DisconnectCh: 454 b.logger.Infoln("Terminal disconnected") 455 return fmt.Errorf("terminal disconnected: %v", err) 456 case signal := <-b.SystemInterrupt: 457 b.logger.Infoln("Terminal disconnected") 458 err := b.Session.Kill() 459 if err != nil { 460 b.Log().WithError(err).Warn("Failed to kill session") 461 } 462 return fmt.Errorf("terminal disconnected by system signal: %v", signal) 463 } 464 } 465 466 // getTerminalTimeout checks if the the job timeout comes before the 467 // configured terminal timeout. 468 func (b *Build) getTerminalTimeout(ctx context.Context, timeout time.Duration) time.Duration { 469 expiryTime, _ := ctx.Deadline() 470 471 if expiryTime.Before(time.Now().Add(timeout)) { 472 timeout = expiryTime.Sub(time.Now()) 473 } 474 475 return timeout 476 } 477 478 func (b *Build) setTraceStatus(trace JobTrace, err error) { 479 logger := b.logger.WithFields(logrus.Fields{ 480 "duration": b.Duration(), 481 }) 482 483 if err == nil { 484 logger.Infoln("Job succeeded") 485 trace.Success() 486 487 return 488 } 489 490 if buildError, ok := err.(*BuildError); ok { 491 logger.SoftErrorln("Job failed:", err) 492 493 failureReason := buildError.FailureReason 494 if failureReason == "" { 495 failureReason = ScriptFailure 496 } 497 498 trace.Fail(err, failureReason) 499 500 return 501 } 502 503 logger.Errorln("Job failed (system failure):", err) 504 trace.Fail(err, RunnerSystemFailure) 505 } 506 507 func (b *Build) CurrentExecutorStage() ExecutorStage { 508 if b.executorStageResolver == nil { 509 b.executorStageResolver = func() ExecutorStage { 510 return ExecutorStage("") 511 } 512 } 513 514 return b.executorStageResolver() 515 } 516 517 func (b *Build) Run(globalConfig *Config, trace JobTrace) (err error) { 518 var executor Executor 519 520 b.logger = NewBuildLogger(trace, b.Log()) 521 b.logger.Println("Running with", AppVersion.Line()) 522 if b.Runner != nil && b.Runner.ShortDescription() != "" { 523 b.logger.Println(" on", b.Runner.Name, b.Runner.ShortDescription()) 524 } 525 526 b.CurrentState = BuildRunStatePending 527 528 defer func() { 529 b.setTraceStatus(trace, err) 530 531 if executor != nil { 532 executor.Cleanup() 533 } 534 }() 535 536 ctx, cancel := context.WithTimeout(context.Background(), b.GetBuildTimeout()) 537 defer cancel() 538 539 trace.SetCancelFunc(cancel) 540 trace.SetMasked(b.GetAllVariables().Masked()) 541 542 options := ExecutorPrepareOptions{ 543 Config: b.Runner, 544 Build: b, 545 Trace: trace, 546 User: globalConfig.User, 547 Context: ctx, 548 } 549 550 provider := GetExecutor(b.Runner.Executor) 551 if provider == nil { 552 return errors.New("executor not found") 553 } 554 555 provider.GetFeatures(&b.ExecutorFeatures) 556 557 section := helpers.BuildSection{ 558 Name: string(BuildStagePrepareExecutor), 559 SkipMetrics: !b.JobResponse.Features.TraceSections, 560 Run: func() error { 561 executor, err = b.retryCreateExecutor(options, provider, b.logger) 562 return err 563 }, 564 } 565 err = section.Execute(&b.logger) 566 567 if err == nil { 568 err = b.run(ctx, executor) 569 if err := b.waitForTerminal(ctx, globalConfig.SessionServer.GetSessionTimeout()); err != nil { 570 b.Log().WithError(err).Debug("Stopped waiting for terminal") 571 } 572 } 573 574 if executor != nil { 575 executor.Finish(err) 576 } 577 578 return err 579 } 580 581 func (b *Build) String() string { 582 return helpers.ToYAML(b) 583 } 584 585 func (b *Build) GetDefaultVariables() JobVariables { 586 return JobVariables{ 587 {Key: "CI_BUILDS_DIR", Value: filepath.FromSlash(b.RootDir), Public: true, Internal: true, File: false}, 588 {Key: "CI_PROJECT_DIR", Value: filepath.FromSlash(b.FullProjectDir()), Public: true, Internal: true, File: false}, 589 {Key: "CI_CONCURRENT_ID", Value: strconv.Itoa(b.RunnerID), Public: true, Internal: true, File: false}, 590 {Key: "CI_CONCURRENT_PROJECT_ID", Value: strconv.Itoa(b.ProjectRunnerID), Public: true, Internal: true, File: false}, 591 {Key: "CI_SERVER", Value: "yes", Public: true, Internal: true, File: false}, 592 } 593 } 594 595 func (b *Build) GetDefaultFeatureFlagsVariables() JobVariables { 596 variables := make(JobVariables, 0) 597 for _, featureFlag := range featureflags.GetAll() { 598 variables = append(variables, JobVariable{ 599 Key: featureFlag.Name, 600 Value: featureFlag.DefaultValue, 601 Public: true, 602 Internal: true, 603 File: false, 604 }) 605 } 606 607 return variables 608 } 609 610 func (b *Build) GetSharedEnvVariable() JobVariable { 611 env := JobVariable{Value: "true", Public: true, Internal: true, File: false} 612 if b.IsSharedEnv() { 613 env.Key = "CI_SHARED_ENVIRONMENT" 614 } else { 615 env.Key = "CI_DISPOSABLE_ENVIRONMENT" 616 } 617 618 return env 619 } 620 621 func (b *Build) GetTLSVariables(caFile, certFile, keyFile string) JobVariables { 622 variables := JobVariables{} 623 624 if b.TLSCAChain != "" { 625 variables = append(variables, JobVariable{ 626 Key: caFile, 627 Value: b.TLSCAChain, 628 Public: true, 629 Internal: true, 630 File: true, 631 }) 632 } 633 634 if b.TLSAuthCert != "" && b.TLSAuthKey != "" { 635 variables = append(variables, JobVariable{ 636 Key: certFile, 637 Value: b.TLSAuthCert, 638 Public: true, 639 Internal: true, 640 File: true, 641 }) 642 643 variables = append(variables, JobVariable{ 644 Key: keyFile, 645 Value: b.TLSAuthKey, 646 Internal: true, 647 File: true, 648 }) 649 } 650 651 return variables 652 } 653 654 func (b *Build) GetCITLSVariables() JobVariables { 655 return b.GetTLSVariables(tls.VariableCAFile, tls.VariableCertFile, tls.VariableKeyFile) 656 } 657 658 func (b *Build) GetGitTLSVariables() JobVariables { 659 return b.GetTLSVariables("GIT_SSL_CAINFO", "GIT_SSL_CERT", "GIT_SSL_KEY") 660 } 661 662 func (b *Build) IsSharedEnv() bool { 663 return b.ExecutorFeatures.Shared 664 } 665 666 func (b *Build) refreshAllVariables() { 667 b.allVariables = nil 668 } 669 670 func (b *Build) GetAllVariables() JobVariables { 671 if b.allVariables != nil { 672 return b.allVariables 673 } 674 675 variables := make(JobVariables, 0) 676 variables = append(variables, b.GetDefaultFeatureFlagsVariables()...) 677 if b.Runner != nil { 678 variables = append(variables, b.Runner.GetVariables()...) 679 } 680 variables = append(variables, b.GetDefaultVariables()...) 681 variables = append(variables, b.GetCITLSVariables()...) 682 variables = append(variables, b.Variables...) 683 variables = append(variables, b.GetSharedEnvVariable()) 684 variables = append(variables, AppVersion.Variables()...) 685 686 b.allVariables = variables.Expand() 687 return b.allVariables 688 } 689 690 // GetRemoteURL checks if the default clone URL is overwritten by the runner 691 // configuration option: 'CloneURL'. If it is, we use that to create the clone 692 // URL. 693 func (b *Build) GetRemoteURL() string { 694 cloneURL := strings.TrimRight(b.Runner.CloneURL, "/") 695 696 if !strings.HasPrefix(cloneURL, "http") { 697 return b.GitInfo.RepoURL 698 } 699 700 variables := b.GetAllVariables() 701 ciJobToken := variables.Get("CI_JOB_TOKEN") 702 ciProjectPath := variables.Get("CI_PROJECT_PATH") 703 704 splits := strings.SplitAfterN(cloneURL, "://", 2) 705 706 return fmt.Sprintf("%sgitlab-ci-token:%s@%s/%s.git", splits[0], ciJobToken, splits[1], ciProjectPath) 707 } 708 709 func (b *Build) GetGitStrategy() GitStrategy { 710 switch b.GetAllVariables().Get("GIT_STRATEGY") { 711 case "clone": 712 return GitClone 713 714 case "fetch": 715 return GitFetch 716 717 case "none": 718 return GitNone 719 720 default: 721 if b.AllowGitFetch { 722 return GitFetch 723 } 724 725 return GitClone 726 } 727 } 728 729 func (b *Build) GetGitCheckout() bool { 730 if b.GetGitStrategy() == GitNone { 731 return false 732 } 733 734 strCheckout := b.GetAllVariables().Get("GIT_CHECKOUT") 735 if len(strCheckout) == 0 { 736 return true 737 } 738 739 checkout, err := strconv.ParseBool(strCheckout) 740 if err != nil { 741 return true 742 } 743 return checkout 744 } 745 746 func (b *Build) GetSubmoduleStrategy() SubmoduleStrategy { 747 if b.GetGitStrategy() == GitNone { 748 return SubmoduleNone 749 } 750 switch b.GetAllVariables().Get("GIT_SUBMODULE_STRATEGY") { 751 case "normal": 752 return SubmoduleNormal 753 754 case "recursive": 755 return SubmoduleRecursive 756 757 case "none", "": 758 // Default (legacy) behavior is to not update/init submodules 759 return SubmoduleNone 760 761 default: 762 // Will cause an error in AbstractShell) writeSubmoduleUpdateCmds 763 return SubmoduleInvalid 764 } 765 } 766 767 func (b *Build) GetGitCleanFlags() []string { 768 flags := b.GetAllVariables().Get("GIT_CLEAN_FLAGS") 769 if flags == "" { 770 flags = gitCleanFlagsDefault 771 } 772 773 if flags == gitCleanFlagsNone { 774 return []string{} 775 } 776 777 return strings.Fields(flags) 778 } 779 780 func (b *Build) IsDebugTraceEnabled() bool { 781 trace, err := strconv.ParseBool(b.GetAllVariables().Get("CI_DEBUG_TRACE")) 782 if err != nil { 783 trace = false 784 } 785 786 if b.Runner.DebugTraceDisabled { 787 if trace == true { 788 b.logger.Warningln("CI_DEBUG_TRACE usage is disabled on this Runner") 789 } 790 791 return false 792 } 793 794 return trace 795 } 796 797 func (b *Build) GetDockerAuthConfig() string { 798 return b.GetAllVariables().Get("DOCKER_AUTH_CONFIG") 799 } 800 801 func (b *Build) GetGetSourcesAttempts() int { 802 retries, err := strconv.Atoi(b.GetAllVariables().Get("GET_SOURCES_ATTEMPTS")) 803 if err != nil { 804 return DefaultGetSourcesAttempts 805 } 806 return retries 807 } 808 809 func (b *Build) GetDownloadArtifactsAttempts() int { 810 retries, err := strconv.Atoi(b.GetAllVariables().Get("ARTIFACT_DOWNLOAD_ATTEMPTS")) 811 if err != nil { 812 return DefaultArtifactDownloadAttempts 813 } 814 return retries 815 } 816 817 func (b *Build) GetRestoreCacheAttempts() int { 818 retries, err := strconv.Atoi(b.GetAllVariables().Get("RESTORE_CACHE_ATTEMPTS")) 819 if err != nil { 820 return DefaultRestoreCacheAttempts 821 } 822 return retries 823 } 824 825 func (b *Build) GetCacheRequestTimeout() int { 826 timeout, err := strconv.Atoi(b.GetAllVariables().Get("CACHE_REQUEST_TIMEOUT")) 827 if err != nil { 828 return DefaultCacheRequestTimeout 829 } 830 return timeout 831 } 832 833 func (b *Build) Duration() time.Duration { 834 return time.Since(b.createdAt) 835 } 836 837 func NewBuild(jobData JobResponse, runnerConfig *RunnerConfig, systemInterrupt chan os.Signal, executorData ExecutorData) (*Build, error) { 838 // Attempt to perform a deep copy of the RunnerConfig 839 runnerConfigCopy, err := runnerConfig.DeepCopy() 840 if err != nil { 841 return nil, fmt.Errorf("deep copy of runner config failed: %v", err) 842 } 843 844 return &Build{ 845 JobResponse: jobData, 846 Runner: runnerConfigCopy, 847 SystemInterrupt: systemInterrupt, 848 ExecutorData: executorData, 849 createdAt: time.Now(), 850 }, nil 851 } 852 853 func (b *Build) IsFeatureFlagOn(name string) bool { 854 value := b.GetAllVariables().Get(name) 855 856 on, err := featureflags.IsOn(value) 857 if err != nil { 858 logrus.WithError(err). 859 WithField("name", name). 860 WithField("value", value). 861 Error("Error while parsing the value of feature flag") 862 863 return false 864 } 865 866 return on 867 } 868 869 func (b *Build) IsLFSSmudgeDisabled() bool { 870 disabled, err := strconv.ParseBool(b.GetAllVariables().Get("GIT_LFS_SKIP_SMUDGE")) 871 if err != nil { 872 return false 873 } 874 875 return disabled 876 }