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  }