2020-02-13 01:27:37 -06:00
|
|
|
package runner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
2021-12-08 14:57:42 -06:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2020-02-13 01:27:37 -06:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
2021-08-09 10:19:10 -05:00
|
|
|
"strconv"
|
2020-02-13 01:27:37 -06:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/robertkrimen/otto"
|
2020-03-06 15:39:01 -06:00
|
|
|
log "github.com/sirupsen/logrus"
|
2020-02-13 01:27:37 -06:00
|
|
|
)
|
|
|
|
|
2021-02-08 11:14:12 -06:00
|
|
|
var expressionPattern, operatorPattern *regexp.Regexp
|
2020-02-13 01:27:37 -06:00
|
|
|
|
|
|
|
func init() {
|
2020-06-24 09:08:45 -05:00
|
|
|
expressionPattern = regexp.MustCompile(`\${{\s*(.+?)\s*}}`)
|
2020-11-17 11:31:05 -06:00
|
|
|
operatorPattern = regexp.MustCompile("^[!=><|&]+$")
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewExpressionEvaluator creates a new evaluator
|
|
|
|
func (rc *RunContext) NewExpressionEvaluator() ExpressionEvaluator {
|
|
|
|
vm := rc.newVM()
|
2021-12-08 14:57:42 -06:00
|
|
|
|
2020-02-13 01:27:37 -06:00
|
|
|
return &expressionEvaluator{
|
|
|
|
vm,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-23 17:01:25 -06:00
|
|
|
// NewExpressionEvaluator creates a new evaluator
|
|
|
|
func (sc *StepContext) NewExpressionEvaluator() ExpressionEvaluator {
|
|
|
|
vm := sc.RunContext.newVM()
|
2020-02-14 02:41:20 -06:00
|
|
|
configers := []func(*otto.Otto){
|
2020-02-23 17:01:25 -06:00
|
|
|
sc.vmEnv(),
|
2021-12-08 14:57:42 -06:00
|
|
|
sc.vmNeeds(),
|
|
|
|
sc.vmSuccess(),
|
|
|
|
sc.vmFailure(),
|
2020-02-14 02:41:20 -06:00
|
|
|
}
|
|
|
|
for _, configer := range configers {
|
|
|
|
configer(vm)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &expressionEvaluator{
|
|
|
|
vm,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-13 01:27:37 -06:00
|
|
|
// ExpressionEvaluator is the interface for evaluating expressions
|
|
|
|
type ExpressionEvaluator interface {
|
2020-11-17 11:31:05 -06:00
|
|
|
Evaluate(string) (string, bool, error)
|
2020-02-14 02:41:20 -06:00
|
|
|
Interpolate(string) string
|
2020-11-17 11:31:05 -06:00
|
|
|
InterpolateWithStringCheck(string) (string, bool)
|
2020-06-24 09:08:45 -05:00
|
|
|
Rewrite(string) string
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type expressionEvaluator struct {
|
|
|
|
vm *otto.Otto
|
|
|
|
}
|
|
|
|
|
2020-11-17 11:31:05 -06:00
|
|
|
func (ee *expressionEvaluator) Evaluate(in string) (string, bool, error) {
|
2021-01-14 23:37:38 -06:00
|
|
|
if strings.HasPrefix(in, `secrets.`) {
|
|
|
|
in = `secrets.` + strings.ToUpper(strings.SplitN(in, `.`, 2)[1])
|
2021-01-12 11:54:53 -06:00
|
|
|
}
|
2020-06-24 09:08:45 -05:00
|
|
|
re := ee.Rewrite(in)
|
|
|
|
if re != in {
|
2020-11-12 10:15:09 -06:00
|
|
|
log.Debugf("Evaluating '%s' instead of '%s'", re, in)
|
2020-06-24 09:08:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
val, err := ee.vm.Run(re)
|
2020-02-13 01:27:37 -06:00
|
|
|
if err != nil {
|
2020-11-17 11:31:05 -06:00
|
|
|
return "", false, err
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
2020-03-12 19:22:33 -05:00
|
|
|
if val.IsNull() || val.IsUndefined() {
|
2020-11-17 11:31:05 -06:00
|
|
|
return "", false, nil
|
2020-03-12 19:22:33 -05:00
|
|
|
}
|
2020-11-17 11:31:05 -06:00
|
|
|
valAsString, err := val.ToString()
|
|
|
|
if err != nil {
|
|
|
|
return "", false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return valAsString, val.IsString(), err
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
|
|
|
|
2021-01-14 23:37:38 -06:00
|
|
|
func (ee *expressionEvaluator) Interpolate(in string) string {
|
2020-11-17 11:31:05 -06:00
|
|
|
interpolated, _ := ee.InterpolateWithStringCheck(in)
|
|
|
|
return interpolated
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ee *expressionEvaluator) InterpolateWithStringCheck(in string) (string, bool) {
|
2020-02-13 01:27:37 -06:00
|
|
|
errList := make([]error, 0)
|
2020-03-12 19:22:33 -05:00
|
|
|
|
|
|
|
out := in
|
2020-11-17 11:31:05 -06:00
|
|
|
isString := false
|
2020-03-12 19:22:33 -05:00
|
|
|
for {
|
2020-06-24 09:08:45 -05:00
|
|
|
out = expressionPattern.ReplaceAllStringFunc(in, func(match string) string {
|
|
|
|
// Extract and trim the actual expression inside ${{...}} delimiters
|
|
|
|
expression := expressionPattern.ReplaceAllString(match, "$1")
|
2020-11-12 10:15:09 -06:00
|
|
|
|
2020-06-24 09:08:45 -05:00
|
|
|
// Evaluate the expression and retrieve errors if any
|
2020-11-17 11:31:05 -06:00
|
|
|
evaluated, evaluatedIsString, err := ee.Evaluate(expression)
|
2020-03-12 19:22:33 -05:00
|
|
|
if err != nil {
|
|
|
|
errList = append(errList, err)
|
|
|
|
}
|
2020-11-17 11:31:05 -06:00
|
|
|
isString = evaluatedIsString
|
2020-03-12 19:22:33 -05:00
|
|
|
return evaluated
|
|
|
|
})
|
|
|
|
if len(errList) > 0 {
|
2020-11-12 10:15:09 -06:00
|
|
|
log.Errorf("Unable to interpolate string '%s' - %v", in, errList)
|
2020-03-12 19:22:33 -05:00
|
|
|
break
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
2020-03-12 19:22:33 -05:00
|
|
|
if out == in {
|
2020-06-24 09:08:45 -05:00
|
|
|
// No replacement occurred, we're done!
|
2020-03-12 19:22:33 -05:00
|
|
|
break
|
|
|
|
}
|
|
|
|
in = out
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
2020-11-17 11:31:05 -06:00
|
|
|
return out, isString
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
|
|
|
|
2020-06-24 09:08:45 -05:00
|
|
|
// Rewrite tries to transform any javascript property accessor into its bracket notation.
|
|
|
|
// For instance, "object.property" would become "object['property']".
|
|
|
|
func (ee *expressionEvaluator) Rewrite(in string) string {
|
2021-02-08 11:14:12 -06:00
|
|
|
var buf strings.Builder
|
|
|
|
r := strings.NewReader(in)
|
2020-06-24 09:08:45 -05:00
|
|
|
for {
|
2021-02-08 11:14:12 -06:00
|
|
|
c, _, err := r.ReadRune()
|
|
|
|
if err == io.EOF {
|
2020-06-24 09:08:45 -05:00
|
|
|
break
|
|
|
|
}
|
2021-02-08 11:14:12 -06:00
|
|
|
//nolint
|
|
|
|
switch {
|
|
|
|
default:
|
|
|
|
buf.WriteRune(c)
|
|
|
|
case c == '\'':
|
|
|
|
buf.WriteRune(c)
|
|
|
|
ee.advString(&buf, r)
|
|
|
|
case c == '.':
|
|
|
|
buf.WriteString("['")
|
|
|
|
ee.advPropertyName(&buf, r)
|
|
|
|
buf.WriteString("']")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*expressionEvaluator) advString(w *strings.Builder, r *strings.Reader) error {
|
|
|
|
for {
|
|
|
|
c, _, err := r.ReadRune()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if c != '\'' {
|
2021-12-22 11:29:43 -06:00
|
|
|
w.WriteRune(c)
|
2021-02-08 11:14:12 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handles a escaped string: ex. 'It''s ok'
|
|
|
|
c, _, err = r.ReadRune()
|
|
|
|
if err != nil {
|
2021-12-22 11:29:43 -06:00
|
|
|
w.WriteString("'")
|
2021-02-08 11:14:12 -06:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if c != '\'' {
|
2021-12-22 11:29:43 -06:00
|
|
|
w.WriteString("'")
|
2021-02-08 11:14:12 -06:00
|
|
|
if err := r.UnreadRune(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-06-24 09:08:45 -05:00
|
|
|
break
|
|
|
|
}
|
2021-12-22 11:29:43 -06:00
|
|
|
w.WriteString(`\'`)
|
2021-02-08 11:14:12 -06:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-06-24 09:08:45 -05:00
|
|
|
|
2021-02-08 11:14:12 -06:00
|
|
|
func (*expressionEvaluator) advPropertyName(w *strings.Builder, r *strings.Reader) error {
|
|
|
|
for {
|
|
|
|
c, _, err := r.ReadRune()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !isLetter(c) {
|
|
|
|
if err := r.UnreadRune(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
2021-12-22 11:29:43 -06:00
|
|
|
w.WriteRune(c)
|
2020-06-24 09:08:45 -05:00
|
|
|
}
|
2021-02-08 11:14:12 -06:00
|
|
|
return nil
|
|
|
|
}
|
2020-06-24 09:08:45 -05:00
|
|
|
|
2021-02-08 11:14:12 -06:00
|
|
|
func isLetter(c rune) bool {
|
|
|
|
switch {
|
|
|
|
case c >= 'a' && c <= 'z':
|
|
|
|
return true
|
|
|
|
case c >= 'A' && c <= 'Z':
|
|
|
|
return true
|
|
|
|
case c >= '0' && c <= '9':
|
|
|
|
return true
|
|
|
|
case c == '_' || c == '-':
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
2020-06-24 09:08:45 -05:00
|
|
|
}
|
|
|
|
|
2020-02-13 01:27:37 -06:00
|
|
|
func (rc *RunContext) newVM() *otto.Otto {
|
|
|
|
configers := []func(*otto.Otto){
|
|
|
|
vmContains,
|
|
|
|
vmStartsWith,
|
|
|
|
vmEndsWith,
|
|
|
|
vmFormat,
|
|
|
|
vmJoin,
|
|
|
|
vmToJSON,
|
2020-09-01 15:55:29 -05:00
|
|
|
vmFromJSON,
|
2020-02-13 13:47:38 -06:00
|
|
|
vmAlways,
|
2020-02-14 02:41:20 -06:00
|
|
|
rc.vmCancelled(),
|
2020-02-13 13:47:38 -06:00
|
|
|
rc.vmSuccess(),
|
|
|
|
rc.vmFailure(),
|
2020-02-14 02:41:20 -06:00
|
|
|
rc.vmHashFiles(),
|
2020-02-13 13:47:38 -06:00
|
|
|
|
|
|
|
rc.vmGithub(),
|
|
|
|
rc.vmJob(),
|
|
|
|
rc.vmSteps(),
|
|
|
|
rc.vmRunner(),
|
2020-02-14 02:41:20 -06:00
|
|
|
|
|
|
|
rc.vmSecrets(),
|
|
|
|
rc.vmStrategy(),
|
|
|
|
rc.vmMatrix(),
|
2020-05-04 14:18:13 -05:00
|
|
|
rc.vmEnv(),
|
2021-07-01 10:20:20 -05:00
|
|
|
rc.vmNeeds(),
|
2021-12-22 13:19:50 -06:00
|
|
|
rc.vmInputs(),
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
|
|
|
vm := otto.New()
|
|
|
|
for _, configer := range configers {
|
|
|
|
configer(vm)
|
|
|
|
}
|
|
|
|
return vm
|
|
|
|
}
|
|
|
|
|
|
|
|
func vmContains(vm *otto.Otto) {
|
2020-02-13 13:47:38 -06:00
|
|
|
_ = vm.Set("contains", func(searchString interface{}, searchValue string) bool {
|
2020-02-13 01:27:37 -06:00
|
|
|
if searchStringString, ok := searchString.(string); ok {
|
|
|
|
return strings.Contains(strings.ToLower(searchStringString), strings.ToLower(searchValue))
|
|
|
|
} else if searchStringArray, ok := searchString.([]string); ok {
|
|
|
|
for _, s := range searchStringArray {
|
2020-02-13 13:47:38 -06:00
|
|
|
if strings.EqualFold(s, searchValue) {
|
2020-02-13 01:27:37 -06:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func vmStartsWith(vm *otto.Otto) {
|
2020-02-13 13:47:38 -06:00
|
|
|
_ = vm.Set("startsWith", func(searchString string, searchValue string) bool {
|
2020-02-13 01:27:37 -06:00
|
|
|
return strings.HasPrefix(strings.ToLower(searchString), strings.ToLower(searchValue))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func vmEndsWith(vm *otto.Otto) {
|
2020-02-13 13:47:38 -06:00
|
|
|
_ = vm.Set("endsWith", func(searchString string, searchValue string) bool {
|
2020-02-13 01:27:37 -06:00
|
|
|
return strings.HasSuffix(strings.ToLower(searchString), strings.ToLower(searchValue))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func vmFormat(vm *otto.Otto) {
|
2021-08-09 10:19:10 -05:00
|
|
|
_ = vm.Set("format", func(s string, vals ...otto.Value) string {
|
|
|
|
ex := regexp.MustCompile(`(\{[0-9]+\}|\{.?|\}.?)`)
|
|
|
|
return ex.ReplaceAllStringFunc(s, func(seg string) string {
|
|
|
|
switch seg {
|
|
|
|
case "{{":
|
|
|
|
return "{"
|
|
|
|
case "}}":
|
|
|
|
return "}"
|
|
|
|
default:
|
|
|
|
if len(seg) < 3 || !strings.HasPrefix(seg, "{") {
|
|
|
|
log.Errorf("The following format string is invalid: '%v'", s)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
_i := seg[1 : len(seg)-1]
|
|
|
|
i, err := strconv.ParseInt(_i, 10, 32)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("The following format string is invalid: '%v'. Error: %v", s, err)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if i >= int64(len(vals)) {
|
|
|
|
log.Errorf("The following format string references more arguments than were supplied: '%v'", s)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if vals[i].IsNull() || vals[i].IsUndefined() {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return vals[i].String()
|
|
|
|
}
|
|
|
|
})
|
2020-02-13 01:27:37 -06:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func vmJoin(vm *otto.Otto) {
|
2020-02-13 13:47:38 -06:00
|
|
|
_ = vm.Set("join", func(element interface{}, optionalElem string) string {
|
2020-02-13 01:27:37 -06:00
|
|
|
slist := make([]string, 0)
|
|
|
|
if elementString, ok := element.(string); ok {
|
|
|
|
slist = append(slist, elementString)
|
|
|
|
} else if elementArray, ok := element.([]string); ok {
|
|
|
|
slist = append(slist, elementArray...)
|
|
|
|
}
|
|
|
|
if optionalElem != "" {
|
|
|
|
slist = append(slist, optionalElem)
|
|
|
|
}
|
|
|
|
return strings.Join(slist, " ")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func vmToJSON(vm *otto.Otto) {
|
2020-02-28 17:20:31 -06:00
|
|
|
toJSON := func(o interface{}) string {
|
2020-02-13 01:27:37 -06:00
|
|
|
rtn, err := json.MarshalIndent(o, "", " ")
|
|
|
|
if err != nil {
|
2020-11-12 10:15:09 -06:00
|
|
|
log.Errorf("Unable to marshal: %v", err)
|
2020-02-13 01:27:37 -06:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return string(rtn)
|
2020-02-28 17:20:31 -06:00
|
|
|
}
|
|
|
|
_ = vm.Set("toJSON", toJSON)
|
|
|
|
_ = vm.Set("toJson", toJSON)
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
|
|
|
|
2020-09-01 15:55:29 -05:00
|
|
|
func vmFromJSON(vm *otto.Otto) {
|
2021-11-15 08:26:04 -06:00
|
|
|
fromJSON := func(str string) interface{} {
|
|
|
|
var dat interface{}
|
2020-09-01 15:55:29 -05:00
|
|
|
err := json.Unmarshal([]byte(str), &dat)
|
|
|
|
if err != nil {
|
2020-11-12 10:15:09 -06:00
|
|
|
log.Errorf("Unable to unmarshal: %v", err)
|
2020-09-01 15:55:29 -05:00
|
|
|
return dat
|
|
|
|
}
|
|
|
|
return dat
|
|
|
|
}
|
|
|
|
_ = vm.Set("fromJSON", fromJSON)
|
|
|
|
_ = vm.Set("fromJson", fromJSON)
|
|
|
|
}
|
|
|
|
|
2020-02-13 13:47:38 -06:00
|
|
|
func (rc *RunContext) vmHashFiles() func(*otto.Otto) {
|
2020-02-13 01:27:37 -06:00
|
|
|
return func(vm *otto.Otto) {
|
2020-11-09 11:08:57 -06:00
|
|
|
_ = vm.Set("hashFiles", func(paths ...string) string {
|
2021-03-12 18:23:03 -06:00
|
|
|
var files []string
|
2020-11-09 11:08:57 -06:00
|
|
|
for i := range paths {
|
2021-03-12 18:23:03 -06:00
|
|
|
newFiles, err := filepath.Glob(filepath.Join(rc.Config.Workdir, paths[i]))
|
2020-11-09 11:08:57 -06:00
|
|
|
if err != nil {
|
2020-11-12 10:15:09 -06:00
|
|
|
log.Errorf("Unable to glob.Glob: %v", err)
|
2020-11-09 11:08:57 -06:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
files = append(files, newFiles...)
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
|
|
|
hasher := sha256.New()
|
|
|
|
for _, file := range files {
|
2021-03-12 18:23:03 -06:00
|
|
|
f, err := os.Open(file)
|
2020-02-13 01:27:37 -06:00
|
|
|
if err != nil {
|
2020-11-12 10:15:09 -06:00
|
|
|
log.Errorf("Unable to os.Open: %v", err)
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
|
|
|
if _, err := io.Copy(hasher, f); err != nil {
|
2020-11-12 10:15:09 -06:00
|
|
|
log.Errorf("Unable to io.Copy: %v", err)
|
|
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
|
|
log.Errorf("Unable to Close file: %v", err)
|
2020-02-13 01:27:37 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-02-13 13:47:38 -06:00
|
|
|
|
|
|
|
func (rc *RunContext) vmSuccess() func(*otto.Otto) {
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("success", func() bool {
|
2021-12-08 14:57:42 -06:00
|
|
|
jobs := rc.Run.Workflow.Jobs
|
|
|
|
jobNeeds := rc.getNeedsTransitive(rc.Run.Job())
|
|
|
|
|
|
|
|
for _, needs := range jobNeeds {
|
|
|
|
if jobs[needs].Result != "success" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
2020-02-13 13:47:38 -06:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-12-08 14:57:42 -06:00
|
|
|
|
2020-02-13 13:47:38 -06:00
|
|
|
func (rc *RunContext) vmFailure() func(*otto.Otto) {
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("failure", func() bool {
|
2021-12-08 14:57:42 -06:00
|
|
|
jobs := rc.Run.Workflow.Jobs
|
|
|
|
jobNeeds := rc.getNeedsTransitive(rc.Run.Job())
|
|
|
|
|
|
|
|
for _, needs := range jobNeeds {
|
|
|
|
if jobs[needs].Result == "failure" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
2020-02-13 13:47:38 -06:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func vmAlways(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("always", func() bool {
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
2020-02-14 02:41:20 -06:00
|
|
|
func (rc *RunContext) vmCancelled() func(vm *otto.Otto) {
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("cancelled", func() bool {
|
|
|
|
return rc.getJobContext().Status == "cancelled"
|
|
|
|
})
|
|
|
|
}
|
2020-02-13 13:47:38 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (rc *RunContext) vmGithub() func(*otto.Otto) {
|
2020-02-14 02:41:20 -06:00
|
|
|
github := rc.getGithubContext()
|
2020-02-13 13:47:38 -06:00
|
|
|
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("github", github)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-04 14:18:13 -05:00
|
|
|
func (rc *RunContext) vmEnv() func(*otto.Otto) {
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
env := rc.GetEnv()
|
|
|
|
log.Debugf("context env => %v", env)
|
|
|
|
_ = vm.Set("env", env)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-23 17:01:25 -06:00
|
|
|
func (sc *StepContext) vmEnv() func(*otto.Otto) {
|
2020-02-13 13:47:38 -06:00
|
|
|
return func(vm *otto.Otto) {
|
2020-03-06 15:39:01 -06:00
|
|
|
log.Debugf("context env => %v", sc.Env)
|
2020-02-23 17:01:25 -06:00
|
|
|
_ = vm.Set("env", sc.Env)
|
2020-02-13 13:47:38 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-22 13:19:50 -06:00
|
|
|
func (rc *RunContext) vmInputs() func(*otto.Otto) {
|
2020-02-24 00:34:48 -06:00
|
|
|
return func(vm *otto.Otto) {
|
2021-12-22 13:19:50 -06:00
|
|
|
_ = vm.Set("inputs", rc.Inputs)
|
2020-02-24 00:34:48 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-08 14:57:42 -06:00
|
|
|
func (sc *StepContext) vmNeeds() func(*otto.Otto) {
|
|
|
|
jobs := sc.RunContext.Run.Workflow.Jobs
|
|
|
|
jobNeeds := sc.RunContext.Run.Job().Needs()
|
2021-07-01 10:20:20 -05:00
|
|
|
|
|
|
|
using := make(map[string]map[string]map[string]string)
|
|
|
|
for _, needs := range jobNeeds {
|
|
|
|
using[needs] = map[string]map[string]string{
|
|
|
|
"outputs": jobs[needs].Outputs,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
log.Debugf("context needs => %v", using)
|
|
|
|
_ = vm.Set("needs", using)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-08 14:57:42 -06:00
|
|
|
func (sc *StepContext) vmSuccess() func(*otto.Otto) {
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("success", func() bool {
|
|
|
|
return sc.RunContext.getJobContext().Status == "success"
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sc *StepContext) vmFailure() func(*otto.Otto) {
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("failure", func() bool {
|
|
|
|
return sc.RunContext.getJobContext().Status == "failure"
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type vmNeedsStruct struct {
|
|
|
|
Outputs map[string]string `json:"outputs"`
|
|
|
|
Result string `json:"result"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rc *RunContext) vmNeeds() func(*otto.Otto) {
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
needsFunc := func() otto.Value {
|
|
|
|
jobs := rc.Run.Workflow.Jobs
|
|
|
|
jobNeeds := rc.Run.Job().Needs()
|
|
|
|
|
|
|
|
using := make(map[string]vmNeedsStruct)
|
|
|
|
for _, needs := range jobNeeds {
|
|
|
|
using[needs] = vmNeedsStruct{
|
|
|
|
Outputs: jobs[needs].Outputs,
|
|
|
|
Result: jobs[needs].Result,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("context needs => %+v", using)
|
|
|
|
|
|
|
|
value, err := vm.ToValue(using)
|
|
|
|
if err != nil {
|
|
|
|
return vm.MakeTypeError(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
|
|
|
// Results might change after the Otto VM was created
|
|
|
|
// and initialized. To access the current state
|
|
|
|
// we can't just pass a copy to Otto - instead we
|
|
|
|
// created a 'live-binding'.
|
|
|
|
// Technical Note: We don't want to pollute the global
|
|
|
|
// js namespace (and add things github actions hasn't)
|
|
|
|
// we delete the helper function after installing it
|
|
|
|
// as a getter.
|
|
|
|
global, _ := vm.Run("this")
|
|
|
|
_ = global.Object().Set("__needs__", needsFunc)
|
|
|
|
_, _ = vm.Run(`
|
|
|
|
(function (global) {
|
|
|
|
Object.defineProperty(global, 'needs', { get: global.__needs__ });
|
|
|
|
delete global.__needs__;
|
|
|
|
})(this)
|
|
|
|
`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-13 13:47:38 -06:00
|
|
|
func (rc *RunContext) vmJob() func(*otto.Otto) {
|
2020-02-14 02:41:20 -06:00
|
|
|
job := rc.getJobContext()
|
2020-02-13 13:47:38 -06:00
|
|
|
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("job", job)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rc *RunContext) vmSteps() func(*otto.Otto) {
|
2021-11-27 11:55:26 -06:00
|
|
|
ctxSteps := rc.getStepsContext()
|
|
|
|
|
|
|
|
steps := make(map[string]interface{})
|
|
|
|
for id, ctxStep := range ctxSteps {
|
|
|
|
steps[id] = map[string]interface{}{
|
|
|
|
"conclusion": ctxStep.Conclusion.String(),
|
|
|
|
"outcome": ctxStep.Outcome.String(),
|
|
|
|
"outputs": ctxStep.Outputs,
|
|
|
|
}
|
|
|
|
}
|
2020-02-13 13:47:38 -06:00
|
|
|
|
|
|
|
return func(vm *otto.Otto) {
|
2021-11-27 11:55:26 -06:00
|
|
|
log.Debugf("context steps => %v", steps)
|
2020-02-13 13:47:38 -06:00
|
|
|
_ = vm.Set("steps", steps)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rc *RunContext) vmRunner() func(*otto.Otto) {
|
|
|
|
runner := map[string]interface{}{
|
|
|
|
"os": "Linux",
|
|
|
|
"temp": "/tmp",
|
2020-02-27 01:29:43 -06:00
|
|
|
"tool_cache": "/opt/hostedtoolcache",
|
2020-02-13 13:47:38 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("runner", runner)
|
|
|
|
}
|
|
|
|
}
|
2020-02-14 02:41:20 -06:00
|
|
|
|
|
|
|
func (rc *RunContext) vmSecrets() func(*otto.Otto) {
|
|
|
|
return func(vm *otto.Otto) {
|
2021-12-22 13:19:50 -06:00
|
|
|
// Hide secrets from composite actions
|
|
|
|
if rc.Composite == nil {
|
|
|
|
_ = vm.Set("secrets", rc.Config.Secrets)
|
|
|
|
}
|
2020-02-14 02:41:20 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rc *RunContext) vmStrategy() func(*otto.Otto) {
|
|
|
|
job := rc.Run.Job()
|
|
|
|
strategy := make(map[string]interface{})
|
|
|
|
if job.Strategy != nil {
|
|
|
|
strategy["fail-fast"] = job.Strategy.FailFast
|
|
|
|
strategy["max-parallel"] = job.Strategy.MaxParallel
|
|
|
|
}
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("strategy", strategy)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rc *RunContext) vmMatrix() func(*otto.Otto) {
|
|
|
|
return func(vm *otto.Otto) {
|
|
|
|
_ = vm.Set("matrix", rc.Matrix)
|
|
|
|
}
|
|
|
|
}
|
2021-12-08 14:57:42 -06:00
|
|
|
|
|
|
|
// EvalBool evaluates an expression against given evaluator
|
|
|
|
func EvalBool(evaluator ExpressionEvaluator, expr string) (bool, error) {
|
|
|
|
if splitPattern == nil {
|
|
|
|
splitPattern = regexp.MustCompile(fmt.Sprintf(`%s|%s|\S+`, expressionPattern.String(), operatorPattern.String()))
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(strings.TrimSpace(expr), "!") {
|
|
|
|
return false, errors.New("expressions starting with ! must be wrapped in ${{ }}")
|
|
|
|
}
|
|
|
|
if expr != "" {
|
|
|
|
parts := splitPattern.FindAllString(expr, -1)
|
|
|
|
var evaluatedParts []string
|
|
|
|
for i, part := range parts {
|
|
|
|
if operatorPattern.MatchString(part) {
|
|
|
|
evaluatedParts = append(evaluatedParts, part)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
interpolatedPart, isString := evaluator.InterpolateWithStringCheck(part)
|
|
|
|
|
|
|
|
// This peculiar transformation has to be done because the GitHub parser
|
|
|
|
// treats false returned from contexts as a string, not a boolean.
|
|
|
|
// Hence env.SOMETHING will be evaluated to true in an if: expression
|
|
|
|
// regardless if SOMETHING is set to false, true or any other string.
|
|
|
|
// It also handles some other weirdness that I found by trial and error.
|
|
|
|
if (expressionPattern.MatchString(part) && // it is an expression
|
|
|
|
!strings.Contains(part, "!")) && // but it's not negated
|
|
|
|
interpolatedPart == "false" && // and the interpolated string is false
|
|
|
|
(isString || previousOrNextPartIsAnOperator(i, parts)) { // and it's of type string or has an logical operator before or after
|
|
|
|
interpolatedPart = fmt.Sprintf("'%s'", interpolatedPart) // then we have to quote the false expression
|
|
|
|
}
|
|
|
|
|
|
|
|
evaluatedParts = append(evaluatedParts, interpolatedPart)
|
|
|
|
}
|
|
|
|
|
|
|
|
joined := strings.Join(evaluatedParts, " ")
|
|
|
|
v, _, err := evaluator.Evaluate(fmt.Sprintf("Boolean(%s)", joined))
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
log.Debugf("expression '%s' evaluated to '%s'", expr, v)
|
|
|
|
return v == "true", nil
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|