in common/build.go [518:625]
func (b *Build) executeScript(ctx context.Context, trace JobTrace, executor Executor) error {
// track job start and create referees
startTime := time.Now()
b.createReferees(executor)
// Prepare stage
err := b.executeStage(ctx, BuildStagePrepare, executor)
if err != nil {
return asRunnerSystemFailure(fmt.Errorf(
"prepare environment: %w. "+
"Check https://docs.gitlab.com/runner/shells/#shell-profile-loading for more information", err))
}
err = asRunnerSystemFailure(b.attemptExecuteStage(ctx, BuildStageGetSources, executor, b.GetGetSourcesAttempts(), func(attempt int) error {
if attempt == 1 {
// If GetSources fails we delete all tracked and untracked files. This is
// because Git's submodule support has various bugs that cause fetches to
// fail if submodules have changed.
return b.executeStage(ctx, BuildStageClearWorktree, executor)
}
return nil
}))
if err == nil {
err = asRunnerSystemFailure(b.attemptExecuteStage(ctx, BuildStageRestoreCache, executor, b.GetRestoreCacheAttempts(), nil))
}
if err == nil {
err = asRunnerSystemFailure(b.attemptExecuteStage(ctx, BuildStageDownloadArtifacts, executor, b.GetDownloadArtifactsAttempts(), nil))
}
//nolint:nestif
if err == nil {
timeouts := b.getStageTimeoutContexts(ctx,
stageTimeout{"RUNNER_SCRIPT_TIMEOUT", 0},
stageTimeout{"RUNNER_AFTER_SCRIPT_TIMEOUT", AfterScriptTimeout})
scriptCtx, cancel := timeouts["RUNNER_SCRIPT_TIMEOUT"]()
defer cancel()
// update trace's cancel function so that the main script can be cancelled,
// with after_script and later stages to still complete.
trace.SetCancelFunc(cancel)
for _, s := range b.Steps {
// after_script has a separate BuildStage. See common.BuildStageAfterScript
if s.Name == StepNameAfterScript {
continue
}
err = b.executeStage(scriptCtx, StepToBuildStage(s), executor)
if err != nil {
break
}
}
switch {
// if parent context is fine but script context was cancelled we ensure the build error
// failure reason is "canceled".
case ctx.Err() == nil && errors.Is(scriptCtx.Err(), context.Canceled):
err = &BuildError{
Inner: ErrJobCanceled,
FailureReason: JobCanceled,
}
b.logger.Warningln("script canceled externally (UI, API)")
// If the parent context reached deadline, don't do anything different than usual.
// If the script context reached deadline, return the deadline error.
case !errors.Is(ctx.Err(), context.DeadlineExceeded) && errors.Is(scriptCtx.Err(), context.DeadlineExceeded):
err = &BuildError{
Inner: fmt.Errorf("%w: %w", ErrJobScriptTimeout, scriptCtx.Err()),
FailureReason: JobExecutionTimeout,
}
}
afterScriptCtx, cancel := timeouts["RUNNER_AFTER_SCRIPT_TIMEOUT"]()
defer cancel()
if afterScriptErr := b.executeAfterScript(afterScriptCtx, err, executor); afterScriptErr != nil {
// the parent deadline being exceeded is reported at a later stage, so we
// only focus on errors specific to after_script here.
if !errors.Is(ctx.Err(), context.DeadlineExceeded) {
// By default after-script ignores errors, but this can
// be disabled via the AFTER_SCRIPT_IGNORE_ERRORS variable.
if b.Settings().AfterScriptIgnoreErrors {
b.logger.Warningln("after_script failed, but job will continue unaffected:", afterScriptErr)
} else if err == nil {
// If there's an existing error don't overwrite it with
// the after-script error.
err = afterScriptErr
}
}
}
}
archiveCacheErr := asRunnerSystemFailure(b.executeArchiveCache(ctx, err, executor))
artifactUploadErr := asRunnerSystemFailure(b.executeUploadArtifacts(ctx, err, executor))
// track job end and execute referees
endTime := time.Now()
b.executeUploadReferees(ctx, startTime, endTime)
b.removeFileBasedVariables(ctx, executor)
return b.pickPriorityError(err, archiveCacheErr, artifactUploadErr)
}