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 }