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