// 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) }