github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/jobspec/parse_group.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/mitchellh/mapstructure" 11 ) 12 13 func parseGroups(result *api.Job, list *ast.ObjectList) error { 14 list = list.Children() 15 if len(list.Items) == 0 { 16 return nil 17 } 18 19 // Go through each object and turn it into an actual result. 20 collection := make([]*api.TaskGroup, 0, len(list.Items)) 21 seen := make(map[string]struct{}) 22 for _, item := range list.Items { 23 n := item.Keys[0].Token.Value().(string) 24 25 // Make sure we haven't already found this 26 if _, ok := seen[n]; ok { 27 return fmt.Errorf("group '%s' defined more than once", n) 28 } 29 seen[n] = struct{}{} 30 31 // We need this later 32 var listVal *ast.ObjectList 33 if ot, ok := item.Val.(*ast.ObjectType); ok { 34 listVal = ot.List 35 } else { 36 return fmt.Errorf("group '%s': should be an object", n) 37 } 38 39 // Check for invalid keys 40 valid := []string{ 41 "count", 42 "constraint", 43 "affinity", 44 "restart", 45 "meta", 46 "task", 47 "ephemeral_disk", 48 "update", 49 "reschedule", 50 "vault", 51 "migrate", 52 "spread", 53 "shutdown_delay", 54 "network", 55 "service", 56 "volume", 57 "scaling", 58 "stop_after_client_disconnect", 59 } 60 if err := checkHCLKeys(listVal, valid); err != nil { 61 return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n)) 62 } 63 64 var m map[string]interface{} 65 if err := hcl.DecodeObject(&m, item.Val); err != nil { 66 return err 67 } 68 69 delete(m, "constraint") 70 delete(m, "affinity") 71 delete(m, "meta") 72 delete(m, "task") 73 delete(m, "restart") 74 delete(m, "ephemeral_disk") 75 delete(m, "update") 76 delete(m, "vault") 77 delete(m, "migrate") 78 delete(m, "spread") 79 delete(m, "network") 80 delete(m, "service") 81 delete(m, "volume") 82 delete(m, "scaling") 83 84 // Build the group with the basic decode 85 var g api.TaskGroup 86 g.Name = stringToPtr(n) 87 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 88 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 89 WeaklyTypedInput: true, 90 Result: &g, 91 }) 92 93 if err != nil { 94 return err 95 } 96 if err := dec.Decode(m); err != nil { 97 return err 98 } 99 100 // Parse constraints 101 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 102 if err := parseConstraints(&g.Constraints, o); err != nil { 103 return multierror.Prefix(err, fmt.Sprintf("'%s', constraint ->", n)) 104 } 105 } 106 107 // Parse affinities 108 if o := listVal.Filter("affinity"); len(o.Items) > 0 { 109 if err := parseAffinities(&g.Affinities, o); err != nil { 110 return multierror.Prefix(err, fmt.Sprintf("'%s', affinity ->", n)) 111 } 112 } 113 114 // Parse restart policy 115 if o := listVal.Filter("restart"); len(o.Items) > 0 { 116 if err := parseRestartPolicy(&g.RestartPolicy, o); err != nil { 117 return multierror.Prefix(err, fmt.Sprintf("'%s', restart ->", n)) 118 } 119 } 120 121 // Parse spread 122 if o := listVal.Filter("spread"); len(o.Items) > 0 { 123 if err := parseSpread(&g.Spreads, o); err != nil { 124 return multierror.Prefix(err, "spread ->") 125 } 126 } 127 128 // Parse network 129 if o := listVal.Filter("network"); len(o.Items) > 0 { 130 networks, err := ParseNetwork(o) 131 if err != nil { 132 return err 133 } 134 g.Networks = []*api.NetworkResource{networks} 135 } 136 137 // Parse reschedule policy 138 if o := listVal.Filter("reschedule"); len(o.Items) > 0 { 139 if err := parseReschedulePolicy(&g.ReschedulePolicy, o); err != nil { 140 return multierror.Prefix(err, fmt.Sprintf("'%s', reschedule ->", n)) 141 } 142 } 143 // Parse ephemeral disk 144 if o := listVal.Filter("ephemeral_disk"); len(o.Items) > 0 { 145 g.EphemeralDisk = &api.EphemeralDisk{} 146 if err := parseEphemeralDisk(&g.EphemeralDisk, o); err != nil { 147 return multierror.Prefix(err, fmt.Sprintf("'%s', ephemeral_disk ->", n)) 148 } 149 } 150 151 // If we have an update strategy, then parse that 152 if o := listVal.Filter("update"); len(o.Items) > 0 { 153 if err := parseUpdate(&g.Update, o); err != nil { 154 return multierror.Prefix(err, "update ->") 155 } 156 } 157 158 // If we have a migration strategy, then parse that 159 if o := listVal.Filter("migrate"); len(o.Items) > 0 { 160 if err := parseMigrate(&g.Migrate, o); err != nil { 161 return multierror.Prefix(err, "migrate ->") 162 } 163 } 164 165 // Parse out meta fields. These are in HCL as a list so we need 166 // to iterate over them and merge them. 167 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 168 for _, o := range metaO.Elem().Items { 169 var m map[string]interface{} 170 if err := hcl.DecodeObject(&m, o.Val); err != nil { 171 return err 172 } 173 if err := mapstructure.WeakDecode(m, &g.Meta); err != nil { 174 return err 175 } 176 } 177 } 178 179 // Parse any volume declarations 180 if o := listVal.Filter("volume"); len(o.Items) > 0 { 181 if err := parseVolumes(&g.Volumes, o); err != nil { 182 return multierror.Prefix(err, "volume ->") 183 } 184 } 185 186 // Parse scaling policy 187 if o := listVal.Filter("scaling"); len(o.Items) > 0 { 188 if err := parseGroupScalingPolicy(&g.Scaling, o); err != nil { 189 return multierror.Prefix(err, "scaling ->") 190 } 191 } 192 193 // Parse tasks 194 if o := listVal.Filter("task"); len(o.Items) > 0 { 195 if err := parseTasks(&g.Tasks, o); err != nil { 196 return multierror.Prefix(err, fmt.Sprintf("'%s', task:", n)) 197 } 198 } 199 200 // If we have a vault block, then parse that 201 if o := listVal.Filter("vault"); len(o.Items) > 0 { 202 tgVault := &api.Vault{ 203 Env: boolToPtr(true), 204 ChangeMode: stringToPtr("restart"), 205 } 206 207 if err := parseVault(tgVault, o); err != nil { 208 return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n)) 209 } 210 211 // Go through the tasks and if they don't have a Vault block, set it 212 for _, task := range g.Tasks { 213 if task.Vault == nil { 214 task.Vault = tgVault 215 } 216 } 217 } 218 219 if o := listVal.Filter("service"); len(o.Items) > 0 { 220 if err := parseGroupServices(&g, o); err != nil { 221 return multierror.Prefix(err, fmt.Sprintf("'%s',", n)) 222 } 223 } 224 collection = append(collection, &g) 225 } 226 227 result.TaskGroups = append(result.TaskGroups, collection...) 228 return nil 229 } 230 231 func parseEphemeralDisk(result **api.EphemeralDisk, list *ast.ObjectList) error { 232 list = list.Elem() 233 if len(list.Items) > 1 { 234 return fmt.Errorf("only one 'ephemeral_disk' block allowed") 235 } 236 237 // Get our ephemeral_disk object 238 obj := list.Items[0] 239 240 // Check for invalid keys 241 valid := []string{ 242 "sticky", 243 "size", 244 "migrate", 245 } 246 if err := checkHCLKeys(obj.Val, valid); err != nil { 247 return err 248 } 249 250 var m map[string]interface{} 251 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 252 return err 253 } 254 255 var ephemeralDisk api.EphemeralDisk 256 if err := mapstructure.WeakDecode(m, &ephemeralDisk); err != nil { 257 return err 258 } 259 *result = &ephemeralDisk 260 261 return nil 262 } 263 264 func parseRestartPolicy(final **api.RestartPolicy, list *ast.ObjectList) error { 265 list = list.Elem() 266 if len(list.Items) > 1 { 267 return fmt.Errorf("only one 'restart' block allowed") 268 } 269 270 // Get our job object 271 obj := list.Items[0] 272 273 // Check for invalid keys 274 valid := []string{ 275 "attempts", 276 "interval", 277 "delay", 278 "mode", 279 } 280 if err := checkHCLKeys(obj.Val, valid); err != nil { 281 return err 282 } 283 284 var m map[string]interface{} 285 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 286 return err 287 } 288 289 var result api.RestartPolicy 290 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 291 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 292 WeaklyTypedInput: true, 293 Result: &result, 294 }) 295 if err != nil { 296 return err 297 } 298 if err := dec.Decode(m); err != nil { 299 return err 300 } 301 302 *final = &result 303 return nil 304 } 305 306 func parseVolumes(out *map[string]*api.VolumeRequest, list *ast.ObjectList) error { 307 hcl.DecodeObject(out, list) 308 309 for k, v := range *out { 310 err := unusedKeys(v) 311 if err != nil { 312 return err 313 } 314 // This is supported by `hcl:",key"`, but that only works if we start at the 315 // parent ast.ObjectItem 316 v.Name = k 317 } 318 319 return nil 320 } 321 322 func parseGroupScalingPolicy(out **api.ScalingPolicy, list *ast.ObjectList) error { 323 if len(list.Items) > 1 { 324 return fmt.Errorf("only one 'scaling' block allowed") 325 } 326 item := list.Items[0] 327 if len(item.Keys) != 0 { 328 return fmt.Errorf("task group scaling policy should not have a name") 329 } 330 p, err := parseScalingPolicy(item) 331 if err != nil { 332 return err 333 } 334 335 // group-specific validation 336 if p.Max == nil { 337 return fmt.Errorf("missing 'max'") 338 } 339 if p.Type == "" { 340 p.Type = "horizontal" 341 } else if p.Type != "horizontal" { 342 return fmt.Errorf("task group scaling policy had invalid type: %q", p.Type) 343 } 344 *out = p 345 return nil 346 } 347 348 func parseScalingPolicy(item *ast.ObjectItem) (*api.ScalingPolicy, error) { 349 // We need this later 350 var listVal *ast.ObjectList 351 if ot, ok := item.Val.(*ast.ObjectType); ok { 352 listVal = ot.List 353 } else { 354 return nil, fmt.Errorf("should be an object") 355 } 356 357 valid := []string{ 358 "min", 359 "max", 360 "policy", 361 "enabled", 362 "type", 363 } 364 if err := checkHCLKeys(item.Val, valid); err != nil { 365 return nil, err 366 } 367 368 var m map[string]interface{} 369 if err := hcl.DecodeObject(&m, item.Val); err != nil { 370 return nil, err 371 } 372 delete(m, "policy") 373 374 var result api.ScalingPolicy 375 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 376 WeaklyTypedInput: true, 377 Result: &result, 378 }) 379 if err != nil { 380 return nil, err 381 } 382 if err := dec.Decode(m); err != nil { 383 return nil, err 384 } 385 386 // If we have policy, then parse that 387 if o := listVal.Filter("policy"); len(o.Items) > 0 { 388 if len(o.Elem().Items) > 1 { 389 return nil, fmt.Errorf("only one 'policy' block allowed per 'scaling' block") 390 } 391 p := o.Elem().Items[0] 392 var m map[string]interface{} 393 if err := hcl.DecodeObject(&m, p.Val); err != nil { 394 return nil, err 395 } 396 if err := mapstructure.WeakDecode(m, &result.Policy); err != nil { 397 return nil, err 398 } 399 } 400 401 return &result, nil 402 }