diff --git a/docs/docs/20-usage/50-environment.md b/docs/docs/20-usage/50-environment.md index b6d2690d9..78cb4bbe7 100644 --- a/docs/docs/20-usage/50-environment.md +++ b/docs/docs/20-usage/50-environment.md @@ -82,6 +82,7 @@ This is the reference list of all environment variables available to your pipeli | | **Current pipeline** | | | `CI_PIPELINE_NUMBER` | pipeline number | `8` | | `CI_PIPELINE_PARENT` | number of parent pipeline | `0` | +| `CI_PIPELINE_STATUS` | state of the workflow right before the step was started | `success`, `failure` | | `CI_PIPELINE_EVENT` | pipeline event (see [`event`](../20-usage/20-workflow-syntax.md#event)) | `push`, `pull_request`, `pull_request_closed`, `pull_request_metadata`, `tag`, `release`, `manual`, `cron` | | `CI_PIPELINE_EVENT_REASON` | exact reason why `pull_request_metadata` event was send. it is forge instance specific and can change | `label_updated`, `milestoned`, `demilestoned`, `assigned`, `edited`, ... | | `CI_PIPELINE_URL` | link to the web UI for the pipeline | `https://ci.example.com/repos/7/pipeline/8` | diff --git a/pipeline/runtime/runtime.go b/pipeline/runtime/runtime.go index a52bee41e..0b0d279fe 100644 --- a/pipeline/runtime/runtime.go +++ b/pipeline/runtime/runtime.go @@ -43,9 +43,8 @@ type Runtime struct { // Cleanup operations should use the runnerCtx passed to Run(). ctx context.Context - tracer tracing.Tracer - tracerLock sync.Mutex - logger logging.Logger + tracer tracing.Tracer + logger logging.Logger uploadWait sync.WaitGroup @@ -63,7 +62,6 @@ func New(spec *backend_types.Config, backend backend_types.Backend, opts ...Opti r.ctx = context.Background() r.taskUUID = ulid.Make().String() r.tracer = tracing.NoOpTracer - r.tracerLock = sync.Mutex{} for _, opt := range opts { opt(r) } diff --git a/pipeline/runtime/runtime_test.go b/pipeline/runtime/runtime_test.go index 01d913bf6..d327b6adf 100644 --- a/pipeline/runtime/runtime_test.go +++ b/pipeline/runtime/runtime_test.go @@ -271,18 +271,20 @@ func TestWorkflowWithServiceStep(t *testing.T) { assert.Greater(t, testExit.Workflow.Started, int64(0)) // Strip runtime-injected env for a structural comparison of the step itself. - delete(testExit.CurrStep.Environment, "CI_PIPELINE_STARTED") delete(testExit.CurrStep.Environment, "CI_STEP_STARTED") delete(testExit.CurrStep.Environment, dummy.EnvKeyStepSleep) assert.EqualValues(t, state.State{ Workflow: state.Workflow{Started: testExit.Workflow.Started}, CurrStep: &backend_types.Step{ - Name: "test", - UUID: "test-uuid", - Type: "commands", - OnSuccess: true, - Environment: map[string]string{}, - Commands: []string{"echo test"}, + Name: "test", + UUID: "test-uuid", + Type: "commands", + OnSuccess: true, + Environment: map[string]string{ + "CI_PIPELINE_STARTED": fmt.Sprintf("%d", r.started), + "CI_PIPELINE_STATUS": "success", + }, + Commands: []string{"echo test"}, }, CurrStepState: backend_types.State{ Started: testExit.CurrStepState.Started, @@ -478,6 +480,7 @@ func TestWorkflowPluginStep(t *testing.T) { delete(lastPluginTrace.CurrStep.Environment, "CI_STEP_STARTED") assert.EqualValues(t, map[string]string{ + "CI_PIPELINE_STATUS": "success", "DRONE_BUILD_STATUS": "success", "DRONE_REPO_SCM": "git", "EXPECT_TYPE": "plugin", diff --git a/pipeline/runtime/step.go b/pipeline/runtime/step.go index 482440314..efae86c65 100644 --- a/pipeline/runtime/step.go +++ b/pipeline/runtime/step.go @@ -17,6 +17,7 @@ package runtime import ( "context" "errors" + "fmt" "strconv" "sync" "time" @@ -45,9 +46,9 @@ func (r *Runtime) executeStep(runnerCtx context.Context, step *backend_types.Ste return err } - // Add compatibility environment variables for drone-ci plugins. - if step.Type == backend_types.StepTypePlugin { - metadata.SetDroneEnviron(step.Environment) + // Set runtime specific step environment + if err := r.setStepEnv(step); err != nil { + return err } logger.Debug().Str("step", step.Name).Msg("executing") @@ -83,6 +84,29 @@ func (r *Runtime) shouldSkipStep(step *backend_types.Step) bool { return false } +// setStepEnv sets runtime specific step environment variables. +// It also adds the drone plugin compatibility layer. +func (r *Runtime) setStepEnv(step *backend_types.Step) error { + if step.Environment == nil { + return fmt.Errorf("step %q (%q) has no environment variables initialized", step.Name, step.UUID) + } + + // Add compatibility environment variables for drone-ci plugins. + if step.Type == backend_types.StepTypePlugin { + metadata.SetDroneEnviron(step.Environment) + } + + if r.err.Get() != nil { + step.Environment["CI_PIPELINE_STATUS"] = "failure" + } else { + step.Environment["CI_PIPELINE_STATUS"] = "success" + } + step.Environment["CI_PIPELINE_STARTED"] = strconv.FormatInt(r.started, 10) + step.Environment["CI_STEP_STARTED"] = strconv.FormatInt(time.Now().Unix(), 10) + + return nil +} + // startStep starts the step container and spawns a goroutine to stream its logs. // It returns: // - waitForLogs: must be called before WaitStep — it blocks until the log stream @@ -220,7 +244,7 @@ func (r *Runtime) runDetachedStep(runnerCtx context.Context, step *backend_types err = pipeline_errors.ErrCancel } if err != nil { - logger.Error().Err(err).Str("step", step.Name).Msg("detached step failed after while running") + logger.Error().Err(err).Str("step", step.Name).Msg("detached step failed while running") } if traceErr := r.traceStep(processState, err, step); traceErr != nil { @@ -257,22 +281,6 @@ func (r *Runtime) traceStep(processState *backend_types.State, err error, step * // processState == nil && err == nil: step just started, leave s.CurrStepState zero-valued. } - // The traceStep should just trace changes, but it currently also updates step env vars. - { - r.tracerLock.Lock() - defer r.tracerLock.Unlock() - - if s.CurrStepState.Exited { - if s.CurrStep.Environment == nil { - s.CurrStep.Environment = map[string]string{} - } - - // TODO: find better way to insert runtime step environment variables. - s.CurrStep.Environment["CI_PIPELINE_STARTED"] = strconv.FormatInt(s.Workflow.Started, 10) - s.CurrStep.Environment["CI_STEP_STARTED"] = strconv.FormatInt(s.Workflow.Started, 10) - } - } - if traceErr := r.tracer.Trace(s); traceErr != nil { logger := r.makeLogger() logger.Error().Err(traceErr).Msg("could not trace step state change")