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