github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/jobspec/parse_job.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 parseJob(result *api.Job, list *ast.ObjectList) error { 14 if len(list.Items) != 1 { 15 return fmt.Errorf("only one 'job' block allowed") 16 } 17 list = list.Children() 18 if len(list.Items) != 1 { 19 return fmt.Errorf("'job' block missing name") 20 } 21 22 // Get our job object 23 obj := list.Items[0] 24 25 // Decode the full thing into a map[string]interface for ease 26 var m map[string]interface{} 27 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 28 return err 29 } 30 delete(m, "constraint") 31 delete(m, "affinity") 32 delete(m, "meta") 33 delete(m, "migrate") 34 delete(m, "parameterized") 35 delete(m, "periodic") 36 delete(m, "reschedule") 37 delete(m, "update") 38 delete(m, "vault") 39 delete(m, "spread") 40 delete(m, "multiregion") 41 42 // Set the ID and name to the object key 43 result.ID = stringToPtr(obj.Keys[0].Token.Value().(string)) 44 result.Name = stringToPtr(*result.ID) 45 46 // Decode the rest 47 if err := mapstructure.WeakDecode(m, result); err != nil { 48 return err 49 } 50 51 // Value should be an object 52 var listVal *ast.ObjectList 53 if ot, ok := obj.Val.(*ast.ObjectType); ok { 54 listVal = ot.List 55 } else { 56 return fmt.Errorf("job '%s' value: should be an object", *result.ID) 57 } 58 59 // Check for invalid keys 60 valid := []string{ 61 "all_at_once", 62 "constraint", 63 "affinity", 64 "spread", 65 "datacenters", 66 "group", 67 "id", 68 "meta", 69 "migrate", 70 "name", 71 "namespace", 72 "parameterized", 73 "periodic", 74 "priority", 75 "region", 76 "reschedule", 77 "task", 78 "type", 79 "update", 80 "vault", 81 "vault_token", 82 "consul_token", 83 "multiregion", 84 } 85 if err := checkHCLKeys(listVal, valid); err != nil { 86 return multierror.Prefix(err, "job:") 87 } 88 89 // Parse constraints 90 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 91 if err := parseConstraints(&result.Constraints, o); err != nil { 92 return multierror.Prefix(err, "constraint ->") 93 } 94 } 95 96 // Parse affinities 97 if o := listVal.Filter("affinity"); len(o.Items) > 0 { 98 if err := parseAffinities(&result.Affinities, o); err != nil { 99 return multierror.Prefix(err, "affinity ->") 100 } 101 } 102 103 // If we have an update strategy, then parse that 104 if o := listVal.Filter("update"); len(o.Items) > 0 { 105 if err := parseUpdate(&result.Update, o); err != nil { 106 return multierror.Prefix(err, "update ->") 107 } 108 } 109 110 // If we have a periodic definition, then parse that 111 if o := listVal.Filter("periodic"); len(o.Items) > 0 { 112 if err := parsePeriodic(&result.Periodic, o); err != nil { 113 return multierror.Prefix(err, "periodic ->") 114 } 115 } 116 117 // Parse spread 118 if o := listVal.Filter("spread"); len(o.Items) > 0 { 119 if err := parseSpread(&result.Spreads, o); err != nil { 120 return multierror.Prefix(err, "spread ->") 121 } 122 } 123 124 // If we have a parameterized definition, then parse that 125 if o := listVal.Filter("parameterized"); len(o.Items) > 0 { 126 if err := parseParameterizedJob(&result.ParameterizedJob, o); err != nil { 127 return multierror.Prefix(err, "parameterized ->") 128 } 129 } 130 131 // If we have a reschedule stanza, then parse that 132 if o := listVal.Filter("reschedule"); len(o.Items) > 0 { 133 if err := parseReschedulePolicy(&result.Reschedule, o); err != nil { 134 return multierror.Prefix(err, "reschedule ->") 135 } 136 } 137 138 // If we have a migration strategy, then parse that 139 if o := listVal.Filter("migrate"); len(o.Items) > 0 { 140 if err := parseMigrate(&result.Migrate, o); err != nil { 141 return multierror.Prefix(err, "migrate ->") 142 } 143 } 144 145 // If we have a multiregion block, then parse that 146 if o := listVal.Filter("multiregion"); len(o.Items) > 0 { 147 var mr api.Multiregion 148 if err := parseMultiregion(&mr, o); err != nil { 149 return multierror.Prefix(err, "multiregion ->") 150 } 151 result.Multiregion = &mr 152 } 153 154 // Parse out meta fields. These are in HCL as a list so we need 155 // to iterate over them and merge them. 156 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 157 for _, o := range metaO.Elem().Items { 158 var m map[string]interface{} 159 if err := hcl.DecodeObject(&m, o.Val); err != nil { 160 return err 161 } 162 if err := mapstructure.WeakDecode(m, &result.Meta); err != nil { 163 return err 164 } 165 } 166 } 167 168 // If we have tasks outside, create TaskGroups for them 169 if o := listVal.Filter("task"); len(o.Items) > 0 { 170 var tasks []*api.Task 171 if err := parseTasks(&tasks, o); err != nil { 172 return multierror.Prefix(err, "task:") 173 } 174 175 result.TaskGroups = make([]*api.TaskGroup, len(tasks), len(tasks)*2) 176 for i, t := range tasks { 177 result.TaskGroups[i] = &api.TaskGroup{ 178 Name: stringToPtr(t.Name), 179 Tasks: []*api.Task{t}, 180 } 181 } 182 } 183 184 // Parse the task groups 185 if o := listVal.Filter("group"); len(o.Items) > 0 { 186 if err := parseGroups(result, o); err != nil { 187 return multierror.Prefix(err, "group:") 188 } 189 } 190 191 // If we have a vault block, then parse that 192 if o := listVal.Filter("vault"); len(o.Items) > 0 { 193 jobVault := &api.Vault{ 194 Env: boolToPtr(true), 195 ChangeMode: stringToPtr("restart"), 196 } 197 198 if err := parseVault(jobVault, o); err != nil { 199 return multierror.Prefix(err, "vault ->") 200 } 201 202 // Go through the task groups/tasks and if they don't have a Vault block, set it 203 for _, tg := range result.TaskGroups { 204 for _, task := range tg.Tasks { 205 if task.Vault == nil { 206 task.Vault = jobVault 207 } 208 } 209 } 210 } 211 212 return nil 213 } 214 215 func parsePeriodic(result **api.PeriodicConfig, list *ast.ObjectList) error { 216 list = list.Elem() 217 if len(list.Items) > 1 { 218 return fmt.Errorf("only one 'periodic' block allowed per job") 219 } 220 221 // Get our resource object 222 o := list.Items[0] 223 224 var m map[string]interface{} 225 if err := hcl.DecodeObject(&m, o.Val); err != nil { 226 return err 227 } 228 229 // Check for invalid keys 230 valid := []string{ 231 "enabled", 232 "cron", 233 "prohibit_overlap", 234 "time_zone", 235 } 236 if err := checkHCLKeys(o.Val, valid); err != nil { 237 return err 238 } 239 240 if value, ok := m["enabled"]; ok { 241 enabled, err := parseBool(value) 242 if err != nil { 243 return fmt.Errorf("periodic.enabled should be set to true or false; %v", err) 244 } 245 m["Enabled"] = enabled 246 } 247 248 // If "cron" is provided, set the type to "cron" and store the spec. 249 if cron, ok := m["cron"]; ok { 250 m["SpecType"] = api.PeriodicSpecCron 251 m["Spec"] = cron 252 } 253 254 // Build the constraint 255 var p api.PeriodicConfig 256 if err := mapstructure.WeakDecode(m, &p); err != nil { 257 return err 258 } 259 *result = &p 260 return nil 261 } 262 263 func parseParameterizedJob(result **api.ParameterizedJobConfig, list *ast.ObjectList) error { 264 list = list.Elem() 265 if len(list.Items) > 1 { 266 return fmt.Errorf("only one 'parameterized' block allowed per job") 267 } 268 269 // Get our resource object 270 o := list.Items[0] 271 272 var m map[string]interface{} 273 if err := hcl.DecodeObject(&m, o.Val); err != nil { 274 return err 275 } 276 277 // Check for invalid keys 278 valid := []string{ 279 "payload", 280 "meta_required", 281 "meta_optional", 282 } 283 if err := checkHCLKeys(o.Val, valid); err != nil { 284 return err 285 } 286 287 // Build the parameterized job block 288 var d api.ParameterizedJobConfig 289 if err := mapstructure.WeakDecode(m, &d); err != nil { 290 return err 291 } 292 293 *result = &d 294 return nil 295 }