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 }