/* * compatibility issue. A single merge node was always optional * means: <<: (( merge )) == <<: (( merge || nil )) * the first pass, just parses the dynaml * only the second pass, evaluates a dynaml node! */ func simpleMergeCompatibilityCheck(initial bool, node yaml.Node) bool { if !initial { merge, ok := node.Value().(dynaml.MergeExpr) return ok && !merge.Required } return false }
func isExpression(node yaml.Node) bool { if node == nil { return false } _, ok := node.Value().(Expression) return ok }
func ProcessKeyTag(val yaml.Node) (yaml.Node, string) { keyName := "" m, ok := val.Value().(map[string]yaml.Node) if ok { found := false for key, _ := range m { split := strings.Index(key, ":") if split > 0 { if key[:split] == "key" { keyName = key[split+1:] found = true } } } if found { newMap := make(map[string]yaml.Node) for key, v := range m { split := strings.Index(key, ":") if split > 0 { if key[:split] == "key" { key = key[split+1:] } } newMap[key] = v } return yaml.SubstituteNode(newMap, val), keyName } } return val, keyName }
func flow(root yaml.Node, env Environment, shouldOverride bool) yaml.Node { if root == nil { return root } switch val := root.Value().(type) { case map[string]yaml.Node: return flowMap(root, env) case []yaml.Node: return flowList(root, env) case dynaml.Expression: result, ok := val.Evaluate(env) if !ok { return root } return result } if shouldOverride { overridden, found := env.FindInStubs(env.Path) if found { return overridden } } _, ok := root.Value().(string) if ok { return flowString(root, env) } return root }
func flowMap(root yaml.Node, env Environment) yaml.Node { rootMap := root.Value().(map[string]yaml.Node) env = env.WithScope(rootMap) newMap := make(map[string]yaml.Node) for key, val := range rootMap { if key == "<<" { base := flow(val, env, true) baseMap, ok := base.Value().(map[string]yaml.Node) if ok { for k, v := range baseMap { newMap[k] = v } } continue } newMap[key] = flow(val, env.WithPath(key), true) } return yaml.NewNode(newMap, root.SourceName()) }
func flowList(root yaml.Node, env Environment) yaml.Node { rootList := root.Value().([]yaml.Node) debug.Debug("HANDLE LIST %v\n", env.Path) merged, process, replaced, redirectPath, keyName := processMerges(root, rootList, env) if process { newList := []yaml.Node{} if len(redirectPath) > 0 { env = env.RedirectOverwrite(redirectPath) } for idx, val := range merged { step := stepName(idx, val, keyName) debug.Debug(" step %s\n", step) newList = append(newList, flow(val, env.WithPath(step), false)) } merged = newList } if keyName != "" { root = yaml.KeyNameNode(root, keyName) } debug.Debug("LIST DONE (%s)%v\n", root.KeyName(), env.Path) if replaced { return yaml.ReplaceNode(merged, root, redirectPath) } if len(redirectPath) > 0 { return yaml.RedirectNode(merged, root, redirectPath) } return yaml.SubstituteNode(merged, root) }
func flowMap(root yaml.Node, env Environment) yaml.Node { rootMap := root.Value().(map[string]yaml.Node) env = env.WithScope(rootMap) newMap := make(map[string]yaml.Node) sortedKeys := getSortedKeys(rootMap) // iteration order matters for the "<<" operator, it must be the first key in the map that is handled for i := range sortedKeys { key := sortedKeys[i] val := rootMap[key] if key == "<<" { base := flow(val, env, true) baseMap, ok := base.Value().(map[string]yaml.Node) if ok { for k, v := range baseMap { newMap[k] = v } } continue } newMap[key] = flow(val, env.WithPath(key), true) } return yaml.NewNode(newMap, root.SourceName()) }
func isResolved(node yaml.Node) bool { if node == nil { return true } switch node.Value().(type) { case Expression: return false case []yaml.Node: for _, n := range node.Value().([]yaml.Node) { if !isResolved(n) { return false } } return true case map[string]yaml.Node: for _, n := range node.Value().(map[string]yaml.Node) { if !isResolved(n) { return false } } return true case string: if yaml.EmbeddedDynaml(node) != nil { return false } return true default: return true } }
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 }
func flowList(root yaml.Node, env Environment) yaml.Node { rootList := root.Value().([]yaml.Node) merged := processMerges(rootList, env) newList := []yaml.Node{} for idx, val := range merged { step := stepName(idx, val) newList = append(newList, flow(val, env.WithPath(step), false)) } return yaml.NewNode(newList, root.SourceName()) }
func flowString(root yaml.Node, env Environment) yaml.Node { rootString := root.Value().(string) sub := embeddedDynaml.FindStringSubmatch(rootString) if sub == nil { return root } expr, err := dynaml.Parse(sub[1], env.Path) if err != nil { return root } return yaml.NewNode(expr, root.SourceName()) }
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 dynaml.Expression: nodes = append(nodes, UnresolvedNode{ Node: root, Context: context, }) } return nodes }
func compare(a, b yaml.Node, path []string) []Diff { mismatch := Diff{A: a, B: b, Path: path} switch av := a.Value().(type) { case map[string]yaml.Node: switch bv := b.Value().(type) { case map[string]yaml.Node: return compareMap(av, bv, path) case []yaml.Node: toMap := listToMap(bv) if toMap != nil { return compareMap(av, toMap, path) } else { return []Diff{mismatch} } default: return []Diff{mismatch} } case []yaml.Node: switch bv := b.Value().(type) { case []yaml.Node: return compareList(av, bv, path) default: return []Diff{mismatch} } default: atype := reflect.TypeOf(a) btype := reflect.TypeOf(b) if atype != btype { return []Diff{mismatch} } if av != b.Value() { return []Diff{Diff{A: a, B: b, Path: path}} } } return []Diff{} }
func processMerges(orig yaml.Node, root []yaml.Node, env Environment) ([]yaml.Node, bool, bool, []string, string) { spliced := []yaml.Node{} process := true keyName := orig.KeyName() replaced := orig.ReplaceFlag() redirectPath := orig.RedirectPath() for _, val := range root { if val == nil { continue } inlineNode, ok := yaml.UnresolvedListEntryMerge(val) if ok { debug.Debug("*** %+v\n", inlineNode.Value()) _, initial := inlineNode.Value().(string) result := flow(inlineNode, env, false) if result.KeyName() != "" { keyName = result.KeyName() } debug.Debug("=== (%s)%+v\n", keyName, result) _, ok := result.Value().(dynaml.Expression) if ok { if simpleMergeCompatibilityCheck(initial, inlineNode) { continue } newMap := make(map[string]yaml.Node) newMap["<<"] = result val = yaml.SubstituteNode(newMap, orig) process = false } else { inline, ok := result.Value().([]yaml.Node) if ok { inlineNew := newEntries(inline, root, keyName) replaced = result.ReplaceFlag() redirectPath = result.RedirectPath() if replaced { spliced = inlineNew process = false break } else { spliced = append(spliced, inlineNew...) } } continue } } val, newKey := ProcessKeyTag(val) if newKey != "" { keyName = newKey } spliced = append(spliced, val) } debug.Debug("--> %+v proc=%v replaced=%v redirect=%v key=%s\n", spliced, process, replaced, redirectPath, keyName) return spliced, process, replaced, redirectPath, keyName }
func isLocallyResolved(node yaml.Node) bool { switch v := node.Value().(type) { case Expression: return false case map[string]yaml.Node: if !yaml.IsMapResolved(v) { return false } case []yaml.Node: if !yaml.IsListResolved(v) { return false } default: } return true }
func concatenateStringAndInt(a yaml.Node, b yaml.Node) (string, bool) { aString, aOk := a.Value().(string) if aOk { bString, bOk := b.Value().(string) if bOk { return aString + bString, true } else { bInt, bOk := b.Value().(int64) if bOk { return aString + strconv.FormatInt(bInt, 10), true } } } return "", false }
func flowMap(root yaml.Node, env Environment) yaml.Node { processed := true rootMap := root.Value().(map[string]yaml.Node) env = env.WithScope(rootMap) redirect := root.RedirectPath() replace := root.ReplaceFlag() newMap := make(map[string]yaml.Node) sortedKeys := getSortedKeys(rootMap) debug.Debug("HANDLE MAP %v\n", env.Path) // iteration order matters for the "<<" operator, it must be the first key in the map that is handled for i := range sortedKeys { key := sortedKeys[i] val := rootMap[key] if key == "<<" { _, initial := val.Value().(string) base := flow(val, env, false) _, ok := base.Value().(dynaml.Expression) if ok { if simpleMergeCompatibilityCheck(initial, base) { continue } val = base processed = false } else { baseMap, ok := base.Value().(map[string]yaml.Node) if base != nil && base.RedirectPath() != nil { redirect = base.RedirectPath() env = env.RedirectOverwrite(redirect) } if ok { for k, v := range baseMap { newMap[k] = v } } replace = base.ReplaceFlag() if replace { break } continue } } else { if processed { val = flow(val, env.WithPath(key), true) } } debug.Debug("MAP (%s)%s\n", val.KeyName(), key) newMap[key] = val } debug.Debug("MAP DONE %v\n", env.Path) if replace { return yaml.ReplaceNode(newMap, root, redirect) } return yaml.RedirectNode(newMap, root, redirect) }
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 }