github.com/sfdevops1/terrra4orm@v0.11.12-beta1/configs/named_values.go (about) 1 package configs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl2/ext/typeexpr" 7 "github.com/hashicorp/hcl2/gohcl" 8 "github.com/hashicorp/hcl2/hcl" 9 "github.com/hashicorp/hcl2/hcl/hclsyntax" 10 "github.com/zclconf/go-cty/cty" 11 "github.com/zclconf/go-cty/cty/convert" 12 ) 13 14 // A consistent detail message for all "not a valid identifier" diagnostics. 15 const badIdentifierDetail = "A name must start with a letter and may contain only letters, digits, underscores, and dashes." 16 17 // Variable represents a "variable" block in a module or file. 18 type Variable struct { 19 Name string 20 Description string 21 Default cty.Value 22 Type cty.Type 23 ParsingMode VariableParsingMode 24 25 DescriptionSet bool 26 27 DeclRange hcl.Range 28 } 29 30 func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagnostics) { 31 v := &Variable{ 32 Name: block.Labels[0], 33 DeclRange: block.DefRange, 34 } 35 36 // Unless we're building an override, we'll set some defaults 37 // which we might override with attributes below. We leave these 38 // as zero-value in the override case so we can recognize whether 39 // or not they are set when we merge. 40 if !override { 41 v.Type = cty.DynamicPseudoType 42 v.ParsingMode = VariableParseLiteral 43 } 44 45 content, diags := block.Body.Content(variableBlockSchema) 46 47 if !hclsyntax.ValidIdentifier(v.Name) { 48 diags = append(diags, &hcl.Diagnostic{ 49 Severity: hcl.DiagError, 50 Summary: "Invalid variable name", 51 Detail: badIdentifierDetail, 52 Subject: &block.LabelRanges[0], 53 }) 54 } 55 56 // Don't allow declaration of variables that would conflict with the 57 // reserved attribute and block type names in a "module" block, since 58 // these won't be usable for child modules. 59 for _, attr := range moduleBlockSchema.Attributes { 60 if attr.Name == v.Name { 61 diags = append(diags, &hcl.Diagnostic{ 62 Severity: hcl.DiagError, 63 Summary: "Invalid variable name", 64 Detail: fmt.Sprintf("The variable name %q is reserved due to its special meaning inside module blocks.", attr.Name), 65 Subject: &block.LabelRanges[0], 66 }) 67 } 68 } 69 70 if attr, exists := content.Attributes["description"]; exists { 71 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Description) 72 diags = append(diags, valDiags...) 73 v.DescriptionSet = true 74 } 75 76 if attr, exists := content.Attributes["type"]; exists { 77 ty, parseMode, tyDiags := decodeVariableType(attr.Expr) 78 diags = append(diags, tyDiags...) 79 v.Type = ty 80 v.ParsingMode = parseMode 81 } 82 83 if attr, exists := content.Attributes["default"]; exists { 84 val, valDiags := attr.Expr.Value(nil) 85 diags = append(diags, valDiags...) 86 87 // Convert the default to the expected type so we can catch invalid 88 // defaults early and allow later code to assume validity. 89 // Note that this depends on us having already processed any "type" 90 // attribute above. 91 // However, we can't do this if we're in an override file where 92 // the type might not be set; we'll catch that during merge. 93 if v.Type != cty.NilType { 94 var err error 95 val, err = convert.Convert(val, v.Type) 96 if err != nil { 97 diags = append(diags, &hcl.Diagnostic{ 98 Severity: hcl.DiagError, 99 Summary: "Invalid default value for variable", 100 Detail: fmt.Sprintf("This default value is not compatible with the variable's type constraint: %s.", err), 101 Subject: attr.Expr.Range().Ptr(), 102 }) 103 val = cty.DynamicVal 104 } 105 } 106 107 v.Default = val 108 } 109 110 return v, diags 111 } 112 113 func decodeVariableType(expr hcl.Expression) (cty.Type, VariableParsingMode, hcl.Diagnostics) { 114 if exprIsNativeQuotedString(expr) { 115 // Here we're accepting the pre-0.12 form of variable type argument where 116 // the string values "string", "list" and "map" are accepted has a hint 117 // about the type used primarily for deciding how to parse values 118 // given on the command line and in environment variables. 119 // Only the native syntax ends up in this codepath; we handle the 120 // JSON syntax (which is, of course, quoted even in the new format) 121 // in the normal codepath below. 122 val, diags := expr.Value(nil) 123 if diags.HasErrors() { 124 return cty.DynamicPseudoType, VariableParseHCL, diags 125 } 126 str := val.AsString() 127 switch str { 128 case "string": 129 return cty.String, VariableParseLiteral, diags 130 case "list": 131 return cty.List(cty.DynamicPseudoType), VariableParseHCL, diags 132 case "map": 133 return cty.Map(cty.DynamicPseudoType), VariableParseHCL, diags 134 default: 135 return cty.DynamicPseudoType, VariableParseHCL, hcl.Diagnostics{{ 136 Severity: hcl.DiagError, 137 Summary: "Invalid legacy variable type hint", 138 Detail: `The legacy variable type hint form, using a quoted string, allows only the values "string", "list", and "map". To provide a full type expression, remove the surrounding quotes and give the type expression directly.`, 139 Subject: expr.Range().Ptr(), 140 }} 141 } 142 } 143 144 // First we'll deal with some shorthand forms that the HCL-level type 145 // expression parser doesn't include. These both emulate pre-0.12 behavior 146 // of allowing a list or map of any element type as long as all of the 147 // elements are consistent. This is the same as list(any) or map(any). 148 switch hcl.ExprAsKeyword(expr) { 149 case "list": 150 return cty.List(cty.DynamicPseudoType), VariableParseHCL, nil 151 case "map": 152 return cty.Map(cty.DynamicPseudoType), VariableParseHCL, nil 153 } 154 155 ty, diags := typeexpr.TypeConstraint(expr) 156 if diags.HasErrors() { 157 return cty.DynamicPseudoType, VariableParseHCL, diags 158 } 159 160 switch { 161 case ty.IsPrimitiveType(): 162 // Primitive types use literal parsing. 163 return ty, VariableParseLiteral, diags 164 default: 165 // Everything else uses HCL parsing 166 return ty, VariableParseHCL, diags 167 } 168 } 169 170 // VariableParsingMode defines how values of a particular variable given by 171 // text-only mechanisms (command line arguments and environment variables) 172 // should be parsed to produce the final value. 173 type VariableParsingMode rune 174 175 // VariableParseLiteral is a variable parsing mode that just takes the given 176 // string directly as a cty.String value. 177 const VariableParseLiteral VariableParsingMode = 'L' 178 179 // VariableParseHCL is a variable parsing mode that attempts to parse the given 180 // string as an HCL expression and returns the result. 181 const VariableParseHCL VariableParsingMode = 'H' 182 183 // Parse uses the receiving parsing mode to process the given variable value 184 // string, returning the result along with any diagnostics. 185 // 186 // A VariableParsingMode does not know the expected type of the corresponding 187 // variable, so it's the caller's responsibility to attempt to convert the 188 // result to the appropriate type and return to the user any diagnostics that 189 // conversion may produce. 190 // 191 // The given name is used to create a synthetic filename in case any diagnostics 192 // must be generated about the given string value. This should be the name 193 // of the root module variable whose value will be populated from the given 194 // string. 195 // 196 // If the returned diagnostics has errors, the returned value may not be 197 // valid. 198 func (m VariableParsingMode) Parse(name, value string) (cty.Value, hcl.Diagnostics) { 199 switch m { 200 case VariableParseLiteral: 201 return cty.StringVal(value), nil 202 case VariableParseHCL: 203 fakeFilename := fmt.Sprintf("<value for var.%s>", name) 204 expr, diags := hclsyntax.ParseExpression([]byte(value), fakeFilename, hcl.Pos{Line: 1, Column: 1}) 205 if diags.HasErrors() { 206 return cty.DynamicVal, diags 207 } 208 val, valDiags := expr.Value(nil) 209 diags = append(diags, valDiags...) 210 return val, diags 211 default: 212 // Should never happen 213 panic(fmt.Errorf("Parse called on invalid VariableParsingMode %#v", m)) 214 } 215 } 216 217 // Output represents an "output" block in a module or file. 218 type Output struct { 219 Name string 220 Description string 221 Expr hcl.Expression 222 DependsOn []hcl.Traversal 223 Sensitive bool 224 225 DescriptionSet bool 226 SensitiveSet bool 227 228 DeclRange hcl.Range 229 } 230 231 func decodeOutputBlock(block *hcl.Block, override bool) (*Output, hcl.Diagnostics) { 232 o := &Output{ 233 Name: block.Labels[0], 234 DeclRange: block.DefRange, 235 } 236 237 schema := outputBlockSchema 238 if override { 239 schema = schemaForOverrides(schema) 240 } 241 242 content, diags := block.Body.Content(schema) 243 244 if !hclsyntax.ValidIdentifier(o.Name) { 245 diags = append(diags, &hcl.Diagnostic{ 246 Severity: hcl.DiagError, 247 Summary: "Invalid output name", 248 Detail: badIdentifierDetail, 249 Subject: &block.LabelRanges[0], 250 }) 251 } 252 253 if attr, exists := content.Attributes["description"]; exists { 254 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Description) 255 diags = append(diags, valDiags...) 256 o.DescriptionSet = true 257 } 258 259 if attr, exists := content.Attributes["value"]; exists { 260 o.Expr = attr.Expr 261 } 262 263 if attr, exists := content.Attributes["sensitive"]; exists { 264 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Sensitive) 265 diags = append(diags, valDiags...) 266 o.SensitiveSet = true 267 } 268 269 if attr, exists := content.Attributes["depends_on"]; exists { 270 deps, depsDiags := decodeDependsOn(attr) 271 diags = append(diags, depsDiags...) 272 o.DependsOn = append(o.DependsOn, deps...) 273 } 274 275 return o, diags 276 } 277 278 // Local represents a single entry from a "locals" block in a module or file. 279 // The "locals" block itself is not represented, because it serves only to 280 // provide context for us to interpret its contents. 281 type Local struct { 282 Name string 283 Expr hcl.Expression 284 285 DeclRange hcl.Range 286 } 287 288 func decodeLocalsBlock(block *hcl.Block) ([]*Local, hcl.Diagnostics) { 289 attrs, diags := block.Body.JustAttributes() 290 if len(attrs) == 0 { 291 return nil, diags 292 } 293 294 locals := make([]*Local, 0, len(attrs)) 295 for name, attr := range attrs { 296 if !hclsyntax.ValidIdentifier(name) { 297 diags = append(diags, &hcl.Diagnostic{ 298 Severity: hcl.DiagError, 299 Summary: "Invalid local value name", 300 Detail: badIdentifierDetail, 301 Subject: &attr.NameRange, 302 }) 303 } 304 305 locals = append(locals, &Local{ 306 Name: name, 307 Expr: attr.Expr, 308 DeclRange: attr.Range, 309 }) 310 } 311 return locals, diags 312 } 313 314 var variableBlockSchema = &hcl.BodySchema{ 315 Attributes: []hcl.AttributeSchema{ 316 { 317 Name: "description", 318 }, 319 { 320 Name: "default", 321 }, 322 { 323 Name: "type", 324 }, 325 }, 326 } 327 328 var outputBlockSchema = &hcl.BodySchema{ 329 Attributes: []hcl.AttributeSchema{ 330 { 331 Name: "description", 332 }, 333 { 334 Name: "value", 335 Required: true, 336 }, 337 { 338 Name: "depends_on", 339 }, 340 { 341 Name: "sensitive", 342 }, 343 }, 344 }