github.com/nektos/act@v0.2.63/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  //nolint:gocyclo
   154  func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableNode) (interface{}, error) {
   155  	switch strings.ToLower(variableNode.Name) {
   156  	case "github":
   157  		return impl.env.Github, nil
   158  	case "env":
   159  		return impl.env.Env, nil
   160  	case "job":
   161  		return impl.env.Job, nil
   162  	case "jobs":
   163  		if impl.env.Jobs == nil {
   164  			return nil, fmt.Errorf("Unavailable context: jobs")
   165  		}
   166  		return impl.env.Jobs, nil
   167  	case "steps":
   168  		return impl.env.Steps, nil
   169  	case "runner":
   170  		return impl.env.Runner, nil
   171  	case "secrets":
   172  		return impl.env.Secrets, nil
   173  	case "vars":
   174  		return impl.env.Vars, nil
   175  	case "strategy":
   176  		return impl.env.Strategy, nil
   177  	case "matrix":
   178  		return impl.env.Matrix, nil
   179  	case "needs":
   180  		return impl.env.Needs, nil
   181  	case "inputs":
   182  		return impl.env.Inputs, nil
   183  	case "infinity":
   184  		return math.Inf(1), nil
   185  	case "nan":
   186  		return math.NaN(), nil
   187  	default:
   188  		return nil, fmt.Errorf("Unavailable context: %s", variableNode.Name)
   189  	}
   190  }
   191  
   192  func (impl *interperterImpl) evaluateIndexAccess(indexAccessNode *actionlint.IndexAccessNode) (interface{}, error) {
   193  	left, err := impl.evaluateNode(indexAccessNode.Operand)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	leftValue := reflect.ValueOf(left)
   199  
   200  	right, err := impl.evaluateNode(indexAccessNode.Index)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	rightValue := reflect.ValueOf(right)
   206  
   207  	switch rightValue.Kind() {
   208  	case reflect.String:
   209  		return impl.getPropertyValue(leftValue, rightValue.String())
   210  
   211  	case reflect.Int:
   212  		switch leftValue.Kind() {
   213  		case reflect.Slice:
   214  			if rightValue.Int() < 0 || rightValue.Int() >= int64(leftValue.Len()) {
   215  				return nil, nil
   216  			}
   217  			return leftValue.Index(int(rightValue.Int())).Interface(), nil
   218  		default:
   219  			return nil, nil
   220  		}
   221  
   222  	default:
   223  		return nil, nil
   224  	}
   225  }
   226  
   227  func (impl *interperterImpl) evaluateObjectDeref(objectDerefNode *actionlint.ObjectDerefNode) (interface{}, error) {
   228  	left, err := impl.evaluateNode(objectDerefNode.Receiver)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	return impl.getPropertyValue(reflect.ValueOf(left), objectDerefNode.Property)
   234  }
   235  
   236  func (impl *interperterImpl) evaluateArrayDeref(arrayDerefNode *actionlint.ArrayDerefNode) (interface{}, error) {
   237  	left, err := impl.evaluateNode(arrayDerefNode.Receiver)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	return impl.getSafeValue(reflect.ValueOf(left)), nil
   243  }
   244  
   245  func (impl *interperterImpl) getPropertyValue(left reflect.Value, property string) (value interface{}, err error) {
   246  	switch left.Kind() {
   247  	case reflect.Ptr:
   248  		return impl.getPropertyValue(left.Elem(), property)
   249  
   250  	case reflect.Struct:
   251  		leftType := left.Type()
   252  		for i := 0; i < leftType.NumField(); i++ {
   253  			jsonName := leftType.Field(i).Tag.Get("json")
   254  			if jsonName == property {
   255  				property = leftType.Field(i).Name
   256  				break
   257  			}
   258  		}
   259  
   260  		fieldValue := left.FieldByNameFunc(func(name string) bool {
   261  			return strings.EqualFold(name, property)
   262  		})
   263  
   264  		if fieldValue.Kind() == reflect.Invalid {
   265  			return "", nil
   266  		}
   267  
   268  		i := fieldValue.Interface()
   269  		// The type stepStatus int is an integer, but should be treated as string
   270  		if m, ok := i.(encoding.TextMarshaler); ok {
   271  			text, err := m.MarshalText()
   272  			if err != nil {
   273  				return nil, err
   274  			}
   275  			return string(text), nil
   276  		}
   277  		return i, nil
   278  
   279  	case reflect.Map:
   280  		iter := left.MapRange()
   281  
   282  		for iter.Next() {
   283  			key := iter.Key()
   284  
   285  			switch key.Kind() {
   286  			case reflect.String:
   287  				if strings.EqualFold(key.String(), property) {
   288  					return impl.getMapValue(iter.Value())
   289  				}
   290  
   291  			default:
   292  				return nil, fmt.Errorf("'%s' in map key not implemented", key.Kind())
   293  			}
   294  		}
   295  
   296  		return nil, nil
   297  
   298  	case reflect.Slice:
   299  		var values []interface{}
   300  
   301  		for i := 0; i < left.Len(); i++ {
   302  			value, err := impl.getPropertyValue(left.Index(i).Elem(), property)
   303  			if err != nil {
   304  				return nil, err
   305  			}
   306  
   307  			values = append(values, value)
   308  		}
   309  
   310  		return values, nil
   311  	}
   312  
   313  	return nil, nil
   314  }
   315  
   316  func (impl *interperterImpl) getMapValue(value reflect.Value) (interface{}, error) {
   317  	if value.Kind() == reflect.Ptr {
   318  		return impl.getMapValue(value.Elem())
   319  	}
   320  
   321  	return value.Interface(), nil
   322  }
   323  
   324  func (impl *interperterImpl) evaluateNot(notNode *actionlint.NotOpNode) (interface{}, error) {
   325  	operand, err := impl.evaluateNode(notNode.Operand)
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  
   330  	return !IsTruthy(operand), nil
   331  }
   332  
   333  func (impl *interperterImpl) evaluateCompare(compareNode *actionlint.CompareOpNode) (interface{}, error) {
   334  	left, err := impl.evaluateNode(compareNode.Left)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  
   339  	right, err := impl.evaluateNode(compareNode.Right)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  
   344  	leftValue := reflect.ValueOf(left)
   345  	rightValue := reflect.ValueOf(right)
   346  
   347  	return impl.compareValues(leftValue, rightValue, compareNode.Kind)
   348  }
   349  
   350  func (impl *interperterImpl) compareValues(leftValue reflect.Value, rightValue reflect.Value, kind actionlint.CompareOpNodeKind) (interface{}, error) {
   351  	if leftValue.Kind() != rightValue.Kind() {
   352  		if !impl.isNumber(leftValue) {
   353  			leftValue = impl.coerceToNumber(leftValue)
   354  		}
   355  		if !impl.isNumber(rightValue) {
   356  			rightValue = impl.coerceToNumber(rightValue)
   357  		}
   358  	}
   359  
   360  	switch leftValue.Kind() {
   361  	case reflect.Bool:
   362  		return impl.compareNumber(float64(impl.coerceToNumber(leftValue).Int()), float64(impl.coerceToNumber(rightValue).Int()), kind)
   363  	case reflect.String:
   364  		return impl.compareString(strings.ToLower(leftValue.String()), strings.ToLower(rightValue.String()), kind)
   365  
   366  	case reflect.Int:
   367  		if rightValue.Kind() == reflect.Float64 {
   368  			return impl.compareNumber(float64(leftValue.Int()), rightValue.Float(), kind)
   369  		}
   370  
   371  		return impl.compareNumber(float64(leftValue.Int()), float64(rightValue.Int()), kind)
   372  
   373  	case reflect.Float64:
   374  		if rightValue.Kind() == reflect.Int {
   375  			return impl.compareNumber(leftValue.Float(), float64(rightValue.Int()), kind)
   376  		}
   377  
   378  		return impl.compareNumber(leftValue.Float(), rightValue.Float(), kind)
   379  
   380  	case reflect.Invalid:
   381  		if rightValue.Kind() == reflect.Invalid {
   382  			return true, nil
   383  		}
   384  
   385  		// not possible situation - params are converted to the same type in code above
   386  		return nil, fmt.Errorf("Compare params of Invalid type: left: %+v, right: %+v", leftValue.Kind(), rightValue.Kind())
   387  
   388  	default:
   389  		return nil, fmt.Errorf("Compare not implemented for types: left: %+v, right: %+v", leftValue.Kind(), rightValue.Kind())
   390  	}
   391  }
   392  
   393  func (impl *interperterImpl) coerceToNumber(value reflect.Value) reflect.Value {
   394  	switch value.Kind() {
   395  	case reflect.Invalid:
   396  		return reflect.ValueOf(0)
   397  
   398  	case reflect.Bool:
   399  		switch value.Bool() {
   400  		case true:
   401  			return reflect.ValueOf(1)
   402  		case false:
   403  			return reflect.ValueOf(0)
   404  		}
   405  
   406  	case reflect.String:
   407  		if value.String() == "" {
   408  			return reflect.ValueOf(0)
   409  		}
   410  
   411  		// try to parse the string as a number
   412  		evaluated, err := impl.Evaluate(value.String(), DefaultStatusCheckNone)
   413  		if err != nil {
   414  			return reflect.ValueOf(math.NaN())
   415  		}
   416  
   417  		if value := reflect.ValueOf(evaluated); impl.isNumber(value) {
   418  			return value
   419  		}
   420  	}
   421  
   422  	return reflect.ValueOf(math.NaN())
   423  }
   424  
   425  func (impl *interperterImpl) coerceToString(value reflect.Value) reflect.Value {
   426  	switch value.Kind() {
   427  	case reflect.Invalid:
   428  		return reflect.ValueOf("")
   429  
   430  	case reflect.Bool:
   431  		switch value.Bool() {
   432  		case true:
   433  			return reflect.ValueOf("true")
   434  		case false:
   435  			return reflect.ValueOf("false")
   436  		}
   437  
   438  	case reflect.String:
   439  		return value
   440  
   441  	case reflect.Int:
   442  		return reflect.ValueOf(fmt.Sprint(value))
   443  
   444  	case reflect.Float64:
   445  		if math.IsInf(value.Float(), 1) {
   446  			return reflect.ValueOf("Infinity")
   447  		} else if math.IsInf(value.Float(), -1) {
   448  			return reflect.ValueOf("-Infinity")
   449  		}
   450  		return reflect.ValueOf(fmt.Sprintf("%.15G", value.Float()))
   451  
   452  	case reflect.Slice:
   453  		return reflect.ValueOf("Array")
   454  
   455  	case reflect.Map:
   456  		return reflect.ValueOf("Object")
   457  	}
   458  
   459  	return value
   460  }
   461  
   462  func (impl *interperterImpl) compareString(left string, right string, kind actionlint.CompareOpNodeKind) (bool, error) {
   463  	switch kind {
   464  	case actionlint.CompareOpNodeKindLess:
   465  		return left < right, nil
   466  	case actionlint.CompareOpNodeKindLessEq:
   467  		return left <= right, nil
   468  	case actionlint.CompareOpNodeKindGreater:
   469  		return left > right, nil
   470  	case actionlint.CompareOpNodeKindGreaterEq:
   471  		return left >= right, nil
   472  	case actionlint.CompareOpNodeKindEq:
   473  		return left == right, nil
   474  	case actionlint.CompareOpNodeKindNotEq:
   475  		return left != right, nil
   476  	default:
   477  		return false, fmt.Errorf("TODO: not implemented to compare '%+v'", kind)
   478  	}
   479  }
   480  
   481  func (impl *interperterImpl) compareNumber(left float64, right float64, kind actionlint.CompareOpNodeKind) (bool, error) {
   482  	switch kind {
   483  	case actionlint.CompareOpNodeKindLess:
   484  		return left < right, nil
   485  	case actionlint.CompareOpNodeKindLessEq:
   486  		return left <= right, nil
   487  	case actionlint.CompareOpNodeKindGreater:
   488  		return left > right, nil
   489  	case actionlint.CompareOpNodeKindGreaterEq:
   490  		return left >= right, nil
   491  	case actionlint.CompareOpNodeKindEq:
   492  		return left == right, nil
   493  	case actionlint.CompareOpNodeKindNotEq:
   494  		return left != right, nil
   495  	default:
   496  		return false, fmt.Errorf("TODO: not implemented to compare '%+v'", kind)
   497  	}
   498  }
   499  
   500  func IsTruthy(input interface{}) bool {
   501  	value := reflect.ValueOf(input)
   502  	switch value.Kind() {
   503  	case reflect.Bool:
   504  		return value.Bool()
   505  
   506  	case reflect.String:
   507  		return value.String() != ""
   508  
   509  	case reflect.Int:
   510  		return value.Int() != 0
   511  
   512  	case reflect.Float64:
   513  		if math.IsNaN(value.Float()) {
   514  			return false
   515  		}
   516  
   517  		return value.Float() != 0
   518  
   519  	case reflect.Map, reflect.Slice:
   520  		return true
   521  
   522  	default:
   523  		return false
   524  	}
   525  }
   526  
   527  func (impl *interperterImpl) isNumber(value reflect.Value) bool {
   528  	switch value.Kind() {
   529  	case reflect.Int, reflect.Float64:
   530  		return true
   531  	default:
   532  		return false
   533  	}
   534  }
   535  
   536  func (impl *interperterImpl) getSafeValue(value reflect.Value) interface{} {
   537  	switch value.Kind() {
   538  	case reflect.Invalid:
   539  		return nil
   540  
   541  	case reflect.Float64:
   542  		if value.Float() == 0 {
   543  			return 0
   544  		}
   545  	}
   546  
   547  	return value.Interface()
   548  }
   549  
   550  func (impl *interperterImpl) evaluateLogicalCompare(compareNode *actionlint.LogicalOpNode) (interface{}, error) {
   551  	left, err := impl.evaluateNode(compareNode.Left)
   552  	if err != nil {
   553  		return nil, err
   554  	}
   555  
   556  	leftValue := reflect.ValueOf(left)
   557  
   558  	if IsTruthy(left) == (compareNode.Kind == actionlint.LogicalOpNodeKindOr) {
   559  		return impl.getSafeValue(leftValue), nil
   560  	}
   561  
   562  	right, err := impl.evaluateNode(compareNode.Right)
   563  	if err != nil {
   564  		return nil, err
   565  	}
   566  
   567  	rightValue := reflect.ValueOf(right)
   568  
   569  	switch compareNode.Kind {
   570  	case actionlint.LogicalOpNodeKindAnd:
   571  		return impl.getSafeValue(rightValue), nil
   572  	case actionlint.LogicalOpNodeKindOr:
   573  		return impl.getSafeValue(rightValue), nil
   574  	}
   575  
   576  	return nil, fmt.Errorf("Unable to compare incompatibles types '%s' and '%s'", leftValue.Kind(), rightValue.Kind())
   577  }
   578  
   579  //nolint:gocyclo
   580  func (impl *interperterImpl) evaluateFuncCall(funcCallNode *actionlint.FuncCallNode) (interface{}, error) {
   581  	args := make([]reflect.Value, 0)
   582  
   583  	for _, arg := range funcCallNode.Args {
   584  		value, err := impl.evaluateNode(arg)
   585  		if err != nil {
   586  			return nil, err
   587  		}
   588  
   589  		args = append(args, reflect.ValueOf(value))
   590  	}
   591  
   592  	switch strings.ToLower(funcCallNode.Callee) {
   593  	case "contains":
   594  		return impl.contains(args[0], args[1])
   595  	case "startswith":
   596  		return impl.startsWith(args[0], args[1])
   597  	case "endswith":
   598  		return impl.endsWith(args[0], args[1])
   599  	case "format":
   600  		return impl.format(args[0], args[1:]...)
   601  	case "join":
   602  		if len(args) == 1 {
   603  			return impl.join(args[0], reflect.ValueOf(","))
   604  		}
   605  		return impl.join(args[0], args[1])
   606  	case "tojson":
   607  		return impl.toJSON(args[0])
   608  	case "fromjson":
   609  		return impl.fromJSON(args[0])
   610  	case "hashfiles":
   611  		if impl.env.HashFiles != nil {
   612  			return impl.env.HashFiles(args)
   613  		}
   614  		return impl.hashFiles(args...)
   615  	case "always":
   616  		return impl.always()
   617  	case "success":
   618  		if impl.config.Context == "job" {
   619  			return impl.jobSuccess()
   620  		}
   621  		if impl.config.Context == "step" {
   622  			return impl.stepSuccess()
   623  		}
   624  		return nil, fmt.Errorf("Context '%s' must be one of 'job' or 'step'", impl.config.Context)
   625  	case "failure":
   626  		if impl.config.Context == "job" {
   627  			return impl.jobFailure()
   628  		}
   629  		if impl.config.Context == "step" {
   630  			return impl.stepFailure()
   631  		}
   632  		return nil, fmt.Errorf("Context '%s' must be one of 'job' or 'step'", impl.config.Context)
   633  	case "cancelled":
   634  		return impl.cancelled()
   635  	default:
   636  		return nil, fmt.Errorf("TODO: '%s' not implemented", funcCallNode.Callee)
   637  	}
   638  }