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