github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/jobspec2/types.config.go (about) 1 package jobspec2 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/hashicorp/hcl/v2/ext/dynblock" 9 "github.com/hashicorp/nomad/api" 10 "github.com/hashicorp/nomad/jobspec2/hclutil" 11 "github.com/zclconf/go-cty/cty" 12 ) 13 14 const ( 15 variablesLabel = "variables" 16 variableLabel = "variable" 17 localsLabel = "locals" 18 vaultLabel = "vault" 19 taskLabel = "task" 20 21 inputVariablesAccessor = "var" 22 localsAccessor = "local" 23 ) 24 25 type jobConfig struct { 26 JobID string `hcl:",label"` 27 Job *api.Job 28 29 ParseConfig *ParseConfig 30 31 Vault *api.Vault `hcl:"vault,block"` 32 Tasks []*api.Task `hcl:"task,block"` 33 34 InputVariables Variables 35 LocalVariables Variables 36 37 LocalBlocks []*LocalBlock 38 } 39 40 func newJobConfig(parseConfig *ParseConfig) *jobConfig { 41 return &jobConfig{ 42 ParseConfig: parseConfig, 43 44 InputVariables: Variables{}, 45 LocalVariables: Variables{}, 46 } 47 } 48 49 var jobConfigSchema = &hcl.BodySchema{ 50 Blocks: []hcl.BlockHeaderSchema{ 51 {Type: variablesLabel}, 52 {Type: variableLabel, LabelNames: []string{"name"}}, 53 {Type: localsLabel}, 54 {Type: "job", LabelNames: []string{"name"}}, 55 }, 56 } 57 58 func (c *jobConfig) decodeBody(body hcl.Body) hcl.Diagnostics { 59 content, diags := body.Content(jobConfigSchema) 60 if len(diags) != 0 { 61 return diags 62 } 63 64 diags = append(diags, c.decodeInputVariables(content)...) 65 diags = append(diags, c.parseLocalVariables(content)...) 66 diags = append(diags, c.collectInputVariableValues(c.ParseConfig.Envs, c.ParseConfig.parsedVarFiles, toVars(c.ParseConfig.ArgVars))...) 67 68 _, moreDiags := c.InputVariables.Values() 69 diags = append(diags, moreDiags...) 70 _, moreDiags = c.LocalVariables.Values() 71 diags = append(diags, moreDiags...) 72 diags = append(diags, c.evaluateLocalVariables(c.LocalBlocks)...) 73 74 // Errors at this point are likely syntax errors which can result in 75 // invalid state when we try to decode the rest of the job. If we continue 76 // we may panic and that will obscure the error, so return early so the 77 // user can be told how to fix their jobspec. 78 if diags.HasErrors() { 79 return diags 80 } 81 nctx := c.EvalContext() 82 83 diags = append(diags, c.decodeJob(content, nctx)...) 84 return diags 85 } 86 87 // decodeInputVariables looks in the found blocks for 'variables' and 88 // 'variable' blocks. It should be called firsthand so that other blocks can 89 // use the variables. 90 func (c *jobConfig) decodeInputVariables(content *hcl.BodyContent) hcl.Diagnostics { 91 var diags hcl.Diagnostics 92 93 for _, block := range content.Blocks { 94 switch block.Type { 95 case variableLabel: 96 moreDiags := c.InputVariables.decodeVariableBlock(block, nil) 97 diags = append(diags, moreDiags...) 98 case variablesLabel: 99 attrs, moreDiags := block.Body.JustAttributes() 100 diags = append(diags, moreDiags...) 101 for key, attr := range attrs { 102 moreDiags = c.InputVariables.decodeVariable(key, attr, nil) 103 diags = append(diags, moreDiags...) 104 } 105 } 106 } 107 return diags 108 } 109 110 // parseLocalVariables looks in the found blocks for 'locals' blocks. It 111 // should be called after parsing input variables so that they can be 112 // referenced. 113 func (c *jobConfig) parseLocalVariables(content *hcl.BodyContent) hcl.Diagnostics { 114 var diags hcl.Diagnostics 115 116 for _, block := range content.Blocks { 117 switch block.Type { 118 case localsLabel: 119 attrs, moreDiags := block.Body.JustAttributes() 120 diags = append(diags, moreDiags...) 121 for name, attr := range attrs { 122 if _, found := c.LocalVariables[name]; found { 123 diags = append(diags, &hcl.Diagnostic{ 124 Severity: hcl.DiagError, 125 Summary: "Duplicate value in " + localsLabel, 126 Detail: "Duplicate " + name + " definition found.", 127 Subject: attr.NameRange.Ptr(), 128 Context: block.DefRange.Ptr(), 129 }) 130 return diags 131 } 132 c.LocalBlocks = append(c.LocalBlocks, &LocalBlock{ 133 Name: name, 134 Expr: attr.Expr, 135 }) 136 } 137 } 138 } 139 140 return diags 141 } 142 143 func (c *jobConfig) decodeTopLevelExtras(content *hcl.BodyContent, ctx *hcl.EvalContext) hcl.Diagnostics { 144 var diags hcl.Diagnostics 145 146 var foundVault *hcl.Block 147 for _, b := range content.Blocks { 148 if b.Type == vaultLabel { 149 if foundVault != nil { 150 diags = append(diags, &hcl.Diagnostic{ 151 Severity: hcl.DiagError, 152 Summary: fmt.Sprintf("Duplicate %s block", b.Type), 153 Detail: fmt.Sprintf( 154 "Only one block of type %q is allowed. Previous definition was at %s.", 155 b.Type, foundVault.DefRange.String(), 156 ), 157 Subject: &b.DefRange, 158 }) 159 continue 160 } 161 foundVault = b 162 163 v := &api.Vault{} 164 diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, v)...) 165 c.Vault = v 166 167 } else if b.Type == taskLabel { 168 t := &api.Task{} 169 diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, t)...) 170 if len(b.Labels) == 1 { 171 t.Name = b.Labels[0] 172 c.Tasks = append(c.Tasks, t) 173 } 174 } 175 } 176 177 return diags 178 } 179 180 func (c *jobConfig) evaluateLocalVariables(locals []*LocalBlock) hcl.Diagnostics { 181 var diags hcl.Diagnostics 182 183 if len(locals) > 0 && c.LocalVariables == nil { 184 c.LocalVariables = Variables{} 185 } 186 187 var retry, previousL int 188 for len(locals) > 0 { 189 local := locals[0] 190 moreDiags := c.evaluateLocalVariable(local) 191 if moreDiags.HasErrors() { 192 if len(locals) == 1 { 193 // If this is the only local left there's no need 194 // to try evaluating again 195 return append(diags, moreDiags...) 196 } 197 if previousL == len(locals) { 198 if retry == 100 { 199 // To get to this point, locals must have a circle dependency 200 return append(diags, moreDiags...) 201 } 202 retry++ 203 } 204 previousL = len(locals) 205 206 // If local uses another local that has not been evaluated yet this could be the reason of errors 207 // Push local to the end of slice to be evaluated later 208 locals = append(locals, local) 209 } else { 210 retry = 0 211 diags = append(diags, moreDiags...) 212 } 213 // Remove local from slice 214 locals = append(locals[:0], locals[1:]...) 215 } 216 217 return diags 218 } 219 220 func (c *jobConfig) evaluateLocalVariable(local *LocalBlock) hcl.Diagnostics { 221 var diags hcl.Diagnostics 222 223 value, moreDiags := local.Expr.Value(c.EvalContext()) 224 diags = append(diags, moreDiags...) 225 if moreDiags.HasErrors() { 226 return diags 227 } 228 c.LocalVariables[local.Name] = &Variable{ 229 Name: local.Name, 230 Values: []VariableAssignment{{ 231 Value: value, 232 Expr: local.Expr, 233 From: "default", 234 }}, 235 Type: value.Type(), 236 } 237 238 return diags 239 } 240 241 func (c *jobConfig) decodeJob(content *hcl.BodyContent, ctx *hcl.EvalContext) hcl.Diagnostics { 242 var diags hcl.Diagnostics 243 244 c.Job = &api.Job{} 245 246 var found *hcl.Block 247 for _, b := range content.Blocks { 248 if b.Type != "job" { 249 continue 250 } 251 252 body := hclutil.BlocksAsAttrs(b.Body) 253 body = dynblock.Expand(body, ctx) 254 255 if found != nil { 256 diags = append(diags, &hcl.Diagnostic{ 257 Severity: hcl.DiagError, 258 Summary: fmt.Sprintf("Duplicate %s block", b.Type), 259 Detail: fmt.Sprintf( 260 "Only one block of type %q is allowed. Previous definition was at %s.", 261 b.Type, found.DefRange.String(), 262 ), 263 Subject: &b.DefRange, 264 }) 265 continue 266 } 267 found = b 268 269 c.JobID = b.Labels[0] 270 271 metaAttr, body, mdiags := decodeAsAttribute(body, ctx, "meta") 272 diags = append(diags, mdiags...) 273 274 extra, remain, mdiags := body.PartialContent(&hcl.BodySchema{ 275 Blocks: []hcl.BlockHeaderSchema{ 276 {Type: "vault"}, 277 {Type: "task", LabelNames: []string{"name"}}, 278 }, 279 }) 280 281 diags = append(diags, mdiags...) 282 diags = append(diags, c.decodeTopLevelExtras(extra, ctx)...) 283 diags = append(diags, hclDecoder.DecodeBody(remain, ctx, c.Job)...) 284 285 if metaAttr != nil { 286 c.Job.Meta = metaAttr 287 } 288 } 289 290 if found == nil { 291 diags = append(diags, &hcl.Diagnostic{ 292 Severity: hcl.DiagError, 293 Summary: "Missing job block", 294 Detail: "A job block is required", 295 }) 296 } 297 298 return diags 299 300 } 301 302 func (c *jobConfig) EvalContext() *hcl.EvalContext { 303 vars, _ := c.InputVariables.Values() 304 locals, _ := c.LocalVariables.Values() 305 return &hcl.EvalContext{ 306 Functions: Functions(c.ParseConfig.BaseDir, c.ParseConfig.AllowFS), 307 Variables: map[string]cty.Value{ 308 inputVariablesAccessor: cty.ObjectVal(vars), 309 localsAccessor: cty.ObjectVal(locals), 310 }, 311 UnknownVariable: func(expr string) (cty.Value, error) { 312 v := "${" + expr + "}" 313 return cty.StringVal(v), nil 314 }, 315 } 316 } 317 318 func toVars(vars []string) map[string]string { 319 attrs := make(map[string]string, len(vars)) 320 for _, arg := range vars { 321 parts := strings.SplitN(arg, "=", 2) 322 if len(parts) == 2 { 323 attrs[parts[0]] = parts[1] 324 } 325 } 326 327 return attrs 328 }