github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/jobspec/parse.go (about) 1 package jobspec 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "regexp" 10 "strings" 11 "time" 12 13 "github.com/hashicorp/hcl" 14 hclobj "github.com/hashicorp/hcl/hcl" 15 "github.com/hashicorp/nomad/nomad/structs" 16 "github.com/mitchellh/mapstructure" 17 ) 18 19 // Parse parses the job spec from the given io.Reader. 20 // 21 // Due to current internal limitations, the entire contents of the 22 // io.Reader will be copied into memory first before parsing. 23 func Parse(r io.Reader) (*structs.Job, error) { 24 // Copy the reader into an in-memory buffer first since HCL requires it. 25 var buf bytes.Buffer 26 if _, err := io.Copy(&buf, r); err != nil { 27 return nil, err 28 } 29 30 // Parse the buffer 31 obj, err := hcl.Parse(buf.String()) 32 if err != nil { 33 return nil, fmt.Errorf("error parsing: %s", err) 34 } 35 buf.Reset() 36 37 var job structs.Job 38 39 // Parse the job out 40 jobO := obj.Get("job", false) 41 if jobO == nil { 42 return nil, fmt.Errorf("'job' stanza not found") 43 } 44 if err := parseJob(&job, jobO); err != nil { 45 return nil, fmt.Errorf("error parsing 'job': %s", err) 46 } 47 48 return &job, nil 49 } 50 51 // ParseFile parses the given path as a job spec. 52 func ParseFile(path string) (*structs.Job, error) { 53 path, err := filepath.Abs(path) 54 if err != nil { 55 return nil, err 56 } 57 58 f, err := os.Open(path) 59 if err != nil { 60 return nil, err 61 } 62 defer f.Close() 63 64 return Parse(f) 65 } 66 67 func parseJob(result *structs.Job, obj *hclobj.Object) error { 68 if obj.Len() > 1 { 69 return fmt.Errorf("only one 'job' block allowed") 70 } 71 72 // Get our job object 73 obj = obj.Elem(true)[0] 74 75 // Decode the full thing into a map[string]interface for ease 76 var m map[string]interface{} 77 if err := hcl.DecodeObject(&m, obj); err != nil { 78 return err 79 } 80 delete(m, "constraint") 81 delete(m, "meta") 82 delete(m, "update") 83 84 // Set the ID and name to the object key 85 result.ID = obj.Key 86 result.Name = obj.Key 87 88 // Defaults 89 result.Priority = 50 90 result.Region = "global" 91 result.Type = "service" 92 93 // Decode the rest 94 if err := mapstructure.WeakDecode(m, result); err != nil { 95 return err 96 } 97 98 // Parse constraints 99 if o := obj.Get("constraint", false); o != nil { 100 if err := parseConstraints(&result.Constraints, o); err != nil { 101 return err 102 } 103 } 104 105 // If we have an update strategy, then parse that 106 if o := obj.Get("update", false); o != nil { 107 if err := parseUpdate(&result.Update, o); err != nil { 108 return err 109 } 110 } 111 112 // Parse out meta fields. These are in HCL as a list so we need 113 // to iterate over them and merge them. 114 if metaO := obj.Get("meta", false); metaO != nil { 115 for _, o := range metaO.Elem(false) { 116 var m map[string]interface{} 117 if err := hcl.DecodeObject(&m, o); err != nil { 118 return err 119 } 120 if err := mapstructure.WeakDecode(m, &result.Meta); err != nil { 121 return err 122 } 123 } 124 } 125 126 // If we have tasks outside, do those 127 if o := obj.Get("task", false); o != nil { 128 var tasks []*structs.Task 129 if err := parseTasks(&tasks, o); err != nil { 130 return err 131 } 132 133 result.TaskGroups = make([]*structs.TaskGroup, len(tasks), len(tasks)*2) 134 for i, t := range tasks { 135 result.TaskGroups[i] = &structs.TaskGroup{ 136 Name: t.Name, 137 Count: 1, 138 Tasks: []*structs.Task{t}, 139 } 140 } 141 } 142 143 // Parse the task groups 144 if o := obj.Get("group", false); o != nil { 145 if err := parseGroups(result, o); err != nil { 146 return fmt.Errorf("error parsing 'group': %s", err) 147 } 148 } 149 150 return nil 151 } 152 153 func parseGroups(result *structs.Job, obj *hclobj.Object) error { 154 // Get all the maps of keys to the actual object 155 objects := make(map[string]*hclobj.Object) 156 for _, o1 := range obj.Elem(false) { 157 for _, o2 := range o1.Elem(true) { 158 if _, ok := objects[o2.Key]; ok { 159 return fmt.Errorf( 160 "group '%s' defined more than once", 161 o2.Key) 162 } 163 164 objects[o2.Key] = o2 165 } 166 } 167 168 if len(objects) == 0 { 169 return nil 170 } 171 172 // Go through each object and turn it into an actual result. 173 collection := make([]*structs.TaskGroup, 0, len(objects)) 174 for n, o := range objects { 175 var m map[string]interface{} 176 if err := hcl.DecodeObject(&m, o); err != nil { 177 return err 178 } 179 delete(m, "constraint") 180 delete(m, "meta") 181 delete(m, "task") 182 183 // Default count to 1 if not specified 184 if _, ok := m["count"]; !ok { 185 m["count"] = 1 186 } 187 188 // Build the group with the basic decode 189 var g structs.TaskGroup 190 g.Name = n 191 if err := mapstructure.WeakDecode(m, &g); err != nil { 192 return err 193 } 194 195 // Parse constraints 196 if o := o.Get("constraint", false); o != nil { 197 if err := parseConstraints(&g.Constraints, o); err != nil { 198 return err 199 } 200 } 201 202 // Parse out meta fields. These are in HCL as a list so we need 203 // to iterate over them and merge them. 204 if metaO := o.Get("meta", false); metaO != nil { 205 for _, o := range metaO.Elem(false) { 206 var m map[string]interface{} 207 if err := hcl.DecodeObject(&m, o); err != nil { 208 return err 209 } 210 if err := mapstructure.WeakDecode(m, &g.Meta); err != nil { 211 return err 212 } 213 } 214 } 215 216 // Parse tasks 217 if o := o.Get("task", false); o != nil { 218 if err := parseTasks(&g.Tasks, o); err != nil { 219 return err 220 } 221 } 222 223 collection = append(collection, &g) 224 } 225 226 result.TaskGroups = append(result.TaskGroups, collection...) 227 return nil 228 } 229 230 func parseConstraints(result *[]*structs.Constraint, obj *hclobj.Object) error { 231 for _, o := range obj.Elem(false) { 232 var m map[string]interface{} 233 if err := hcl.DecodeObject(&m, o); err != nil { 234 return err 235 } 236 m["LTarget"] = m["attribute"] 237 m["RTarget"] = m["value"] 238 m["Operand"] = m["operator"] 239 240 // Default constraint to being hard 241 if _, ok := m["hard"]; !ok { 242 m["hard"] = true 243 } 244 245 // If "version" is provided, set the operand 246 // to "version" and the value to the "RTarget" 247 if constraint, ok := m["version"]; ok { 248 m["Operand"] = "version" 249 m["RTarget"] = constraint 250 } 251 252 // If "regexp" is provided, set the operand 253 // to "regexp" and the value to the "RTarget" 254 if constraint, ok := m["regexp"]; ok { 255 m["Operand"] = "regexp" 256 m["RTarget"] = constraint 257 } 258 259 // Build the constraint 260 var c structs.Constraint 261 if err := mapstructure.WeakDecode(m, &c); err != nil { 262 return err 263 } 264 if c.Operand == "" { 265 c.Operand = "=" 266 } 267 268 *result = append(*result, &c) 269 } 270 271 return nil 272 } 273 274 func parseTasks(result *[]*structs.Task, obj *hclobj.Object) error { 275 // Get all the maps of keys to the actual object 276 objects := make([]*hclobj.Object, 0, 5) 277 set := make(map[string]struct{}) 278 for _, o1 := range obj.Elem(false) { 279 for _, o2 := range o1.Elem(true) { 280 if _, ok := set[o2.Key]; ok { 281 return fmt.Errorf( 282 "group '%s' defined more than once", 283 o2.Key) 284 } 285 286 objects = append(objects, o2) 287 set[o2.Key] = struct{}{} 288 } 289 } 290 291 if len(objects) == 0 { 292 return nil 293 } 294 295 for _, o := range objects { 296 var m map[string]interface{} 297 if err := hcl.DecodeObject(&m, o); err != nil { 298 return err 299 } 300 delete(m, "config") 301 delete(m, "env") 302 delete(m, "constraint") 303 delete(m, "meta") 304 delete(m, "resources") 305 306 // Build the task 307 var t structs.Task 308 t.Name = o.Key 309 if err := mapstructure.WeakDecode(m, &t); err != nil { 310 return err 311 } 312 313 // If we have env, then parse them 314 if o := o.Get("env", false); o != nil { 315 for _, o := range o.Elem(false) { 316 var m map[string]interface{} 317 if err := hcl.DecodeObject(&m, o); err != nil { 318 return err 319 } 320 if err := mapstructure.WeakDecode(m, &t.Env); err != nil { 321 return err 322 } 323 } 324 } 325 326 // If we have config, then parse that 327 if o := o.Get("config", false); o != nil { 328 for _, o := range o.Elem(false) { 329 var m map[string]interface{} 330 if err := hcl.DecodeObject(&m, o); err != nil { 331 return err 332 } 333 if err := mapstructure.WeakDecode(m, &t.Config); err != nil { 334 return err 335 } 336 } 337 } 338 339 // Parse constraints 340 if o := o.Get("constraint", false); o != nil { 341 if err := parseConstraints(&t.Constraints, o); err != nil { 342 return err 343 } 344 } 345 346 // Parse out meta fields. These are in HCL as a list so we need 347 // to iterate over them and merge them. 348 if metaO := o.Get("meta", false); metaO != nil { 349 for _, o := range metaO.Elem(false) { 350 var m map[string]interface{} 351 if err := hcl.DecodeObject(&m, o); err != nil { 352 return err 353 } 354 if err := mapstructure.WeakDecode(m, &t.Meta); err != nil { 355 return err 356 } 357 } 358 } 359 360 // If we have resources, then parse that 361 if o := o.Get("resources", false); o != nil { 362 var r structs.Resources 363 if err := parseResources(&r, o); err != nil { 364 return fmt.Errorf("task '%s': %s", t.Name, err) 365 } 366 367 t.Resources = &r 368 } 369 370 *result = append(*result, &t) 371 } 372 373 return nil 374 } 375 376 var reDynamicPorts *regexp.Regexp = regexp.MustCompile("^[a-zA-Z0-9_]+$") 377 var errDynamicPorts = fmt.Errorf("DynamicPort label does not conform to naming requirements %s", reDynamicPorts.String()) 378 379 func parseResources(result *structs.Resources, obj *hclobj.Object) error { 380 if obj.Len() > 1 { 381 return fmt.Errorf("only one 'resource' block allowed per task") 382 } 383 384 for _, o := range obj.Elem(false) { 385 var m map[string]interface{} 386 if err := hcl.DecodeObject(&m, o); err != nil { 387 return err 388 } 389 delete(m, "network") 390 391 if err := mapstructure.WeakDecode(m, result); err != nil { 392 return err 393 } 394 395 // Parse the network resources 396 if o := o.Get("network", false); o != nil { 397 if o.Len() > 1 { 398 return fmt.Errorf("only one 'network' resource allowed") 399 } 400 401 var r structs.NetworkResource 402 var m map[string]interface{} 403 if err := hcl.DecodeObject(&m, o); err != nil { 404 return err 405 } 406 if err := mapstructure.WeakDecode(m, &r); err != nil { 407 return err 408 } 409 410 // Keep track of labels we've already seen so we can ensure there 411 // are no collisions when we turn them into environment variables. 412 // lowercase:NomalCase so we can get the first for the error message 413 seenLabel := map[string]string{} 414 415 for _, label := range r.DynamicPorts { 416 if !reDynamicPorts.MatchString(label) { 417 return errDynamicPorts 418 } 419 first, seen := seenLabel[strings.ToLower(label)] 420 if seen { 421 return fmt.Errorf("Found a port label collision: `%s` overlaps with previous `%s`", label, first) 422 } else { 423 seenLabel[strings.ToLower(label)] = label 424 } 425 426 } 427 428 result.Networks = []*structs.NetworkResource{&r} 429 } 430 431 } 432 433 return nil 434 } 435 436 func parseUpdate(result *structs.UpdateStrategy, obj *hclobj.Object) error { 437 if obj.Len() > 1 { 438 return fmt.Errorf("only one 'update' block allowed per job") 439 } 440 441 for _, o := range obj.Elem(false) { 442 var m map[string]interface{} 443 if err := hcl.DecodeObject(&m, o); err != nil { 444 return err 445 } 446 for _, key := range []string{"stagger", "Stagger"} { 447 if raw, ok := m[key]; ok { 448 switch v := raw.(type) { 449 case string: 450 dur, err := time.ParseDuration(v) 451 if err != nil { 452 return fmt.Errorf("invalid stagger time '%s'", raw) 453 } 454 m[key] = dur 455 case int: 456 m[key] = time.Duration(v) * time.Second 457 default: 458 return fmt.Errorf("invalid type for stagger time '%s'", 459 raw) 460 } 461 } 462 } 463 464 if err := mapstructure.WeakDecode(m, result); err != nil { 465 return err 466 } 467 } 468 return nil 469 }