func mapList(source []yaml.Node, names []string, e Expression, binding Binding) ([]yaml.Node, EvaluationInfo, bool) { inp := map[string]yaml.Node{} result := []yaml.Node{} info := DefaultInfo() for i, n := range source { debug.Debug("map: mapping for %d: %+v\n", i, n) inp[names[0]] = n if len(names) > 1 { inp[names[1]] = node(i) } ctx := MapContext{binding, inp} mapped, info, ok := e.Evaluate(ctx) if !ok { debug.Debug("map: %d %+v: failed\n", i, n) return nil, info, false } _, ok = mapped.Value().(Expression) if ok { debug.Debug("map: %d unresolved -> KEEP\n") return nil, info, true } debug.Debug("map: %d --> %+v\n", i, mapped) result = append(result, mapped) } return result, info, true }
func func_exec(arguments []interface{}, binding Binding) (yaml.Node, EvaluationInfo, bool) { info := DefaultInfo() if len(arguments) < 1 { return nil, info, false } args := []string{} debug.Debug("exec: found %d arguments for call\n", len(arguments)) for i, arg := range arguments { list, ok := arg.([]yaml.Node) if i == 0 && ok { debug.Debug("exec: found array as first argument\n") if len(arguments) == 1 && len(list) > 0 { // handle single list argument to gain command and argument for j, arg := range list { v, ok := getArg(j, arg.Value()) if !ok { info.Issue = "command argument must be string" return nil, info, false } args = append(args, v) } } else { info.Issue = "list not allowed for command argument" return nil, info, false } } else { v, ok := getArg(i, arg) if !ok { info.Issue = "command argument must be string" return nil, info, false } args = append(args, v) } } result, err := cachedExecute(args) if err != nil { info.Issue = "execution '" + args[0] + "' failed" // expression set to undefined return nil, info, false } str := string(result) execYML, err := yaml.Parse("exec", result) if strings.HasPrefix(str, "---\n") && err == nil { debug.Debug("exec: found yaml result %+v\n", execYML) return execYML, info, true } else { if strings.HasSuffix(str, "\n") { str = str[:len(str)-1] } int64YML, err := strconv.ParseInt(str, 10, 64) if err == nil { debug.Debug("exec: found integer result: %s\n", int64YML) return node(int64YML), info, true } debug.Debug("exec: found string result: %s\n", string(result)) return node(str), info, true } }
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 (e MapExpr) Evaluate(binding Binding) (yaml.Node, EvaluationInfo, bool) { resolved := true debug.Debug("evaluate mapping\n") value, info, ok := ResolveExpressionOrPushEvaluation(&e.A, &resolved, nil, binding) if !ok { return nil, info, false } if !resolved { return node(e), info, ok } debug.Debug("map: using expression %+v\n", e.B) var result []yaml.Node switch value.(type) { case []yaml.Node: result, info, ok = mapList(value.([]yaml.Node), e.Names, e.B, binding) case map[string]yaml.Node: result, info, ok = mapMap(value.(map[string]yaml.Node), e.Names, e.B, binding) default: info.Issue = "map or list required for mapping" return nil, info, false } if !ok { return nil, info, false } if result == nil { return node(e), info, true } debug.Debug("map: --> %+v\n", result) return node(result), info, true }
func (e ReferenceExpr) Evaluate(binding Binding) (yaml.Node, EvaluationInfo, bool) { var step yaml.Node var ok bool info := DefaultInfo() fromRoot := e.Path[0] == "" debug.Debug("reference: %v\n", e.Path) for i := 0; i < len(e.Path); i++ { if fromRoot { step, ok = binding.FindFromRoot(e.Path[1 : i+1]) } else { step, ok = binding.FindReference(e.Path[:i+1]) } debug.Debug(" %d: %v %+v\n", i, ok, step) if !ok { info.Issue = fmt.Sprintf("'%s' not found", strings.Join(e.Path, ".")) return nil, info, false } if !isLocallyResolved(step) { debug.Debug(" unresolved\n") return node(e), info, true } } if !isResolved(step) { debug.Debug(" unresolved\n") return node(e), info, true } debug.Debug("reference %v -> %+v\n", e.Path, step) return yaml.ReferencedNode(step), info, true }
func mapMap(source map[string]yaml.Node, names []string, e Expression, binding Binding) ([]yaml.Node, EvaluationInfo, bool) { inp := map[string]yaml.Node{} result := []yaml.Node{} info := DefaultInfo() keys := getSortedKeys(source) for _, k := range keys { n := source[k] debug.Debug("map: mapping for %s: %+v\n", k, n) inp[names[0]] = n if len(names) > 1 { inp[names[1]] = node(k) } ctx := MapContext{binding, inp} mapped, info, ok := e.Evaluate(ctx) if !ok { debug.Debug("map: %s %+v: failed\n", k, n) return nil, info, false } _, ok = mapped.Value().(Expression) if ok { debug.Debug("map: %d unresolved -> KEEP\n") return nil, info, true } debug.Debug("map: %s --> %+v\n", k, mapped) result = append(result, mapped) } return result, info, true }
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 (c MapContext) FindReference(path []string) (yaml.Node, bool) { for name, node := range c.names { if len(path) >= 1 && path[0] == name { debug.Debug("map: catch find ref: %v\n", path) if len(path) == 1 { return node, true } return yaml.Find(node, path[1:]...) } } debug.Debug("map: forward find ref: %v\n", path) return c.Binding.FindReference(path) }
func cachedExecute(args []string) ([]byte, error) { h := md5.New() for _, arg := range args { h.Write([]byte(arg)) } hash := fmt.Sprintf("%x", h.Sum(nil)) result := cache[hash] if result != nil { debug.Debug("exec: reusing cache %s for %v\n", hash, args) return result, nil } debug.Debug("exec: calling %v\n", args) cmd := exec.Command(args[0], args[1:]...) result, err := cmd.Output() cache[hash] = result return result, err }
func Flow(source yaml.Node, stubs ...yaml.Node) (yaml.Node, error) { result := source for { debug.Debug("@@@ loop: %+v\n", result) next := flow(result, Environment{Stubs: stubs}, true) debug.Debug("@@@ ---> %+v\n", next) if reflect.DeepEqual(result, next) { break } result = next } debug.Debug("@@@ Done\n") unresolved := dynaml.FindUnresolvedNodes(result) if len(unresolved) > 0 { return nil, dynaml.UnresolvedNodes{unresolved} } return result, nil }
func flowString(root yaml.Node, env Environment) yaml.Node { sub := yaml.EmbeddedDynaml(root) if sub == nil { return root } debug.Debug("dynaml: %v: %s\n", env.Path, *sub) expr, err := dynaml.Parse(*sub, env.Path, env.StubPath) if err != nil { return root } return yaml.SubstituteNode(expr, root) }
func (e MergeExpr) Evaluate(binding Binding) (yaml.Node, EvaluationInfo, bool) { var info EvaluationInfo if e.Redirect { info.RedirectPath = e.Path } info.KeyName = e.KeyName debug.Debug("/// lookup %v\n", e.Path) node, ok := binding.FindInStubs(e.Path) if ok { info.Replace = e.Replace info.Merged = true } else { info.Issue = fmt.Sprintf("'%s' not found in any stub", strings.Join(e.Path, ".")) } return node, info, ok }
func getArg(i int, value interface{}) (string, bool) { debug.Debug("arg %d: %+v\n", i, value) switch value.(type) { case string: return value.(string), true case int64: return strconv.FormatInt(value.(int64), 10), true default: if i == 0 || value == nil { return "", false } yaml, err := candiedyaml.Marshal(node(value)) if err != nil { log.Fatalln("error marshalling manifest:", err) } return "---\n" + string(yaml), true } }
func (e ConcatenationExpr) Evaluate(binding Binding) (yaml.Node, EvaluationInfo, bool) { resolved := true debug.Debug("CONCAT %+v,%+v\n", e.A, e.B) a, infoa, ok := ResolveExpressionOrPushEvaluation(&e.A, &resolved, nil, binding) if !ok { debug.Debug(" eval a failed\n") return nil, infoa, false } b, info, ok := ResolveExpressionOrPushEvaluation(&e.B, &resolved, &infoa, binding) if !ok { debug.Debug(" eval b failed\n") return nil, info, false } if !resolved { debug.Debug(" still unresolved operands\n") return node(e), info, true } debug.Debug("CONCAT resolved %+v,%+v\n", a, b) val, ok := concatenateStringAndInt(a, b) if ok { debug.Debug("CONCAT --> string %+v\n", val) return node(val), info, true } alist, aok := a.([]yaml.Node) if !aok { switch a.(type) { case map[string]yaml.Node: info.Issue = "first argument must be list or simple value" default: info.Issue = "simple value can only be concatenated with simple values" } return nil, info, false } switch b.(type) { case []yaml.Node: return node(append(alist, b.([]yaml.Node)...)), info, true case nil: return node(a), info, true default: return node(append(alist, node(b))), info, true } }
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 buildExpression(grammar *DynamlGrammar, path []string, stubPath []string) Expression { tokens := &tokenStack{} // flags for parsing merge options in merge expression // this expression is NOT recursive, therefore single flag variables are sufficient replace := false required := false keyName := "" for token := range grammar.Tokens() { contents := grammar.Buffer[token.begin:token.end] switch token.pegRule { case ruleDynaml: return tokens.Pop() case rulePrefer: tokens.Push(PreferExpr{tokens.Pop()}) case ruleAuto: tokens.Push(AutoExpr{path}) case ruleMerge: replace = false required = false keyName = "" case ruleSimpleMerge: debug.Debug("*** rule simple merge\n") redirect := !equals(path, stubPath) tokens.Push(MergeExpr{stubPath, redirect, replace, replace || required || redirect, keyName}) case ruleRefMerge: debug.Debug("*** rule ref merge\n") rhs := tokens.Pop() tokens.Push(MergeExpr{rhs.(ReferenceExpr).Path, true, replace, true, keyName}) case ruleReplace: replace = true case ruleRequired: required = true case ruleOn: keyName = tokens.Pop().(nameHelper).name case ruleReference: tokens.Push(ReferenceExpr{strings.Split(contents, ".")}) case ruleInteger: val, err := strconv.ParseInt(contents, 10, 64) if err != nil { panic(err) } tokens.Push(IntegerExpr{val}) case ruleNil: tokens.Push(NilExpr{}) case ruleBoolean: tokens.Push(BooleanExpr{contents == "true"}) case ruleString: val := strings.Replace(contents[1:len(contents)-1], `\"`, `"`, -1) tokens.Push(StringExpr{val}) case ruleOr: rhs := tokens.Pop() lhs := tokens.Pop() tokens.Push(OrExpr{A: lhs, B: rhs}) case ruleConcatenation: rhs := tokens.Pop() lhs := tokens.Pop() tokens.Push(ConcatenationExpr{A: lhs, B: rhs}) case ruleAddition: rhs := tokens.Pop() lhs := tokens.Pop() tokens.Push(AdditionExpr{A: lhs, B: rhs}) case ruleSubtraction: rhs := tokens.Pop() lhs := tokens.Pop() tokens.Push(SubtractionExpr{A: lhs, B: rhs}) case ruleMultiplication: rhs := tokens.Pop() lhs := tokens.Pop() tokens.Push(MultiplicationExpr{A: lhs, B: rhs}) case ruleDivision: rhs := tokens.Pop() lhs := tokens.Pop() tokens.Push(DivisionExpr{A: lhs, B: rhs}) case ruleModulo: rhs := tokens.Pop() lhs := tokens.Pop() tokens.Push(ModuloExpr{A: lhs, B: rhs}) case ruleCall: tokens.Push(CallExpr{ Name: tokens.Pop().(nameHelper).name, Arguments: tokens.GetExpressionList(), }) case ruleName: tokens.Push(nameHelper{name: contents}) case ruleMapping: rhs := tokens.Pop() names := []string{tokens.Pop().(nameHelper).name} lhs := tokens.Pop() name, ok := lhs.(nameHelper) if ok { names = append(names, name.name) lhs = tokens.Pop() } tokens.Push(MapExpr{A: lhs, Names: names, B: rhs}) case ruleList: seq := tokens.GetExpressionList() tokens.Push(ListExpr{seq}) case ruleNextExpression: rhs := tokens.Pop() list := tokens.PopExpressionList() list.list = append(list.list, rhs) tokens.Push(list) case ruleContents, ruleArguments: tokens.SetExpressionList(tokens.PopExpressionList()) case ruleKey: case ruleGrouped: case ruleLevel0, ruleLevel1, ruleLevel2, ruleLevel3, ruleLevel4: case ruleExpression: case rulews: case rulereq_ws: default: panic("unhandled:" + rul3s[token.pegRule]) } } panic("unreachable") }
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 }