diff --git a/pkg/runner/expression.go b/pkg/runner/expression.go index ca40e95c..111274b0 100644 --- a/pkg/runner/expression.go +++ b/pkg/runner/expression.go @@ -158,67 +158,117 @@ func (ee expressionEvaluator) evaluate(ctx context.Context, in string, defaultSt return evaluated, err } -func (ee expressionEvaluator) evaluateScalarYamlNode(ctx context.Context, node *yaml.Node) error { +func (ee expressionEvaluator) evaluateScalarYamlNode(ctx context.Context, node *yaml.Node) (*yaml.Node, error) { var in string if err := node.Decode(&in); err != nil { - return err + return nil, err } if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") { - return nil + return nil, nil } expr, _ := rewriteSubExpression(ctx, in, false) res, err := ee.evaluate(ctx, expr, exprparser.DefaultStatusCheckNone) if err != nil { - return err + return nil, err } - return node.Encode(res) + ret := &yaml.Node{} + if err := ret.Encode(res); err != nil { + return nil, err + } + return ret, err } -func (ee expressionEvaluator) evaluateMappingYamlNode(ctx context.Context, node *yaml.Node) error { +func (ee expressionEvaluator) evaluateMappingYamlNode(ctx context.Context, node *yaml.Node) (*yaml.Node, error) { + var ret *yaml.Node = nil // 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; { + for i := 0; i < len(node.Content)/2; i++ { + 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 + } k := node.Content[i*2] v := node.Content[i*2+1] - if err := ee.EvaluateYamlNode(ctx, v); err != nil { - return err + ev, err := ee.evaluateYamlNodeInternal(ctx, v) + if err != nil { + return nil, err + } + if ev != nil { + if err := changed(); err != nil { + return nil, err + } + } else { + ev = v } var sk string // Merge the nested map of the insert directive if k.Decode(&sk) == nil && insertDirective.MatchString(sk) { - node.Content = append(append(node.Content[:i*2], v.Content...), node.Content[(i+1)*2:]...) - i += len(v.Content) / 2 - } else { - if err := ee.EvaluateYamlNode(ctx, k); err != nil { - return err + 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 { + ek, err := ee.evaluateYamlNodeInternal(ctx, k) + if err != nil { + return nil, err + } + if ek != nil { + if err := changed(); err != nil { + return nil, err + } + } else { + ek = k + } + if ret != nil { + ret.Content = append(ret.Content, ek, ev) } - i++ } } - return nil + return ret, nil } -func (ee expressionEvaluator) evaluateSequenceYamlNode(ctx context.Context, node *yaml.Node) error { - for i := 0; i < len(node.Content); { +func (ee expressionEvaluator) evaluateSequenceYamlNode(ctx context.Context, node *yaml.Node) (*yaml.Node, error) { + var ret *yaml.Node = nil + for i := 0; i < len(node.Content); i++ { v := node.Content[i] // Preserve nested sequences wasseq := v.Kind == yaml.SequenceNode - if err := ee.EvaluateYamlNode(ctx, v); err != nil { - return err + ev, err := ee.evaluateYamlNodeInternal(ctx, v) + if err != nil { + return nil, err } - // GitHub has this undocumented feature to merge sequences / arrays - // We have a nested sequence via evaluation, merge the arrays - if v.Kind == yaml.SequenceNode && !wasseq { - node.Content = append(append(node.Content[:i], v.Content...), node.Content[i+1:]...) - i += len(v.Content) - } else { - i++ + 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 nil + return ret, nil } -func (ee expressionEvaluator) EvaluateYamlNode(ctx context.Context, node *yaml.Node) error { +func (ee expressionEvaluator) evaluateYamlNodeInternal(ctx context.Context, node *yaml.Node) (*yaml.Node, error) { switch node.Kind { case yaml.ScalarNode: return ee.evaluateScalarYamlNode(ctx, node) @@ -227,10 +277,21 @@ func (ee expressionEvaluator) EvaluateYamlNode(ctx context.Context, node *yaml.N case yaml.SequenceNode: return ee.evaluateSequenceYamlNode(ctx, node) default: - return nil + return nil, nil } } +func (ee expressionEvaluator) EvaluateYamlNode(ctx context.Context, node *yaml.Node) error { + ret, err := ee.evaluateYamlNodeInternal(ctx, node) + if err != nil { + return err + } + if ret != nil { + return ret.Decode(node) + } + return nil +} + func (ee expressionEvaluator) Interpolate(ctx context.Context, in string) string { if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") { return in