kubesphere.io/s2irun@v3.2.1+incompatible/pkg/build/strategies/sti/sti.go (about)

     1  package sti
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/kubesphere/s2irun/pkg/api"
    16  	"github.com/kubesphere/s2irun/pkg/api/constants"
    17  	"github.com/kubesphere/s2irun/pkg/build"
    18  	"github.com/kubesphere/s2irun/pkg/build/strategies/layered"
    19  	dockerpkg "github.com/kubesphere/s2irun/pkg/docker"
    20  	s2ierr "github.com/kubesphere/s2irun/pkg/errors"
    21  	"github.com/kubesphere/s2irun/pkg/ignore"
    22  	"github.com/kubesphere/s2irun/pkg/outputresult"
    23  	"github.com/kubesphere/s2irun/pkg/scm"
    24  	"github.com/kubesphere/s2irun/pkg/scm/git"
    25  	"github.com/kubesphere/s2irun/pkg/scripts"
    26  	"github.com/kubesphere/s2irun/pkg/tar"
    27  	"github.com/kubesphere/s2irun/pkg/utils"
    28  	"github.com/kubesphere/s2irun/pkg/utils/cmd"
    29  	"github.com/kubesphere/s2irun/pkg/utils/fs"
    30  	utilglog "github.com/kubesphere/s2irun/pkg/utils/glog"
    31  	utilstatus "github.com/kubesphere/s2irun/pkg/utils/status"
    32  )
    33  
    34  const (
    35  	injectionResultFile = "/tmp/injection-result"
    36  	rmInjectionsScript  = "/tmp/rm-injections"
    37  )
    38  
    39  var (
    40  	glog = utilglog.StderrLog
    41  
    42  	// List of directories that needs to be present inside working dir
    43  	workingDirs = []string{
    44  		constants.UploadScripts,
    45  		constants.Source,
    46  		constants.DefaultScripts,
    47  		constants.UserScripts,
    48  	}
    49  
    50  	errMissingRequirements = errors.New("missing requirements")
    51  )
    52  
    53  // STI strategy executes the S2I build.
    54  // For more details about S2I, visit https://github.com/s2iservice
    55  type STI struct {
    56  	config                 *api.Config
    57  	result                 *api.Result
    58  	postExecutor           dockerpkg.PostExecutor
    59  	installer              scripts.Installer
    60  	runtimeInstaller       scripts.Installer
    61  	git                    git.Git
    62  	fs                     fs.FileSystem
    63  	tar                    tar.Tar
    64  	docker                 dockerpkg.Docker
    65  	incrementalDocker      dockerpkg.Docker
    66  	runtimeDocker          dockerpkg.Docker
    67  	pushDocker             dockerpkg.Docker
    68  	callbackInvoker        utils.CallbackInvoker
    69  	requiredScripts        []string
    70  	optionalScripts        []string
    71  	optionalRuntimeScripts []string
    72  	externalScripts        map[string]bool
    73  	installedScripts       map[string]bool
    74  	scriptsURL             map[string]string
    75  	incremental            bool
    76  	sourceInfo             *git.SourceInfo
    77  	env                    []string
    78  	newLabels              map[string]string
    79  
    80  	// Interfaces
    81  	preparer  build.Preparer
    82  	ignorer   build.Ignorer
    83  	artifacts build.IncrementalBuilder
    84  	scripts   build.ScriptsHandler
    85  	source    build.Downloader
    86  	garbage   build.Cleaner
    87  	layered   build.Builder
    88  
    89  	// post executors steps
    90  	postExecutorStage            int
    91  	postExecutorFirstStageSteps  []postExecutorStep
    92  	postExecutorSecondStageSteps []postExecutorStep
    93  	postExecutorStepsContext     *postExecutorStepContext
    94  }
    95  
    96  // New returns the instance of STI builder strategy for the given config.
    97  // If the layeredBuilder parameter is specified, then the builder provided will
    98  // be used for the case that the base Docker image does not have 'tar' or 'bash'
    99  // installed.
   100  func New(client dockerpkg.Client, config *api.Config, fs fs.FileSystem, overrides build.Overrides) (*STI, error) {
   101  	excludePattern, err := regexp.Compile(config.ExcludeRegExp)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	docker := dockerpkg.New(client, config.PullAuthentication, config.PushAuthentication)
   107  	var incrementalDocker dockerpkg.Docker
   108  	if config.Incremental {
   109  		incrementalDocker = dockerpkg.New(client, config.IncrementalAuthentication, config.PushAuthentication)
   110  	}
   111  
   112  	inst := scripts.NewInstaller(
   113  		config.BuilderImage,
   114  		config.ScriptsURL,
   115  		config.ScriptDownloadProxyConfig,
   116  		docker,
   117  		config.PullAuthentication,
   118  		fs,
   119  	)
   120  	tarHandler := tar.NewParanoid(fs)
   121  	tarHandler.SetExclusionPattern(excludePattern)
   122  
   123  	builder := &STI{
   124  		installer:              inst,
   125  		config:                 config,
   126  		docker:                 docker,
   127  		incrementalDocker:      incrementalDocker,
   128  		git:                    git.New(fs, cmd.NewCommandRunner()),
   129  		fs:                     fs,
   130  		tar:                    tarHandler,
   131  		callbackInvoker:        utils.NewCallbackInvoker(),
   132  		requiredScripts:        scripts.RequiredScripts,
   133  		optionalScripts:        scripts.OptionalScripts,
   134  		optionalRuntimeScripts: []string{constants.AssembleRuntime},
   135  		externalScripts:        map[string]bool{},
   136  		installedScripts:       map[string]bool{},
   137  		scriptsURL:             map[string]string{},
   138  		newLabels:              map[string]string{},
   139  	}
   140  
   141  	if len(config.RuntimeImage) > 0 {
   142  		builder.runtimeDocker = dockerpkg.New(client, config.RuntimeAuthentication, config.PushAuthentication)
   143  
   144  		builder.runtimeInstaller = scripts.NewInstaller(
   145  			config.RuntimeImage,
   146  			config.ScriptsURL,
   147  			config.ScriptDownloadProxyConfig,
   148  			builder.runtimeDocker,
   149  			config.RuntimeAuthentication,
   150  			builder.fs,
   151  		)
   152  	}
   153  	// The sources are downloaded using the Git downloader.
   154  	// TODO: Add more SCM in future.
   155  	// TODO: explicit decision made to customize processing for usage specifically vs.
   156  	// leveraging overrides; also, we ultimately want to simplify s2i usage a good bit,
   157  	// which would lead to replacing this quick short circuit (so this change is tactical)
   158  	builder.source = overrides.Downloader
   159  	if builder.source == nil && !config.Usage {
   160  		downloader, err := scm.DownloaderForSource(builder.fs, config.Source, config.ForceCopy)
   161  		if err != nil {
   162  			return nil, err
   163  		}
   164  		builder.source = downloader
   165  	}
   166  	builder.garbage = build.NewDefaultCleaner(builder.fs, builder.docker)
   167  
   168  	builder.layered, err = layered.New(client, config, builder.fs, builder, overrides)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	// Set interfaces
   174  	builder.preparer = builder
   175  	// later on, if we support say .gitignore func in addition to .dockerignore
   176  	// func, setting ignorer will be based on config setting
   177  	builder.ignorer = &ignore.DockerIgnorer{}
   178  	builder.artifacts = builder
   179  	builder.scripts = builder
   180  	builder.postExecutor = builder
   181  	builder.initPostExecutorSteps()
   182  
   183  	return builder, nil
   184  }
   185  
   186  // Build processes a Request and returns a *api.Result and an error.
   187  // An error represents a failure performing the build rather than a failure
   188  // of the build itself.  Callers should check the Success field of the result
   189  // to determine whether a build succeeded or not.
   190  func (builder *STI) Build(config *api.Config) (*api.Result, error) {
   191  	builder.result = &api.Result{}
   192  
   193  	if len(builder.config.CallbackURL) > 0 {
   194  		defer func() {
   195  			builder.result.Messages = builder.callbackInvoker.ExecuteCallback(
   196  				builder.config.CallbackURL,
   197  				builder.result.Success,
   198  				builder.postExecutorStepsContext.labels,
   199  				builder.result.Messages,
   200  			)
   201  		}()
   202  	}
   203  	defer builder.garbage.Cleanup(config)
   204  
   205  	glog.V(1).Infof("Preparing to build %s", config.Tag)
   206  	if err := builder.preparer.Prepare(config); err != nil {
   207  		return builder.result, err
   208  	}
   209  
   210  	if builder.incremental = builder.artifacts.Exists(config); builder.incremental {
   211  		tag := utils.FirstNonEmpty(config.IncrementalFromTag, config.Tag)
   212  		glog.V(1).Infof("Existing image for tag %s detected for incremental build", tag)
   213  	} else {
   214  		glog.V(1).Info("Clean build will be performed")
   215  	}
   216  
   217  	glog.V(9).Infof("Performing source build from %s", config.Source)
   218  	if builder.incremental {
   219  		if err := builder.artifacts.Save(config); err != nil {
   220  			glog.Warning("Clean build will be performed because of error saving previous build artifacts")
   221  			glog.V(2).Infof("error: %v", err)
   222  		}
   223  	}
   224  
   225  	if len(config.AssembleUser) > 0 {
   226  		glog.V(1).Infof("Running %q in %q as %q user", constants.Assemble, config.Tag, config.AssembleUser)
   227  	} else {
   228  		glog.V(1).Infof("Running %q in %q", constants.Assemble, config.Tag)
   229  	}
   230  	startTime := time.Now()
   231  	if err := builder.scripts.Execute(constants.Assemble, config.AssembleUser, config); err != nil {
   232  		if err == errMissingRequirements {
   233  			glog.V(1).Info("Image is missing basic requirements (sh or tar), layered build will be performed")
   234  			return builder.layered.Build(config)
   235  		}
   236  		if e, ok := err.(s2ierr.ContainerError); ok {
   237  			if !isMissingRequirements(e.Output) {
   238  				builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   239  					utilstatus.ReasonAssembleFailed,
   240  					utilstatus.ReasonMessageAssembleFailed,
   241  				)
   242  				return builder.result, err
   243  			}
   244  			glog.V(1).Info("Image is missing basic requirements (sh or tar), layered build will be performed")
   245  			buildResult, err := builder.layered.Build(config)
   246  			return buildResult, err
   247  		}
   248  
   249  		return builder.result, err
   250  	}
   251  	if builder.config.Export {
   252  		err := builder.docker.PushImage(builder.config.Tag)
   253  		if err != nil {
   254  			builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   255  				utilstatus.ReasonPushImageFailed,
   256  				utilstatus.ReasonMessagePushImageFailed,
   257  			)
   258  			return builder.result, err
   259  		}
   260  	}
   261  	builder.result.BuildInfo.Stages = api.RecordStageAndStepInfo(builder.result.BuildInfo.Stages, api.StageAssemble, api.StepAssembleBuildScripts, startTime, time.Now())
   262  	builder.result.Success = true
   263  
   264  	if builder.config.OutputBuildResult == true {
   265  		dockerInspect, err := builder.docker.InspectImage(builder.config.Tag)
   266  		if err != nil {
   267  			glog.V(1).Info("Inspect image failed.")
   268  		}
   269  		glog.V(0).Info("Start output build info.")
   270  		buildResult := outputresult.OutputResult(builder.config, dockerInspect, builder.result)
   271  		err = outputresult.AddBuildResultToAnnotation(buildResult)
   272  		if err != nil {
   273  			glog.V(1).Info("Output build result failed, reason: %s.", err)
   274  		}
   275  	}
   276  
   277  	return builder.result, nil
   278  }
   279  
   280  // Prepare prepares the source code and tar for build.
   281  // NOTE: this func serves both the sti and onbuild strategies, as the OnBuild
   282  // struct Build func leverages the STI struct Prepare func directly below.
   283  func (builder *STI) Prepare(config *api.Config) error {
   284  	var err error
   285  	if builder.result == nil {
   286  		builder.result = &api.Result{}
   287  	}
   288  
   289  	if len(config.WorkingDir) == 0 {
   290  		if config.WorkingDir, err = builder.fs.CreateWorkingDirectory(); err != nil {
   291  			builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   292  				utilstatus.ReasonFSOperationFailed,
   293  				utilstatus.ReasonMessageFSOperationFailed,
   294  			)
   295  			return err
   296  		}
   297  	}
   298  
   299  	builder.result.WorkingDir = config.WorkingDir
   300  
   301  	if len(config.RuntimeImage) > 0 {
   302  		startTime := time.Now()
   303  		dockerpkg.GetRuntimeImage(builder.runtimeDocker, config)
   304  		builder.result.BuildInfo.Stages = api.RecordStageAndStepInfo(builder.result.BuildInfo.Stages, api.StagePullImages, api.StepPullRuntimeImage, startTime, time.Now())
   305  
   306  		if err != nil {
   307  			builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   308  				utilstatus.ReasonPullRuntimeImageFailed,
   309  				utilstatus.ReasonMessagePullRuntimeImageFailed,
   310  			)
   311  			glog.Errorf("Unable to pull runtime image %q: %v", config.RuntimeImage, err)
   312  			return err
   313  		}
   314  
   315  		// user didn't specify mapping, let's take it from the runtime image then
   316  		if len(builder.config.RuntimeArtifacts) == 0 {
   317  			var mapping string
   318  			mapping, err = builder.docker.GetAssembleInputFiles(config.RuntimeImage)
   319  			if err != nil {
   320  				builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   321  					utilstatus.ReasonInvalidArtifactsMapping,
   322  					utilstatus.ReasonMessageInvalidArtifactsMapping,
   323  				)
   324  				return err
   325  			}
   326  			if len(mapping) == 0 {
   327  				builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   328  					utilstatus.ReasonGenericS2IBuildFailed,
   329  					utilstatus.ReasonMessageGenericS2iBuildFailed,
   330  				)
   331  				return errors.New("no runtime artifacts to copy were specified")
   332  			}
   333  			for _, value := range strings.Split(mapping, ";") {
   334  				if err = builder.config.RuntimeArtifacts.Set(value); err != nil {
   335  					builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   336  						utilstatus.ReasonGenericS2IBuildFailed,
   337  						utilstatus.ReasonMessageGenericS2iBuildFailed,
   338  					)
   339  					return fmt.Errorf("could not  parse %q label with value %q on image %q: %v",
   340  						constants.AssembleInputFilesLabelLog, mapping, config.RuntimeImage, err)
   341  				}
   342  			}
   343  		}
   344  		// we're validating values here to be sure that we're handling both of the cases of the invocation:
   345  		// from main() and as a method from OpenShift
   346  		for _, volumeSpec := range builder.config.RuntimeArtifacts {
   347  			var volumeErr error
   348  
   349  			switch {
   350  			case !path.IsAbs(filepath.ToSlash(volumeSpec.Source)):
   351  				volumeErr = fmt.Errorf("invalid runtime artifacts mapping: %q -> %q: source must be an absolute path", volumeSpec.Source, volumeSpec.Destination)
   352  			case path.IsAbs(volumeSpec.Destination):
   353  				volumeErr = fmt.Errorf("invalid runtime artifacts mapping: %q -> %q: destination must be a relative path", volumeSpec.Source, volumeSpec.Destination)
   354  			case strings.HasPrefix(volumeSpec.Destination, ".."):
   355  				volumeErr = fmt.Errorf("invalid runtime artifacts mapping: %q -> %q: destination cannot start with '..'", volumeSpec.Source, volumeSpec.Destination)
   356  			default:
   357  				continue
   358  			}
   359  			if volumeErr != nil {
   360  				builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   361  					utilstatus.ReasonInvalidArtifactsMapping,
   362  					utilstatus.ReasonMessageInvalidArtifactsMapping,
   363  				)
   364  				return volumeErr
   365  			}
   366  		}
   367  	}
   368  
   369  	// Setup working directories
   370  	for _, v := range workingDirs {
   371  		if err = builder.fs.MkdirAllWithPermissions(filepath.Join(config.WorkingDir, v), 0755); err != nil {
   372  			builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   373  				utilstatus.ReasonFSOperationFailed,
   374  				utilstatus.ReasonMessageFSOperationFailed,
   375  			)
   376  			return err
   377  		}
   378  	}
   379  
   380  	// fetch sources, for their .s2i/bin might contain s2i scripts
   381  	if config.Source != nil {
   382  		if builder.sourceInfo, err = builder.source.Download(config); err != nil {
   383  			builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   384  				utilstatus.ReasonFetchSourceFailed,
   385  				utilstatus.ReasonMessageFetchSourceFailed,
   386  			)
   387  			return err
   388  		}
   389  		if builder.sourceInfo != nil {
   390  			config.SourceInfo = builder.sourceInfo
   391  		}
   392  	}
   393  
   394  	// get the scripts
   395  	required, err := builder.installer.InstallRequired(builder.requiredScripts, config.WorkingDir)
   396  	if err != nil {
   397  		builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   398  			utilstatus.ReasonInstallScriptsFailed,
   399  			utilstatus.ReasonMessageInstallScriptsFailed,
   400  		)
   401  		return err
   402  	}
   403  	optional := builder.installer.InstallOptional(builder.optionalScripts, config.WorkingDir)
   404  
   405  	requiredAndOptional := append(required, optional...)
   406  
   407  	if len(config.RuntimeImage) > 0 && builder.runtimeInstaller != nil {
   408  		optionalRuntime := builder.runtimeInstaller.InstallOptional(builder.optionalRuntimeScripts, config.WorkingDir)
   409  		requiredAndOptional = append(requiredAndOptional, optionalRuntime...)
   410  	}
   411  
   412  	// If a ScriptsURL was specified, but no scripts were downloaded from it, throw an error
   413  	if len(config.ScriptsURL) > 0 {
   414  		failedCount := 0
   415  		for _, result := range requiredAndOptional {
   416  			if utils.Includes(result.FailedSources, scripts.ScriptURLHandler) {
   417  				failedCount++
   418  			}
   419  		}
   420  		if failedCount == len(requiredAndOptional) {
   421  			builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   422  				utilstatus.ReasonScriptsFetchFailed,
   423  				utilstatus.ReasonMessageScriptsFetchFailed,
   424  			)
   425  			return fmt.Errorf("could not download any scripts from URL %v", config.ScriptsURL)
   426  		}
   427  	}
   428  
   429  	for _, r := range requiredAndOptional {
   430  		if r.Error != nil {
   431  			glog.Warningf("Error getting %v from %s: %v", r.Script, r.URL, r.Error)
   432  			continue
   433  		}
   434  
   435  		builder.externalScripts[r.Script] = r.Downloaded
   436  		builder.installedScripts[r.Script] = r.Installed
   437  		builder.scriptsURL[r.Script] = r.URL
   438  	}
   439  
   440  	// see if there is a .s2iignore file, and if so, read in the patterns an then
   441  	// search and delete on
   442  	return builder.ignorer.Ignore(config)
   443  }
   444  
   445  // SetScripts allows to override default required and optional scripts
   446  func (builder *STI) SetScripts(required, optional []string) {
   447  	builder.requiredScripts = required
   448  	builder.optionalScripts = optional
   449  }
   450  
   451  // PostExecute allows to execute post-build actions after the Docker
   452  // container execution finishes.
   453  func (builder *STI) PostExecute(containerID, destination string) error {
   454  	builder.postExecutorStepsContext.containerID = containerID
   455  	builder.postExecutorStepsContext.destination = destination
   456  
   457  	stageSteps := builder.postExecutorFirstStageSteps
   458  	if builder.postExecutorStage > 0 {
   459  		stageSteps = builder.postExecutorSecondStageSteps
   460  	}
   461  
   462  	for _, step := range stageSteps {
   463  		if err := step.execute(builder.postExecutorStepsContext); err != nil {
   464  			glog.V(0).Info("error: Execution of post execute step failed")
   465  			return err
   466  		}
   467  	}
   468  
   469  	return nil
   470  }
   471  
   472  // CreateBuildEnvironment constructs the environment variables to be provided to the assemble
   473  // script and committed in the new image.
   474  func CreateBuildEnvironment(sourcePath string, cfgEnv api.EnvironmentList) []string {
   475  	s2iEnv, err := scripts.GetEnvironment(filepath.Join(sourcePath, constants.Source))
   476  	if err != nil {
   477  		glog.V(3).Infof("No user environment provided (%v)", err)
   478  	}
   479  
   480  	return append(scripts.ConvertEnvironmentList(s2iEnv), scripts.ConvertEnvironmentList(cfgEnv)...)
   481  }
   482  
   483  // Exists determines if the current build supports incremental workflow.
   484  // It checks if the previous image exists in the system and if so, then it
   485  // verifies that the save-artifacts script is present.
   486  func (builder *STI) Exists(config *api.Config) bool {
   487  	if !config.Incremental {
   488  		return false
   489  	}
   490  
   491  	policy := config.PreviousImagePullPolicy
   492  	if len(policy) == 0 {
   493  		policy = api.DefaultPreviousImagePullPolicy
   494  	}
   495  
   496  	tag := utils.FirstNonEmpty(config.IncrementalFromTag, config.Tag)
   497  
   498  	startTime := time.Now()
   499  	result, err := dockerpkg.PullImage(tag, builder.incrementalDocker, policy)
   500  	builder.result.BuildInfo.Stages = api.RecordStageAndStepInfo(builder.result.BuildInfo.Stages, api.StagePullImages, api.StepPullPreviousImage, startTime, time.Now())
   501  
   502  	if err != nil {
   503  		builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   504  			utilstatus.ReasonPullPreviousImageFailed,
   505  			utilstatus.ReasonMessagePullPreviousImageFailed,
   506  		)
   507  		glog.V(2).Infof("Unable to pull previously built image %q: %v", tag, err)
   508  		return false
   509  	}
   510  
   511  	return result.Image != nil && builder.installedScripts[constants.SaveArtifacts]
   512  }
   513  
   514  // Save extracts and restores the build artifacts from the previous build to
   515  // the current build.
   516  func (builder *STI) Save(config *api.Config) (err error) {
   517  	artifactTmpDir := filepath.Join(config.WorkingDir, "upload", "artifacts")
   518  	if builder.result == nil {
   519  		builder.result = &api.Result{}
   520  	}
   521  
   522  	if err = builder.fs.Mkdir(artifactTmpDir); err != nil {
   523  		builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   524  			utilstatus.ReasonFSOperationFailed,
   525  			utilstatus.ReasonMessageFSOperationFailed,
   526  		)
   527  		return err
   528  	}
   529  
   530  	image := utils.FirstNonEmpty(config.IncrementalFromTag, config.Tag)
   531  
   532  	outReader, outWriter := io.Pipe()
   533  	errReader, errWriter := io.Pipe()
   534  	glog.V(1).Infof("Saving build artifacts from image %s to path %s", image, artifactTmpDir)
   535  	extractFunc := func(string) error {
   536  		startTime := time.Now()
   537  		extractErr := builder.tar.ExtractTarStream(artifactTmpDir, outReader)
   538  		io.Copy(ioutil.Discard, outReader) // must ensure reader from container is drained
   539  		builder.result.BuildInfo.Stages = api.RecordStageAndStepInfo(builder.result.BuildInfo.Stages, api.StageRetrieve, api.StepRetrievePreviousArtifacts, startTime, time.Now())
   540  		return extractErr
   541  	}
   542  
   543  	user := config.AssembleUser
   544  	if len(user) == 0 {
   545  		user, err = builder.docker.GetImageUser(image)
   546  		if err != nil {
   547  			builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   548  				utilstatus.ReasonGenericS2IBuildFailed,
   549  				utilstatus.ReasonMessageGenericS2iBuildFailed,
   550  			)
   551  			return err
   552  		}
   553  		glog.V(3).Infof("The assemble user is not set, defaulting to %q user", user)
   554  	} else {
   555  		glog.V(3).Infof("Using assemble user %q to extract artifacts", user)
   556  	}
   557  
   558  	opts := dockerpkg.RunContainerOptions{
   559  		Image:           image,
   560  		User:            user,
   561  		ExternalScripts: builder.externalScripts[constants.SaveArtifacts],
   562  		ScriptsURL:      config.ScriptsURL,
   563  		Destination:     config.Destination,
   564  		PullImage:       false,
   565  		Command:         constants.SaveArtifacts,
   566  		Stdout:          outWriter,
   567  		Stderr:          errWriter,
   568  		OnStart:         extractFunc,
   569  		NetworkMode:     string(config.DockerNetworkMode),
   570  		CGroupLimits:    config.CGroupLimits,
   571  		CapDrop:         config.DropCapabilities,
   572  		Binds:           config.BuildVolumes,
   573  		SecurityOpt:     config.SecurityOpt,
   574  		AddHost:         config.AddHost,
   575  	}
   576  
   577  	dockerpkg.StreamContainerIO(errReader, nil, func(s string) { glog.Info(s) })
   578  	err = builder.docker.RunContainer(opts)
   579  	if e, ok := err.(s2ierr.ContainerError); ok {
   580  		err = s2ierr.NewSaveArtifactsError(image, e.Output, err)
   581  	}
   582  
   583  	builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   584  		utilstatus.ReasonGenericS2IBuildFailed,
   585  		utilstatus.ReasonMessageGenericS2iBuildFailed,
   586  	)
   587  	return err
   588  }
   589  
   590  // Execute runs the specified STI script in the builder image.
   591  func (builder *STI) Execute(command string, user string, config *api.Config) error {
   592  	glog.V(2).Infof("Using image name %s", config.BuilderImage)
   593  
   594  	// Ensure that the builder image is present in the local Docker daemon.
   595  	// The image should have been pulled when the strategy was created, so
   596  	// this should be a quick inspect of the existing image. However, if
   597  	// the image has been deleted since the strategy was created, this will ensure
   598  	// it exists before executing a script on it.
   599  	builder.docker.CheckAndPullImage(config.BuilderImage)
   600  
   601  	// we can't invoke this method before (for example in New() method)
   602  	// because of later initialization of config.WorkingDir
   603  	builder.env = CreateBuildEnvironment(config.WorkingDir, config.Environment)
   604  
   605  	errOutput := ""
   606  	outReader, outWriter := io.Pipe()
   607  	errReader, errWriter := io.Pipe()
   608  	externalScripts := builder.externalScripts[command]
   609  	// if LayeredBuild is called then all the scripts will be placed inside the image
   610  	if config.LayeredBuild {
   611  		externalScripts = false
   612  	}
   613  
   614  	opts := dockerpkg.RunContainerOptions{
   615  		Image:  config.BuilderImage,
   616  		Stdout: outWriter,
   617  		Stderr: errWriter,
   618  		// The PullImage is false because the PullImage function should be called
   619  		// before we run the container
   620  		PullImage:       false,
   621  		ExternalScripts: externalScripts,
   622  		ScriptsURL:      config.ScriptsURL,
   623  		Destination:     config.Destination,
   624  		Command:         command,
   625  		Env:             builder.env,
   626  		User:            user,
   627  		PostExec:        builder.postExecutor,
   628  		NetworkMode:     string(config.DockerNetworkMode),
   629  		CGroupLimits:    config.CGroupLimits,
   630  		CapDrop:         config.DropCapabilities,
   631  		Binds:           config.BuildVolumes,
   632  		SecurityOpt:     config.SecurityOpt,
   633  		AddHost:         config.AddHost,
   634  	}
   635  
   636  	// If there are injections specified, override the original assemble script
   637  	// and wait till all injections are uploaded into the container that runs the
   638  	// assemble script.
   639  	injectionError := make(chan error)
   640  	if len(config.Injections) > 0 && command == constants.Assemble {
   641  		workdir, err := builder.docker.GetImageWorkdir(config.BuilderImage)
   642  		if err != nil {
   643  			builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   644  				utilstatus.ReasonGenericS2IBuildFailed,
   645  				utilstatus.ReasonMessageGenericS2iBuildFailed,
   646  			)
   647  			return err
   648  		}
   649  		config.Injections = utils.FixInjectionsWithRelativePath(workdir, config.Injections)
   650  		truncatedFiles, err := utils.ListFilesToTruncate(builder.fs, config.Injections)
   651  		if err != nil {
   652  			builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   653  				utilstatus.ReasonInstallScriptsFailed,
   654  				utilstatus.ReasonMessageInstallScriptsFailed,
   655  			)
   656  			return err
   657  		}
   658  		rmScript, err := utils.CreateTruncateFilesScript(truncatedFiles, rmInjectionsScript)
   659  		if len(rmScript) != 0 {
   660  			defer os.Remove(rmScript)
   661  		}
   662  		if err != nil {
   663  			builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   664  				utilstatus.ReasonGenericS2IBuildFailed,
   665  				utilstatus.ReasonMessageGenericS2iBuildFailed,
   666  			)
   667  			return err
   668  		}
   669  		opts.CommandOverrides = func(cmd string) string {
   670  			// If an s2i build has injections, the s2i container's main command must be altered to
   671  			// do the following:
   672  			//     1) Wait for the injections to be uploaded
   673  			//     2) Check if there were any errors uploading the injections
   674  			//     3) Run the injection removal script after `assemble` completes
   675  			//
   676  			// The injectionResultFile should always be uploaded to the s2i container after the
   677  			// injected volumes are added. If this file is non-empty, it indicates that an error
   678  			// occurred during the injection process and the s2i build should fail.
   679  			return fmt.Sprintf("while [ ! -f %[1]q ]; do sleep 0.5; done; if [ -s %[1]q ]; then exit 1; fi; %[2]s; result=$?; . %[3]s; exit $result",
   680  				injectionResultFile, cmd, rmInjectionsScript)
   681  		}
   682  		originalOnStart := opts.OnStart
   683  		opts.OnStart = func(containerID string) error {
   684  			defer close(injectionError)
   685  			injectErr := builder.uploadInjections(config, rmScript, containerID)
   686  			if err := builder.uploadInjectionResult(injectErr, containerID); err != nil {
   687  				injectionError <- err
   688  				return err
   689  			}
   690  			if originalOnStart != nil {
   691  				return originalOnStart(containerID)
   692  			}
   693  			return nil
   694  		}
   695  	} else {
   696  		close(injectionError)
   697  	}
   698  
   699  	if !config.LayeredBuild {
   700  		r, w := io.Pipe()
   701  		opts.Stdin = r
   702  
   703  		go func() {
   704  			// Wait for the injections to complete and check the error. Do not start
   705  			// streaming the sources when the injection failed.
   706  			if <-injectionError != nil {
   707  				w.Close()
   708  				return
   709  			}
   710  			glog.V(2).Info("starting the source uploading ...")
   711  			uploadDir := filepath.Join(config.WorkingDir, "upload")
   712  			w.CloseWithError(builder.tar.CreateTarStream(uploadDir, false, w))
   713  		}()
   714  	}
   715  
   716  	dockerpkg.StreamContainerIO(outReader, nil, func(s string) {
   717  		if !config.Quiet {
   718  			glog.Info(strings.TrimSpace(s))
   719  		}
   720  	})
   721  
   722  	c := dockerpkg.StreamContainerIO(errReader, &errOutput, func(s string) { glog.Info(s) })
   723  
   724  	err := builder.docker.RunContainer(opts)
   725  	if err != nil {
   726  		// Must wait for StreamContainerIO goroutine above to exit before reading errOutput.
   727  		<-c
   728  
   729  		if isMissingRequirements(errOutput) {
   730  			err = errMissingRequirements
   731  		} else if e, ok := err.(s2ierr.ContainerError); ok {
   732  			err = s2ierr.NewContainerError(config.BuilderImage, e.ErrorCode, errOutput+e.Output)
   733  		}
   734  	}
   735  
   736  	return err
   737  }
   738  
   739  // uploadInjections uploads the injected volumes to the s2i container, along with the source
   740  // removal script to truncate volumes that should not be kept.
   741  func (builder *STI) uploadInjections(config *api.Config, rmScript, containerID string) error {
   742  	glog.V(2).Info("starting the injections uploading ...")
   743  	for _, s := range config.Injections {
   744  		if err := builder.docker.UploadToContainer(builder.fs, s.Source, s.Destination, containerID); err != nil {
   745  			return utils.HandleInjectionError(s, err)
   746  		}
   747  	}
   748  	if err := builder.docker.UploadToContainer(builder.fs, rmScript, rmInjectionsScript, containerID); err != nil {
   749  		return utils.HandleInjectionError(api.VolumeSpec{Source: rmScript, Destination: rmInjectionsScript}, err)
   750  	}
   751  	return nil
   752  }
   753  
   754  func (builder *STI) initPostExecutorSteps() {
   755  	builder.postExecutorStepsContext = &postExecutorStepContext{}
   756  	if len(builder.config.RuntimeImage) == 0 {
   757  		builder.postExecutorFirstStageSteps = []postExecutorStep{
   758  			&storePreviousImageStep{
   759  				builder: builder,
   760  				docker:  builder.docker,
   761  			},
   762  			&commitImageStep{
   763  				image:   builder.config.BuilderImage,
   764  				builder: builder,
   765  				docker:  builder.docker,
   766  				fs:      builder.fs,
   767  				tar:     builder.tar,
   768  			},
   769  			&reportSuccessStep{
   770  				builder: builder,
   771  			},
   772  			&removePreviousImageStep{
   773  				builder: builder,
   774  				docker:  builder.docker,
   775  			},
   776  		}
   777  	} else {
   778  		builder.postExecutorFirstStageSteps = []postExecutorStep{
   779  			&downloadFilesFromBuilderImageStep{
   780  				builder: builder,
   781  				docker:  builder.docker,
   782  				fs:      builder.fs,
   783  				tar:     builder.tar,
   784  			},
   785  			&startRuntimeImageAndUploadFilesStep{
   786  				builder: builder,
   787  				docker:  builder.docker,
   788  				fs:      builder.fs,
   789  			},
   790  		}
   791  		builder.postExecutorSecondStageSteps = []postExecutorStep{
   792  			&commitImageStep{
   793  				image:   builder.config.RuntimeImage,
   794  				builder: builder,
   795  				docker:  builder.docker,
   796  				tar:     builder.tar,
   797  			},
   798  			&reportSuccessStep{
   799  				builder: builder,
   800  			},
   801  		}
   802  	}
   803  }
   804  
   805  // uploadInjectionResult uploads a result file to the s2i container, indicating
   806  // that the injections have completed. If a non-nil error is passed in, it is returned
   807  // to ensure the error status of the injection upload is reported.
   808  func (builder *STI) uploadInjectionResult(startErr error, containerID string) error {
   809  	resultFile, err := utils.CreateInjectionResultFile(startErr)
   810  	if len(resultFile) > 0 {
   811  		defer os.Remove(resultFile)
   812  	}
   813  	if err != nil {
   814  		return err
   815  	}
   816  	err = builder.docker.UploadToContainer(builder.fs, resultFile, injectionResultFile, containerID)
   817  	if err != nil {
   818  		return utils.HandleInjectionError(api.VolumeSpec{Source: resultFile, Destination: injectionResultFile}, err)
   819  	}
   820  	return startErr
   821  }
   822  
   823  func isMissingRequirements(text string) bool {
   824  	tarCommand, _ := regexp.MatchString(`.*tar.*not found`, text)
   825  	shCommand, _ := regexp.MatchString(`.*/bin/sh.*no such file or directory`, text)
   826  	return tarCommand || shCommand
   827  }