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