github.com/drone/runner-go@v1.12.0/pipeline/state.go (about)

     1  // Copyright 2019 Drone.IO Inc. All rights reserved.
     2  // Use of this source code is governed by the Polyform License
     3  // that can be found in the LICENSE file.
     4  
     5  package pipeline
     6  
     7  import (
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/drone/drone-go/drone"
    12  )
    13  
    14  // State stores the pipeline state.
    15  type State struct {
    16  	sync.Mutex
    17  
    18  	Build  *drone.Build
    19  	Repo   *drone.Repo
    20  	Stage  *drone.Stage
    21  	System *drone.System
    22  }
    23  
    24  // Cancel cancels the pipeline.
    25  func (s *State) Cancel() {
    26  	s.Lock()
    27  	s.skipall()
    28  	s.killall()
    29  	s.update()
    30  	s.Unlock()
    31  }
    32  
    33  // Cancelled returns true if the pipeline is cancelled.
    34  func (s *State) Cancelled() bool {
    35  	s.Lock()
    36  	v := s.killed()
    37  	s.Unlock()
    38  	return v
    39  }
    40  
    41  // Fail fails the named pipeline step with error.
    42  func (s *State) Fail(name string, err error) {
    43  	s.Lock()
    44  	v := s.find(name)
    45  	s.fail(v, err)
    46  	s.update()
    47  	s.Unlock()
    48  }
    49  
    50  // FailAll fails the entire pipeline.
    51  func (s *State) FailAll(err error) {
    52  	s.Lock()
    53  	s.failAll(err)
    54  	s.skipall()
    55  	s.update()
    56  	s.Unlock()
    57  }
    58  
    59  // Failed returns true if the pipeline failed.
    60  func (s *State) Failed() bool {
    61  	s.Lock()
    62  	v := s.failed()
    63  	s.Unlock()
    64  	return v
    65  }
    66  
    67  // Skip skips the named pipeline step.
    68  func (s *State) Skip(name string) {
    69  	s.Lock()
    70  	v := s.find(name)
    71  	s.skip(v)
    72  	s.update()
    73  	s.Unlock()
    74  }
    75  
    76  // SkipAll skips all pipeline steps.
    77  func (s *State) SkipAll() {
    78  	s.Lock()
    79  	s.skipall()
    80  	s.update()
    81  	s.Unlock()
    82  }
    83  
    84  // Start sets the named pipeline step to started.
    85  func (s *State) Start(name string) {
    86  	s.Lock()
    87  	v := s.find(name)
    88  	s.start(v)
    89  	s.Unlock()
    90  }
    91  
    92  // Finish sets the pipeline step to finished.
    93  func (s *State) Finish(name string, code int) {
    94  	s.Lock()
    95  	v := s.find(name)
    96  	s.finish(v, code)
    97  	s.update()
    98  	s.Unlock()
    99  }
   100  
   101  // FinishAll finishes all pipeline steps.
   102  func (s *State) FinishAll() {
   103  	s.Lock()
   104  	s.finishAll()
   105  	s.update()
   106  	s.Unlock()
   107  }
   108  
   109  // Finished returns true if the step is finished.
   110  func (s *State) Finished(name string) bool {
   111  	s.Lock()
   112  	v := s.find(name)
   113  	d := s.finished(v)
   114  	s.Unlock()
   115  	return d
   116  }
   117  
   118  // Find returns the named pipeline step.
   119  func (s *State) Find(name string) *drone.Step {
   120  	s.Lock()
   121  	v := s.find(name)
   122  	s.Unlock()
   123  	return v
   124  }
   125  
   126  //
   127  // Helper functions. INTERNAL USE ONLY
   128  //
   129  
   130  // helper function skips all pipeline steps.
   131  func (s *State) skipall() {
   132  	for _, v := range s.Stage.Steps {
   133  		s.skip(v)
   134  	}
   135  }
   136  
   137  // helper function that updates the state of an individual step
   138  // to indicate the step to skipped.
   139  func (s *State) skip(v *drone.Step) {
   140  	if v.Status == drone.StatusPending {
   141  		v.Started = time.Now().Unix()
   142  		v.Stopped = time.Now().Unix()
   143  		v.Status = drone.StatusSkipped
   144  		v.ExitCode = 0
   145  		v.Error = ""
   146  	}
   147  }
   148  
   149  // helper function kills all pipeline steps.
   150  func (s *State) killall() {
   151  	s.Stage.Error = ""
   152  	s.Stage.ExitCode = 0
   153  	s.Stage.Status = drone.StatusKilled
   154  	s.Stage.Stopped = time.Now().Unix()
   155  	if s.Stage.Started == 0 {
   156  		s.Stage.Started = s.Stage.Stopped
   157  	}
   158  	for _, v := range s.Stage.Steps {
   159  		s.kill(v)
   160  	}
   161  }
   162  
   163  // helper function that updates the state of an individual step
   164  // to indicate the step to killed.
   165  func (s *State) kill(v *drone.Step) {
   166  	if v.Status == drone.StatusRunning {
   167  		v.Status = drone.StatusKilled
   168  		v.Stopped = time.Now().Unix()
   169  		v.ExitCode = 137
   170  		v.Error = ""
   171  		if v.Started == 0 {
   172  			v.Started = v.Stopped
   173  		}
   174  	}
   175  }
   176  
   177  // helper function returns true if the overall pipeline status
   178  // is killed.
   179  func (s *State) killed() bool {
   180  	return s.Stage.Status == drone.StatusKilled
   181  }
   182  
   183  // helper function that updates the state of an individual step
   184  // to indicate the step is started.
   185  func (s *State) start(v *drone.Step) {
   186  	if v.Status == drone.StatusPending {
   187  		v.Status = drone.StatusRunning
   188  		v.Started = time.Now().Unix()
   189  		v.Stopped = 0
   190  		v.ExitCode = 0
   191  		v.Error = ""
   192  	}
   193  }
   194  
   195  // helper function updates the state of an individual step
   196  // based on the exit code.
   197  func (s *State) finish(v *drone.Step, code int) {
   198  	switch v.Status {
   199  	case drone.StatusRunning, drone.StatusPending:
   200  	default:
   201  		return
   202  	}
   203  	v.ExitCode = code
   204  	v.Stopped = time.Now().Unix()
   205  	if v.Started == 0 {
   206  		v.Started = v.Stopped
   207  	}
   208  	switch code {
   209  	case 0, 78:
   210  		v.Status = drone.StatusPassing
   211  	default:
   212  		v.Status = drone.StatusFailing
   213  	}
   214  }
   215  
   216  // helper function returns true if the step is finished.
   217  func (s *State) finished(v *drone.Step) bool {
   218  	switch v.Status {
   219  	case drone.StatusRunning, drone.StatusPending:
   220  		return false
   221  	default:
   222  		return true
   223  	}
   224  }
   225  
   226  // helper function finishes all pipeline steps.
   227  func (s *State) finishAll() {
   228  	for _, v := range s.Stage.Steps {
   229  		switch v.Status {
   230  		case drone.StatusPending:
   231  			s.skip(v)
   232  		case drone.StatusRunning:
   233  			s.finish(v, 0)
   234  		}
   235  	}
   236  	switch s.Stage.Status {
   237  	case drone.StatusRunning, drone.StatusPending:
   238  		s.Stage.Stopped = time.Now().Unix()
   239  		s.Stage.Status = drone.StatusPassing
   240  		if s.Stage.Started == 0 {
   241  			s.Stage.Started = s.Stage.Stopped
   242  		}
   243  		if s.failed() {
   244  			s.Stage.Status = drone.StatusFailing
   245  		}
   246  	default:
   247  		if s.Stage.Started == 0 {
   248  			s.Stage.Started = time.Now().Unix()
   249  		}
   250  		if s.Stage.Stopped == 0 {
   251  			s.Stage.Stopped = time.Now().Unix()
   252  		}
   253  	}
   254  }
   255  
   256  // helper function fails an individual step.
   257  func (s *State) fail(v *drone.Step, err error) {
   258  	v.Status = drone.StatusError
   259  	v.Error = err.Error()
   260  	v.ExitCode = 255
   261  	v.Stopped = time.Now().Unix()
   262  	if v.Started == 0 {
   263  		v.Started = v.Stopped
   264  	}
   265  }
   266  
   267  // helper function fails the overall pipeline.
   268  func (s *State) failAll(err error) {
   269  	switch s.Stage.Status {
   270  	case drone.StatusPending,
   271  		drone.StatusRunning:
   272  		s.Stage.Status = drone.StatusError
   273  		s.Stage.Error = err.Error()
   274  		s.Stage.Stopped = time.Now().Unix()
   275  		s.Stage.ExitCode = 255
   276  		if s.Stage.Started == 0 {
   277  			s.Stage.Started = s.Stage.Stopped
   278  		}
   279  	}
   280  }
   281  
   282  // helper function returns true if the overall pipeline status
   283  // is failing.
   284  func (s *State) failed() bool {
   285  	switch s.Stage.Status {
   286  	case drone.StatusFailing,
   287  		drone.StatusError,
   288  		drone.StatusKilled:
   289  		return true
   290  	}
   291  	for _, v := range s.Stage.Steps {
   292  		if v.ErrIgnore {
   293  			continue
   294  		}
   295  		switch v.Status {
   296  		case drone.StatusFailing,
   297  			drone.StatusError,
   298  			drone.StatusKilled:
   299  			return true
   300  		}
   301  	}
   302  	return false
   303  }
   304  
   305  // helper function updates the build and stage based on the
   306  // aggregate
   307  func (s *State) update() {
   308  	for _, v := range s.Stage.Steps {
   309  		switch v.Status {
   310  		case drone.StatusKilled:
   311  			s.Stage.ExitCode = 137
   312  			s.Stage.Status = drone.StatusKilled
   313  			s.Build.Status = drone.StatusKilled
   314  			return
   315  		case drone.StatusError:
   316  			s.Stage.Error = v.Error
   317  			s.Stage.ExitCode = 255
   318  			s.Stage.Status = drone.StatusError
   319  			s.Build.Status = drone.StatusError
   320  			return
   321  		case drone.StatusFailing:
   322  			if v.ErrIgnore == false {
   323  				s.Stage.Status = drone.StatusFailing
   324  				s.Build.Status = drone.StatusFailing
   325  				return
   326  			}
   327  		}
   328  	}
   329  }
   330  
   331  // helper function finds the step by name.
   332  func (s *State) find(name string) *drone.Step {
   333  	for _, step := range s.Stage.Steps {
   334  		if step.Name == name {
   335  			return step
   336  		}
   337  	}
   338  	panic("step not found: " + name)
   339  }