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  }