github.com/nektos/act@v0.2.83/pkg/exprparser/interpreter.go (about)

     1  package exprparser
     2  
     3  import (
     4  	"encoding"
     5  	"fmt"
     6  	"math"
     7  	"reflect"
     8  	"strings"
     9  
    10  	"github.com/nektos/act/pkg/model"
    11  	"github.com/rhysd/actionlint"
    12  )
    13  
    14  type EvaluationEnvironment struct {
    15  	Github    *model.GithubContext
    16  	Env       map[string]string
    17  	Job       *model.JobContext
    18  	Jobs      *map[string]*model.WorkflowCallResult
    19  	Steps     map[string]*model.StepResult
    20  	Runner    map[string]interface{}
    21  	Secrets   map[string]string
    22  	Vars      map[string]string
    23  	Strategy  map[string]interface{}
    24  	Matrix    map[string]interface{}
    25  	Needs     map[string]Needs
    26  	Inputs    map[string]interface{}
    27  	HashFiles func([]reflect.Value) (interface{}, error)
    28  }
    29  
    30  type Needs struct {
    31  	Outputs map[string]string `json:"outputs"`
    32  	Result  string            `json:"result"`
    33  }
    34  
    35  type Config struct {
    36  	Run        *model.Run
    37  	WorkingDir string
    38  	Context    string
    39  }
    40  
    41  type DefaultStatusCheck int
    42  
    43  const (
    44  	DefaultStatusCheckNone DefaultStatusCheck = iota
    45  	DefaultStatusCheckSuccess
    46  	DefaultStatusCheckAlways
    47  	DefaultStatusCheckCanceled
    48  	DefaultStatusCheckFailure
    49  )
    50  
    51  func (dsc DefaultStatusCheck) String() string {
    52  	switch dsc {
    53  	case DefaultStatusCheckSuccess:
    54  		return "success"
    55  	case DefaultStatusCheckAlways:
    56  		return "always"
    57  	case DefaultStatusCheckCanceled:
    58  		return "cancelled"
    59  	case DefaultStatusCheckFailure:
    60  		return "failure"
    61  	}
    62  	return ""
    63  }
    64  
    65  type Interpreter interface {
    66  	Evaluate(input string, defaultStatusCheck DefaultStatusCheck) (interface{}, error)
    67  }
    68  
    69  type interperterImpl struct {
    70  	env    *EvaluationEnvironment
    71  	config Config
    72  }
    73  
    74  func NewInterpeter(env *EvaluationEnvironment, config Config) Interpreter {
    75  	return &interperterImpl{
    76  		env:    env,
    77  		config: config,
    78  	}
    79  }
    80  
    81  func (impl *interperterImpl) Evaluate(input string, defaultStatusCheck DefaultStatusCheck) (interface{}, error) {
    82  	input = strings.TrimPrefix(input, "${{")
    83  	if defaultStatusCheck != DefaultStatusCheckNone && input == "" {
    84  		input = "success()"
    85  	}
    86  	parser := actionlint.NewExprParser()
    87  	exprNode, err := parser.Parse(actionlint.NewExprLexer(input + "}}"))
    88  	if err != nil {
    89  		return nil, fmt.Errorf("Failed to parse: %s", err.Message)
    90  	}
    91  
    92  	if defaultStatusCheck != DefaultStatusCheckNone {
    93  		hasStatusCheckFunction := false
    94  		actionlint.VisitExprNode(exprNode, func(node, _ actionlint.ExprNode, entering bool) {
    95  			if funcCallNode, ok := node.(*actionlint.FuncCallNode); entering && ok {
    96  				switch strings.ToLower(funcCallNode.Callee) {
    97  				case "success", "always", "cancelled", "failure":
    98  					hasStatusCheckFunction = true
    99  				}
   100  			}
   101  		})
   102  
   103  		if !hasStatusCheckFunction {
   104  			exprNode = &actionlint.LogicalOpNode{
   105  				Kind: actionlint.LogicalOpNodeKindAnd,
   106  				Left: &actionlint.FuncCallNode{
   107  					Callee: defaultStatusCheck.String(),
   108  					Args:   []actionlint.ExprNode{},
   109  				},
   110  				Right: exprNode,
   111  			}
   112  		}
   113  	}
   114  
   115  	result, err2 := impl.evaluateNode(exprNode)
   116  
   117  	return result, err2
   118  }
   119  
   120  func (impl *interperterImpl) evaluateNode(exprNode actionlint.ExprNode) (interface{}, error) {
   121  	switch node := exprNode.(type) {
   122  	case *actionlint.VariableNode:
   123  		return impl.evaluateVariable(node)
   124  	case *actionlint.BoolNode:
   125  		return node.Value, nil
   126  	case *actionlint.NullNode:
   127  		return nil, nil
   128  	case *actionlint.IntNode:
   129  		return node.Value, nil
   130  	case *actionlint.FloatNode:
   131  		return node.Value, nil
   132  	case *actionlint.StringNode:
   133  		return node.Value, nil
   134  	case *actionlint.IndexAccessNode:
   135  		return impl.evaluateIndexAccess(node)
   136  	case *actionlint.ObjectDerefNode:
   137  		return impl.evaluateObjectDeref(node)
   138  	case *actionlint.ArrayDerefNode:
   139  		return impl.evaluateArrayDeref(node)
   140  	case *actionlint.NotOpNode:
   141  		return impl.evaluateNot(node)
   142  	case *actionlint.CompareOpNode:
   143  		return impl.evaluateCompare(node)
   144  	case *actionlint.LogicalOpNode:
   145  		return impl.evaluateLogicalCompare(node)
   146  	case *actionlint.FuncCallNode:
   147  		return impl.evaluateFuncCall(node)
   148  	default:
   149  		return nil, fmt.Errorf("Fatal error! Unknown node type: %s node: %+v", reflect.TypeOf(exprNode), exprNode)
   150  	}
   151  }
   152  
   153  func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableNode) (interface{}, error) {
   154  	switch strings.ToLower(variableNode.Name) {
   155  	case "github":
   156  		return impl.env.Github, nil
   157  	case "env":
   158  		return impl.env.Env, nil
   159  	case "job":
   160  		return impl.env.Job, nil
   161  	case "jobs":
   162  		if impl.env.Jobs == nil {
   163  			return nil, fmt.Errorf("Unavailable context: jobs")
   164  		}
   165  		return impl.env.Jobs, nil
   166  	case "steps":
   167  		return impl.env.Steps, nil
   168  	case "runner":
   169  		return impl.env.Runner, nil
   170  	case "secrets":
   171  		return impl.env.Secrets, nil
   172  	case "vars":
   173  		return impl.env.Vars, nil
   174  	case "strategy":
   175  		return impl.env.Strategy, nil
   176  	case "matrix":
   177  		return impl.env.Matrix, nil
   178  	case "needs":
   179  		return impl.env.Needs, nil
   180  	case "inputs":
   181  		return impl.env.Inputs, nil
   182  	case "infinity":
   183  		return math.Inf(1), nil
   184  	case "nan":
   185  		return math.NaN(), nil
   186  	default:
   187  		return nil, fmt.Errorf("Unavailable context: %s", variableNode.Name)
   188  	}
   189  }
   190  
   191  func (impl *interperterImpl) evaluateIndexAccess(indexAccessNode *actionlint.IndexAccessNode) (interface{}, error) {
   192  	left, err := impl.evaluateNode(indexAccessNode.Operand)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	leftValue := reflect.ValueOf(left)
   198  
   199  	right, err := impl.evaluateNode(indexAccessNode.Index)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	rightValue := reflect.ValueOf(right)
   205  
   206  	switch rightValue.Kind() {
   207  	case reflect.String:
   208  		return impl.getPropertyValue(leftValue, rightValue.String())
   209  
   210  	case reflect.Int:
   211  		switch leftValue.Kind() {
   212  		case reflect.Slice:
   213  			if rightValue.Int() < 0 || rightValue.Int() >= int64(leftValue.Len()) {
   214  				return nil, nil
   215  			}
   216  			return leftValue.Index(int(rightValue.Int())).Interface(), nil
   217  		default:
   218  			return nil, nil
   219  		}
   220  
   221  	default:
   222  		return nil, nil
   223  	}
   224  }
   225  
   226  func (impl *interperterImpl) evaluateObjectDeref(objectDerefNode *actionlint.ObjectDerefNode) (interface{}, error) {
   227  	left, err := impl.evaluateNode(objectDerefNode.Receiver)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	_, receiverIsDeref := objectDerefNode.Receiver.(*actionlint.ArrayDerefNode)
   233  	if receiverIsDeref {
   234  		return impl.getPropertyValueDereferenced(reflect.ValueOf(left), objectDerefNode.Property)
   235  	}
   236  	return impl.getPropertyValue(reflect.ValueOf(left), objectDerefNode.Property)
   237  }
   238  
   239  func (impl *interperterImpl) evaluateArrayDeref(arrayDerefNode *actionlint.ArrayDerefNode) (interface{}, error) {
   240  	left, err := impl.evaluateNode(arrayDerefNode.Receiver)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	return impl.getSafeValue(reflect.ValueOf(left)), nil
   246  }
   247  
   248  func (impl *interperterImpl) getPropertyValue(left reflect.Value, property string) (value interface{}, err error) {
   249  	switch left.Kind() {
   250  	case reflect.Ptr:
   251  		return impl.getPropertyValue(left.Elem(), property)
   252  
   253  	case reflect.Struct:
   254  		leftType := left.Type()
   255  		for i := 0; i < leftType.NumField(); i++ {
   256  			jsonName := leftType.Field(i).Tag.Get("json")
   257  			if jsonName == property {
   258  				property = leftType.Field(i).Name
   259  				break
   260  			}
   261  		}
   262  
   263  		fieldValue := left.FieldByNameFunc(func(name string) bool {
   264  			return strings.EqualFold(name, property)
   265  		})
   266  
   267  		if fieldValue.Kind() == reflect.Invalid {
   268  			return "", nil
   269  		}
   270  
   271  		i := fieldValue.Interface()
   272  		// The type stepStatus int is an integer, but should be treated as string
   273  		if m, ok := i.(encoding.TextMarshaler); ok {
   274  			text, err := m.MarshalText()
   275  			if err != nil {
   276  				return nil, err
   277  			}
   278  			return string(text), nil
   279  		}
   280  		return i, nil
   281  
   282  	case reflect.Map:
   283  		iter := left.MapRange()
   284  
   285  		for iter.Next() {
   286  			key := iter.Key()
   287  
   288  			switch key.Kind() {
   289  			case reflect.String:
   290  				if strings.EqualFold(key.String(), property) {
   291  					return impl.getMapValue(iter.Value())
   292  				}
   293  
   294  			default:
   295  				return nil, fmt.Errorf("'%s' in map key not implemented", key.Kind())
   296  			}
   297  		}
   298  
   299  		return nil, nil
   300  
   301  	case reflect.Slice:
   302  		var values []interface{}
   303  
   304  		for i := 0; i < left.Len(); i++ {
   305  			value, err := impl.getPropertyValue(left.Index(i).Elem(), property)
   306  			if err != nil {
   307  				return nil, err
   308  			}
   309  
   310  			values = append(values, value)
   311  		}
   312  
   313  		return values, nil
   314  	}
   315  
   316  	return nil, nil
   317  }
   318  
   319  func (impl *interperterImpl) getPropertyValueDereferenced(left reflect.Value, property string) (value interface{}, err error) {
   320  	switch left.Kind() {
   321  	case reflect.Ptr:
   322  		return impl.getPropertyValue(left, property)
   323  
   324  	case reflect.Struct:
   325  		return impl.getPropertyValue(left, property)
   326  	case reflect.Map:
   327  		iter := left.MapRange()
   328  
   329  		var values []interface{}
   330  		for iter.Next() {
   331  			value, err := impl.getPropertyValue(iter.Value(), property)
   332  			if err != nil {
   333  				return nil, err
   334  			}
   335  
   336  			values = append(values, value)
   337  		}
   338  
   339  		return values, nil
   340  	case reflect.Slice:
   341  		return impl.getPropertyValue(left, property)
   342  	}
   343  
   344  	return nil, nil
   345  }
   346  
   347  func (impl *interperterImpl) getMapValue(value reflect.Value) (interface{}, error) {
   348  	if value.Kind() == reflect.Ptr {
   349  		return impl.getMapValue(value.Elem())
   350  	}
   351  
   352  	return value.Interface(), nil
   353  }
   354  
   355  func (impl *interperterImpl) evaluateNot(notNode *actionlint.NotOpNode) (interface{}, error) {
   356  	operand, err := impl.evaluateNode(notNode.Operand)
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  
   361  	return !IsTruthy(operand), nil
   362  }
   363  
   364  func (impl *interperterImpl) evaluateCompare(compareNode *actionlint.CompareOpNode) (interface{}, error) {
   365  	left, err := impl.evaluateNode(compareNode.Left)
   366  	if err != nil {
   367  		return nil, err
   368  	}
   369  
   370  	right, err := impl.evaluateNode(compareNode.Right)
   371  	if err != nil {
   372  		return nil, err
   373  	}
   374  
   375  	leftValue := reflect.ValueOf(left)
   376  	rightValue := reflect.ValueOf(right)
   377  
   378  	return impl.compareValues(leftValue, rightValue, compareNode.Kind)
   379  }
   380  
   381  func (impl *interperterImpl) compareValues(leftValue reflect.Value, rightValue reflect.Value, kind actionlint.CompareOpNodeKind) (interface{}, error) {
   382  	if leftValue.Kind() != rightValue.Kind() {
   383  		if !impl.isNumber(leftValue) {
   384  			leftValue = impl.coerceToNumber(leftValue)
   385  		}
   386  		if !impl.isNumber(rightValue) {
   387  			rightValue = impl.coerceToNumber(rightValue)
   388  		}
   389  	}
   390  
   391  	switch leftValue.Kind() {
   392  	case reflect.Bool:
   393  		return impl.compareNumber(float64(impl.coerceToNumber(leftValue).Int()), float64(impl.coerceToNumber(rightValue).Int()), kind)
   394  	case reflect.String:
   395  		return impl.compareString(strings.ToLower(leftValue.String()), strings.ToLower(rightValue.String()), kind)
   396  
   397  	case reflect.Int:
   398  		if rightValue.Kind() == reflect.Float64 {
   399  			return impl.compareNumber(float64(leftValue.Int()), rightValue.Float(), kind)
   400  		}
   401  
   402  		return impl.compareNumber(float64(leftValue.Int()), float64(rightValue.Int()), kind)
   403  
   404  	case reflect.Float64:
   405  		if rightValue.Kind() == reflect.Int {
   406  			return impl.compareNumber(leftValue.Float(), float64(rightValue.Int()), kind)
   407  		}
   408  
   409  		return impl.compareNumber(leftValue.Float(), rightValue.Float(), kind)
   410  
   411  	case reflect.Invalid:
   412  		if rightValue.Kind() == reflect.Invalid {
   413  			return true, nil
   414  		}
   415  
   416  		// not possible situation - params are converted to the same type in code above
   417  		return nil, fmt.Errorf("Compare params of Invalid type: left: %+v, right: %+v", leftValue.Kind(), rightValue.Kind())
   418  
   419  	default:
   420  		return nil, fmt.Errorf("Compare not implemented for types: left: %+v, right: %+v", leftValue.Kind(), rightValue.Kind())
   421  	}
   422  }
   423  
   424  func (impl *interperterImpl) coerceToNumber(value reflect.Value) reflect.Value {
   425  	switch value.Kind() {
   426  	case reflect.Invalid:
   427  		return reflect.ValueOf(0)
   428  
   429  	case reflect.Bool:
   430  		switch value.Bool() {
   431  		case true:
   432  			return reflect.ValueOf(1)
   433  		case false:
   434  			return reflect.ValueOf(0)
   435  		}
   436  
   437  	case reflect.String:
   438  		if value.String() == "" {
   439  			return reflect.ValueOf(0)
   440  		}
   441  
   442  		// try to parse the string as a number
   443  		evaluated, err := impl.Evaluate(value.String(), DefaultStatusCheckNone)
   444  		if err != nil {
   445  			return reflect.ValueOf(math.NaN())
   446  		}
   447  
   448  		if value := reflect.ValueOf(evaluated); impl.isNumber(value) {
   449  			return value
   450  		}
   451  	}
   452  
   453  	return reflect.ValueOf(math.NaN())
   454  }
   455  
   456  func (impl *interperterImpl) coerceToString(value reflect.Value) reflect.Value {
   457  	switch value.Kind() {
   458  	case reflect.Invalid:
   459  		return reflect.ValueOf("")
   460  
   461  	case reflect.Bool:
   462  		switch value.Bool() {
   463  		case true:
   464  			return reflect.ValueOf("true")
   465  		case false:
   466  			return reflect.ValueOf("false")
   467  		}
   468  
   469  	case reflect.String:
   470  		return value
   471  
   472  	case reflect.Int:
   473  		return reflect.ValueOf(fmt.Sprint(value))
   474  
   475  	case reflect.Float64:
   476  		if math.IsInf(value.Float(), 1) {
   477  			return reflect.ValueOf("Infinity")
   478  		} else if math.IsInf(value.Float(), -1) {
   479  			return reflect.ValueOf("-Infinity")
   480  		}
   481  		return reflect.ValueOf(fmt.Sprintf("%.15G", value.Float()))
   482  
   483  	case reflect.Slice:
   484  		return reflect.ValueOf("Array")
   485  
   486  	case reflect.Map:
   487  		return reflect.ValueOf("Object")
   488  	}
   489  
   490  	return value
   491  }
   492  
   493  func (impl *interperterImpl) compareString(left string, right string, kind actionlint.CompareOpNodeKind) (bool, error) {
   494  	switch kind {
   495  	case actionlint.CompareOpNodeKindLess:
   496  		return left < right, nil
   497  	case actionlint.CompareOpNodeKindLessEq:
   498  		return left <= right, nil
   499  	case actionlint.CompareOpNodeKindGreater:
   500  		return left > right, nil
   501  	case actionlint.CompareOpNodeKindGreaterEq:
   502  		return left >= right, nil
   503  	case actionlint.CompareOpNodeKindEq:
   504  		return left == right, nil
   505  	case actionlint.CompareOpNodeKindNotEq:
   506  		return left != right, nil
   507  	default:
   508  		return false, fmt.Errorf("TODO: not implemented to compare '%+v'", kind)
   509  	}
   510  }
   511  
   512  func (impl *interperterImpl) compareNumber(left float64, right float64, kind actionlint.CompareOpNodeKind) (bool, error) {
   513  	switch kind {
   514  	case actionlint.CompareOpNodeKindLess:
   515  		return left < right, nil
   516  	case actionlint.CompareOpNodeKindLessEq:
   517  		return left <= right, nil
   518  	case actionlint.CompareOpNodeKindGreater:
   519  		return left > right, nil
   520  	case actionlint.CompareOpNodeKindGreaterEq:
   521  		return left >= right, nil
   522  	case actionlint.CompareOpNodeKindEq:
   523  		return left == right, nil
   524  	case actionlint.CompareOpNodeKindNotEq:
   525  		return left != right, nil
   526  	default:
   527  		return false, fmt.Errorf("TODO: not implemented to compare '%+v'", kind)
   528  	}
   529  }
   530  
   531  func IsTruthy(input interface{}) bool {
   532  	value := reflect.ValueOf(input)
   533  	switch value.Kind() {
   534  	case reflect.Bool:
   535  		return value.Bool()
   536  
   537  	case reflect.String:
   538  		return value.String() != ""
   539  
   540  	case reflect.Int:
   541  		return value.Int() != 0
   542  
   543  	case reflect.Float64:
   544  		if math.IsNaN(value.Float()) {
   545  			return false
   546  		}
   547  
   548  		return value.Float() != 0
   549  
   550  	case reflect.Map, reflect.Slice:
   551  		return true
   552  
   553  	default:
   554  		return false
   555  	}
   556  }
   557  
   558  func (impl *interperterImpl) isNumber(value reflect.Value) bool {
   559  	switch value.Kind() {
   560  	case reflect.Int, reflect.Float64:
   561  		return true
   562  	default:
   563  		return false
   564  	}
   565  }
   566  
   567  func (impl *interperterImpl) getSafeValue(value reflect.Value) interface{} {
   568  	switch value.Kind() {
   569  	case reflect.Invalid:
   570  		return nil
   571  
   572  	case reflect.Float64:
   573  		if value.Float() == 0 {
   574  			return 0
   575  		}
   576  	}
   577  
   578  	return value.Interface()
   579  }
   580  
   581  func (impl *interperterImpl) evaluateLogicalCompare(compareNode *actionlint.LogicalOpNode) (interface{}, error) {
   582  	left, err := impl.evaluateNode(compareNode.Left)
   583  	if err != nil {
   584  		return nil, err
   585  	}
   586  
   587  	leftValue := reflect.ValueOf(left)
   588  
   589  	if IsTruthy(left) == (compareNode.Kind == actionlint.LogicalOpNodeKindOr) {
   590  		return impl.getSafeValue(leftValue), nil
   591  	}
   592  
   593  	right, err := impl.evaluateNode(compareNode.Right)
   594  	if err != nil {
   595  		return nil, err
   596  	}
   597  
   598  	rightValue := reflect.ValueOf(right)
   599  
   600  	switch compareNode.Kind {
   601  	case actionlint.LogicalOpNodeKindAnd:
   602  		return impl.getSafeValue(rightValue), nil
   603  	case actionlint.LogicalOpNodeKindOr:
   604  		return impl.getSafeValue(rightValue), nil
   605  	}
   606  
   607  	return nil, fmt.Errorf("Unable to compare incompatibles types '%s' and '%s'", leftValue.Kind(), rightValue.Kind())
   608  }
   609  
   610  //nolint:gocyclo
   611  func (impl *interperterImpl) evaluateFuncCall(funcCallNode *actionlint.FuncCallNode) (interface{}, error) {
   612  	args := make([]reflect.Value, 0)
   613  
   614  	for _, arg := range funcCallNode.Args {
   615  		value, err := impl.evaluateNode(arg)
   616  		if err != nil {
   617  			return nil, err
   618  		}
   619  
   620  		args = append(args, reflect.ValueOf(value))
   621  	}
   622  
   623  	switch strings.ToLower(funcCallNode.Callee) {
   624  	case "contains":
   625  		return impl.contains(args[0], args[1])
   626  	case "startswith":
   627  		return impl.startsWith(args[0], args[1])
   628  	case "endswith":
   629  		return impl.endsWith(args[0], args[1])
   630  	case "format":
   631  		return impl.format(args[0], args[1:]...)
   632  	case "join":
   633  		if len(args) == 1 {
   634  			return impl.join(args[0], reflect.ValueOf(","))
   635  		}
   636  		return impl.join(args[0], args[1])
   637  	case "tojson":
   638  		return impl.toJSON(args[0])
   639  	case "fromjson":
   640  		return impl.fromJSON(args[0])
   641  	case "hashfiles":
   642  		if impl.env.HashFiles != nil {
   643  			return impl.env.HashFiles(args)
   644  		}
   645  		return impl.hashFiles(args...)
   646  	case "always":
   647  		return impl.always()
   648  	case "success":
   649  		if impl.config.Context == "job" {
   650  			return impl.jobSuccess()
   651  		}
   652  		if impl.config.Context == "step" {
   653  			return impl.stepSuccess()
   654  		}
   655  		return nil, fmt.Errorf("Context '%s' must be one of 'job' or 'step'", impl.config.Context)
   656  	case "failure":
   657  		if impl.config.Context == "job" {
   658  			return impl.jobFailure()
   659  		}
   660  		if impl.config.Context == "step" {
   661  			return impl.stepFailure()
   662  		}
   663  		return nil, fmt.Errorf("Context '%s' must be one of 'job' or 'step'", impl.config.Context)
   664  	case "cancelled":
   665  		return impl.cancelled()
   666  	default:
   667  		return nil, fmt.Errorf("TODO: '%s' not implemented", funcCallNode.Callee)
   668  	}
   669  }