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