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  }