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