github.com/superfly/nomad@v0.10.5-fly/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/hashicorp/nomad/helper" 11 "github.com/mitchellh/mapstructure" 12 ) 13 14 func parseGroupServices(g *api.TaskGroup, serviceObjs *ast.ObjectList) error { 15 g.Services = make([]*api.Service, len(serviceObjs.Items)) 16 for idx, o := range serviceObjs.Items { 17 service, err := parseService(o) 18 if err != nil { 19 return multierror.Prefix(err, fmt.Sprintf("service (%d):", idx)) 20 } 21 g.Services[idx] = service 22 } 23 24 return nil 25 } 26 27 func parseServices(serviceObjs *ast.ObjectList) ([]*api.Service, error) { 28 services := make([]*api.Service, len(serviceObjs.Items)) 29 for idx, o := range serviceObjs.Items { 30 service, err := parseService(o) 31 if err != nil { 32 return nil, multierror.Prefix(err, fmt.Sprintf("service (%d):", idx)) 33 } 34 services[idx] = service 35 } 36 return services, nil 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 "port", 45 "check", 46 "address_mode", 47 "check_restart", 48 "connect", 49 "meta", 50 "canary_meta", 51 } 52 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 53 return nil, err 54 } 55 56 var service api.Service 57 var m map[string]interface{} 58 if err := hcl.DecodeObject(&m, o.Val); err != nil { 59 return nil, err 60 } 61 62 delete(m, "check") 63 delete(m, "check_restart") 64 delete(m, "connect") 65 delete(m, "meta") 66 delete(m, "canary_meta") 67 68 if err := mapstructure.WeakDecode(m, &service); err != nil { 69 return nil, err 70 } 71 72 // Filter list 73 var listVal *ast.ObjectList 74 if ot, ok := o.Val.(*ast.ObjectType); ok { 75 listVal = ot.List 76 } else { 77 return nil, fmt.Errorf("'%s': should be an object", service.Name) 78 } 79 80 if co := listVal.Filter("check"); len(co.Items) > 0 { 81 if err := parseChecks(&service, co); err != nil { 82 return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name)) 83 } 84 } 85 86 // Filter check_restart 87 if cro := listVal.Filter("check_restart"); len(cro.Items) > 0 { 88 if len(cro.Items) > 1 { 89 return nil, fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", service.Name) 90 } 91 cr, err := parseCheckRestart(cro.Items[0]) 92 if err != nil { 93 return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name)) 94 } 95 service.CheckRestart = cr 96 97 } 98 99 // Filter connect 100 if co := listVal.Filter("connect"); len(co.Items) > 0 { 101 if len(co.Items) > 1 { 102 return nil, fmt.Errorf("connect '%s': cannot have more than 1 connect stanza", service.Name) 103 } 104 105 c, err := parseConnect(co.Items[0]) 106 if err != nil { 107 return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name)) 108 } 109 110 service.Connect = c 111 } 112 113 // Parse out meta fields. These are in HCL as a list so we need 114 // to iterate over them and merge them. 115 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 116 for _, o := range metaO.Elem().Items { 117 var m map[string]interface{} 118 if err := hcl.DecodeObject(&m, o.Val); err != nil { 119 return nil, err 120 } 121 if err := mapstructure.WeakDecode(m, &service.Meta); err != nil { 122 return nil, err 123 } 124 } 125 } 126 127 // Parse out canary_meta fields. These are in HCL as a list so we need 128 // to iterate over them and merge them. 129 if metaO := listVal.Filter("canary_meta"); len(metaO.Items) > 0 { 130 for _, o := range metaO.Elem().Items { 131 var m map[string]interface{} 132 if err := hcl.DecodeObject(&m, o.Val); err != nil { 133 return nil, err 134 } 135 if err := mapstructure.WeakDecode(m, &service.CanaryMeta); err != nil { 136 return nil, err 137 } 138 } 139 } 140 141 return &service, nil 142 } 143 144 func parseConnect(co *ast.ObjectItem) (*api.ConsulConnect, error) { 145 valid := []string{ 146 "native", 147 "sidecar_service", 148 "sidecar_task", 149 } 150 151 if err := helper.CheckHCLKeys(co.Val, valid); err != nil { 152 return nil, multierror.Prefix(err, "connect ->") 153 } 154 155 var connect api.ConsulConnect 156 var m map[string]interface{} 157 if err := hcl.DecodeObject(&m, co.Val); err != nil { 158 return nil, err 159 } 160 161 delete(m, "sidecar_service") 162 delete(m, "sidecar_task") 163 164 if err := mapstructure.WeakDecode(m, &connect); err != nil { 165 return nil, err 166 } 167 168 var connectList *ast.ObjectList 169 if ot, ok := co.Val.(*ast.ObjectType); ok { 170 connectList = ot.List 171 } else { 172 return nil, fmt.Errorf("connect should be an object") 173 } 174 175 // Parse the sidecar_service 176 o := connectList.Filter("sidecar_service") 177 if len(o.Items) == 0 { 178 return &connect, nil 179 } 180 if len(o.Items) > 1 { 181 return nil, fmt.Errorf("only one 'sidecar_service' block allowed per task") 182 } 183 184 r, err := parseSidecarService(o.Items[0]) 185 if err != nil { 186 return nil, fmt.Errorf("sidecar_service, %v", err) 187 } 188 connect.SidecarService = r 189 190 // Parse the sidecar_task 191 o = connectList.Filter("sidecar_task") 192 if len(o.Items) == 0 { 193 return &connect, nil 194 } 195 if len(o.Items) > 1 { 196 return nil, fmt.Errorf("only one 'sidecar_task' block allowed per task") 197 } 198 199 t, err := parseSidecarTask(o.Items[0]) 200 if err != nil { 201 return nil, fmt.Errorf("sidecar_task, %v", err) 202 } 203 connect.SidecarTask = t 204 205 return &connect, nil 206 } 207 208 func parseSidecarService(o *ast.ObjectItem) (*api.ConsulSidecarService, error) { 209 valid := []string{ 210 "port", 211 "proxy", 212 "tags", 213 } 214 215 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 216 return nil, multierror.Prefix(err, "sidecar_service ->") 217 } 218 219 var sidecar api.ConsulSidecarService 220 var m map[string]interface{} 221 if err := hcl.DecodeObject(&m, o.Val); err != nil { 222 return nil, err 223 } 224 225 delete(m, "proxy") 226 227 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 228 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 229 WeaklyTypedInput: true, 230 Result: &sidecar, 231 }) 232 if err != nil { 233 return nil, err 234 } 235 if err := dec.Decode(m); err != nil { 236 return nil, fmt.Errorf("sidecar_service: %v", err) 237 } 238 239 var proxyList *ast.ObjectList 240 if ot, ok := o.Val.(*ast.ObjectType); ok { 241 proxyList = ot.List 242 } else { 243 return nil, fmt.Errorf("sidecar_service: should be an object") 244 } 245 246 // Parse the proxy 247 po := proxyList.Filter("proxy") 248 if len(po.Items) == 0 { 249 return &sidecar, nil 250 } 251 if len(po.Items) > 1 { 252 return nil, fmt.Errorf("only one 'proxy' block allowed per task") 253 } 254 255 r, err := parseProxy(po.Items[0]) 256 if err != nil { 257 return nil, fmt.Errorf("proxy, %v", err) 258 } 259 sidecar.Proxy = r 260 261 return &sidecar, nil 262 } 263 264 func parseSidecarTask(item *ast.ObjectItem) (*api.SidecarTask, error) { 265 // We need this later 266 var listVal *ast.ObjectList 267 if ot, ok := item.Val.(*ast.ObjectType); ok { 268 listVal = ot.List 269 } else { 270 return nil, fmt.Errorf("should be an object") 271 } 272 273 // Check for invalid keys 274 valid := []string{ 275 "config", 276 "driver", 277 "env", 278 "kill_timeout", 279 "logs", 280 "meta", 281 "resources", 282 "shutdown_delay", 283 "user", 284 "kill_signal", 285 } 286 if err := helper.CheckHCLKeys(listVal, valid); err != nil { 287 return nil, err 288 } 289 290 task, err := parseTask(item) 291 if err != nil { 292 return nil, err 293 } 294 295 sidecarTask := &api.SidecarTask{ 296 Name: task.Name, 297 Driver: task.Driver, 298 User: task.User, 299 Config: task.Config, 300 Env: task.Env, 301 Resources: task.Resources, 302 Meta: task.Meta, 303 KillTimeout: task.KillTimeout, 304 LogConfig: task.LogConfig, 305 KillSignal: task.KillSignal, 306 } 307 308 // Parse ShutdownDelay seperatly to get pointer 309 var m map[string]interface{} 310 if err := hcl.DecodeObject(&m, item.Val); err != nil { 311 return nil, err 312 } 313 314 m = map[string]interface{}{ 315 "shutdown_delay": m["shutdown_delay"], 316 } 317 318 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 319 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 320 WeaklyTypedInput: true, 321 Result: sidecarTask, 322 }) 323 324 if err != nil { 325 return nil, err 326 } 327 if err := dec.Decode(m); err != nil { 328 return nil, err 329 } 330 return sidecarTask, nil 331 } 332 333 func parseProxy(o *ast.ObjectItem) (*api.ConsulProxy, error) { 334 valid := []string{ 335 "local_service_address", 336 "local_service_port", 337 "upstreams", 338 "config", 339 } 340 341 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 342 return nil, multierror.Prefix(err, "proxy ->") 343 } 344 345 var proxy api.ConsulProxy 346 347 var listVal *ast.ObjectList 348 if ot, ok := o.Val.(*ast.ObjectType); ok { 349 listVal = ot.List 350 } else { 351 return nil, fmt.Errorf("proxy: should be an object") 352 } 353 354 // Parse the proxy 355 uo := listVal.Filter("upstreams") 356 proxy.Upstreams = make([]*api.ConsulUpstream, len(uo.Items)) 357 for i := range uo.Items { 358 u, err := parseUpstream(uo.Items[i]) 359 if err != nil { 360 return nil, err 361 } 362 363 proxy.Upstreams[i] = u 364 } 365 366 // If we have config, then parse that 367 if o := listVal.Filter("config"); len(o.Items) > 1 { 368 return nil, fmt.Errorf("only 1 meta object supported") 369 } else if len(o.Items) == 1 { 370 var mSlice []map[string]interface{} 371 if err := hcl.DecodeObject(&mSlice, o.Items[0].Val); err != nil { 372 return nil, err 373 } 374 375 if len(mSlice) > 1 { 376 return nil, fmt.Errorf("only 1 meta object supported") 377 } 378 379 m := mSlice[0] 380 381 if err := mapstructure.WeakDecode(m, &proxy.Config); err != nil { 382 return nil, err 383 } 384 385 proxy.Config = flattenMapSlice(proxy.Config) 386 } 387 388 return &proxy, nil 389 } 390 391 func parseUpstream(uo *ast.ObjectItem) (*api.ConsulUpstream, error) { 392 valid := []string{ 393 "destination_name", 394 "local_bind_port", 395 } 396 397 if err := helper.CheckHCLKeys(uo.Val, valid); err != nil { 398 return nil, multierror.Prefix(err, "upstream ->") 399 } 400 401 var upstream api.ConsulUpstream 402 var m map[string]interface{} 403 if err := hcl.DecodeObject(&m, uo.Val); err != nil { 404 return nil, err 405 } 406 407 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 408 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 409 WeaklyTypedInput: true, 410 Result: &upstream, 411 }) 412 if err != nil { 413 return nil, err 414 } 415 416 if err := dec.Decode(m); err != nil { 417 return nil, err 418 } 419 420 return &upstream, nil 421 } 422 func parseChecks(service *api.Service, checkObjs *ast.ObjectList) error { 423 service.Checks = make([]api.ServiceCheck, len(checkObjs.Items)) 424 for idx, co := range checkObjs.Items { 425 // Check for invalid keys 426 valid := []string{ 427 "name", 428 "type", 429 "interval", 430 "timeout", 431 "path", 432 "protocol", 433 "port", 434 "command", 435 "args", 436 "initial_status", 437 "tls_skip_verify", 438 "header", 439 "method", 440 "check_restart", 441 "address_mode", 442 "grpc_service", 443 "grpc_use_tls", 444 "task", 445 } 446 if err := helper.CheckHCLKeys(co.Val, valid); err != nil { 447 return multierror.Prefix(err, "check ->") 448 } 449 450 var check api.ServiceCheck 451 var cm map[string]interface{} 452 if err := hcl.DecodeObject(&cm, co.Val); err != nil { 453 return err 454 } 455 456 // HCL allows repeating stanzas so merge 'header' into a single 457 // map[string][]string. 458 if headerI, ok := cm["header"]; ok { 459 headerRaw, ok := headerI.([]map[string]interface{}) 460 if !ok { 461 return fmt.Errorf("check -> header -> expected a []map[string][]string but found %T", headerI) 462 } 463 m := map[string][]string{} 464 for _, rawm := range headerRaw { 465 for k, vI := range rawm { 466 vs, ok := vI.([]interface{}) 467 if !ok { 468 return fmt.Errorf("check -> header -> %q expected a []string but found %T", k, vI) 469 } 470 for _, vI := range vs { 471 v, ok := vI.(string) 472 if !ok { 473 return fmt.Errorf("check -> header -> %q expected a string but found %T", k, vI) 474 } 475 m[k] = append(m[k], v) 476 } 477 } 478 } 479 480 check.Header = m 481 482 // Remove "header" as it has been parsed 483 delete(cm, "header") 484 } 485 486 delete(cm, "check_restart") 487 488 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 489 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 490 WeaklyTypedInput: true, 491 Result: &check, 492 }) 493 if err != nil { 494 return err 495 } 496 if err := dec.Decode(cm); err != nil { 497 return err 498 } 499 500 // Filter check_restart 501 var checkRestartList *ast.ObjectList 502 if ot, ok := co.Val.(*ast.ObjectType); ok { 503 checkRestartList = ot.List 504 } else { 505 return fmt.Errorf("check_restart '%s': should be an object", check.Name) 506 } 507 508 if cro := checkRestartList.Filter("check_restart"); len(cro.Items) > 0 { 509 if len(cro.Items) > 1 { 510 return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", check.Name) 511 } 512 cr, err := parseCheckRestart(cro.Items[0]) 513 if err != nil { 514 return multierror.Prefix(err, fmt.Sprintf("check: '%s',", check.Name)) 515 } 516 check.CheckRestart = cr 517 } 518 519 service.Checks[idx] = check 520 } 521 522 return nil 523 } 524 525 func parseCheckRestart(cro *ast.ObjectItem) (*api.CheckRestart, error) { 526 valid := []string{ 527 "limit", 528 "grace", 529 "ignore_warnings", 530 } 531 532 if err := helper.CheckHCLKeys(cro.Val, valid); err != nil { 533 return nil, multierror.Prefix(err, "check_restart ->") 534 } 535 536 var checkRestart api.CheckRestart 537 var crm map[string]interface{} 538 if err := hcl.DecodeObject(&crm, cro.Val); err != nil { 539 return nil, err 540 } 541 542 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 543 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 544 WeaklyTypedInput: true, 545 Result: &checkRestart, 546 }) 547 if err != nil { 548 return nil, err 549 } 550 if err := dec.Decode(crm); err != nil { 551 return nil, err 552 } 553 554 return &checkRestart, nil 555 }