github.com/nektos/act@v0.2.63/pkg/common/executor.go (about) 1 package common 2 3 import ( 4 "context" 5 "fmt" 6 7 log "github.com/sirupsen/logrus" 8 ) 9 10 // Warning that implements `error` but safe to ignore 11 type Warning struct { 12 Message string 13 } 14 15 // Error the contract for error 16 func (w Warning) Error() string { 17 return w.Message 18 } 19 20 // Warningf create a warning 21 func Warningf(format string, args ...interface{}) Warning { 22 w := Warning{ 23 Message: fmt.Sprintf(format, args...), 24 } 25 return w 26 } 27 28 // Executor define contract for the steps of a workflow 29 type Executor func(ctx context.Context) error 30 31 // Conditional define contract for the conditional predicate 32 type Conditional func(ctx context.Context) bool 33 34 // NewInfoExecutor is an executor that logs messages 35 func NewInfoExecutor(format string, args ...interface{}) Executor { 36 return func(ctx context.Context) error { 37 logger := Logger(ctx) 38 logger.Infof(format, args...) 39 return nil 40 } 41 } 42 43 // NewDebugExecutor is an executor that logs messages 44 func NewDebugExecutor(format string, args ...interface{}) Executor { 45 return func(ctx context.Context) error { 46 logger := Logger(ctx) 47 logger.Debugf(format, args...) 48 return nil 49 } 50 } 51 52 // NewPipelineExecutor creates a new executor from a series of other executors 53 func NewPipelineExecutor(executors ...Executor) Executor { 54 if len(executors) == 0 { 55 return func(ctx context.Context) error { 56 return nil 57 } 58 } 59 var rtn Executor 60 for _, executor := range executors { 61 if rtn == nil { 62 rtn = executor 63 } else { 64 rtn = rtn.Then(executor) 65 } 66 } 67 return rtn 68 } 69 70 // NewConditionalExecutor creates a new executor based on conditions 71 func NewConditionalExecutor(conditional Conditional, trueExecutor Executor, falseExecutor Executor) Executor { 72 return func(ctx context.Context) error { 73 if conditional(ctx) { 74 if trueExecutor != nil { 75 return trueExecutor(ctx) 76 } 77 } else { 78 if falseExecutor != nil { 79 return falseExecutor(ctx) 80 } 81 } 82 return nil 83 } 84 } 85 86 // NewErrorExecutor creates a new executor that always errors out 87 func NewErrorExecutor(err error) Executor { 88 return func(ctx context.Context) error { 89 return err 90 } 91 } 92 93 // NewParallelExecutor creates a new executor from a parallel of other executors 94 func NewParallelExecutor(parallel int, executors ...Executor) Executor { 95 return func(ctx context.Context) error { 96 work := make(chan Executor, len(executors)) 97 errs := make(chan error, len(executors)) 98 99 if 1 > parallel { 100 log.Debugf("Parallel tasks (%d) below minimum, setting to 1", parallel) 101 parallel = 1 102 } 103 104 for i := 0; i < parallel; i++ { 105 go func(work <-chan Executor, errs chan<- error) { 106 for executor := range work { 107 errs <- executor(ctx) 108 } 109 }(work, errs) 110 } 111 112 for i := 0; i < len(executors); i++ { 113 work <- executors[i] 114 } 115 close(work) 116 117 // Executor waits all executors to cleanup these resources. 118 var firstErr error 119 for i := 0; i < len(executors); i++ { 120 err := <-errs 121 if firstErr == nil { 122 firstErr = err 123 } 124 } 125 126 if err := ctx.Err(); err != nil { 127 return err 128 } 129 return firstErr 130 } 131 } 132 133 // Then runs another executor if this executor succeeds 134 func (e Executor) Then(then Executor) Executor { 135 return func(ctx context.Context) error { 136 err := e(ctx) 137 if err != nil { 138 switch err.(type) { 139 case Warning: 140 Logger(ctx).Warning(err.Error()) 141 default: 142 return err 143 } 144 } 145 if ctx.Err() != nil { 146 return ctx.Err() 147 } 148 return then(ctx) 149 } 150 } 151 152 // If only runs this executor if conditional is true 153 func (e Executor) If(conditional Conditional) Executor { 154 return func(ctx context.Context) error { 155 if conditional(ctx) { 156 return e(ctx) 157 } 158 return nil 159 } 160 } 161 162 // IfNot only runs this executor if conditional is true 163 func (e Executor) IfNot(conditional Conditional) Executor { 164 return func(ctx context.Context) error { 165 if !conditional(ctx) { 166 return e(ctx) 167 } 168 return nil 169 } 170 } 171 172 // IfBool only runs this executor if conditional is true 173 func (e Executor) IfBool(conditional bool) Executor { 174 return e.If(func(ctx context.Context) bool { 175 return conditional 176 }) 177 } 178 179 // Finally adds an executor to run after other executor 180 func (e Executor) Finally(finally Executor) Executor { 181 return func(ctx context.Context) error { 182 err := e(ctx) 183 err2 := finally(ctx) 184 if err2 != nil { 185 return fmt.Errorf("Error occurred running finally: %v (original error: %v)", err2, err) 186 } 187 return err 188 } 189 } 190 191 // Not return an inverted conditional 192 func (c Conditional) Not() Conditional { 193 return func(ctx context.Context) bool { 194 return !c(ctx) 195 } 196 }