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 }