func newMetricStepPayload(step core.Step) *metricStepPayload { return &metricStepPayload{ Owner: step.Owner(), Name: step.Name(), Version: step.Version(), FullName: fmt.Sprintf("%s/%s", step.Owner(), step.Name()), UniqueName: formatUniqueStepName(step), } }
//RecoverInteractive restarts the box with a terminal attached func (b *DockerBox) RecoverInteractive(cwd string, pipeline core.Pipeline, step core.Step) error { // TODO(termie): maybe move the container manipulation outside of here? client := b.client container, err := b.Restart() if err != nil { b.logger.Panicln("box restart failed") return err } env := []string{} env = append(env, pipeline.Env().Export()...) env = append(env, pipeline.Env().Hidden.Export()...) env = append(env, step.Env().Export()...) env = append(env, fmt.Sprintf("cd %s", cwd)) cmd := []string{b.cmd} return client.AttachInteractive(container.ID, cmd, env) }
func executePipeline(cmdCtx context.Context, options *core.PipelineOptions, dockerOptions *dockerlocal.DockerOptions, getter pipelineGetter) (*RunnerShared, error) { // Boilerplate soft := NewSoftExit(options.GlobalOptions) logger := util.RootLogger().WithField("Logger", "Main") e, err := core.EmitterFromContext(cmdCtx) if err != nil { return nil, err } f := &util.Formatter{options.GlobalOptions.ShowColors} // Set up the runner r, err := NewRunner(cmdCtx, options, dockerOptions, getter) if err != nil { return nil, err } // Main timer mainTimer := util.NewTimer() timer := util.NewTimer() // These will be emitted at the end of the execution, we're going to be // pessimistic and report that we failed, unless overridden at the end of the // execution. fullPipelineFinisher := r.StartFullPipeline(options) pipelineArgs := &core.FullPipelineFinishedArgs{} defer fullPipelineFinisher.Finish(pipelineArgs) buildFinisher := r.StartBuild(options) buildFinishedArgs := &core.BuildFinishedArgs{Box: nil, Result: "failed"} defer buildFinisher.Finish(buildFinishedArgs) // Debug information DumpOptions(options) // Do some sanity checks before starting err = dockerlocal.RequireDockerEndpoint(dockerOptions) if err != nil { return nil, soft.Exit(err) } // Start copying code logger.Println(f.Info("Executing pipeline")) timer.Reset() _, err = r.EnsureCode() if err != nil { e.Emit(core.Logs, &core.LogsArgs{ Stream: "stderr", Logs: err.Error() + "\n", }) return nil, soft.Exit(err) } err = r.CleanupOldBuilds() if err != nil { e.Emit(core.Logs, &core.LogsArgs{ Stream: "stderr", Logs: err.Error() + "\n", }) } if options.Verbose { logger.Printf(f.Success("Copied working dir", timer.String())) } // Setup environment is still a fairly special step, it needs // to start our boxes and get everything set up logger.Println(f.Info("Running step", "setup environment")) timer.Reset() shared, err := r.SetupEnvironment(cmdCtx) if shared.box != nil { if options.ShouldRemove { defer shared.box.Clean() } defer shared.box.Stop() } if err != nil { logger.Errorln(f.Fail("Step failed", "setup environment", timer.String())) e.Emit(core.Logs, &core.LogsArgs{ Stream: "stderr", Logs: err.Error() + "\n", }) return nil, soft.Exit(err) } if options.Verbose { logger.Printf(f.Success("Step passed", "setup environment", timer.String())) } // Expand our context object box := shared.box buildFinishedArgs.Box = box pipeline := shared.pipeline repoName := pipeline.DockerRepo() tag := pipeline.DockerTag() message := pipeline.DockerMessage() shouldStore := options.ShouldArtifacts // TODO(termie): hack for now, probably can be made into a naive class var storeStep core.Step if shouldStore { storeStep = &core.ExternalStep{ BaseStep: core.NewBaseStep(core.BaseStepOptions{ Name: "store", Owner: "wercker", Version: util.Version(), }), } } e.Emit(core.BuildStepsAdded, &core.BuildStepsAddedArgs{ Build: pipeline, Steps: pipeline.Steps(), StoreStep: storeStep, AfterSteps: pipeline.AfterSteps(), }) pr := &core.PipelineResult{ Success: true, FailedStepName: "", FailedStepMessage: "", } // stepCounter starts at 3, step 1 is "get code", step 2 is "setup // environment". stepCounter := &util.Counter{Current: 3} checkpoint := false for _, step := range pipeline.Steps() { // we always want to run the wercker-init step to provide some functions if !checkpoint && stepCounter.Current > 3 { if options.EnableDevSteps && options.Checkpoint != "" { logger.Printf(f.Info("Skipping step", step.DisplayName())) // start at the one after the checkpoint if step.Checkpoint() == options.Checkpoint { logger.Printf(f.Info("Found checkpoint", options.Checkpoint)) checkpoint = true } stepCounter.Increment() continue } } logger.Printf(f.Info("Running step", step.DisplayName())) timer.Reset() sr, err := r.RunStep(shared, step, stepCounter.Increment()) if err != nil { pr.Success = false pr.FailedStepName = step.DisplayName() pr.FailedStepMessage = sr.Message logger.Printf(f.Fail("Step failed", step.DisplayName(), timer.String())) break } if options.EnableDevSteps && step.Checkpoint() != "" { logger.Printf(f.Info("Checkpointing", step.Checkpoint())) box.Commit(box.Repository(), fmt.Sprintf("w-%s", step.Checkpoint()), "checkpoint", false) } if options.Verbose { logger.Printf(f.Success("Step passed", step.DisplayName(), timer.String())) } } if options.ShouldCommit { _, err = box.Commit(repoName, tag, message, true) if err != nil { logger.Errorln("Failed to commit:", err.Error()) } } // We need to wind the counter to where it should be if we failed a step // so that is the number of steps + get code + setup environment + store // TODO(termie): remove all the this "order" stuff completely stepCounter.Current = len(pipeline.Steps()) + 3 if pr.Success && options.ShouldArtifacts { // At this point the build has effectively passed but we can still mess it // up by being unable to deliver the artifacts err = func() error { sr := &StepResult{ Success: false, Artifact: nil, Message: "", PackageURL: "", ExitCode: 1, } finisher := r.StartStep(shared, storeStep, stepCounter.Increment()) defer finisher.Finish(sr) pr.FailedStepName = storeStep.Name() pr.FailedStepMessage = "Unable to store pipeline output" e.Emit(core.Logs, &core.LogsArgs{ Logs: "Storing artifacts\n", }) artifact, err := pipeline.CollectArtifact(shared.containerID) // Ignore ErrEmptyTarball errors if err != util.ErrEmptyTarball { if err != nil { sr.Message = err.Error() e.Emit(core.Logs, &core.LogsArgs{ Logs: fmt.Sprintf("Storing artifacts failed: %s\n", sr.Message), }) return err } e.Emit(core.Logs, &core.LogsArgs{ Logs: fmt.Sprintf("Collecting files from %s\n", artifact.GuestPath), }) ignoredDirectories := []string{".git", "node_modules", "vendor", "site-packages"} nameEmit := func(path string, info os.FileInfo, err error) error { relativePath := strings.TrimPrefix(path, artifact.HostPath) if info.IsDir() { if util.ContainsString(ignoredDirectories, info.Name()) { e.Emit(core.Logs, &core.LogsArgs{ Logs: fmt.Sprintf(".%s/ (content omitted)\n", relativePath), }) return filepath.SkipDir } return nil } e.Emit(core.Logs, &core.LogsArgs{ Logs: fmt.Sprintf(".%s\n", relativePath), }) return nil } err = filepath.Walk(artifact.HostPath, nameEmit) if err != nil { sr.Message = err.Error() e.Emit(core.Logs, &core.LogsArgs{ Logs: fmt.Sprintf("Storing artifacts failed: %s\n", sr.Message), }) return err } tarInfo, err := os.Stat(artifact.HostTarPath) if err != nil { if os.IsNotExist(err) { e.Emit(core.Logs, &core.LogsArgs{ Logs: "No artifacts stored", }) } else { sr.Message = err.Error() e.Emit(core.Logs, &core.LogsArgs{ Logs: fmt.Sprintf("Storing artifacts failed: %s\n", sr.Message), }) return err } } else { size, unit := util.ConvertUnit(tarInfo.Size()) e.Emit(core.Logs, &core.LogsArgs{ Logs: fmt.Sprintf("Total artifact size: %d %s\n", size, unit), }) } if options.ShouldStoreS3 { artificer := dockerlocal.NewArtificer(options, dockerOptions) err = artificer.Upload(artifact) if err != nil { sr.Message = err.Error() e.Emit(core.Logs, &core.LogsArgs{ Logs: fmt.Sprintf("Storing artifacts failed: %s\n", sr.Message), }) return err } } sr.PackageURL = artifact.URL() } else { e.Emit(core.Logs, &core.LogsArgs{ Logs: "No artifacts found\n", }) } e.Emit(core.Logs, &core.LogsArgs{ Logs: "Storing artifacts complete\n", }) sr.Success = true sr.ExitCode = 0 return nil }() if err != nil { pr.Success = false logger.WithField("Error", err).Error("Unable to store pipeline output") } } else { stepCounter.Increment() } // We're sending our build finished but we're not done yet, // now is time to run after-steps if we have any if pr.Success { logger.Println(f.Success("Steps passed", mainTimer.String())) buildFinishedArgs.Result = "passed" } buildFinisher.Finish(buildFinishedArgs) pipelineArgs.MainSuccessful = pr.Success if len(pipeline.AfterSteps()) == 0 { // We're about to end the build, so pull the cache and explode it // into the CacheDir if !options.DirectMount { timer.Reset() err = pipeline.CollectCache(shared.containerID) if err != nil { logger.WithField("Error", err).Error("Unable to store cache") } if options.Verbose { logger.Printf(f.Success("Exported Cache", timer.String())) } } if pr.Success { logger.Println(f.Success("Pipeline finished", mainTimer.String())) } else { logger.Println(f.Fail("Pipeline failed", mainTimer.String())) } if !pr.Success { return nil, fmt.Errorf("Step failed: %s", pr.FailedStepName) } return shared, nil } pipelineArgs.RanAfterSteps = true logger.Println(f.Info("Starting after-steps")) // The container may have died, either way we'll have a fresh env container, err := box.Restart() if err != nil { logger.Panicln(err) } newSessCtx, newSess, err := r.GetSession(cmdCtx, container.ID) if err != nil { logger.Panicln(err) } newShared := &RunnerShared{ box: shared.box, pipeline: shared.pipeline, sess: newSess, sessionCtx: newSessCtx, containerID: shared.containerID, config: shared.config, } // Set up the base environment err = pipeline.ExportEnvironment(newSessCtx, newSess) if err != nil { return nil, err } // Add the After-Step parts err = pr.ExportEnvironment(newSessCtx, newSess) if err != nil { return nil, err } for _, step := range pipeline.AfterSteps() { logger.Println(f.Info("Running after-step", step.DisplayName())) timer.Reset() _, err := r.RunStep(newShared, step, stepCounter.Increment()) if err != nil { logger.Println(f.Fail("After-step failed", step.DisplayName(), timer.String())) break } logger.Println(f.Success("After-step passed", step.DisplayName(), timer.String())) } // We're about to end the build, so pull the cache and explode it // into the CacheDir if !options.DirectMount { timer.Reset() err = pipeline.CollectCache(newShared.containerID) if err != nil { logger.WithField("Error", err).Error("Unable to store cache") } if options.Verbose { logger.Printf(f.Success("Exported Cache", timer.String())) } } if pr.Success { logger.Println(f.Success("Pipeline finished", mainTimer.String())) } else { logger.Println(f.Fail("Pipeline failed", mainTimer.String())) } if !pr.Success { return nil, fmt.Errorf("Step failed: %s", pr.FailedStepName) } pipelineArgs.AfterStepSuccessful = pr.Success return shared, nil }
// RunStep runs a step and tosses error if it fails func (p *Runner) RunStep(shared *RunnerShared, step core.Step, order int) (*StepResult, error) { finisher := p.StartStep(shared, step, order) sr := &StepResult{ Success: false, Artifact: nil, Message: "", ExitCode: 1, } defer finisher.Finish(sr) if step.ShouldSyncEnv() { err := shared.pipeline.SyncEnvironment(shared.sessionCtx, shared.sess) if err != nil { // If an error occured, just log and ignore it p.logger.WithField("Error", err).Warn("Unable to sync environment") } } step.InitEnv(shared.pipeline.Env()) p.logger.Debugln("Step Environment") for _, pair := range step.Env().Ordered() { p.logger.Debugln(" ", pair[0], pair[1]) } exit, err := step.Execute(shared.sessionCtx, shared.sess) if exit != 0 { sr.ExitCode = exit if p.options.AttachOnError { shared.box.RecoverInteractive( p.options.SourcePath(), shared.pipeline, step, ) } } else if err == nil { sr.Success = true sr.ExitCode = 0 } // Grab the message var message bytes.Buffer messageErr := step.CollectFile(shared.containerID, step.ReportPath(), "message.txt", &message) if messageErr != nil { if messageErr != util.ErrEmptyTarball { return sr, messageErr } } sr.Message = message.String() // This is the error from the step.Execute above if err != nil { if sr.Message == "" { sr.Message = err.Error() } return sr, err } // Grab artifacts if we want them if p.options.ShouldArtifacts { artifact, err := step.CollectArtifact(shared.containerID) if err != nil { return sr, err } if artifact != nil { artificer := dockerlocal.NewArtificer(p.options, p.dockerOptions) err = artificer.Upload(artifact) if err != nil { return sr, err } } sr.Artifact = artifact } if !sr.Success { return sr, fmt.Errorf("Step failed with exit code: %d", sr.ExitCode) } return sr, nil }
func formatUniqueStepName(step core.Step) string { return fmt.Sprintf("%s/%s@%s", step.Owner(), step.Name(), step.Version()) }