github.com/adityamillind98/nomad@v0.11.8/jobspec/parse.go (about) 1 package jobspec 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "regexp" 10 "strconv" 11 12 multierror "github.com/hashicorp/go-multierror" 13 "github.com/hashicorp/hcl" 14 "github.com/hashicorp/hcl/hcl/ast" 15 "github.com/hashicorp/nomad/api" 16 "github.com/hashicorp/nomad/helper" 17 "github.com/mitchellh/mapstructure" 18 ) 19 20 var reDynamicPorts = regexp.MustCompile("^[a-zA-Z0-9_]+$") 21 var errPortLabel = fmt.Errorf("Port label does not conform to naming requirements %s", reDynamicPorts.String()) 22 23 // Parse parses the job spec from the given io.Reader. 24 // 25 // Due to current internal limitations, the entire contents of the 26 // io.Reader will be copied into memory first before parsing. 27 func Parse(r io.Reader) (*api.Job, error) { 28 // Copy the reader into an in-memory buffer first since HCL requires it. 29 var buf bytes.Buffer 30 if _, err := io.Copy(&buf, r); err != nil { 31 return nil, err 32 } 33 34 // Parse the buffer 35 root, err := hcl.Parse(buf.String()) 36 if err != nil { 37 return nil, fmt.Errorf("error parsing: %s", err) 38 } 39 buf.Reset() 40 41 // Top-level item should be a list 42 list, ok := root.Node.(*ast.ObjectList) 43 if !ok { 44 return nil, fmt.Errorf("error parsing: root should be an object") 45 } 46 47 // Check for invalid keys 48 valid := []string{ 49 "job", 50 } 51 if err := helper.CheckHCLKeys(list, valid); err != nil { 52 return nil, err 53 } 54 55 var job api.Job 56 57 // Parse the job out 58 matches := list.Filter("job") 59 if len(matches.Items) == 0 { 60 return nil, fmt.Errorf("'job' stanza not found") 61 } 62 if err := parseJob(&job, matches); err != nil { 63 return nil, fmt.Errorf("error parsing 'job': %s", err) 64 } 65 66 return &job, nil 67 } 68 69 // ParseFile parses the given path as a job spec. 70 func ParseFile(path string) (*api.Job, error) { 71 path, err := filepath.Abs(path) 72 if err != nil { 73 return nil, err 74 } 75 76 f, err := os.Open(path) 77 if err != nil { 78 return nil, err 79 } 80 defer f.Close() 81 82 return Parse(f) 83 } 84 85 func parseReschedulePolicy(final **api.ReschedulePolicy, list *ast.ObjectList) error { 86 list = list.Elem() 87 if len(list.Items) > 1 { 88 return fmt.Errorf("only one 'reschedule' block allowed") 89 } 90 91 // Get our job object 92 obj := list.Items[0] 93 94 // Check for invalid keys 95 valid := []string{ 96 "attempts", 97 "interval", 98 "unlimited", 99 "delay", 100 "max_delay", 101 "delay_function", 102 } 103 if err := helper.CheckHCLKeys(obj.Val, valid); err != nil { 104 return err 105 } 106 107 var m map[string]interface{} 108 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 109 return err 110 } 111 112 var result api.ReschedulePolicy 113 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 114 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 115 WeaklyTypedInput: true, 116 Result: &result, 117 }) 118 if err != nil { 119 return err 120 } 121 if err := dec.Decode(m); err != nil { 122 return err 123 } 124 125 *final = &result 126 return nil 127 } 128 129 func parseConstraints(result *[]*api.Constraint, list *ast.ObjectList) error { 130 for _, o := range list.Elem().Items { 131 // Check for invalid keys 132 valid := []string{ 133 "attribute", 134 "distinct_hosts", 135 "distinct_property", 136 "operator", 137 "regexp", 138 "set_contains", 139 "value", 140 "version", 141 "semver", 142 } 143 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 144 return err 145 } 146 147 var m map[string]interface{} 148 if err := hcl.DecodeObject(&m, o.Val); err != nil { 149 return err 150 } 151 152 m["LTarget"] = m["attribute"] 153 m["RTarget"] = m["value"] 154 m["Operand"] = m["operator"] 155 156 // If "version" is provided, set the operand 157 // to "version" and the value to the "RTarget" 158 if constraint, ok := m[api.ConstraintVersion]; ok { 159 m["Operand"] = api.ConstraintVersion 160 m["RTarget"] = constraint 161 } 162 163 // If "semver" is provided, set the operand 164 // to "semver" and the value to the "RTarget" 165 if constraint, ok := m[api.ConstraintSemver]; ok { 166 m["Operand"] = api.ConstraintSemver 167 m["RTarget"] = constraint 168 } 169 170 // If "regexp" is provided, set the operand 171 // to "regexp" and the value to the "RTarget" 172 if constraint, ok := m[api.ConstraintRegex]; ok { 173 m["Operand"] = api.ConstraintRegex 174 m["RTarget"] = constraint 175 } 176 177 // If "set_contains" is provided, set the operand 178 // to "set_contains" and the value to the "RTarget" 179 if constraint, ok := m[api.ConstraintSetContains]; ok { 180 m["Operand"] = api.ConstraintSetContains 181 m["RTarget"] = constraint 182 } 183 184 if value, ok := m[api.ConstraintDistinctHosts]; ok { 185 enabled, err := parseBool(value) 186 if err != nil { 187 return fmt.Errorf("distinct_hosts should be set to true or false; %v", err) 188 } 189 190 // If it is not enabled, skip the constraint. 191 if !enabled { 192 continue 193 } 194 195 m["Operand"] = api.ConstraintDistinctHosts 196 } 197 198 if property, ok := m[api.ConstraintDistinctProperty]; ok { 199 m["Operand"] = api.ConstraintDistinctProperty 200 m["LTarget"] = property 201 } 202 203 // Build the constraint 204 var c api.Constraint 205 if err := mapstructure.WeakDecode(m, &c); err != nil { 206 return err 207 } 208 if c.Operand == "" { 209 c.Operand = "=" 210 } 211 212 *result = append(*result, &c) 213 } 214 215 return nil 216 } 217 218 func parseAffinities(result *[]*api.Affinity, list *ast.ObjectList) error { 219 for _, o := range list.Elem().Items { 220 // Check for invalid keys 221 valid := []string{ 222 "attribute", 223 "operator", 224 "regexp", 225 "set_contains", 226 "set_contains_any", 227 "set_contains_all", 228 "value", 229 "version", 230 "semver", 231 "weight", 232 } 233 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 234 return err 235 } 236 237 var m map[string]interface{} 238 if err := hcl.DecodeObject(&m, o.Val); err != nil { 239 return err 240 } 241 242 m["LTarget"] = m["attribute"] 243 m["RTarget"] = m["value"] 244 m["Operand"] = m["operator"] 245 246 // If "version" is provided, set the operand 247 // to "version" and the value to the "RTarget" 248 if affinity, ok := m[api.ConstraintVersion]; ok { 249 m["Operand"] = api.ConstraintVersion 250 m["RTarget"] = affinity 251 } 252 253 // If "semver" is provided, set the operand 254 // to "semver" and the value to the "RTarget" 255 if affinity, ok := m[api.ConstraintSemver]; ok { 256 m["Operand"] = api.ConstraintSemver 257 m["RTarget"] = affinity 258 } 259 260 // If "regexp" is provided, set the operand 261 // to "regexp" and the value to the "RTarget" 262 if affinity, ok := m[api.ConstraintRegex]; ok { 263 m["Operand"] = api.ConstraintRegex 264 m["RTarget"] = affinity 265 } 266 267 // If "set_contains_any" is provided, set the operand 268 // to "set_contains_any" and the value to the "RTarget" 269 if affinity, ok := m[api.ConstraintSetContainsAny]; ok { 270 m["Operand"] = api.ConstraintSetContainsAny 271 m["RTarget"] = affinity 272 } 273 274 // If "set_contains_all" is provided, set the operand 275 // to "set_contains_all" and the value to the "RTarget" 276 if affinity, ok := m[api.ConstraintSetContainsAll]; ok { 277 m["Operand"] = api.ConstraintSetContainsAll 278 m["RTarget"] = affinity 279 } 280 281 // set_contains is a synonym of set_contains_all 282 if affinity, ok := m[api.ConstraintSetContains]; ok { 283 m["Operand"] = api.ConstraintSetContains 284 m["RTarget"] = affinity 285 } 286 287 // Build the affinity 288 var a api.Affinity 289 if err := mapstructure.WeakDecode(m, &a); err != nil { 290 return err 291 } 292 if a.Operand == "" { 293 a.Operand = "=" 294 } 295 296 *result = append(*result, &a) 297 } 298 299 return nil 300 } 301 302 func parseSpread(result *[]*api.Spread, list *ast.ObjectList) error { 303 for _, o := range list.Elem().Items { 304 // Check for invalid keys 305 valid := []string{ 306 "attribute", 307 "weight", 308 "target", 309 } 310 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 311 return err 312 } 313 314 // We need this later 315 var listVal *ast.ObjectList 316 if ot, ok := o.Val.(*ast.ObjectType); ok { 317 listVal = ot.List 318 } else { 319 return fmt.Errorf("spread should be an object") 320 } 321 322 var m map[string]interface{} 323 if err := hcl.DecodeObject(&m, o.Val); err != nil { 324 return err 325 } 326 delete(m, "target") 327 // Build spread 328 var s api.Spread 329 if err := mapstructure.WeakDecode(m, &s); err != nil { 330 return err 331 } 332 333 // Parse spread target 334 if o := listVal.Filter("target"); len(o.Items) > 0 { 335 if err := parseSpreadTarget(&s.SpreadTarget, o); err != nil { 336 return multierror.Prefix(err, fmt.Sprintf("target ->")) 337 } 338 } 339 340 *result = append(*result, &s) 341 } 342 343 return nil 344 } 345 346 func parseSpreadTarget(result *[]*api.SpreadTarget, list *ast.ObjectList) error { 347 seen := make(map[string]struct{}) 348 for _, item := range list.Items { 349 if len(item.Keys) != 1 { 350 return fmt.Errorf("missing spread target") 351 } 352 n := item.Keys[0].Token.Value().(string) 353 354 // Make sure we haven't already found this 355 if _, ok := seen[n]; ok { 356 return fmt.Errorf("target '%s' defined more than once", n) 357 } 358 seen[n] = struct{}{} 359 360 // We need this later 361 var listVal *ast.ObjectList 362 if ot, ok := item.Val.(*ast.ObjectType); ok { 363 listVal = ot.List 364 } else { 365 return fmt.Errorf("target should be an object") 366 } 367 368 // Check for invalid keys 369 valid := []string{ 370 "percent", 371 "value", 372 } 373 if err := helper.CheckHCLKeys(listVal, valid); err != nil { 374 return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n)) 375 } 376 377 var m map[string]interface{} 378 if err := hcl.DecodeObject(&m, item.Val); err != nil { 379 return err 380 } 381 382 // Decode spread target 383 var g api.SpreadTarget 384 g.Value = n 385 if err := mapstructure.WeakDecode(m, &g); err != nil { 386 return err 387 } 388 *result = append(*result, &g) 389 } 390 return nil 391 } 392 393 // parseBool takes an interface value and tries to convert it to a boolean and 394 // returns an error if the type can't be converted. 395 func parseBool(value interface{}) (bool, error) { 396 var enabled bool 397 var err error 398 switch value.(type) { 399 case string: 400 enabled, err = strconv.ParseBool(value.(string)) 401 case bool: 402 enabled = value.(bool) 403 default: 404 err = fmt.Errorf("%v couldn't be converted to boolean value", value) 405 } 406 407 return enabled, err 408 } 409 410 func parseUpdate(result **api.UpdateStrategy, list *ast.ObjectList) error { 411 list = list.Elem() 412 if len(list.Items) > 1 { 413 return fmt.Errorf("only one 'update' block allowed") 414 } 415 416 // Get our resource object 417 o := list.Items[0] 418 419 var m map[string]interface{} 420 if err := hcl.DecodeObject(&m, o.Val); err != nil { 421 return err 422 } 423 424 // Check for invalid keys 425 valid := []string{ 426 "stagger", 427 "max_parallel", 428 "health_check", 429 "min_healthy_time", 430 "healthy_deadline", 431 "progress_deadline", 432 "auto_revert", 433 "auto_promote", 434 "canary", 435 } 436 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 437 return err 438 } 439 440 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 441 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 442 WeaklyTypedInput: true, 443 Result: result, 444 }) 445 if err != nil { 446 return err 447 } 448 return dec.Decode(m) 449 } 450 451 func parseMigrate(result **api.MigrateStrategy, list *ast.ObjectList) error { 452 list = list.Elem() 453 if len(list.Items) > 1 { 454 return fmt.Errorf("only one 'migrate' block allowed") 455 } 456 457 // Get our resource object 458 o := list.Items[0] 459 460 var m map[string]interface{} 461 if err := hcl.DecodeObject(&m, o.Val); err != nil { 462 return err 463 } 464 465 // Check for invalid keys 466 valid := []string{ 467 "max_parallel", 468 "health_check", 469 "min_healthy_time", 470 "healthy_deadline", 471 } 472 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 473 return err 474 } 475 476 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 477 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 478 WeaklyTypedInput: true, 479 Result: result, 480 }) 481 if err != nil { 482 return err 483 } 484 return dec.Decode(m) 485 } 486 487 func parseVault(result *api.Vault, list *ast.ObjectList) error { 488 list = list.Elem() 489 if len(list.Items) == 0 { 490 return nil 491 } 492 if len(list.Items) > 1 { 493 return fmt.Errorf("only one 'vault' block allowed per task") 494 } 495 496 // Get our resource object 497 o := list.Items[0] 498 499 // We need this later 500 var listVal *ast.ObjectList 501 if ot, ok := o.Val.(*ast.ObjectType); ok { 502 listVal = ot.List 503 } else { 504 return fmt.Errorf("vault: should be an object") 505 } 506 507 // Check for invalid keys 508 valid := []string{ 509 "policies", 510 "env", 511 "change_mode", 512 "change_signal", 513 } 514 if err := helper.CheckHCLKeys(listVal, valid); err != nil { 515 return multierror.Prefix(err, "vault ->") 516 } 517 518 var m map[string]interface{} 519 if err := hcl.DecodeObject(&m, o.Val); err != nil { 520 return err 521 } 522 523 if err := mapstructure.WeakDecode(m, result); err != nil { 524 return err 525 } 526 527 return nil 528 }