func FindUnresolvedNodes(root yaml.Node, context ...string) (nodes []UnresolvedNode) { if root == nil { return nodes } switch val := root.Value().(type) { case map[string]yaml.Node: for key, val := range val { nodes = append( nodes, FindUnresolvedNodes(val, addContext(context, key)...)..., ) } case []yaml.Node: for i, val := range val { context := addContext(context, fmt.Sprintf("[%d]", i)) nodes = append( nodes, FindUnresolvedNodes(val, context...)..., ) } case Expression: var path []string switch val := root.Value().(type) { case AutoExpr: path = val.Path case MergeExpr: path = val.Path } nodes = append(nodes, UnresolvedNode{ Node: root, Context: context, Path: path, }) case string: if yaml.EmbeddedDynaml(root) != nil { nodes = append(nodes, UnresolvedNode{ Node: yaml.IssueNode(root, "unparseable expression"), Context: context, Path: []string{}, }) } } return nodes }
}) }) Context("when some dynaml nodes cannot be resolved", func() { It("returns an error", func() { source := parseYAML(` --- foo: (( auto )) `) _, err := Flow(source) Expect(err).To(Equal(dynaml.UnresolvedNodes{ Nodes: []dynaml.UnresolvedNode{ { Node: yaml.IssueNode(yaml.NewNode( dynaml.AutoExpr{Path: []string{"foo"}}, "test", ), "auto only allowed for size entry in resource pools"), Context: []string{"foo"}, Path: []string{"foo"}, }, }, })) }) }) Context("when a reference is made to a yet-to-be-resolved node, in a || expression", func() { It("eventually resolves to the referenced node", func() { source := parseYAML(` --- properties: template_only: (( merge ))
func flow(root yaml.Node, env Environment, shouldOverride bool) yaml.Node { if root == nil { return root } replace := root.ReplaceFlag() redirect := root.RedirectPath() preferred := root.Preferred() merged := root.Merged() keyName := root.KeyName() if redirect != nil { env = env.RedirectOverwrite(redirect) } if !replace { switch val := root.Value().(type) { case map[string]yaml.Node: return flowMap(root, env) case []yaml.Node: return flowList(root, env) case dynaml.Expression: debug.Debug("??? eval %+v\n", val) result, info, ok := val.Evaluate(env) if !ok { root = yaml.IssueNode(root, info.Issue) debug.Debug("??? failed ---> KEEP\n") if !shouldOverride { return root } replace = replace || info.Replace } else { _, ok = result.Value().(string) if ok { // map result to potential expression result = flowString(result, env) } _, expr := result.Value().(dynaml.Expression) // preserve accumulated node attributes if preferred || info.Preferred { debug.Debug(" PREFERRED") result = yaml.PreferredNode(result) } if info.KeyName != "" { keyName = info.KeyName result = yaml.KeyNameNode(result, keyName) } if len(info.RedirectPath) > 0 { redirect = info.RedirectPath } if len(redirect) > 0 { debug.Debug(" REDIRECT -> %v\n", redirect) result = yaml.RedirectNode(result.Value(), result, redirect) } if replace || info.Replace { debug.Debug(" REPLACE\n") result = yaml.ReplaceNode(result.Value(), result, redirect) } else { if merged || info.Merged { debug.Debug(" MERGED\n") result = yaml.MergedNode(result) } } if expr || result.Merged() || !shouldOverride || result.Preferred() { debug.Debug(" prefer expression over override") debug.Debug("??? ---> %+v\n", result) return result } debug.Debug("??? try override") replace = result.ReplaceFlag() root = result } case string: result := flowString(root, env) if result != nil { _, ok := result.Value().(dynaml.Expression) if ok { // analyse expression before overriding return result } } } } if !merged && shouldOverride { debug.Debug("/// lookup stub %v -> %v\n", env.Path, env.StubPath) overridden, found := env.FindInStubs(env.StubPath) if found { root = overridden if keyName != "" { root = yaml.KeyNameNode(root, keyName) } if replace { return yaml.ReplaceNode(root.Value(), root, redirect) } if redirect != nil { return yaml.RedirectNode(root.Value(), root, redirect) } if merged { return yaml.MergedNode(root) } } } return root }