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