github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/jobspec/parse_service.go (about) 1 package jobspec 2 3 import ( 4 "fmt" 5 6 multierror "github.com/hashicorp/go-multierror" 7 "github.com/hashicorp/hcl" 8 "github.com/hashicorp/hcl/hcl/ast" 9 "github.com/hashicorp/nomad/api" 10 "github.com/mitchellh/mapstructure" 11 ) 12 13 func parseGroupServices(g *api.TaskGroup, serviceObjs *ast.ObjectList) error { 14 g.Services = make([]*api.Service, len(serviceObjs.Items)) 15 for idx, o := range serviceObjs.Items { 16 service, err := parseService(o) 17 if err != nil { 18 return multierror.Prefix(err, fmt.Sprintf("service (%d):", idx)) 19 } 20 g.Services[idx] = service 21 } 22 23 return nil 24 } 25 26 func parseServices(serviceObjs *ast.ObjectList) ([]*api.Service, error) { 27 services := make([]*api.Service, len(serviceObjs.Items)) 28 for idx, o := range serviceObjs.Items { 29 service, err := parseService(o) 30 if err != nil { 31 return nil, multierror.Prefix(err, fmt.Sprintf("service (%d):", idx)) 32 } 33 services[idx] = service 34 } 35 return services, nil 36 } 37 38 func parseService(o *ast.ObjectItem) (*api.Service, error) { 39 // Check for invalid keys 40 valid := []string{ 41 "name", 42 "tags", 43 "canary_tags", 44 "enable_tag_override", 45 "port", 46 "check", 47 "address_mode", 48 "check_restart", 49 "connect", 50 "task", 51 "meta", 52 "canary_meta", 53 } 54 if err := checkHCLKeys(o.Val, valid); err != nil { 55 return nil, err 56 } 57 58 var service api.Service 59 var m map[string]interface{} 60 if err := hcl.DecodeObject(&m, o.Val); err != nil { 61 return nil, err 62 } 63 64 delete(m, "check") 65 delete(m, "check_restart") 66 delete(m, "connect") 67 delete(m, "meta") 68 delete(m, "canary_meta") 69 70 if err := mapstructure.WeakDecode(m, &service); err != nil { 71 return nil, err 72 } 73 74 // Filter list 75 var listVal *ast.ObjectList 76 if ot, ok := o.Val.(*ast.ObjectType); ok { 77 listVal = ot.List 78 } else { 79 return nil, fmt.Errorf("'%s': should be an object", service.Name) 80 } 81 82 if co := listVal.Filter("check"); len(co.Items) > 0 { 83 if err := parseChecks(&service, co); err != nil { 84 return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name)) 85 } 86 } 87 88 // Filter check_restart 89 if cro := listVal.Filter("check_restart"); len(cro.Items) > 0 { 90 if len(cro.Items) > 1 { 91 return nil, fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", service.Name) 92 } 93 cr, err := parseCheckRestart(cro.Items[0]) 94 if err != nil { 95 return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name)) 96 } 97 service.CheckRestart = cr 98 99 } 100 101 // Filter connect 102 if co := listVal.Filter("connect"); len(co.Items) > 0 { 103 if len(co.Items) > 1 { 104 return nil, fmt.Errorf("connect '%s': cannot have more than 1 connect stanza", service.Name) 105 } 106 107 c, err := parseConnect(co.Items[0]) 108 if err != nil { 109 return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name)) 110 } 111 112 service.Connect = c 113 } 114 115 // Parse out meta fields. These are in HCL as a list so we need 116 // to iterate over them and merge them. 117 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 118 for _, o := range metaO.Elem().Items { 119 var m map[string]interface{} 120 if err := hcl.DecodeObject(&m, o.Val); err != nil { 121 return nil, err 122 } 123 if err := mapstructure.WeakDecode(m, &service.Meta); err != nil { 124 return nil, err 125 } 126 } 127 } 128 129 // Parse out canary_meta fields. These are in HCL as a list so we need 130 // to iterate over them and merge them. 131 if metaO := listVal.Filter("canary_meta"); len(metaO.Items) > 0 { 132 for _, o := range metaO.Elem().Items { 133 var m map[string]interface{} 134 if err := hcl.DecodeObject(&m, o.Val); err != nil { 135 return nil, err 136 } 137 if err := mapstructure.WeakDecode(m, &service.CanaryMeta); err != nil { 138 return nil, err 139 } 140 } 141 } 142 143 return &service, nil 144 } 145 146 func parseConnect(co *ast.ObjectItem) (*api.ConsulConnect, error) { 147 valid := []string{ 148 "native", 149 "gateway", 150 "sidecar_service", 151 "sidecar_task", 152 } 153 154 if err := checkHCLKeys(co.Val, valid); err != nil { 155 return nil, multierror.Prefix(err, "connect ->") 156 } 157 158 var connect api.ConsulConnect 159 var m map[string]interface{} 160 if err := hcl.DecodeObject(&m, co.Val); err != nil { 161 return nil, err 162 } 163 164 delete(m, "gateway") 165 delete(m, "sidecar_service") 166 delete(m, "sidecar_task") 167 168 if err := mapstructure.WeakDecode(m, &connect); err != nil { 169 return nil, err 170 } 171 172 var connectList *ast.ObjectList 173 if ot, ok := co.Val.(*ast.ObjectType); ok { 174 connectList = ot.List 175 } else { 176 return nil, fmt.Errorf("connect should be an object") 177 } 178 179 // Parse the gateway 180 o := connectList.Filter("gateway") 181 if len(o.Items) > 1 { 182 return nil, fmt.Errorf("only one 'gateway' block allowed per task") 183 } else if len(o.Items) == 1 { 184 g, err := parseGateway(o.Items[0]) 185 if err != nil { 186 return nil, fmt.Errorf("gateway, %v", err) 187 } 188 connect.Gateway = g 189 } 190 191 // Parse the sidecar_service 192 o = connectList.Filter("sidecar_service") 193 if len(o.Items) == 0 { 194 return &connect, nil 195 } 196 if len(o.Items) > 1 { 197 return nil, fmt.Errorf("only one 'sidecar_service' block allowed per task") 198 } 199 200 r, err := parseSidecarService(o.Items[0]) 201 if err != nil { 202 return nil, fmt.Errorf("sidecar_service, %v", err) 203 } 204 connect.SidecarService = r 205 206 // Parse the sidecar_task 207 o = connectList.Filter("sidecar_task") 208 if len(o.Items) == 0 { 209 return &connect, nil 210 } 211 if len(o.Items) > 1 { 212 return nil, fmt.Errorf("only one 'sidecar_task' block allowed per task") 213 } 214 215 t, err := parseSidecarTask(o.Items[0]) 216 if err != nil { 217 return nil, fmt.Errorf("sidecar_task, %v", err) 218 } 219 connect.SidecarTask = t 220 221 return &connect, nil 222 } 223 224 func parseGateway(o *ast.ObjectItem) (*api.ConsulGateway, error) { 225 valid := []string{ 226 "proxy", 227 "ingress", 228 } 229 230 if err := checkHCLKeys(o.Val, valid); err != nil { 231 return nil, multierror.Prefix(err, "gateway ->") 232 } 233 234 var gateway api.ConsulGateway 235 var m map[string]interface{} 236 if err := hcl.DecodeObject(&m, o.Val); err != nil { 237 return nil, err 238 } 239 240 delete(m, "proxy") 241 delete(m, "ingress") 242 243 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 244 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 245 WeaklyTypedInput: true, 246 Result: &gateway, 247 }) 248 if err != nil { 249 return nil, err 250 } 251 if err := dec.Decode(m); err != nil { 252 return nil, fmt.Errorf("gateway: %v", err) 253 } 254 255 // list of parameters 256 var listVal *ast.ObjectList 257 if ot, ok := o.Val.(*ast.ObjectType); ok { 258 listVal = ot.List 259 } else { 260 return nil, fmt.Errorf("proxy: should be an object") 261 } 262 263 // extract and parse the proxy block 264 po := listVal.Filter("proxy") 265 if len(po.Items) != 1 { 266 return nil, fmt.Errorf("must have one 'proxy' block") 267 } 268 proxy, err := parseGatewayProxy(po.Items[0]) 269 if err != nil { 270 return nil, fmt.Errorf("proxy, %v", err) 271 } 272 gateway.Proxy = proxy 273 274 // extract and parse the ingress block 275 io := listVal.Filter("ingress") 276 if len(io.Items) != 1 { 277 // in the future, may be terminating or mesh block instead 278 return nil, fmt.Errorf("must have one 'ingress' block") 279 } 280 ingress, err := parseIngressConfigEntry(io.Items[0]) 281 if err != nil { 282 return nil, fmt.Errorf("ingress, %v", err) 283 } 284 gateway.Ingress = ingress 285 286 return &gateway, nil 287 } 288 289 // parseGatewayProxy parses envoy gateway proxy options supported by Consul. 290 // 291 // consul.io/docs/connect/proxies/envoy#gateway-options 292 func parseGatewayProxy(o *ast.ObjectItem) (*api.ConsulGatewayProxy, error) { 293 valid := []string{ 294 "connect_timeout", 295 "envoy_gateway_bind_tagged_addresses", 296 "envoy_gateway_bind_addresses", 297 "envoy_gateway_no_default_bind", 298 "envoy_dns_discovery_type", 299 "config", 300 } 301 302 if err := checkHCLKeys(o.Val, valid); err != nil { 303 return nil, multierror.Prefix(err, "proxy ->") 304 } 305 306 var proxy api.ConsulGatewayProxy 307 var m map[string]interface{} 308 if err := hcl.DecodeObject(&m, o.Val); err != nil { 309 return nil, err 310 } 311 312 delete(m, "config") 313 delete(m, "envoy_gateway_bind_addresses") 314 315 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 316 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 317 WeaklyTypedInput: true, 318 Result: &proxy, 319 }) 320 if err != nil { 321 return nil, err 322 } 323 if err := dec.Decode(m); err != nil { 324 return nil, fmt.Errorf("proxy: %v", err) 325 } 326 327 var listVal *ast.ObjectList 328 if ot, ok := o.Val.(*ast.ObjectType); ok { 329 listVal = ot.List 330 } else { 331 return nil, fmt.Errorf("proxy: should be an object") 332 } 333 334 // need to parse envoy_gateway_bind_addresses if present 335 336 if ebo := listVal.Filter("envoy_gateway_bind_addresses"); len(ebo.Items) > 0 { 337 proxy.EnvoyGatewayBindAddresses = make(map[string]*api.ConsulGatewayBindAddress) 338 for _, listenerM := range ebo.Items { // object item, each listener object 339 listenerName := listenerM.Keys[0].Token.Value().(string) 340 341 var listenerListVal *ast.ObjectList 342 if ot, ok := listenerM.Val.(*ast.ObjectType); ok { 343 listenerListVal = ot.List 344 } else { 345 return nil, fmt.Errorf("listener: should be an object") 346 } 347 348 var bind api.ConsulGatewayBindAddress 349 if err := hcl.DecodeObject(&bind, listenerListVal); err != nil { 350 return nil, fmt.Errorf("port: should be an int") 351 } 352 bind.Name = listenerName 353 proxy.EnvoyGatewayBindAddresses[listenerName] = &bind 354 } 355 } 356 357 // need to parse the opaque config if present 358 359 if co := listVal.Filter("config"); len(co.Items) > 1 { 360 return nil, fmt.Errorf("only 1 meta object supported") 361 } else if len(co.Items) == 1 { 362 var mSlice []map[string]interface{} 363 if err := hcl.DecodeObject(&mSlice, co.Items[0].Val); err != nil { 364 return nil, err 365 } 366 367 if len(mSlice) > 1 { 368 return nil, fmt.Errorf("only 1 meta object supported") 369 } 370 371 m := mSlice[0] 372 373 if err := mapstructure.WeakDecode(m, &proxy.Config); err != nil { 374 return nil, err 375 } 376 377 proxy.Config = flattenMapSlice(proxy.Config) 378 } 379 380 return &proxy, nil 381 } 382 383 func parseConsulIngressService(o *ast.ObjectItem) (*api.ConsulIngressService, error) { 384 valid := []string{ 385 "name", 386 "hosts", 387 } 388 389 if err := checkHCLKeys(o.Val, valid); err != nil { 390 return nil, multierror.Prefix(err, "service ->") 391 } 392 393 var service api.ConsulIngressService 394 var m map[string]interface{} 395 if err := hcl.DecodeObject(&m, o.Val); err != nil { 396 return nil, err 397 } 398 399 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 400 Result: &service, 401 }) 402 if err != nil { 403 return nil, err 404 } 405 406 if err := dec.Decode(m); err != nil { 407 return nil, err 408 } 409 410 return &service, nil 411 } 412 413 func parseConsulIngressListener(o *ast.ObjectItem) (*api.ConsulIngressListener, error) { 414 valid := []string{ 415 "port", 416 "protocol", 417 "service", 418 } 419 420 if err := checkHCLKeys(o.Val, valid); err != nil { 421 return nil, multierror.Prefix(err, "listener ->") 422 } 423 424 var listener api.ConsulIngressListener 425 var m map[string]interface{} 426 if err := hcl.DecodeObject(&m, o.Val); err != nil { 427 return nil, err 428 } 429 430 delete(m, "service") 431 432 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 433 Result: &listener, 434 }) 435 if err != nil { 436 return nil, err 437 } 438 439 if err := dec.Decode(m); err != nil { 440 return nil, err 441 } 442 443 // Parse services 444 445 var listVal *ast.ObjectList 446 if ot, ok := o.Val.(*ast.ObjectType); ok { 447 listVal = ot.List 448 } else { 449 return nil, fmt.Errorf("listener: should be an object") 450 } 451 452 so := listVal.Filter("service") 453 if len(so.Items) > 0 { 454 listener.Services = make([]*api.ConsulIngressService, len(so.Items)) 455 for i := range so.Items { 456 is, err := parseConsulIngressService(so.Items[i]) 457 if err != nil { 458 return nil, err 459 } 460 listener.Services[i] = is 461 } 462 } 463 return &listener, nil 464 } 465 466 func parseConsulGatewayTLS(o *ast.ObjectItem) (*api.ConsulGatewayTLSConfig, error) { 467 valid := []string{ 468 "enabled", 469 } 470 471 if err := checkHCLKeys(o.Val, valid); err != nil { 472 return nil, multierror.Prefix(err, "tls ->") 473 } 474 475 var tls api.ConsulGatewayTLSConfig 476 var m map[string]interface{} 477 if err := hcl.DecodeObject(&m, o.Val); err != nil { 478 return nil, err 479 } 480 481 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 482 Result: &tls, 483 }) 484 if err != nil { 485 return nil, err 486 } 487 488 if err := dec.Decode(m); err != nil { 489 return nil, err 490 } 491 492 return &tls, nil 493 } 494 495 func parseIngressConfigEntry(o *ast.ObjectItem) (*api.ConsulIngressConfigEntry, error) { 496 valid := []string{ 497 "tls", 498 "listener", 499 } 500 501 if err := checkHCLKeys(o.Val, valid); err != nil { 502 return nil, multierror.Prefix(err, "ingress ->") 503 } 504 505 var ingress api.ConsulIngressConfigEntry 506 var m map[string]interface{} 507 if err := hcl.DecodeObject(&m, o.Val); err != nil { 508 return nil, err 509 } 510 511 delete(m, "tls") 512 delete(m, "listener") 513 514 // Parse tls and listener(s) 515 516 var listVal *ast.ObjectList 517 if ot, ok := o.Val.(*ast.ObjectType); ok { 518 listVal = ot.List 519 } else { 520 return nil, fmt.Errorf("ingress: should be an object") 521 } 522 523 if to := listVal.Filter("tls"); len(to.Items) > 1 { 524 return nil, fmt.Errorf("only 1 tls object supported") 525 } else if len(to.Items) == 1 { 526 if tls, err := parseConsulGatewayTLS(to.Items[0]); err != nil { 527 return nil, err 528 } else { 529 ingress.TLS = tls 530 } 531 } 532 533 lo := listVal.Filter("listener") 534 if len(lo.Items) > 0 { 535 ingress.Listeners = make([]*api.ConsulIngressListener, len(lo.Items)) 536 for i := range lo.Items { 537 listener, err := parseConsulIngressListener(lo.Items[i]) 538 if err != nil { 539 return nil, err 540 } 541 ingress.Listeners[i] = listener 542 } 543 } 544 545 return &ingress, nil 546 } 547 548 func parseSidecarService(o *ast.ObjectItem) (*api.ConsulSidecarService, error) { 549 valid := []string{ 550 "port", 551 "proxy", 552 "tags", 553 } 554 555 if err := checkHCLKeys(o.Val, valid); err != nil { 556 return nil, multierror.Prefix(err, "sidecar_service ->") 557 } 558 559 var sidecar api.ConsulSidecarService 560 var m map[string]interface{} 561 if err := hcl.DecodeObject(&m, o.Val); err != nil { 562 return nil, err 563 } 564 565 delete(m, "proxy") 566 567 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 568 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 569 WeaklyTypedInput: true, 570 Result: &sidecar, 571 }) 572 if err != nil { 573 return nil, err 574 } 575 if err := dec.Decode(m); err != nil { 576 return nil, fmt.Errorf("sidecar_service: %v", err) 577 } 578 579 var proxyList *ast.ObjectList 580 if ot, ok := o.Val.(*ast.ObjectType); ok { 581 proxyList = ot.List 582 } else { 583 return nil, fmt.Errorf("sidecar_service: should be an object") 584 } 585 586 // Parse the proxy 587 po := proxyList.Filter("proxy") 588 if len(po.Items) == 0 { 589 return &sidecar, nil 590 } 591 if len(po.Items) > 1 { 592 return nil, fmt.Errorf("only one 'proxy' block allowed per task") 593 } 594 595 r, err := parseProxy(po.Items[0]) 596 if err != nil { 597 return nil, fmt.Errorf("proxy, %v", err) 598 } 599 sidecar.Proxy = r 600 601 return &sidecar, nil 602 } 603 604 func parseSidecarTask(item *ast.ObjectItem) (*api.SidecarTask, error) { 605 task, err := parseTask(item, sidecarTaskKeys) 606 if err != nil { 607 return nil, err 608 } 609 610 sidecarTask := &api.SidecarTask{ 611 Name: task.Name, 612 Driver: task.Driver, 613 User: task.User, 614 Config: task.Config, 615 Env: task.Env, 616 Resources: task.Resources, 617 Meta: task.Meta, 618 KillTimeout: task.KillTimeout, 619 LogConfig: task.LogConfig, 620 KillSignal: task.KillSignal, 621 } 622 623 // Parse ShutdownDelay separatly to get pointer 624 var m map[string]interface{} 625 if err := hcl.DecodeObject(&m, item.Val); err != nil { 626 return nil, err 627 } 628 629 m = map[string]interface{}{ 630 "shutdown_delay": m["shutdown_delay"], 631 } 632 633 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 634 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 635 WeaklyTypedInput: true, 636 Result: sidecarTask, 637 }) 638 639 if err != nil { 640 return nil, err 641 } 642 if err := dec.Decode(m); err != nil { 643 return nil, err 644 } 645 return sidecarTask, nil 646 } 647 648 func parseProxy(o *ast.ObjectItem) (*api.ConsulProxy, error) { 649 valid := []string{ 650 "local_service_address", 651 "local_service_port", 652 "upstreams", 653 "expose", 654 "config", 655 } 656 657 if err := checkHCLKeys(o.Val, valid); err != nil { 658 return nil, multierror.Prefix(err, "proxy ->") 659 } 660 661 var proxy api.ConsulProxy 662 var m map[string]interface{} 663 if err := hcl.DecodeObject(&m, o.Val); err != nil { 664 return nil, err 665 } 666 667 delete(m, "upstreams") 668 delete(m, "expose") 669 delete(m, "config") 670 671 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 672 Result: &proxy, 673 }) 674 if err != nil { 675 return nil, err 676 } 677 if err := dec.Decode(m); err != nil { 678 return nil, fmt.Errorf("proxy: %v", err) 679 } 680 681 // Parse upstreams, expose, and config 682 683 var listVal *ast.ObjectList 684 if ot, ok := o.Val.(*ast.ObjectType); ok { 685 listVal = ot.List 686 } else { 687 return nil, fmt.Errorf("proxy: should be an object") 688 } 689 690 uo := listVal.Filter("upstreams") 691 if len(uo.Items) > 0 { 692 proxy.Upstreams = make([]*api.ConsulUpstream, len(uo.Items)) 693 for i := range uo.Items { 694 u, err := parseUpstream(uo.Items[i]) 695 if err != nil { 696 return nil, err 697 } 698 proxy.Upstreams[i] = u 699 } 700 } 701 702 if eo := listVal.Filter("expose"); len(eo.Items) > 1 { 703 return nil, fmt.Errorf("only 1 expose object supported") 704 } else if len(eo.Items) == 1 { 705 if e, err := parseExpose(eo.Items[0]); err != nil { 706 return nil, err 707 } else { 708 proxy.ExposeConfig = e 709 } 710 } 711 712 // If we have config, then parse that 713 if o := listVal.Filter("config"); len(o.Items) > 1 { 714 return nil, fmt.Errorf("only 1 meta object supported") 715 } else if len(o.Items) == 1 { 716 var mSlice []map[string]interface{} 717 if err := hcl.DecodeObject(&mSlice, o.Items[0].Val); err != nil { 718 return nil, err 719 } 720 721 if len(mSlice) > 1 { 722 return nil, fmt.Errorf("only 1 meta object supported") 723 } 724 725 m := mSlice[0] 726 727 if err := mapstructure.WeakDecode(m, &proxy.Config); err != nil { 728 return nil, err 729 } 730 731 proxy.Config = flattenMapSlice(proxy.Config) 732 } 733 734 return &proxy, nil 735 } 736 737 func parseExpose(eo *ast.ObjectItem) (*api.ConsulExposeConfig, error) { 738 valid := []string{ 739 "path", // an array of path blocks 740 } 741 742 if err := checkHCLKeys(eo.Val, valid); err != nil { 743 return nil, multierror.Prefix(err, "expose ->") 744 } 745 746 var expose api.ConsulExposeConfig 747 748 var listVal *ast.ObjectList 749 if eoType, ok := eo.Val.(*ast.ObjectType); ok { 750 listVal = eoType.List 751 } else { 752 return nil, fmt.Errorf("expose: should be an object") 753 } 754 755 // Parse the expose block 756 757 po := listVal.Filter("path") // array 758 if len(po.Items) > 0 { 759 expose.Path = make([]*api.ConsulExposePath, len(po.Items)) 760 for i := range po.Items { 761 p, err := parseExposePath(po.Items[i]) 762 if err != nil { 763 return nil, err 764 } 765 expose.Path[i] = p 766 } 767 } 768 769 return &expose, nil 770 } 771 772 func parseExposePath(epo *ast.ObjectItem) (*api.ConsulExposePath, error) { 773 valid := []string{ 774 "path", 775 "protocol", 776 "local_path_port", 777 "listener_port", 778 } 779 780 if err := checkHCLKeys(epo.Val, valid); err != nil { 781 return nil, multierror.Prefix(err, "path ->") 782 } 783 784 var path api.ConsulExposePath 785 var m map[string]interface{} 786 if err := hcl.DecodeObject(&m, epo.Val); err != nil { 787 return nil, err 788 } 789 790 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 791 Result: &path, 792 }) 793 if err != nil { 794 return nil, err 795 } 796 797 if err := dec.Decode(m); err != nil { 798 return nil, err 799 } 800 801 return &path, nil 802 } 803 804 func parseUpstream(uo *ast.ObjectItem) (*api.ConsulUpstream, error) { 805 valid := []string{ 806 "destination_name", 807 "local_bind_port", 808 "local_bind_address", 809 "datacenter", 810 } 811 812 if err := checkHCLKeys(uo.Val, valid); err != nil { 813 return nil, multierror.Prefix(err, "upstream ->") 814 } 815 816 var upstream api.ConsulUpstream 817 var m map[string]interface{} 818 if err := hcl.DecodeObject(&m, uo.Val); err != nil { 819 return nil, err 820 } 821 822 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 823 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 824 WeaklyTypedInput: true, 825 Result: &upstream, 826 }) 827 if err != nil { 828 return nil, err 829 } 830 831 if err := dec.Decode(m); err != nil { 832 return nil, err 833 } 834 835 return &upstream, nil 836 } 837 838 func parseChecks(service *api.Service, checkObjs *ast.ObjectList) error { 839 service.Checks = make([]api.ServiceCheck, len(checkObjs.Items)) 840 for idx, co := range checkObjs.Items { 841 // Check for invalid keys 842 valid := []string{ 843 "name", 844 "type", 845 "interval", 846 "timeout", 847 "path", 848 "protocol", 849 "port", 850 "expose", 851 "command", 852 "args", 853 "initial_status", 854 "tls_skip_verify", 855 "header", 856 "method", 857 "check_restart", 858 "address_mode", 859 "grpc_service", 860 "grpc_use_tls", 861 "task", 862 "success_before_passing", 863 "failures_before_critical", 864 } 865 if err := checkHCLKeys(co.Val, valid); err != nil { 866 return multierror.Prefix(err, "check ->") 867 } 868 869 var check api.ServiceCheck 870 var cm map[string]interface{} 871 if err := hcl.DecodeObject(&cm, co.Val); err != nil { 872 return err 873 } 874 875 // HCL allows repeating stanzas so merge 'header' into a single 876 // map[string][]string. 877 if headerI, ok := cm["header"]; ok { 878 headerRaw, ok := headerI.([]map[string]interface{}) 879 if !ok { 880 return fmt.Errorf("check -> header -> expected a []map[string][]string but found %T", headerI) 881 } 882 m := map[string][]string{} 883 for _, rawm := range headerRaw { 884 for k, vI := range rawm { 885 vs, ok := vI.([]interface{}) 886 if !ok { 887 return fmt.Errorf("check -> header -> %q expected a []string but found %T", k, vI) 888 } 889 for _, vI := range vs { 890 v, ok := vI.(string) 891 if !ok { 892 return fmt.Errorf("check -> header -> %q expected a string but found %T", k, vI) 893 } 894 m[k] = append(m[k], v) 895 } 896 } 897 } 898 899 check.Header = m 900 901 // Remove "header" as it has been parsed 902 delete(cm, "header") 903 } 904 905 delete(cm, "check_restart") 906 907 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 908 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 909 WeaklyTypedInput: true, 910 Result: &check, 911 }) 912 if err != nil { 913 return err 914 } 915 if err := dec.Decode(cm); err != nil { 916 return err 917 } 918 919 // Filter check_restart 920 var checkRestartList *ast.ObjectList 921 if ot, ok := co.Val.(*ast.ObjectType); ok { 922 checkRestartList = ot.List 923 } else { 924 return fmt.Errorf("check_restart '%s': should be an object", check.Name) 925 } 926 927 if cro := checkRestartList.Filter("check_restart"); len(cro.Items) > 0 { 928 if len(cro.Items) > 1 { 929 return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", check.Name) 930 } 931 cr, err := parseCheckRestart(cro.Items[0]) 932 if err != nil { 933 return multierror.Prefix(err, fmt.Sprintf("check: '%s',", check.Name)) 934 } 935 check.CheckRestart = cr 936 } 937 938 service.Checks[idx] = check 939 } 940 941 return nil 942 } 943 944 func parseCheckRestart(cro *ast.ObjectItem) (*api.CheckRestart, error) { 945 valid := []string{ 946 "limit", 947 "grace", 948 "ignore_warnings", 949 } 950 951 if err := checkHCLKeys(cro.Val, valid); err != nil { 952 return nil, multierror.Prefix(err, "check_restart ->") 953 } 954 955 var checkRestart api.CheckRestart 956 var crm map[string]interface{} 957 if err := hcl.DecodeObject(&crm, cro.Val); err != nil { 958 return nil, err 959 } 960 961 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 962 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 963 WeaklyTypedInput: true, 964 Result: &checkRestart, 965 }) 966 if err != nil { 967 return nil, err 968 } 969 if err := dec.Decode(crm); err != nil { 970 return nil, err 971 } 972 973 return &checkRestart, nil 974 }