github.com/simonferquel/app@v0.6.1-0.20181012141724-68b7cccf26ac/pkg/yatee/yatee.go (about) 1 package yatee 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "strconv" 8 "strings" 9 10 "github.com/docker/app/internal/yaml" 11 yml "gopkg.in/yaml.v2" 12 ) 13 14 const ( 15 // OptionErrOnMissingKey if set will make rendering fail if a non-existing variable is used 16 OptionErrOnMissingKey = "ErrOnMissingKey" 17 ) 18 19 type options struct { 20 errOnMissingKey bool 21 } 22 23 // flatten flattens a structure: foo.bar.baz -> 'foo.bar.baz' 24 func flatten(in map[string]interface{}, out map[string]interface{}, prefix string) { 25 for k, v := range in { 26 switch vv := v.(type) { 27 case string: 28 out[prefix+k] = vv 29 case map[string]interface{}: 30 flatten(vv, out, prefix+k+".") 31 case []interface{}: 32 values := []string{} 33 for _, i := range vv { 34 values = append(values, fmt.Sprintf("%v", i)) 35 } 36 out[prefix+k] = strings.Join(values, " ") 37 default: 38 out[prefix+k] = v 39 } 40 } 41 } 42 43 func merge(res map[string]interface{}, src map[interface{}]interface{}) { 44 for k, v := range src { 45 kk, ok := k.(string) 46 if !ok { 47 panic(fmt.Sprintf("fatal error, key %v in %#v is not a string", k, src)) 48 } 49 eval, ok := res[kk] 50 switch vv := v.(type) { 51 case map[interface{}]interface{}: 52 if !ok { 53 res[kk] = make(map[string]interface{}) 54 } else { 55 if _, ok2 := eval.(map[string]interface{}); !ok2 { 56 res[kk] = make(map[string]interface{}) 57 } 58 } 59 merge(res[kk].(map[string]interface{}), vv) 60 default: 61 res[kk] = vv 62 } 63 } 64 } 65 66 // LoadSettings loads a set of settings file and produce a property dictionary 67 func LoadSettings(files []string) (map[string]interface{}, error) { 68 res := make(map[string]interface{}) 69 for _, f := range files { 70 data, err := ioutil.ReadFile(f) 71 if err != nil { 72 return nil, err 73 } 74 s := make(map[interface{}]interface{}) 75 err = yaml.Unmarshal(data, &s) 76 if err != nil { 77 return nil, err 78 } 79 merge(res, s) 80 } 81 return res, nil 82 } 83 84 func isIdentNumChar(r byte) bool { 85 return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || 86 r == '.' || r == '_' 87 } 88 89 // extract extracts an expression from a string 90 // nolint: gocyclo 91 func extract(expr string) (string, error) { 92 if expr == "" { 93 return "", nil 94 } 95 if expr[0] == '{' { 96 closing := strings.Index(expr, "}") 97 if closing == -1 { 98 return "", fmt.Errorf("Missing '}' at end of expression") 99 } 100 return expr[0 : closing+1], nil 101 } 102 if expr[0] == '(' { 103 indent := 1 104 i := 1 105 for ; i < len(expr); i++ { 106 if expr[i] == '(' { 107 indent++ 108 } 109 if expr[i] == ')' { 110 indent-- 111 } 112 if indent == 0 { 113 break 114 } 115 } 116 if indent != 0 { 117 return "", fmt.Errorf("Missing ')' at end of expression") 118 } 119 return expr[0 : i+1], nil 120 } 121 i := 0 122 for ; i < len(expr); i++ { 123 if !((expr[i] >= 'a' && expr[i] <= 'z') || (expr[i] >= 'A' && expr[i] <= 'Z') || 124 expr[i] == '.' || expr[i] == '_') { 125 break 126 } 127 } 128 return expr[0:i], nil 129 } 130 131 func tokenize(expr string) ([]string, error) { 132 var tokens []string 133 p := 0 134 for p < len(expr) { 135 if isIdentNumChar(expr[p]) { 136 pp := p + 1 137 for ; pp < len(expr) && isIdentNumChar(expr[pp]); pp++ { 138 } 139 tokens = append(tokens, expr[p:pp]) 140 p = pp 141 } else { 142 if expr[p] != ' ' { 143 tokens = append(tokens, expr[p:p+1]) 144 } 145 p++ 146 } 147 } 148 return tokens, nil 149 } 150 151 func evalValue(comps []string, i int) (int64, int, error) { 152 c := comps[i] 153 if c == "(" { 154 value, ni, error := evalSub(comps, i+1) 155 if error != nil { 156 return 0, 0, error 157 } 158 return value, ni, nil 159 } 160 v, err := strconv.ParseInt(c, 0, 64) 161 return v, i + 1, err 162 } 163 164 func evalSub(comps []string, i int) (int64, int, error) { 165 current, next, err := evalValue(comps, i) 166 if err != nil { 167 return 0, 0, err 168 } 169 i = next 170 for i < len(comps) { 171 c := comps[i] 172 if c == ")" { 173 return current, i + 1, nil 174 } 175 if c == "*" || c == "+" || c == "-" || c == "/" || c == "%" { 176 rhs, next, err := evalValue(comps, i+1) 177 if err != nil { 178 return 0, 0, err 179 } 180 switch c { 181 case "+": 182 current += rhs 183 case "-": 184 current -= rhs 185 case "/": 186 current /= rhs 187 case "*": 188 current *= rhs 189 case "%": 190 current %= rhs 191 } 192 i = next 193 } else { 194 return 0, 0, fmt.Errorf("expected operator") 195 } 196 } 197 return current, i, nil 198 } 199 200 // resolves an arithmetic expression 201 func evalExpr(expr string) (int64, error) { 202 comps, err := tokenize(expr) 203 if err != nil { 204 return 0, err 205 } 206 v, _, err := evalSub(comps, 0) 207 return v, err 208 } 209 210 // resolves and evaluate all ${foo.bar}, $foo.bar and $(expr) in epr 211 // nolint: gocyclo 212 func eval(expr string, flattened map[string]interface{}, o options) (interface{}, error) { 213 // Since we go from right to left to support nesting, handling $$ escape is 214 // painful, so just hide them and restore them at the end 215 expr = strings.Replace(expr, "$$", "\x00", -1) 216 end := len(expr) 217 // If evaluation resolves to a single value, return the type value, not a string 218 var bypass interface{} 219 iteration := 0 220 for { 221 iteration++ 222 if iteration > 100 { 223 return "", fmt.Errorf("eval loop detected") 224 } 225 i := strings.LastIndex(expr[0:end], "$") 226 if i == -1 { 227 break 228 } 229 bypass = nil 230 comp, err := extract(expr[i+1:]) 231 if err != nil { 232 return "", err 233 } 234 var val interface{} 235 if len(comp) != 0 && comp[0] == '(' { 236 var err error 237 val, err = evalExpr(comp[1 : len(comp)-1]) 238 if err != nil { 239 return "", err 240 } 241 } else { 242 var ok bool 243 if len(comp) != 0 && comp[0] == '{' { 244 content := comp[1 : len(comp)-1] 245 q := strings.Index(content, "?") 246 if q != -1 { 247 s := strings.Index(content, ":") 248 if s == -1 { 249 return "", fmt.Errorf("parse error in ternary '%s', missing ':'", content) 250 } 251 variable := content[0:q] 252 val, ok = flattened[variable] 253 if isTrue(fmt.Sprintf("%v", val)) { 254 val = content[q+1 : s] 255 } else { 256 val = content[s+1:] 257 } 258 } else { 259 val, ok = flattened[comp[1:len(comp)-1]] 260 } 261 } else { 262 val, ok = flattened[comp] 263 } 264 if !ok { 265 if o.errOnMissingKey { 266 return "", fmt.Errorf("variable '%s' not set", comp) 267 } 268 fmt.Fprintf(os.Stderr, "variable '%s' not set, expanding to empty string", comp) 269 } 270 } 271 valstr := fmt.Sprintf("%v", val) 272 expr = expr[0:i] + valstr + expr[i+1+len(comp):] 273 if strings.Trim(expr, " ") == valstr { 274 bypass = val 275 } 276 end = len(expr) 277 } 278 if bypass != nil { 279 return bypass, nil 280 } 281 expr = strings.Replace(expr, "\x00", "$", -1) 282 return expr, nil 283 } 284 285 func isTrue(cond string) bool { 286 ct := strings.TrimLeft(cond, " ") 287 reverse := len(cond) != 0 && cond[0] == '!' 288 if reverse { 289 cond = ct[1:] 290 } 291 cond = strings.Trim(cond, " ") 292 return (cond != "" && cond != "false" && cond != "0") != reverse 293 } 294 295 func recurseList(input []interface{}, settings map[string]interface{}, flattened map[string]interface{}, o options) ([]interface{}, error) { 296 var res []interface{} 297 for _, v := range input { 298 switch vv := v.(type) { 299 case yml.MapSlice: 300 newv, err := recurse(vv, settings, flattened, o) 301 if err != nil { 302 return nil, err 303 } 304 res = append(res, newv) 305 case []interface{}: 306 newv, err := recurseList(vv, settings, flattened, o) 307 if err != nil { 308 return nil, err 309 } 310 res = append(res, newv) 311 case string: 312 vvv, err := eval(vv, flattened, o) 313 if err != nil { 314 return nil, err 315 } 316 if vvvs, ok := vvv.(string); ok { 317 trimed := strings.TrimLeft(vvvs, " ") 318 if strings.HasPrefix(trimed, "@if") { 319 be := strings.Index(trimed, "(") 320 ee := strings.Index(trimed, ")") 321 if be == -1 || ee == -1 || be > ee { 322 return nil, fmt.Errorf("parse error looking for if condition in '%s'", vvv) 323 } 324 cond := trimed[be+1 : ee] 325 if isTrue(cond) { 326 res = append(res, strings.Trim(trimed[ee+1:], " ")) 327 } 328 continue 329 } 330 } 331 res = append(res, vvv) 332 default: 333 res = append(res, v) 334 } 335 } 336 return res, nil 337 } 338 339 // FIXME complexity on this is 47… get it lower than 16 340 // nolint: gocyclo 341 func recurse(input yml.MapSlice, settings map[string]interface{}, flattened map[string]interface{}, o options) (yml.MapSlice, error) { 342 res := yml.MapSlice{} 343 for _, kvp := range input { 344 k := kvp.Key 345 v := kvp.Value 346 rk := k 347 kstr, isks := k.(string) 348 if isks { 349 trimed := strings.TrimLeft(kstr, " ") 350 if strings.HasPrefix(trimed, "@switch ") { 351 mii, ok := v.(yml.MapSlice) 352 if !ok { 353 return nil, fmt.Errorf("@switch value must be a mapping") 354 } 355 key, err := eval(strings.TrimPrefix(trimed, "@switch "), flattened, o) 356 if err != nil { 357 return nil, err 358 } 359 var defaultValue interface{} 360 hit := false 361 for _, sval := range mii { 362 sk := sval.Key 363 sv := sval.Value 364 ssk, ok := sk.(string) 365 if !ok { 366 return nil, fmt.Errorf("@switch entry key must be a string") 367 } 368 if ssk == "default" { 369 defaultValue = sv 370 } 371 if ssk == key { 372 hit = true 373 svv, ok := sv.(yml.MapSlice) 374 if !ok { 375 return nil, fmt.Errorf("@switch entry must be a mapping") 376 } 377 for _, vval := range svv { 378 res = append(res, yml.MapItem{Key: vval.Key, Value: vval.Value}) 379 } 380 } 381 } 382 if !hit && defaultValue != nil { 383 svv, ok := defaultValue.(yml.MapSlice) 384 if !ok { 385 return nil, fmt.Errorf("@switch entry must be a mapping") 386 } 387 for _, vval := range svv { 388 res = append(res, yml.MapItem{Key: vval.Key, Value: vval.Value}) 389 } 390 } 391 continue 392 } 393 if strings.HasPrefix(trimed, "@for ") { 394 mii, ok := v.(yml.MapSlice) 395 if !ok { 396 return nil, fmt.Errorf("@for value must be a mapping") 397 } 398 comps := strings.SplitN(trimed, " ", 4) 399 varname := comps[1] 400 varrangeraw, err := eval(comps[3], flattened, o) 401 if err != nil { 402 return nil, err 403 } 404 varrange, ok := varrangeraw.(string) 405 if !ok { 406 return nil, fmt.Errorf("@for argument must be a string") 407 } 408 mayberange := strings.Split(varrange, "..") 409 if len(mayberange) == 2 { 410 rangestart, err := strconv.ParseInt(mayberange[0], 0, 64) 411 if err != nil { 412 return nil, err 413 } 414 rangeend, err := strconv.ParseInt(mayberange[1], 0, 64) 415 if err != nil { 416 return nil, err 417 } 418 for i := rangestart; i < rangeend; i++ { 419 flattened[varname] = fmt.Sprintf("%v", i) 420 val, err := recurse(mii, settings, flattened, o) 421 if err != nil { 422 return nil, err 423 } 424 for _, vval := range val { 425 res = append(res, yml.MapItem{Key: vval.Key, Value: vval.Value}) 426 } 427 } 428 } else { 429 // treat range as a list 430 rangevalues := strings.Split(varrange, " ") 431 for _, i := range rangevalues { 432 flattened[varname] = i 433 val, err := recurse(mii, settings, flattened, o) 434 if err != nil { 435 return nil, err 436 } 437 for _, vval := range val { 438 res = append(res, yml.MapItem{Key: vval.Key, Value: vval.Value}) 439 } 440 } 441 } 442 continue 443 } 444 if strings.HasPrefix(trimed, "@if ") { 445 cond, err := eval(strings.TrimPrefix(trimed, "@if "), flattened, o) 446 if err != nil { 447 return nil, err 448 } 449 mii, ok := v.(yml.MapSlice) 450 if !ok { 451 return nil, fmt.Errorf("@if value must be a mapping") 452 } 453 if isTrue(fmt.Sprintf("%v", cond)) { 454 val, err := recurse(mii, settings, flattened, o) 455 if err != nil { 456 return nil, err 457 } 458 for _, vval := range val { 459 if vval.Key != "@else" { 460 res = append(res, yml.MapItem{Key: vval.Key, Value: vval.Value}) 461 } 462 } 463 } else { 464 var elseClause interface{} 465 for _, miiv := range mii { 466 if miiv.Key == "@else" { 467 elseClause = miiv.Value 468 break 469 } 470 } 471 if elseClause != nil { 472 elseDict, ok := elseClause.(yml.MapSlice) 473 if !ok { 474 return nil, fmt.Errorf("@else value must be a mapping") 475 } 476 for _, vval := range elseDict { 477 res = append(res, yml.MapItem{Key: vval.Key, Value: vval.Value}) 478 } 479 } 480 } 481 continue 482 } 483 rstr, err := eval(kstr, flattened, o) 484 if err != nil { 485 return nil, err 486 } 487 rk = rstr 488 } 489 switch vv := v.(type) { 490 case yml.MapSlice: 491 newv, err := recurse(vv, settings, flattened, o) 492 if err != nil { 493 return nil, err 494 } 495 res = append(res, yml.MapItem{Key: rk, Value: newv}) 496 case []interface{}: 497 newv, err := recurseList(vv, settings, flattened, o) 498 if err != nil { 499 return nil, err 500 } 501 res = append(res, yml.MapItem{Key: rk, Value: newv}) 502 case string: 503 vvv, err := eval(vv, flattened, o) 504 if err != nil { 505 return nil, err 506 } 507 res = append(res, yml.MapItem{Key: rk, Value: vvv}) 508 default: 509 res = append(res, yml.MapItem{Key: rk, Value: v}) 510 } 511 } 512 return res, nil 513 } 514 515 // ProcessStrings resolves input templated yaml using values in settings yaml 516 func ProcessStrings(input, settings string) (string, error) { 517 ps := make(map[interface{}]interface{}) 518 err := yaml.Unmarshal([]byte(settings), ps) 519 if err != nil { 520 return "", err 521 } 522 s := make(map[string]interface{}) 523 merge(s, ps) 524 res, err := Process(input, s) 525 if err != nil { 526 return "", err 527 } 528 sres, err := yaml.Marshal(res) 529 if err != nil { 530 return "", err 531 } 532 return string(sres), nil 533 } 534 535 // ProcessWithOrder resolves input templated yaml using values given in settings, returning a MapSlice with order preserved 536 func ProcessWithOrder(inputString string, settings map[string]interface{}, opts ...string) (yml.MapSlice, error) { 537 var o options 538 for _, v := range opts { 539 switch v { 540 case OptionErrOnMissingKey: 541 o.errOnMissingKey = true 542 default: 543 return nil, fmt.Errorf("unknown option %q", v) 544 } 545 } 546 var input yml.MapSlice 547 err := yaml.Unmarshal([]byte(inputString), &input) 548 if err != nil { 549 return nil, err 550 } 551 flattened := make(map[string]interface{}) 552 flatten(settings, flattened, "") 553 return recurse(input, settings, flattened, o) 554 } 555 556 // Process resolves input templated yaml using values given in settings, returning a map 557 func Process(inputString string, settings map[string]interface{}, opts ...string) (map[interface{}]interface{}, error) { 558 mapSlice, err := ProcessWithOrder(inputString, settings, opts...) 559 if err != nil { 560 return nil, err 561 } 562 563 res, err := convert(mapSlice) 564 if err != nil { 565 return nil, err 566 } 567 return res, nil 568 } 569 570 func convert(mapSlice yml.MapSlice) (map[interface{}]interface{}, error) { 571 res := make(map[interface{}]interface{}) 572 for _, kv := range mapSlice { 573 v := kv.Value 574 castValue, ok := v.(yml.MapSlice) 575 if !ok { 576 res[kv.Key] = kv.Value 577 } else { 578 recursed, err := convert(castValue) 579 if err != nil { 580 return nil, err 581 } 582 res[kv.Key] = recursed 583 } 584 } 585 return res, nil 586 }