github.com/secure-build/gitlab-runner@v12.5.0+incompatible/shells/abstract.go (about)

     1  package shells
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/url"
     7  	"path"
     8  	"path/filepath"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"gitlab.com/gitlab-org/gitlab-runner/cache"
    13  	"gitlab.com/gitlab-org/gitlab-runner/common"
    14  	"gitlab.com/gitlab-org/gitlab-runner/helpers/tls"
    15  )
    16  
    17  type AbstractShell struct {
    18  }
    19  
    20  func (b *AbstractShell) GetFeatures(features *common.FeaturesInfo) {
    21  	features.Artifacts = true
    22  	features.UploadMultipleArtifacts = true
    23  	features.UploadRawArtifacts = true
    24  	features.Cache = true
    25  	features.Refspecs = true
    26  	features.Masking = true
    27  }
    28  
    29  func (b *AbstractShell) writeCdBuildDir(w ShellWriter, info common.ShellScriptInfo) {
    30  	w.Cd(info.Build.FullProjectDir())
    31  }
    32  
    33  func (b *AbstractShell) cacheFile(build *common.Build, userKey string) (key, file string) {
    34  	if build.CacheDir == "" {
    35  		return
    36  	}
    37  
    38  	// Deduce cache key
    39  	key = path.Join(build.JobInfo.Name, build.GitInfo.Ref)
    40  	if userKey != "" {
    41  		key = build.GetAllVariables().ExpandValue(userKey)
    42  	}
    43  
    44  	// Ignore cache without the key
    45  	if key == "" {
    46  		return
    47  	}
    48  
    49  	file = path.Join(build.CacheDir, key, "cache.zip")
    50  	file, err := filepath.Rel(build.BuildDir, file)
    51  	if err != nil {
    52  		return "", ""
    53  	}
    54  	return
    55  }
    56  
    57  func (b *AbstractShell) guardRunnerCommand(w ShellWriter, runnerCommand string, action string, f func()) {
    58  	if runnerCommand == "" {
    59  		w.Warning("%s is not supported by this executor.", action)
    60  		return
    61  	}
    62  
    63  	w.IfCmd(runnerCommand, "--version")
    64  	f()
    65  	w.Else()
    66  	w.Warning("Missing %s. %s is disabled.", runnerCommand, action)
    67  	w.EndIf()
    68  }
    69  
    70  func (b *AbstractShell) cacheExtractor(w ShellWriter, info common.ShellScriptInfo) error {
    71  	for _, cacheOptions := range info.Build.Cache {
    72  
    73  		// Create list of files to extract
    74  		archiverArgs := []string{}
    75  		for _, path := range cacheOptions.Paths {
    76  			archiverArgs = append(archiverArgs, "--path", path)
    77  		}
    78  
    79  		if cacheOptions.Untracked {
    80  			archiverArgs = append(archiverArgs, "--untracked")
    81  		}
    82  
    83  		// Skip restoring cache if no cache is defined
    84  		if len(archiverArgs) < 1 {
    85  			continue
    86  		}
    87  
    88  		// Skip extraction if no cache is defined
    89  		cacheKey, cacheFile := b.cacheFile(info.Build, cacheOptions.Key)
    90  		if cacheKey == "" {
    91  			w.Notice("Skipping cache extraction due to empty cache key")
    92  			continue
    93  		}
    94  
    95  		if ok, err := cacheOptions.CheckPolicy(common.CachePolicyPull); err != nil {
    96  			return fmt.Errorf("%s for %s", err, cacheKey)
    97  		} else if !ok {
    98  			w.Notice("Not downloading cache %s due to policy", cacheKey)
    99  			continue
   100  		}
   101  
   102  		args := []string{
   103  			"cache-extractor",
   104  			"--file", cacheFile,
   105  			"--timeout", strconv.Itoa(info.Build.GetCacheRequestTimeout()),
   106  		}
   107  
   108  		// Generate cache download address
   109  		if url := cache.GetCacheDownloadURL(info.Build, cacheKey); url != nil {
   110  			args = append(args, "--url", url.String())
   111  		}
   112  
   113  		// Execute cache-extractor command. Failure is not fatal.
   114  		b.guardRunnerCommand(w, info.RunnerCommand, "Extracting cache", func() {
   115  			w.Notice("Checking cache for %s...", cacheKey)
   116  			w.IfCmdWithOutput(info.RunnerCommand, args...)
   117  			w.Notice("Successfully extracted cache")
   118  			w.Else()
   119  			w.Warning("Failed to extract cache")
   120  			w.EndIf()
   121  		})
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  func (b *AbstractShell) downloadArtifacts(w ShellWriter, job common.Dependency, info common.ShellScriptInfo) {
   128  	args := []string{
   129  		"artifacts-downloader",
   130  		"--url",
   131  		info.Build.Runner.URL,
   132  		"--token",
   133  		job.Token,
   134  		"--id",
   135  		strconv.Itoa(job.ID),
   136  	}
   137  
   138  	w.Notice("Downloading artifacts for %s (%d)...", job.Name, job.ID)
   139  	w.Command(info.RunnerCommand, args...)
   140  }
   141  
   142  func (b *AbstractShell) jobArtifacts(info common.ShellScriptInfo) (otherJobs []common.Dependency) {
   143  	for _, otherJob := range info.Build.Dependencies {
   144  		if otherJob.ArtifactsFile.Filename == "" {
   145  			continue
   146  		}
   147  
   148  		otherJobs = append(otherJobs, otherJob)
   149  	}
   150  	return
   151  }
   152  
   153  func (b *AbstractShell) downloadAllArtifacts(w ShellWriter, info common.ShellScriptInfo) {
   154  	otherJobs := b.jobArtifacts(info)
   155  	if len(otherJobs) == 0 {
   156  		return
   157  	}
   158  
   159  	b.guardRunnerCommand(w, info.RunnerCommand, "Artifacts downloading", func() {
   160  		for _, otherJob := range otherJobs {
   161  			b.downloadArtifacts(w, otherJob, info)
   162  		}
   163  	})
   164  }
   165  
   166  func (b *AbstractShell) writePrepareScript(w ShellWriter, info common.ShellScriptInfo) (err error) {
   167  	return nil
   168  }
   169  
   170  func (b *AbstractShell) writeGetSourcesScript(w ShellWriter, info common.ShellScriptInfo) (err error) {
   171  	b.writeExports(w, info)
   172  
   173  	if !info.Build.IsSharedEnv() {
   174  		b.writeGitSSLConfig(w, info.Build, []string{"--global"})
   175  	}
   176  
   177  	if info.PreCloneScript != "" && info.Build.GetGitStrategy() != common.GitNone {
   178  		b.writeCommands(w, info.PreCloneScript)
   179  	}
   180  
   181  	if err := b.writeCloneFetchCmds(w, info); err != nil {
   182  		return err
   183  	}
   184  
   185  	return b.writeSubmoduleUpdateCmds(w, info)
   186  }
   187  
   188  func (b *AbstractShell) writeExports(w ShellWriter, info common.ShellScriptInfo) {
   189  	for _, variable := range info.Build.GetAllVariables() {
   190  		w.Variable(variable)
   191  	}
   192  }
   193  
   194  func (b *AbstractShell) writeGitSSLConfig(w ShellWriter, build *common.Build, where []string) {
   195  	repoURL, err := url.Parse(build.Runner.URL)
   196  	if err != nil {
   197  		w.Warning("git SSL config: Can't parse repository URL. %s", err)
   198  		return
   199  	}
   200  
   201  	repoURL.Path = ""
   202  	host := repoURL.String()
   203  	variables := build.GetCITLSVariables()
   204  	args := append([]string{"config"}, where...)
   205  
   206  	for variable, config := range map[string]string{
   207  		tls.VariableCAFile:   "sslCAInfo",
   208  		tls.VariableCertFile: "sslCert",
   209  		tls.VariableKeyFile:  "sslKey",
   210  	} {
   211  		if variables.Get(variable) == "" {
   212  			continue
   213  		}
   214  
   215  		key := fmt.Sprintf("http.%s.%s", host, config)
   216  		w.Command("git", append(args, key, w.EnvVariableKey(variable))...)
   217  	}
   218  
   219  	return
   220  }
   221  
   222  func (b *AbstractShell) writeCloneFetchCmds(w ShellWriter, info common.ShellScriptInfo) error {
   223  	build := info.Build
   224  
   225  	// If LFS smudging was disabled by the user (by setting the GIT_LFS_SKIP_SMUDGE variable
   226  	// when defining the job) we're skipping this step.
   227  	//
   228  	// In other case we're disabling smudging here to prevent us from memory
   229  	// allocation failures.
   230  	//
   231  	// Please read https://gitlab.com/gitlab-org/gitlab-runner/issues/3366 and
   232  	// https://github.com/git-lfs/git-lfs/issues/3524 for context.
   233  	if !build.IsLFSSmudgeDisabled() {
   234  		w.Variable(common.JobVariable{Key: "GIT_LFS_SKIP_SMUDGE", Value: "1"})
   235  	}
   236  
   237  	err := b.handleGetSourcesStrategy(w, build)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	if build.GetGitCheckout() {
   243  		b.writeCheckoutCmd(w, build)
   244  
   245  		// If LFS smudging was disabled by the user (by setting the GIT_LFS_SKIP_SMUDGE variable
   246  		// when defining the job) we're skipping this step.
   247  		//
   248  		// In other case, because we've disabled LFS smudging above, we need now manually call
   249  		// `git lfs pull` to fetch and checkout all LFS objects that may be present in
   250  		// the repository.
   251  		//
   252  		// Repositories without LFS objects (and without any LFS metadata) will be not
   253  		// affected by this command.
   254  		//
   255  		// Please read https://gitlab.com/gitlab-org/gitlab-runner/issues/3366 and
   256  		// https://github.com/git-lfs/git-lfs/issues/3524 for context.
   257  		if !build.IsLFSSmudgeDisabled() {
   258  			w.IfCmd("git", "lfs", "version")
   259  			w.Command("git", "lfs", "pull")
   260  			w.EmptyLine()
   261  			w.EndIf()
   262  		}
   263  	} else {
   264  		w.Notice("Skipping Git checkout")
   265  	}
   266  
   267  	return nil
   268  }
   269  
   270  func (b *AbstractShell) handleGetSourcesStrategy(w ShellWriter, build *common.Build) error {
   271  	projectDir := build.FullProjectDir()
   272  	gitDir := path.Join(build.FullProjectDir(), ".git")
   273  
   274  	switch build.GetGitStrategy() {
   275  	case common.GitFetch:
   276  		b.writeRefspecFetchCmd(w, build, projectDir, gitDir)
   277  	case common.GitClone:
   278  		w.RmDir(projectDir)
   279  		b.writeRefspecFetchCmd(w, build, projectDir, gitDir)
   280  	case common.GitNone:
   281  		w.Notice("Skipping Git repository setup")
   282  		w.MkDir(projectDir)
   283  	default:
   284  		return errors.New("unknown GIT_STRATEGY")
   285  	}
   286  
   287  	return nil
   288  }
   289  
   290  func (b *AbstractShell) writeRefspecFetchCmd(w ShellWriter, build *common.Build, projectDir string, gitDir string) {
   291  	depth := build.GitInfo.Depth
   292  
   293  	if depth > 0 {
   294  		w.Notice("Fetching changes with git depth set to %d...", depth)
   295  	} else {
   296  		w.Notice("Fetching changes...")
   297  	}
   298  
   299  	// initializing
   300  	templateDir := w.MkTmpDir("git-template")
   301  	templateFile := path.Join(templateDir, "config")
   302  
   303  	w.Command("git", "config", "-f", templateFile, "fetch.recurseSubmodules", "false")
   304  	if build.IsSharedEnv() {
   305  		b.writeGitSSLConfig(w, build, []string{"-f", templateFile})
   306  	}
   307  
   308  	w.Command("git", "init", projectDir, "--template", templateDir)
   309  	w.Cd(projectDir)
   310  	b.writeGitCleanup(w, build)
   311  
   312  	// Add `git remote` or update existing
   313  	w.IfCmd("git", "remote", "add", "origin", build.GetRemoteURL())
   314  	w.Notice("Created fresh repository.")
   315  	w.Else()
   316  	w.Command("git", "remote", "set-url", "origin", build.GetRemoteURL())
   317  	w.EndIf()
   318  
   319  	fetchArgs := []string{"fetch", "origin", "--prune"}
   320  	fetchArgs = append(fetchArgs, build.GitInfo.Refspecs...)
   321  	if depth > 0 {
   322  		fetchArgs = append(fetchArgs, "--depth", strconv.Itoa(depth))
   323  	}
   324  
   325  	w.Command("git", fetchArgs...)
   326  }
   327  
   328  func (b *AbstractShell) writeGitCleanup(w ShellWriter, build *common.Build) {
   329  	// Remove .git/{index,shallow,HEAD}.lock files from .git, which can fail the fetch command
   330  	// The file can be left if previous build was terminated during git operation
   331  	w.RmFile(".git/index.lock")
   332  	w.RmFile(".git/shallow.lock")
   333  	w.RmFile(".git/HEAD.lock")
   334  
   335  	w.RmFile(".git/hooks/post-checkout")
   336  }
   337  
   338  func (b *AbstractShell) writeCheckoutCmd(w ShellWriter, build *common.Build) {
   339  	w.Notice("Checking out %s as %s...", build.GitInfo.Sha[0:8], build.GitInfo.Ref)
   340  	w.Command("git", "checkout", "-f", "-q", build.GitInfo.Sha)
   341  
   342  	cleanFlags := build.GetGitCleanFlags()
   343  	if len(cleanFlags) > 0 {
   344  		cleanArgs := append([]string{"clean"}, cleanFlags...)
   345  		w.Command("git", cleanArgs...)
   346  	}
   347  }
   348  
   349  func (b *AbstractShell) writeSubmoduleUpdateCmds(w ShellWriter, info common.ShellScriptInfo) (err error) {
   350  	build := info.Build
   351  
   352  	switch build.GetSubmoduleStrategy() {
   353  	case common.SubmoduleNormal:
   354  		b.writeSubmoduleUpdateCmd(w, build, false)
   355  
   356  	case common.SubmoduleRecursive:
   357  		b.writeSubmoduleUpdateCmd(w, build, true)
   358  
   359  	case common.SubmoduleNone:
   360  		w.Notice("Skipping Git submodules setup")
   361  
   362  	default:
   363  		return errors.New("unknown GIT_SUBMODULE_STRATEGY")
   364  	}
   365  
   366  	return nil
   367  }
   368  
   369  func (b *AbstractShell) writeSubmoduleUpdateCmd(w ShellWriter, build *common.Build, recursive bool) {
   370  	if recursive {
   371  		w.Notice("Updating/initializing submodules recursively...")
   372  	} else {
   373  		w.Notice("Updating/initializing submodules...")
   374  	}
   375  
   376  	// Sync .git/config to .gitmodules in case URL changes (e.g. new build token)
   377  	args := []string{"submodule", "sync"}
   378  	if recursive {
   379  		args = append(args, "--recursive")
   380  	}
   381  	w.Command("git", args...)
   382  
   383  	// Update / initialize submodules
   384  	updateArgs := []string{"submodule", "update", "--init"}
   385  	foreachArgs := []string{"submodule", "foreach"}
   386  	if recursive {
   387  		updateArgs = append(updateArgs, "--recursive")
   388  		foreachArgs = append(foreachArgs, "--recursive")
   389  	}
   390  
   391  	// Clean changed files in submodules
   392  	// "git submodule update --force" option not supported in Git 1.7.1 (shipped with CentOS 6)
   393  	w.Command("git", append(foreachArgs, "git clean -ffxd")...)
   394  	w.Command("git", append(foreachArgs, "git reset --hard")...)
   395  	w.Command("git", updateArgs...)
   396  
   397  	if !build.IsLFSSmudgeDisabled() {
   398  		w.IfCmd("git", "lfs", "version")
   399  		w.Command("git", append(foreachArgs, "git lfs pull")...)
   400  		w.EndIf()
   401  	}
   402  }
   403  
   404  func (b *AbstractShell) writeRestoreCacheScript(w ShellWriter, info common.ShellScriptInfo) (err error) {
   405  	b.writeExports(w, info)
   406  	b.writeCdBuildDir(w, info)
   407  
   408  	// Try to restore from main cache, if not found cache for master
   409  	return b.cacheExtractor(w, info)
   410  }
   411  
   412  func (b *AbstractShell) writeDownloadArtifactsScript(w ShellWriter, info common.ShellScriptInfo) (err error) {
   413  	b.writeExports(w, info)
   414  	b.writeCdBuildDir(w, info)
   415  
   416  	// Process all artifacts
   417  	b.downloadAllArtifacts(w, info)
   418  	return nil
   419  }
   420  
   421  // Write the given string of commands using the provided ShellWriter object.
   422  func (b *AbstractShell) writeCommands(w ShellWriter, commands ...string) {
   423  	for _, command := range commands {
   424  		command = strings.TrimSpace(command)
   425  		if command != "" {
   426  			lines := strings.SplitN(command, "\n", 2)
   427  			if len(lines) > 1 {
   428  				// TODO: this should be collapsable once we introduce that in GitLab
   429  				w.Notice("$ %s # collapsed multi-line command", lines[0])
   430  			} else {
   431  				w.Notice("$ %s", lines[0])
   432  			}
   433  		} else {
   434  			w.EmptyLine()
   435  		}
   436  		w.Line(command)
   437  		w.CheckForErrors()
   438  	}
   439  }
   440  
   441  func (b *AbstractShell) writeUserScript(w ShellWriter, info common.ShellScriptInfo) (err error) {
   442  	var scriptStep *common.Step
   443  	for _, step := range info.Build.Steps {
   444  		if step.Name == common.StepNameScript {
   445  			scriptStep = &step
   446  			break
   447  		}
   448  	}
   449  
   450  	if scriptStep == nil {
   451  		return nil
   452  	}
   453  
   454  	b.writeExports(w, info)
   455  	b.writeCdBuildDir(w, info)
   456  
   457  	if info.PreBuildScript != "" {
   458  		b.writeCommands(w, info.PreBuildScript)
   459  	}
   460  
   461  	b.writeCommands(w, scriptStep.Script...)
   462  
   463  	if info.PostBuildScript != "" {
   464  		b.writeCommands(w, info.PostBuildScript)
   465  	}
   466  
   467  	return nil
   468  }
   469  
   470  func (b *AbstractShell) cacheArchiver(w ShellWriter, info common.ShellScriptInfo) error {
   471  	for _, cacheOptions := range info.Build.Cache {
   472  		// Skip archiving if no cache is defined
   473  		cacheKey, cacheFile := b.cacheFile(info.Build, cacheOptions.Key)
   474  		if cacheKey == "" {
   475  			w.Notice("Skipping cache archiving due to empty cache key")
   476  			continue
   477  		}
   478  
   479  		if ok, err := cacheOptions.CheckPolicy(common.CachePolicyPush); err != nil {
   480  			return fmt.Errorf("%s for %s", err, cacheKey)
   481  		} else if !ok {
   482  			w.Notice("Not uploading cache %s due to policy", cacheKey)
   483  			continue
   484  		}
   485  
   486  		args := []string{
   487  			"cache-archiver",
   488  			"--file", cacheFile,
   489  			"--timeout", strconv.Itoa(info.Build.GetCacheRequestTimeout()),
   490  		}
   491  
   492  		// Create list of files to archive
   493  		archiverArgs := []string{}
   494  		for _, path := range cacheOptions.Paths {
   495  			archiverArgs = append(archiverArgs, "--path", path)
   496  		}
   497  
   498  		if cacheOptions.Untracked {
   499  			archiverArgs = append(archiverArgs, "--untracked")
   500  		}
   501  
   502  		if len(archiverArgs) < 1 {
   503  			// Skip creating archive
   504  			continue
   505  		}
   506  		args = append(args, archiverArgs...)
   507  
   508  		// Generate cache upload address
   509  		if url := cache.GetCacheUploadURL(info.Build, cacheKey); url != nil {
   510  			args = append(args, "--url", url.String())
   511  		}
   512  
   513  		// Execute cache-archiver command. Failure is not fatal.
   514  		b.guardRunnerCommand(w, info.RunnerCommand, "Creating cache", func() {
   515  			w.Notice("Creating cache %s...", cacheKey)
   516  			w.IfCmdWithOutput(info.RunnerCommand, args...)
   517  			w.Notice("Created cache")
   518  			w.Else()
   519  			w.Warning("Failed to create cache")
   520  			w.EndIf()
   521  		})
   522  	}
   523  
   524  	return nil
   525  }
   526  
   527  func (b *AbstractShell) writeUploadArtifact(w ShellWriter, info common.ShellScriptInfo, artifact common.Artifact) {
   528  	args := []string{
   529  		"artifacts-uploader",
   530  		"--url",
   531  		info.Build.Runner.URL,
   532  		"--token",
   533  		info.Build.Token,
   534  		"--id",
   535  		strconv.Itoa(info.Build.ID),
   536  	}
   537  
   538  	// Create list of files to archive
   539  	archiverArgs := []string{}
   540  	for _, path := range artifact.Paths {
   541  		archiverArgs = append(archiverArgs, "--path", path)
   542  	}
   543  
   544  	if artifact.Untracked {
   545  		archiverArgs = append(archiverArgs, "--untracked")
   546  	}
   547  
   548  	if len(archiverArgs) < 1 {
   549  		// Skip creating archive
   550  		return
   551  	}
   552  	args = append(args, archiverArgs...)
   553  
   554  	if artifact.Name != "" {
   555  		args = append(args, "--name", artifact.Name)
   556  	}
   557  
   558  	if artifact.ExpireIn != "" {
   559  		args = append(args, "--expire-in", artifact.ExpireIn)
   560  	}
   561  
   562  	if artifact.Format != "" {
   563  		args = append(args, "--artifact-format", string(artifact.Format))
   564  	}
   565  
   566  	if artifact.Type != "" {
   567  		args = append(args, "--artifact-type", artifact.Type)
   568  	}
   569  
   570  	b.guardRunnerCommand(w, info.RunnerCommand, "Uploading artifacts", func() {
   571  		w.Notice("Uploading artifacts...")
   572  		w.Command(info.RunnerCommand, args...)
   573  	})
   574  }
   575  
   576  func (b *AbstractShell) writeUploadArtifacts(w ShellWriter, info common.ShellScriptInfo, onSuccess bool) {
   577  	if info.Build.Runner.URL == "" {
   578  		return
   579  	}
   580  
   581  	b.writeExports(w, info)
   582  	b.writeCdBuildDir(w, info)
   583  
   584  	for _, artifact := range info.Build.Artifacts {
   585  		if onSuccess {
   586  			if !artifact.When.OnSuccess() {
   587  				continue
   588  			}
   589  		} else {
   590  			if !artifact.When.OnFailure() {
   591  				continue
   592  			}
   593  		}
   594  
   595  		b.writeUploadArtifact(w, info, artifact)
   596  	}
   597  }
   598  
   599  func (b *AbstractShell) writeAfterScript(w ShellWriter, info common.ShellScriptInfo) error {
   600  	var afterScriptStep *common.Step
   601  	for _, step := range info.Build.Steps {
   602  		if step.Name == common.StepNameAfterScript {
   603  			afterScriptStep = &step
   604  			break
   605  		}
   606  	}
   607  
   608  	if afterScriptStep == nil {
   609  		return nil
   610  	}
   611  
   612  	if len(afterScriptStep.Script) == 0 {
   613  		return nil
   614  	}
   615  
   616  	b.writeExports(w, info)
   617  	b.writeCdBuildDir(w, info)
   618  
   619  	w.Notice("Running after script...")
   620  	b.writeCommands(w, afterScriptStep.Script...)
   621  	return nil
   622  }
   623  
   624  func (b *AbstractShell) writeArchiveCacheScript(w ShellWriter, info common.ShellScriptInfo) (err error) {
   625  	b.writeExports(w, info)
   626  	b.writeCdBuildDir(w, info)
   627  
   628  	// Find cached files and archive them
   629  	return b.cacheArchiver(w, info)
   630  }
   631  
   632  func (b *AbstractShell) writeUploadArtifactsOnSuccessScript(w ShellWriter, info common.ShellScriptInfo) (err error) {
   633  	b.writeUploadArtifacts(w, info, true)
   634  	return
   635  }
   636  
   637  func (b *AbstractShell) writeUploadArtifactsOnFailureScript(w ShellWriter, info common.ShellScriptInfo) (err error) {
   638  	b.writeUploadArtifacts(w, info, false)
   639  	return
   640  }
   641  
   642  func (b *AbstractShell) writeScript(w ShellWriter, buildStage common.BuildStage, info common.ShellScriptInfo) error {
   643  	methods := map[common.BuildStage]func(ShellWriter, common.ShellScriptInfo) error{
   644  		common.BuildStagePrepare:                  b.writePrepareScript,
   645  		common.BuildStageGetSources:               b.writeGetSourcesScript,
   646  		common.BuildStageRestoreCache:             b.writeRestoreCacheScript,
   647  		common.BuildStageDownloadArtifacts:        b.writeDownloadArtifactsScript,
   648  		common.BuildStageUserScript:               b.writeUserScript,
   649  		common.BuildStageAfterScript:              b.writeAfterScript,
   650  		common.BuildStageArchiveCache:             b.writeArchiveCacheScript,
   651  		common.BuildStageUploadOnSuccessArtifacts: b.writeUploadArtifactsOnSuccessScript,
   652  		common.BuildStageUploadOnFailureArtifacts: b.writeUploadArtifactsOnFailureScript,
   653  	}
   654  
   655  	fn := methods[buildStage]
   656  	if fn == nil {
   657  		return errors.New("Not supported script type: " + string(buildStage))
   658  	}
   659  
   660  	return fn(w, info)
   661  }