github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/helper/pluginutils/hclspecutils/dec.go (about) 1 package hclspecutils 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl2/hcl" 7 "github.com/hashicorp/hcl2/hcl/hclsyntax" 8 "github.com/hashicorp/hcl2/hcldec" 9 "github.com/hashicorp/nomad/plugins/shared/hclspec" 10 ) 11 12 var ( 13 // nilSpecDiagnostic is the diagnostic value returned if a nil value is 14 // given 15 nilSpecDiagnostic = &hcl.Diagnostic{ 16 Severity: hcl.DiagError, 17 Summary: "nil spec given", 18 Detail: "Can not convert a nil specification. Pass a valid spec", 19 } 20 21 // emptyPos is the position used when parsing hcl expressions 22 emptyPos = hcl.Pos{ 23 Line: 0, 24 Column: 0, 25 Byte: 0, 26 } 27 28 // specCtx is the context used to evaluate expressions. 29 specCtx = &hcl.EvalContext{ 30 Functions: specFuncs, 31 } 32 ) 33 34 // Convert converts a Spec to an hcl specification. 35 func Convert(spec *hclspec.Spec) (hcldec.Spec, hcl.Diagnostics) { 36 if spec == nil { 37 return nil, hcl.Diagnostics([]*hcl.Diagnostic{nilSpecDiagnostic}) 38 } 39 40 return decodeSpecBlock(spec, "") 41 } 42 43 // decodeSpecBlock is the recursive entry point that converts between the two 44 // spec types. 45 func decodeSpecBlock(spec *hclspec.Spec, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 46 switch spec.Block.(type) { 47 48 case *hclspec.Spec_Object: 49 return decodeObjectSpec(spec.GetObject()) 50 51 case *hclspec.Spec_Array: 52 return decodeArraySpec(spec.GetArray()) 53 54 case *hclspec.Spec_Attr: 55 return decodeAttrSpec(spec.GetAttr(), impliedName) 56 57 case *hclspec.Spec_BlockValue: 58 return decodeBlockSpec(spec.GetBlockValue(), impliedName) 59 60 case *hclspec.Spec_BlockAttrs: 61 return decodeBlockAttrsSpec(spec.GetBlockAttrs(), impliedName) 62 63 case *hclspec.Spec_BlockList: 64 return decodeBlockListSpec(spec.GetBlockList(), impliedName) 65 66 case *hclspec.Spec_BlockSet: 67 return decodeBlockSetSpec(spec.GetBlockSet(), impliedName) 68 69 case *hclspec.Spec_BlockMap: 70 return decodeBlockMapSpec(spec.GetBlockMap(), impliedName) 71 72 case *hclspec.Spec_Default: 73 return decodeDefaultSpec(spec.GetDefault()) 74 75 case *hclspec.Spec_Literal: 76 return decodeLiteralSpec(spec.GetLiteral()) 77 78 default: 79 // Should never happen, because the above cases should be exhaustive 80 // for our schema. 81 var diags hcl.Diagnostics 82 diags = append(diags, &hcl.Diagnostic{ 83 Severity: hcl.DiagError, 84 Summary: "Invalid spec block", 85 Detail: fmt.Sprintf("Blocks of type %T are not expected here.", spec.Block), 86 }) 87 return nil, diags 88 } 89 } 90 91 func decodeObjectSpec(obj *hclspec.Object) (hcldec.Spec, hcl.Diagnostics) { 92 var diags hcl.Diagnostics 93 spec := make(hcldec.ObjectSpec) 94 for attr, block := range obj.GetAttributes() { 95 propSpec, propDiags := decodeSpecBlock(block, attr) 96 diags = append(diags, propDiags...) 97 spec[attr] = propSpec 98 } 99 100 return spec, diags 101 } 102 103 func decodeArraySpec(a *hclspec.Array) (hcldec.Spec, hcl.Diagnostics) { 104 values := a.GetValues() 105 var diags hcl.Diagnostics 106 spec := make(hcldec.TupleSpec, 0, len(values)) 107 for _, block := range values { 108 elemSpec, elemDiags := decodeSpecBlock(block, "") 109 diags = append(diags, elemDiags...) 110 spec = append(spec, elemSpec) 111 } 112 113 return spec, diags 114 } 115 116 func decodeAttrSpec(attr *hclspec.Attr, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 117 // Convert the string type to an hcl.Expression 118 typeExpr, diags := hclsyntax.ParseExpression([]byte(attr.GetType()), "proto", emptyPos) 119 if diags.HasErrors() { 120 return nil, diags 121 } 122 123 spec := &hcldec.AttrSpec{ 124 Name: impliedName, 125 Required: attr.GetRequired(), 126 } 127 128 if n := attr.GetName(); n != "" { 129 spec.Name = n 130 } 131 132 var typeDiags hcl.Diagnostics 133 spec.Type, typeDiags = evalTypeExpr(typeExpr) 134 diags = append(diags, typeDiags...) 135 136 if spec.Name == "" { 137 diags = append(diags, &hcl.Diagnostic{ 138 Severity: hcl.DiagError, 139 Summary: "Missing name in attribute spec", 140 Detail: "The name attribute is required, to specify the attribute name that is expected in an input HCL file.", 141 }) 142 return nil, diags 143 } 144 145 return spec, diags 146 } 147 148 func decodeBlockSpec(block *hclspec.Block, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 149 spec := &hcldec.BlockSpec{ 150 TypeName: impliedName, 151 Required: block.GetRequired(), 152 } 153 154 if n := block.GetName(); n != "" { 155 spec.TypeName = n 156 } 157 158 nested, diags := decodeBlockNestedSpec(block.GetNested()) 159 spec.Nested = nested 160 return spec, diags 161 } 162 163 func decodeBlockAttrsSpec(block *hclspec.BlockAttrs, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 164 // Convert the string type to an hcl.Expression 165 typeExpr, diags := hclsyntax.ParseExpression([]byte(block.GetType()), "proto", emptyPos) 166 if diags.HasErrors() { 167 return nil, diags 168 } 169 170 spec := &hcldec.BlockAttrsSpec{ 171 TypeName: impliedName, 172 Required: block.GetRequired(), 173 } 174 175 if n := block.GetName(); n != "" { 176 spec.TypeName = n 177 } 178 179 var typeDiags hcl.Diagnostics 180 spec.ElementType, typeDiags = evalTypeExpr(typeExpr) 181 diags = append(diags, typeDiags...) 182 183 if spec.TypeName == "" { 184 diags = append(diags, &hcl.Diagnostic{ 185 Severity: hcl.DiagError, 186 Summary: "Missing name in block_attrs spec", 187 Detail: "The name attribute is required, to specify the block attr name that is expected in an input HCL file.", 188 }) 189 return nil, diags 190 } 191 192 return spec, diags 193 } 194 195 func decodeBlockListSpec(block *hclspec.BlockList, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 196 spec := &hcldec.BlockListSpec{ 197 TypeName: impliedName, 198 MinItems: int(block.GetMinItems()), 199 MaxItems: int(block.GetMaxItems()), 200 } 201 202 if n := block.GetName(); n != "" { 203 spec.TypeName = n 204 } 205 206 nested, diags := decodeBlockNestedSpec(block.GetNested()) 207 spec.Nested = nested 208 209 if spec.TypeName == "" { 210 diags = append(diags, &hcl.Diagnostic{ 211 Severity: hcl.DiagError, 212 Summary: "Missing name in block_list spec", 213 Detail: "The name attribute is required, to specify the block type name that is expected in an input HCL file.", 214 }) 215 return nil, diags 216 } 217 218 return spec, diags 219 } 220 221 func decodeBlockSetSpec(block *hclspec.BlockSet, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 222 spec := &hcldec.BlockSetSpec{ 223 TypeName: impliedName, 224 MinItems: int(block.GetMinItems()), 225 MaxItems: int(block.GetMaxItems()), 226 } 227 228 if n := block.GetName(); n != "" { 229 spec.TypeName = n 230 } 231 232 nested, diags := decodeBlockNestedSpec(block.GetNested()) 233 spec.Nested = nested 234 235 if spec.TypeName == "" { 236 diags = append(diags, &hcl.Diagnostic{ 237 Severity: hcl.DiagError, 238 Summary: "Missing name in block_set spec", 239 Detail: "The name attribute is required, to specify the block type name that is expected in an input HCL file.", 240 }) 241 return nil, diags 242 } 243 244 return spec, diags 245 } 246 247 func decodeBlockMapSpec(block *hclspec.BlockMap, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 248 spec := &hcldec.BlockMapSpec{ 249 TypeName: impliedName, 250 LabelNames: block.GetLabels(), 251 } 252 253 if n := block.GetName(); n != "" { 254 spec.TypeName = n 255 } 256 257 nested, diags := decodeBlockNestedSpec(block.GetNested()) 258 spec.Nested = nested 259 260 if spec.TypeName == "" { 261 diags = append(diags, &hcl.Diagnostic{ 262 Severity: hcl.DiagError, 263 Summary: "Missing name in block_map spec", 264 Detail: "The name attribute is required, to specify the block type name that is expected in an input HCL file.", 265 }) 266 return nil, diags 267 } 268 if len(spec.LabelNames) < 1 { 269 diags = append(diags, &hcl.Diagnostic{ 270 Severity: hcl.DiagError, 271 Summary: "Invalid block label name list", 272 Detail: "A block_map must have at least one label specified.", 273 }) 274 return nil, diags 275 } 276 277 return spec, diags 278 } 279 280 func decodeBlockNestedSpec(spec *hclspec.Spec) (hcldec.Spec, hcl.Diagnostics) { 281 if spec == nil { 282 return nil, hcl.Diagnostics([]*hcl.Diagnostic{ 283 { 284 Severity: hcl.DiagError, 285 Summary: "Missing spec block", 286 Detail: "A block spec must have exactly one child spec specifying how to decode block contents.", 287 }}) 288 } 289 290 return decodeSpecBlock(spec, "") 291 } 292 293 func decodeLiteralSpec(l *hclspec.Literal) (hcldec.Spec, hcl.Diagnostics) { 294 // Convert the string value to an hcl.Expression 295 valueExpr, diags := hclsyntax.ParseExpression([]byte(l.GetValue()), "proto", emptyPos) 296 if diags.HasErrors() { 297 return nil, diags 298 } 299 300 value, valueDiags := valueExpr.Value(specCtx) 301 diags = append(diags, valueDiags...) 302 if diags.HasErrors() { 303 return nil, diags 304 } 305 306 return &hcldec.LiteralSpec{ 307 Value: value, 308 }, diags 309 } 310 311 func decodeDefaultSpec(d *hclspec.Default) (hcldec.Spec, hcl.Diagnostics) { 312 // Parse the primary 313 primary, diags := decodeSpecBlock(d.GetPrimary(), "") 314 if diags.HasErrors() { 315 return nil, diags 316 } 317 318 // Parse the default 319 def, defDiags := decodeSpecBlock(d.GetDefault(), "") 320 diags = append(diags, defDiags...) 321 if diags.HasErrors() { 322 return nil, diags 323 } 324 325 spec := &hcldec.DefaultSpec{ 326 Primary: primary, 327 Default: def, 328 } 329 330 return spec, diags 331 }