2017-03-05 09:56:08 +02:00
|
|
|
package pipeline
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-11-25 21:43:31 +02:00
|
|
|
"strings"
|
2017-03-05 09:56:08 +02:00
|
|
|
"time"
|
|
|
|
|
2021-11-23 16:36:52 +02:00
|
|
|
"github.com/rs/zerolog/log"
|
2017-03-05 09:56:08 +02:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
2021-09-24 13:18:34 +02:00
|
|
|
"github.com/woodpecker-ci/woodpecker/pipeline/backend"
|
|
|
|
"github.com/woodpecker-ci/woodpecker/pipeline/multipart"
|
2017-03-05 09:56:08 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
// State defines the pipeline and process state.
|
|
|
|
State struct {
|
|
|
|
// Global state of the pipeline.
|
|
|
|
Pipeline struct {
|
|
|
|
// Pipeline time started
|
|
|
|
Time int64 `json:"time"`
|
|
|
|
// Current pipeline step
|
|
|
|
Step *backend.Step `json:"step"`
|
|
|
|
// Current pipeline error state
|
|
|
|
Error error `json:"error"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Current process state.
|
|
|
|
Process *backend.State
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Runtime is a configuration runtime.
|
|
|
|
type Runtime struct {
|
|
|
|
err error
|
|
|
|
spec *backend.Config
|
|
|
|
engine backend.Engine
|
|
|
|
started int64
|
|
|
|
|
|
|
|
ctx context.Context
|
|
|
|
tracer Tracer
|
|
|
|
logger Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a new runtime using the specified runtime
|
|
|
|
// configuration and runtime engine.
|
|
|
|
func New(spec *backend.Config, opts ...Option) *Runtime {
|
|
|
|
r := new(Runtime)
|
|
|
|
r.spec = spec
|
|
|
|
r.ctx = context.Background()
|
|
|
|
for _, opts := range opts {
|
|
|
|
opts(r)
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run starts the runtime and waits for it to complete.
|
|
|
|
func (r *Runtime) Run() error {
|
|
|
|
defer func() {
|
2021-11-23 16:36:52 +02:00
|
|
|
if err := r.engine.Destroy(r.ctx, r.spec); err != nil {
|
|
|
|
log.Error().Err(err).Msg("could not destroy engine")
|
|
|
|
}
|
2017-03-05 09:56:08 +02:00
|
|
|
}()
|
|
|
|
|
|
|
|
r.started = time.Now().Unix()
|
2018-04-01 20:34:01 +02:00
|
|
|
if err := r.engine.Setup(r.ctx, r.spec); err != nil {
|
2017-03-05 09:56:08 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, stage := range r.spec.Stages {
|
|
|
|
select {
|
|
|
|
case <-r.ctx.Done():
|
|
|
|
return ErrCancel
|
|
|
|
case err := <-r.execAll(stage.Steps):
|
|
|
|
if err != nil {
|
|
|
|
r.err = err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return r.err
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
func (r *Runtime) execAll(procs []*backend.Step) <-chan error {
|
|
|
|
var g errgroup.Group
|
|
|
|
done := make(chan error)
|
|
|
|
|
|
|
|
for _, proc := range procs {
|
|
|
|
proc := proc
|
|
|
|
g.Go(func() error {
|
|
|
|
return r.exec(proc)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
done <- g.Wait()
|
|
|
|
close(done)
|
|
|
|
}()
|
|
|
|
return done
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
func (r *Runtime) exec(proc *backend.Step) error {
|
|
|
|
switch {
|
2021-11-23 16:36:52 +02:00
|
|
|
case r.err != nil && !proc.OnFailure:
|
2017-03-05 09:56:08 +02:00
|
|
|
return nil
|
2021-11-23 16:36:52 +02:00
|
|
|
case r.err == nil && !proc.OnSuccess:
|
2017-03-05 09:56:08 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.tracer != nil {
|
|
|
|
state := new(State)
|
|
|
|
state.Pipeline.Time = r.started
|
|
|
|
state.Pipeline.Error = r.err
|
|
|
|
state.Pipeline.Step = proc
|
|
|
|
state.Process = new(backend.State) // empty
|
|
|
|
if err := r.tracer.Trace(state); err == ErrSkip {
|
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-25 21:43:31 +02:00
|
|
|
// TODO: using DRONE_ will be deprecated with 0.15.0. remove fallback with following release
|
|
|
|
for key, value := range proc.Environment {
|
|
|
|
if strings.HasPrefix(key, "CI_") {
|
|
|
|
proc.Environment[strings.Replace(key, "CI_", "DRONE_", 1)] = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-01 20:34:01 +02:00
|
|
|
if err := r.engine.Exec(r.ctx, proc); err != nil {
|
2017-03-05 09:56:08 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.logger != nil {
|
2018-04-01 20:34:01 +02:00
|
|
|
rc, err := r.engine.Tail(r.ctx, proc)
|
2017-03-05 09:56:08 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
2021-11-23 16:36:52 +02:00
|
|
|
if err := r.logger.Log(proc, multipart.New(rc)); err != nil {
|
|
|
|
log.Error().Err(err).Msg("process logging failed")
|
|
|
|
}
|
|
|
|
_ = rc.Close()
|
2017-03-05 09:56:08 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
if proc.Detached {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-04-01 20:34:01 +02:00
|
|
|
wait, err := r.engine.Wait(r.ctx, proc)
|
2017-03-05 09:56:08 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.tracer != nil {
|
|
|
|
state := new(State)
|
|
|
|
state.Pipeline.Time = r.started
|
|
|
|
state.Pipeline.Error = r.err
|
|
|
|
state.Pipeline.Step = proc
|
|
|
|
state.Process = wait
|
|
|
|
if err := r.tracer.Trace(state); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if wait.OOMKilled {
|
|
|
|
return &OomError{
|
|
|
|
Name: proc.Name,
|
|
|
|
Code: wait.ExitCode,
|
|
|
|
}
|
|
|
|
} else if wait.ExitCode != 0 {
|
|
|
|
return &ExitError{
|
|
|
|
Name: proc.Name,
|
|
|
|
Code: wait.ExitCode,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|