mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-03-03 02:10:18 +00:00
196 lines
5.4 KiB
Go
196 lines
5.4 KiB
Go
package templateeval
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
|
|
v2 "gitea.com/gitea/act_runner/internal/eval/v2"
|
|
exprparser "gitea.com/gitea/act_runner/internal/expr"
|
|
"gitea.com/gitea/act_runner/pkg/schema"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type ExpressionEvaluator struct {
|
|
RestrictEval bool
|
|
EvaluationContext v2.EvaluationContext
|
|
}
|
|
|
|
func isImplExpr(snode *schema.Node) bool {
|
|
def := snode.Schema.GetDefinition(snode.Definition)
|
|
return def.String != nil && def.String.IsExpression
|
|
}
|
|
|
|
func (ee ExpressionEvaluator) evaluateScalarYamlNode(_ context.Context, node *yaml.Node, snode *schema.Node) (*yaml.Node, error) {
|
|
var in string
|
|
if err := node.Decode(&in); err != nil {
|
|
return nil, err
|
|
}
|
|
expr, isExpr, err := rewriteSubExpression(in, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if snode == nil || !isExpr && !isImplExpr(snode) || snode.Schema.GetDefinition(snode.Definition).String.IsExpression || ee.RestrictEval && node.Tag != "!!expr" {
|
|
return node, nil
|
|
}
|
|
parsed, err := exprparser.Parse(expr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
canEvaluate := ee.canEvaluate(parsed, snode)
|
|
if !canEvaluate {
|
|
node.Tag = "!!expr"
|
|
return node, nil
|
|
}
|
|
|
|
eval := v2.NewEvaluator(&ee.EvaluationContext)
|
|
res, err := eval.EvaluateRaw(expr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret := &yaml.Node{}
|
|
if err := ret.Encode(res); err != nil {
|
|
return nil, err
|
|
}
|
|
ret.Line = node.Line
|
|
ret.Column = node.Column
|
|
// Finally check if we found a schema validation error
|
|
return ret, snode.UnmarshalYAML(ret)
|
|
}
|
|
|
|
func (ee ExpressionEvaluator) canEvaluate(parsed exprparser.Node, snode *schema.Node) bool {
|
|
canEvaluate := true
|
|
for _, v := range snode.GetVariables() {
|
|
canEvaluate = canEvaluate && ee.EvaluationContext.Variables.Get(v) != nil
|
|
}
|
|
for _, v := range snode.GetFunctions() {
|
|
canEvaluate = canEvaluate && ee.EvaluationContext.Functions.Get(v.GetName()) != nil
|
|
}
|
|
exprparser.VisitNode(parsed, func(node exprparser.Node) {
|
|
switch el := node.(type) {
|
|
case *exprparser.FunctionNode:
|
|
canEvaluate = canEvaluate && ee.EvaluationContext.Functions.Get(el.Name) != nil
|
|
case *exprparser.ValueNode:
|
|
canEvaluate = canEvaluate && (el.Kind != exprparser.TokenKindNamedValue || ee.EvaluationContext.Variables.Get(el.Value.(string)) != nil)
|
|
}
|
|
})
|
|
return canEvaluate
|
|
}
|
|
|
|
func (ee ExpressionEvaluator) evaluateMappingYamlNode(ctx context.Context, node *yaml.Node, snode *schema.Node) (*yaml.Node, error) {
|
|
var ret *yaml.Node
|
|
// GitHub has this undocumented feature to merge maps, called insert directive
|
|
insertDirective := regexp.MustCompile(`\${{\s*insert\s*}}`)
|
|
for i := 0; i < len(node.Content)/2; i++ {
|
|
k := node.Content[i*2]
|
|
var sk string
|
|
shouldInsert := k.Decode(&sk) == nil && insertDirective.MatchString(sk)
|
|
changed := func() error {
|
|
if ret == nil {
|
|
ret = &yaml.Node{}
|
|
if err := ret.Encode(node); err != nil {
|
|
return err
|
|
}
|
|
ret.Content = ret.Content[:i*2]
|
|
}
|
|
return nil
|
|
}
|
|
var ek *yaml.Node
|
|
if !shouldInsert {
|
|
var err error
|
|
ek, err = ee.evaluateYamlNodeInternal(ctx, k, snode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ek != nil {
|
|
if err := changed(); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
ek = k
|
|
}
|
|
}
|
|
v := node.Content[i*2+1]
|
|
ev, err := ee.evaluateYamlNodeInternal(ctx, v, snode.GetNestedNode(ek.Value))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ev != nil {
|
|
if err := changed(); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
ev = v
|
|
}
|
|
// Merge the nested map of the insert directive
|
|
if shouldInsert {
|
|
if ev.Kind != yaml.MappingNode {
|
|
return nil, fmt.Errorf("failed to insert node %v into mapping %v unexpected type %v expected MappingNode", ev, node, ev.Kind)
|
|
}
|
|
if err := changed(); err != nil {
|
|
return nil, err
|
|
}
|
|
ret.Content = append(ret.Content, ev.Content...)
|
|
} else if ret != nil {
|
|
ret.Content = append(ret.Content, ek, ev)
|
|
}
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (ee ExpressionEvaluator) evaluateSequenceYamlNode(ctx context.Context, node *yaml.Node, snode *schema.Node) (*yaml.Node, error) {
|
|
var ret *yaml.Node
|
|
for i := 0; i < len(node.Content); i++ {
|
|
v := node.Content[i]
|
|
// Preserve nested sequences
|
|
wasseq := v.Kind == yaml.SequenceNode
|
|
ev, err := ee.evaluateYamlNodeInternal(ctx, v, snode.GetNestedNode("*"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ev != nil {
|
|
if ret == nil {
|
|
ret = &yaml.Node{}
|
|
if err := ret.Encode(node); err != nil {
|
|
return nil, err
|
|
}
|
|
ret.Content = ret.Content[:i]
|
|
}
|
|
// GitHub has this undocumented feature to merge sequences / arrays
|
|
// We have a nested sequence via evaluation, merge the arrays
|
|
if ev.Kind == yaml.SequenceNode && !wasseq {
|
|
ret.Content = append(ret.Content, ev.Content...)
|
|
} else {
|
|
ret.Content = append(ret.Content, ev)
|
|
}
|
|
} else if ret != nil {
|
|
ret.Content = append(ret.Content, v)
|
|
}
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (ee ExpressionEvaluator) evaluateYamlNodeInternal(ctx context.Context, node *yaml.Node, snode *schema.Node) (*yaml.Node, error) {
|
|
switch node.Kind {
|
|
case yaml.ScalarNode:
|
|
return ee.evaluateScalarYamlNode(ctx, node, snode)
|
|
case yaml.MappingNode:
|
|
return ee.evaluateMappingYamlNode(ctx, node, snode)
|
|
case yaml.SequenceNode:
|
|
return ee.evaluateSequenceYamlNode(ctx, node, snode)
|
|
default:
|
|
return nil, nil //nolint:nilnil
|
|
}
|
|
}
|
|
|
|
func (ee ExpressionEvaluator) EvaluateYamlNode(ctx context.Context, node *yaml.Node, snode *schema.Node) error {
|
|
ret, err := ee.evaluateYamlNodeInternal(ctx, node, snode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ret != nil {
|
|
return ret.Decode(node)
|
|
}
|
|
return nil
|
|
}
|