You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
runner-go/pipeline/state.go

340 lines
6.7 KiB
Go

// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.
package pipeline
import (
"sync"
"time"
"git.awesome-for.me/liuzhiguo/drone-go/drone"
)
// State stores the pipeline state.
type State struct {
sync.Mutex
Build *drone.Build
Repo *drone.Repo
Stage *drone.Stage
System *drone.System
}
// Cancel cancels the pipeline.
func (s *State) Cancel() {
s.Lock()
s.skipall()
s.killall()
s.update()
s.Unlock()
}
// Cancelled returns true if the pipeline is cancelled.
func (s *State) Cancelled() bool {
s.Lock()
v := s.killed()
s.Unlock()
return v
}
// Fail fails the named pipeline step with error.
func (s *State) Fail(name string, err error) {
s.Lock()
v := s.find(name)
s.fail(v, err)
s.update()
s.Unlock()
}
// FailAll fails the entire pipeline.
func (s *State) FailAll(err error) {
s.Lock()
s.failAll(err)
s.skipall()
s.update()
s.Unlock()
}
// Failed returns true if the pipeline failed.
func (s *State) Failed() bool {
s.Lock()
v := s.failed()
s.Unlock()
return v
}
// Skip skips the named pipeline step.
func (s *State) Skip(name string) {
s.Lock()
v := s.find(name)
s.skip(v)
s.update()
s.Unlock()
}
// SkipAll skips all pipeline steps.
func (s *State) SkipAll() {
s.Lock()
s.skipall()
s.update()
s.Unlock()
}
// Start sets the named pipeline step to started.
func (s *State) Start(name string) {
s.Lock()
v := s.find(name)
s.start(v)
s.Unlock()
}
// Finish sets the pipeline step to finished.
func (s *State) Finish(name string, code int) {
s.Lock()
v := s.find(name)
s.finish(v, code)
s.update()
s.Unlock()
}
// FinishAll finishes all pipeline steps.
func (s *State) FinishAll() {
s.Lock()
s.finishAll()
s.update()
s.Unlock()
}
// Finished returns true if the step is finished.
func (s *State) Finished(name string) bool {
s.Lock()
v := s.find(name)
d := s.finished(v)
s.Unlock()
return d
}
// Find returns the named pipeline step.
func (s *State) Find(name string) *drone.Step {
s.Lock()
v := s.find(name)
s.Unlock()
return v
}
//
// Helper functions. INTERNAL USE ONLY
//
// helper function skips all pipeline steps.
func (s *State) skipall() {
for _, v := range s.Stage.Steps {
s.skip(v)
}
}
// helper function that updates the state of an individual step
// to indicate the step to skipped.
func (s *State) skip(v *drone.Step) {
if v.Status == drone.StatusPending {
v.Started = time.Now().Unix()
v.Stopped = time.Now().Unix()
v.Status = drone.StatusSkipped
v.ExitCode = 0
v.Error = ""
}
}
// helper function kills all pipeline steps.
func (s *State) killall() {
s.Stage.Error = ""
s.Stage.ExitCode = 0
s.Stage.Status = drone.StatusKilled
s.Stage.Stopped = time.Now().Unix()
if s.Stage.Started == 0 {
s.Stage.Started = s.Stage.Stopped
}
for _, v := range s.Stage.Steps {
s.kill(v)
}
}
// helper function that updates the state of an individual step
// to indicate the step to killed.
func (s *State) kill(v *drone.Step) {
if v.Status == drone.StatusRunning {
v.Status = drone.StatusKilled
v.Stopped = time.Now().Unix()
v.ExitCode = 137
v.Error = ""
if v.Started == 0 {
v.Started = v.Stopped
}
}
}
// helper function returns true if the overall pipeline status
// is killed.
func (s *State) killed() bool {
return s.Stage.Status == drone.StatusKilled
}
// helper function that updates the state of an individual step
// to indicate the step is started.
func (s *State) start(v *drone.Step) {
if v.Status == drone.StatusPending {
v.Status = drone.StatusRunning
v.Started = time.Now().Unix()
v.Stopped = 0
v.ExitCode = 0
v.Error = ""
}
}
// helper function updates the state of an individual step
// based on the exit code.
func (s *State) finish(v *drone.Step, code int) {
switch v.Status {
case drone.StatusRunning, drone.StatusPending:
default:
return
}
v.ExitCode = code
v.Stopped = time.Now().Unix()
if v.Started == 0 {
v.Started = v.Stopped
}
switch code {
case 0, 78:
v.Status = drone.StatusPassing
default:
v.Status = drone.StatusFailing
}
}
// helper function returns true if the step is finished.
func (s *State) finished(v *drone.Step) bool {
switch v.Status {
case drone.StatusRunning, drone.StatusPending:
return false
default:
return true
}
}
// helper function finishes all pipeline steps.
func (s *State) finishAll() {
for _, v := range s.Stage.Steps {
switch v.Status {
case drone.StatusPending:
s.skip(v)
case drone.StatusRunning:
s.finish(v, 0)
}
}
switch s.Stage.Status {
case drone.StatusRunning, drone.StatusPending:
s.Stage.Stopped = time.Now().Unix()
s.Stage.Status = drone.StatusPassing
if s.Stage.Started == 0 {
s.Stage.Started = s.Stage.Stopped
}
if s.failed() {
s.Stage.Status = drone.StatusFailing
}
default:
if s.Stage.Started == 0 {
s.Stage.Started = time.Now().Unix()
}
if s.Stage.Stopped == 0 {
s.Stage.Stopped = time.Now().Unix()
}
}
}
// helper function fails an individual step.
func (s *State) fail(v *drone.Step, err error) {
v.Status = drone.StatusError
v.Error = err.Error()
v.ExitCode = 255
v.Stopped = time.Now().Unix()
if v.Started == 0 {
v.Started = v.Stopped
}
}
// helper function fails the overall pipeline.
func (s *State) failAll(err error) {
switch s.Stage.Status {
case drone.StatusPending,
drone.StatusRunning:
s.Stage.Status = drone.StatusError
s.Stage.Error = err.Error()
s.Stage.Stopped = time.Now().Unix()
s.Stage.ExitCode = 255
if s.Stage.Started == 0 {
s.Stage.Started = s.Stage.Stopped
}
}
}
// helper function returns true if the overall pipeline status
// is failing.
func (s *State) failed() bool {
switch s.Stage.Status {
case drone.StatusFailing,
drone.StatusError,
drone.StatusKilled:
return true
}
for _, v := range s.Stage.Steps {
if v.ErrIgnore {
continue
}
switch v.Status {
case drone.StatusFailing,
drone.StatusError,
drone.StatusKilled:
return true
}
}
return false
}
// helper function updates the build and stage based on the
// aggregate
func (s *State) update() {
for _, v := range s.Stage.Steps {
switch v.Status {
case drone.StatusKilled:
s.Stage.ExitCode = 137
s.Stage.Status = drone.StatusKilled
s.Build.Status = drone.StatusKilled
return
case drone.StatusError:
s.Stage.Error = v.Error
s.Stage.ExitCode = 255
s.Stage.Status = drone.StatusError
s.Build.Status = drone.StatusError
return
case drone.StatusFailing:
if v.ErrIgnore == false {
s.Stage.Status = drone.StatusFailing
s.Build.Status = drone.StatusFailing
return
}
}
}
}
// helper function finds the step by name.
func (s *State) find(name string) *drone.Step {
for _, step := range s.Stage.Steps {
if step.Name == name {
return step
}
}
panic("step not found: " + name)
}