github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/jobspec/parse_task.go (about) 1 package jobspec 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 multierror "github.com/hashicorp/go-multierror" 9 "github.com/hashicorp/hcl" 10 "github.com/hashicorp/hcl/hcl/ast" 11 "github.com/hashicorp/nomad/api" 12 "github.com/mitchellh/mapstructure" 13 ) 14 15 var ( 16 commonTaskKeys = []string{ 17 "driver", 18 "user", 19 "config", 20 "env", 21 "resources", 22 "meta", 23 "logs", 24 "kill_timeout", 25 "shutdown_delay", 26 "kill_signal", 27 "scaling", 28 } 29 30 normalTaskKeys = append(commonTaskKeys, 31 "artifact", 32 "constraint", 33 "affinity", 34 "dispatch_payload", 35 "lifecycle", 36 "leader", 37 "restart", 38 "service", 39 "template", 40 "vault", 41 "kind", 42 "volume_mount", 43 "csi_plugin", 44 ) 45 46 sidecarTaskKeys = append(commonTaskKeys, 47 "name", 48 ) 49 ) 50 51 func parseTasks(result *[]*api.Task, list *ast.ObjectList) error { 52 list = list.Children() 53 if len(list.Items) == 0 { 54 return nil 55 } 56 57 // Go through each object and turn it into an actual result. 58 seen := make(map[string]struct{}) 59 for _, item := range list.Items { 60 n := item.Keys[0].Token.Value().(string) 61 62 // Make sure we haven't already found this 63 if _, ok := seen[n]; ok { 64 return fmt.Errorf("task '%s' defined more than once", n) 65 } 66 seen[n] = struct{}{} 67 68 t, err := parseTask(item, normalTaskKeys) 69 if err != nil { 70 return multierror.Prefix(err, fmt.Sprintf("'%s',", n)) 71 } 72 73 t.Name = n 74 75 *result = append(*result, t) 76 } 77 78 return nil 79 } 80 81 func parseTask(item *ast.ObjectItem, keys []string) (*api.Task, error) { 82 // We need this later 83 var listVal *ast.ObjectList 84 if ot, ok := item.Val.(*ast.ObjectType); ok { 85 listVal = ot.List 86 } else { 87 return nil, fmt.Errorf("should be an object") 88 } 89 90 // Check for invalid keys 91 if err := checkHCLKeys(listVal, keys); err != nil { 92 return nil, err 93 } 94 95 var m map[string]interface{} 96 if err := hcl.DecodeObject(&m, item.Val); err != nil { 97 return nil, err 98 } 99 delete(m, "artifact") 100 delete(m, "config") 101 delete(m, "constraint") 102 delete(m, "affinity") 103 delete(m, "dispatch_payload") 104 delete(m, "lifecycle") 105 delete(m, "env") 106 delete(m, "logs") 107 delete(m, "meta") 108 delete(m, "resources") 109 delete(m, "restart") 110 delete(m, "service") 111 delete(m, "template") 112 delete(m, "vault") 113 delete(m, "volume_mount") 114 delete(m, "csi_plugin") 115 delete(m, "scaling") 116 117 // Build the task 118 var t api.Task 119 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 120 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 121 WeaklyTypedInput: true, 122 Result: &t, 123 }) 124 125 if err != nil { 126 return nil, err 127 } 128 if err := dec.Decode(m); err != nil { 129 return nil, err 130 } 131 132 // If we have env, then parse them 133 if o := listVal.Filter("env"); len(o.Items) > 0 { 134 for _, o := range o.Elem().Items { 135 var m map[string]interface{} 136 if err := hcl.DecodeObject(&m, o.Val); err != nil { 137 return nil, err 138 } 139 if err := mapstructure.WeakDecode(m, &t.Env); err != nil { 140 return nil, err 141 } 142 } 143 } 144 145 if o := listVal.Filter("service"); len(o.Items) > 0 { 146 services, err := parseServices(o) 147 if err != nil { 148 return nil, err 149 } 150 151 t.Services = services 152 } 153 154 if o := listVal.Filter("csi_plugin"); len(o.Items) > 0 { 155 if len(o.Items) != 1 { 156 return nil, fmt.Errorf("csi_plugin -> Expected single stanza, got %d", len(o.Items)) 157 } 158 i := o.Elem().Items[0] 159 160 var m map[string]interface{} 161 if err := hcl.DecodeObject(&m, i.Val); err != nil { 162 return nil, err 163 } 164 165 var cfg api.TaskCSIPluginConfig 166 if err := mapstructure.WeakDecode(m, &cfg); err != nil { 167 return nil, err 168 } 169 170 t.CSIPluginConfig = &cfg 171 } 172 173 // If we have config, then parse that 174 if o := listVal.Filter("config"); len(o.Items) > 0 { 175 for _, o := range o.Elem().Items { 176 var m map[string]interface{} 177 if err := hcl.DecodeObject(&m, o.Val); err != nil { 178 return nil, err 179 } 180 181 if err := mapstructure.WeakDecode(m, &t.Config); err != nil { 182 return nil, err 183 } 184 } 185 } 186 187 // Parse constraints 188 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 189 if err := parseConstraints(&t.Constraints, o); err != nil { 190 return nil, multierror.Prefix(err, "constraint ->") 191 } 192 } 193 194 // Parse affinities 195 if o := listVal.Filter("affinity"); len(o.Items) > 0 { 196 if err := parseAffinities(&t.Affinities, o); err != nil { 197 return nil, multierror.Prefix(err, "affinity ->") 198 } 199 } 200 201 // Parse out meta fields. These are in HCL as a list so we need 202 // to iterate over them and merge them. 203 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 204 for _, o := range metaO.Elem().Items { 205 var m map[string]interface{} 206 if err := hcl.DecodeObject(&m, o.Val); err != nil { 207 return nil, err 208 } 209 if err := mapstructure.WeakDecode(m, &t.Meta); err != nil { 210 return nil, err 211 } 212 } 213 } 214 215 // Parse volume mounts 216 if o := listVal.Filter("volume_mount"); len(o.Items) > 0 { 217 if err := parseVolumeMounts(&t.VolumeMounts, o); err != nil { 218 return nil, multierror.Prefix(err, "volume_mount ->") 219 } 220 } 221 222 // If we have resources, then parse that 223 if o := listVal.Filter("resources"); len(o.Items) > 0 { 224 var r api.Resources 225 if err := parseResources(&r, o); err != nil { 226 return nil, multierror.Prefix(err, "resources ->") 227 } 228 229 t.Resources = &r 230 } 231 232 // Parse restart policy 233 if o := listVal.Filter("restart"); len(o.Items) > 0 { 234 if err := parseRestartPolicy(&t.RestartPolicy, o); err != nil { 235 return nil, multierror.Prefix(err, "restart ->") 236 } 237 } 238 239 // If we have logs then parse that 240 if o := listVal.Filter("logs"); len(o.Items) > 0 { 241 if len(o.Items) > 1 { 242 return nil, fmt.Errorf("only one logs block is allowed in a Task. Number of logs block found: %d", len(o.Items)) 243 } 244 var m map[string]interface{} 245 logsBlock := o.Items[0] 246 247 // Check for invalid keys 248 valid := []string{ 249 "max_files", 250 "max_file_size", 251 } 252 if err := checkHCLKeys(logsBlock.Val, valid); err != nil { 253 return nil, multierror.Prefix(err, "logs ->") 254 } 255 256 if err := hcl.DecodeObject(&m, logsBlock.Val); err != nil { 257 return nil, err 258 } 259 260 var log api.LogConfig 261 if err := mapstructure.WeakDecode(m, &log); err != nil { 262 return nil, err 263 } 264 265 t.LogConfig = &log 266 } 267 268 // Parse artifacts 269 if o := listVal.Filter("artifact"); len(o.Items) > 0 { 270 if err := parseArtifacts(&t.Artifacts, o); err != nil { 271 return nil, multierror.Prefix(err, "artifact ->") 272 } 273 } 274 275 // Parse templates 276 if o := listVal.Filter("template"); len(o.Items) > 0 { 277 if err := parseTemplates(&t.Templates, o); err != nil { 278 return nil, multierror.Prefix(err, "template ->") 279 } 280 } 281 282 // Parse scaling policies 283 if o := listVal.Filter("scaling"); len(o.Items) > 0 { 284 if err := parseTaskScalingPolicies(&t.ScalingPolicies, o); err != nil { 285 return nil, err 286 } 287 } 288 289 // If we have a vault block, then parse that 290 if o := listVal.Filter("vault"); len(o.Items) > 0 { 291 v := &api.Vault{ 292 Env: boolToPtr(true), 293 ChangeMode: stringToPtr("restart"), 294 } 295 296 if err := parseVault(v, o); err != nil { 297 return nil, multierror.Prefix(err, "vault ->") 298 } 299 300 t.Vault = v 301 } 302 303 // If we have a dispatch_payload block parse that 304 if o := listVal.Filter("dispatch_payload"); len(o.Items) > 0 { 305 if len(o.Items) > 1 { 306 return nil, fmt.Errorf("only one dispatch_payload block is allowed in a task. Number of dispatch_payload blocks found: %d", len(o.Items)) 307 } 308 var m map[string]interface{} 309 dispatchBlock := o.Items[0] 310 311 // Check for invalid keys 312 valid := []string{ 313 "file", 314 } 315 if err := checkHCLKeys(dispatchBlock.Val, valid); err != nil { 316 return nil, multierror.Prefix(err, "dispatch_payload ->") 317 } 318 319 if err := hcl.DecodeObject(&m, dispatchBlock.Val); err != nil { 320 return nil, err 321 } 322 323 t.DispatchPayload = &api.DispatchPayloadConfig{} 324 if err := mapstructure.WeakDecode(m, t.DispatchPayload); err != nil { 325 return nil, err 326 } 327 } 328 329 // If we have a lifecycle block parse that 330 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { 331 if len(o.Items) > 1 { 332 return nil, fmt.Errorf("only one lifecycle block is allowed in a task. Number of lifecycle blocks found: %d", len(o.Items)) 333 } 334 335 var m map[string]interface{} 336 lifecycleBlock := o.Items[0] 337 338 // Check for invalid keys 339 valid := []string{ 340 "hook", 341 "sidecar", 342 } 343 if err := checkHCLKeys(lifecycleBlock.Val, valid); err != nil { 344 return nil, multierror.Prefix(err, "lifecycle ->") 345 } 346 347 if err := hcl.DecodeObject(&m, lifecycleBlock.Val); err != nil { 348 return nil, err 349 } 350 351 t.Lifecycle = &api.TaskLifecycle{} 352 if err := mapstructure.WeakDecode(m, t.Lifecycle); err != nil { 353 return nil, err 354 } 355 } 356 return &t, nil 357 } 358 359 func parseArtifacts(result *[]*api.TaskArtifact, list *ast.ObjectList) error { 360 for _, o := range list.Elem().Items { 361 // Check for invalid keys 362 valid := []string{ 363 "source", 364 "options", 365 "mode", 366 "destination", 367 } 368 if err := checkHCLKeys(o.Val, valid); err != nil { 369 return err 370 } 371 372 var m map[string]interface{} 373 if err := hcl.DecodeObject(&m, o.Val); err != nil { 374 return err 375 } 376 377 delete(m, "options") 378 379 var ta api.TaskArtifact 380 if err := mapstructure.WeakDecode(m, &ta); err != nil { 381 return err 382 } 383 384 var optionList *ast.ObjectList 385 if ot, ok := o.Val.(*ast.ObjectType); ok { 386 optionList = ot.List 387 } else { 388 return fmt.Errorf("artifact should be an object") 389 } 390 391 if oo := optionList.Filter("options"); len(oo.Items) > 0 { 392 options := make(map[string]string) 393 if err := parseArtifactOption(options, oo); err != nil { 394 return multierror.Prefix(err, "options: ") 395 } 396 ta.GetterOptions = options 397 } 398 399 *result = append(*result, &ta) 400 } 401 402 return nil 403 } 404 405 func parseArtifactOption(result map[string]string, list *ast.ObjectList) error { 406 list = list.Elem() 407 if len(list.Items) > 1 { 408 return fmt.Errorf("only one 'options' block allowed per artifact") 409 } 410 411 // Get our resource object 412 o := list.Items[0] 413 414 var m map[string]interface{} 415 if err := hcl.DecodeObject(&m, o.Val); err != nil { 416 return err 417 } 418 419 if err := mapstructure.WeakDecode(m, &result); err != nil { 420 return err 421 } 422 423 return nil 424 } 425 426 func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error { 427 for _, o := range list.Elem().Items { 428 // Check for invalid keys 429 valid := []string{ 430 "change_mode", 431 "change_signal", 432 "data", 433 "destination", 434 "left_delimiter", 435 "perms", 436 "right_delimiter", 437 "source", 438 "splay", 439 "env", 440 "vault_grace", //COMPAT(0.12) not used; emits warning in 0.11. 441 } 442 if err := checkHCLKeys(o.Val, valid); err != nil { 443 return err 444 } 445 446 var m map[string]interface{} 447 if err := hcl.DecodeObject(&m, o.Val); err != nil { 448 return err 449 } 450 451 templ := &api.Template{ 452 ChangeMode: stringToPtr("restart"), 453 Splay: timeToPtr(5 * time.Second), 454 Perms: stringToPtr("0644"), 455 } 456 457 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 458 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 459 WeaklyTypedInput: true, 460 Result: templ, 461 }) 462 if err != nil { 463 return err 464 } 465 if err := dec.Decode(m); err != nil { 466 return err 467 } 468 469 *result = append(*result, templ) 470 } 471 472 return nil 473 } 474 475 func parseTaskScalingPolicies(result *[]*api.ScalingPolicy, list *ast.ObjectList) error { 476 if len(list.Items) == 0 { 477 return nil 478 } 479 480 errPrefix := "scaling ->" 481 // Go through each object and turn it into an actual result. 482 seen := make(map[string]bool) 483 for _, item := range list.Items { 484 if l := len(item.Keys); l == 0 { 485 return multierror.Prefix(fmt.Errorf("task scaling policy missing name"), errPrefix) 486 } else if l > 1 { 487 return multierror.Prefix(fmt.Errorf("task scaling policy should only have one name"), errPrefix) 488 } 489 n := item.Keys[0].Token.Value().(string) 490 errPrefix = fmt.Sprintf("scaling[%v] ->", n) 491 492 var policyType string 493 switch strings.ToLower(n) { 494 case "cpu": 495 policyType = "vertical_cpu" 496 case "mem": 497 policyType = "vertical_mem" 498 default: 499 return multierror.Prefix(fmt.Errorf(`scaling policy name must be "cpu" or "mem"`), errPrefix) 500 } 501 502 // Make sure we haven't already found this 503 if seen[n] { 504 return multierror.Prefix(fmt.Errorf("scaling policy cannot be defined more than once"), errPrefix) 505 } 506 seen[n] = true 507 508 p, err := parseScalingPolicy(item) 509 if err != nil { 510 return multierror.Prefix(err, errPrefix) 511 } 512 513 if p.Type == "" { 514 p.Type = policyType 515 } else if p.Type != policyType { 516 return multierror.Prefix(fmt.Errorf("policy had invalid 'type': %q", p.Type), errPrefix) 517 } 518 519 *result = append(*result, p) 520 } 521 522 return nil 523 } 524 525 func parseResources(result *api.Resources, list *ast.ObjectList) error { 526 list = list.Elem() 527 if len(list.Items) == 0 { 528 return nil 529 } 530 if len(list.Items) > 1 { 531 return fmt.Errorf("only one 'resource' block allowed per task") 532 } 533 534 // Get our resource object 535 o := list.Items[0] 536 537 // We need this later 538 var listVal *ast.ObjectList 539 if ot, ok := o.Val.(*ast.ObjectType); ok { 540 listVal = ot.List 541 } else { 542 return fmt.Errorf("resource: should be an object") 543 } 544 545 // Check for invalid keys 546 valid := []string{ 547 "cpu", 548 "iops", // COMPAT(0.10): Remove after one release to allow it to be removed from jobspecs 549 "disk", 550 "memory", 551 "network", 552 "device", 553 } 554 if err := checkHCLKeys(listVal, valid); err != nil { 555 return multierror.Prefix(err, "resources ->") 556 } 557 558 var m map[string]interface{} 559 if err := hcl.DecodeObject(&m, o.Val); err != nil { 560 return err 561 } 562 delete(m, "network") 563 delete(m, "device") 564 565 if err := mapstructure.WeakDecode(m, result); err != nil { 566 return err 567 } 568 569 // Parse the network resources 570 if o := listVal.Filter("network"); len(o.Items) > 0 { 571 r, err := ParseNetwork(o) 572 if err != nil { 573 return fmt.Errorf("resource, %v", err) 574 } 575 result.Networks = []*api.NetworkResource{r} 576 } 577 578 // Parse the device resources 579 if o := listVal.Filter("device"); len(o.Items) > 0 { 580 result.Devices = make([]*api.RequestedDevice, len(o.Items)) 581 for idx, do := range o.Items { 582 if l := len(do.Keys); l == 0 { 583 return multierror.Prefix(fmt.Errorf("missing device name"), fmt.Sprintf("resources, device[%d]->", idx)) 584 } else if l > 1 { 585 return multierror.Prefix(fmt.Errorf("only one name may be specified"), fmt.Sprintf("resources, device[%d]->", idx)) 586 } 587 name := do.Keys[0].Token.Value().(string) 588 589 // Value should be an object 590 var listVal *ast.ObjectList 591 if ot, ok := do.Val.(*ast.ObjectType); ok { 592 listVal = ot.List 593 } else { 594 return fmt.Errorf("device should be an object") 595 } 596 597 // Check for invalid keys 598 valid := []string{ 599 "name", 600 "count", 601 "affinity", 602 "constraint", 603 } 604 if err := checkHCLKeys(do.Val, valid); err != nil { 605 return multierror.Prefix(err, fmt.Sprintf("resources, device[%d]->", idx)) 606 } 607 608 // Set the name 609 var r api.RequestedDevice 610 r.Name = name 611 612 var m map[string]interface{} 613 if err := hcl.DecodeObject(&m, do.Val); err != nil { 614 return err 615 } 616 617 delete(m, "constraint") 618 delete(m, "affinity") 619 620 if err := mapstructure.WeakDecode(m, &r); err != nil { 621 return err 622 } 623 624 // Parse constraints 625 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 626 if err := parseConstraints(&r.Constraints, o); err != nil { 627 return multierror.Prefix(err, "constraint ->") 628 } 629 } 630 631 // Parse affinities 632 if o := listVal.Filter("affinity"); len(o.Items) > 0 { 633 if err := parseAffinities(&r.Affinities, o); err != nil { 634 return multierror.Prefix(err, "affinity ->") 635 } 636 } 637 638 result.Devices[idx] = &r 639 } 640 } 641 642 return nil 643 } 644 645 func parseVolumeMounts(out *[]*api.VolumeMount, list *ast.ObjectList) error { 646 mounts := make([]*api.VolumeMount, len(list.Items)) 647 648 for i, item := range list.Items { 649 valid := []string{ 650 "volume", 651 "read_only", 652 "destination", 653 "propagation_mode", 654 } 655 if err := checkHCLKeys(item.Val, valid); err != nil { 656 return err 657 } 658 659 var m map[string]interface{} 660 if err := hcl.DecodeObject(&m, item.Val); err != nil { 661 return err 662 } 663 664 var result api.VolumeMount 665 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 666 WeaklyTypedInput: true, 667 Result: &result, 668 }) 669 if err != nil { 670 return err 671 } 672 if err := dec.Decode(m); err != nil { 673 return err 674 } 675 676 mounts[i] = &result 677 } 678 679 *out = mounts 680 return nil 681 }