]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load_hcl.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform-config-inspect / tfconfig / load_hcl.go
1 package tfconfig
2
3 import (
4 "encoding/json"
5 "fmt"
6 "strings"
7
8 "github.com/hashicorp/hcl2/hcl/hclsyntax"
9
10 "github.com/hashicorp/hcl2/gohcl"
11 "github.com/hashicorp/hcl2/hcl"
12 "github.com/hashicorp/hcl2/hclparse"
13 ctyjson "github.com/zclconf/go-cty/cty/json"
14 )
15
16 func loadModule(dir string) (*Module, Diagnostics) {
17 mod := newModule(dir)
18 primaryPaths, diags := dirFiles(dir)
19
20 parser := hclparse.NewParser()
21
22 for _, filename := range primaryPaths {
23 var file *hcl.File
24 var fileDiags hcl.Diagnostics
25 if strings.HasSuffix(filename, ".json") {
26 file, fileDiags = parser.ParseJSONFile(filename)
27 } else {
28 file, fileDiags = parser.ParseHCLFile(filename)
29 }
30 diags = append(diags, fileDiags...)
31 if file == nil {
32 continue
33 }
34
35 content, _, contentDiags := file.Body.PartialContent(rootSchema)
36 diags = append(diags, contentDiags...)
37
38 for _, block := range content.Blocks {
39 switch block.Type {
40
41 case "terraform":
42 content, _, contentDiags := block.Body.PartialContent(terraformBlockSchema)
43 diags = append(diags, contentDiags...)
44
45 if attr, defined := content.Attributes["required_version"]; defined {
46 var version string
47 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &version)
48 diags = append(diags, valDiags...)
49 if !valDiags.HasErrors() {
50 mod.RequiredCore = append(mod.RequiredCore, version)
51 }
52 }
53
54 for _, block := range content.Blocks {
55 // Our schema only allows required_providers here, so we
56 // assume that we'll only get that block type.
57 attrs, attrDiags := block.Body.JustAttributes()
58 diags = append(diags, attrDiags...)
59
60 for name, attr := range attrs {
61 var version string
62 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &version)
63 diags = append(diags, valDiags...)
64 if !valDiags.HasErrors() {
65 mod.RequiredProviders[name] = append(mod.RequiredProviders[name], version)
66 }
67 }
68 }
69
70 case "variable":
71 content, _, contentDiags := block.Body.PartialContent(variableSchema)
72 diags = append(diags, contentDiags...)
73
74 name := block.Labels[0]
75 v := &Variable{
76 Name: name,
77 Pos: sourcePosHCL(block.DefRange),
78 }
79
80 mod.Variables[name] = v
81
82 if attr, defined := content.Attributes["type"]; defined {
83 // We handle this particular attribute in a somewhat-tricky way:
84 // since Terraform may evolve its type expression syntax in
85 // future versions, we don't want to be overly-strict in how
86 // we handle it here, and so we'll instead just take the raw
87 // source provided by the user, using the source location
88 // information in the expression object.
89 //
90 // However, older versions of Terraform expected the type
91 // to be a string containing a keyword, so we'll need to
92 // handle that as a special case first for backward compatibility.
93
94 var typeExpr string
95
96 var typeExprAsStr string
97 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &typeExprAsStr)
98 if !valDiags.HasErrors() {
99 typeExpr = typeExprAsStr
100 } else {
101
102 rng := attr.Expr.Range()
103 sourceFilename := rng.Filename
104 source, exists := parser.Sources()[sourceFilename]
105 if exists {
106 typeExpr = string(rng.SliceBytes(source))
107 } else {
108 // This should never happen, so we'll just warn about it and leave the type unspecified.
109 diags = append(diags, &hcl.Diagnostic{
110 Severity: hcl.DiagError,
111 Summary: "Source code not available",
112 Detail: fmt.Sprintf("Source code is not available for the file %q, which declares the variable %q.", sourceFilename, name),
113 Subject: &block.DefRange,
114 })
115 typeExpr = ""
116 }
117
118 }
119
120 v.Type = typeExpr
121 }
122
123 if attr, defined := content.Attributes["description"]; defined {
124 var description string
125 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &description)
126 diags = append(diags, valDiags...)
127 v.Description = description
128 }
129
130 if attr, defined := content.Attributes["default"]; defined {
131 // To avoid the caller needing to deal with cty here, we'll
132 // use its JSON encoding to convert into an
133 // approximately-equivalent plain Go interface{} value
134 // to return.
135 val, valDiags := attr.Expr.Value(nil)
136 diags = append(diags, valDiags...)
137 if val.IsWhollyKnown() { // should only be false if there are errors in the input
138 valJSON, err := ctyjson.Marshal(val, val.Type())
139 if err != nil {
140 // Should never happen, since all possible known
141 // values have a JSON mapping.
142 panic(fmt.Errorf("failed to serialize default value as JSON: %s", err))
143 }
144 var def interface{}
145 err = json.Unmarshal(valJSON, &def)
146 if err != nil {
147 // Again should never happen, because valJSON is
148 // guaranteed valid by ctyjson.Marshal.
149 panic(fmt.Errorf("failed to re-parse default value from JSON: %s", err))
150 }
151 v.Default = def
152 }
153 }
154
155 case "output":
156
157 content, _, contentDiags := block.Body.PartialContent(outputSchema)
158 diags = append(diags, contentDiags...)
159
160 name := block.Labels[0]
161 o := &Output{
162 Name: name,
163 Pos: sourcePosHCL(block.DefRange),
164 }
165
166 mod.Outputs[name] = o
167
168 if attr, defined := content.Attributes["description"]; defined {
169 var description string
170 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &description)
171 diags = append(diags, valDiags...)
172 o.Description = description
173 }
174
175 case "provider":
176
177 content, _, contentDiags := block.Body.PartialContent(providerConfigSchema)
178 diags = append(diags, contentDiags...)
179
180 name := block.Labels[0]
181
182 if attr, defined := content.Attributes["version"]; defined {
183 var version string
184 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &version)
185 diags = append(diags, valDiags...)
186 if !valDiags.HasErrors() {
187 mod.RequiredProviders[name] = append(mod.RequiredProviders[name], version)
188 }
189 }
190
191 // Even if there wasn't an explicit version required, we still
192 // need an entry in our map to signal the unversioned dependency.
193 if _, exists := mod.RequiredProviders[name]; !exists {
194 mod.RequiredProviders[name] = []string{}
195 }
196
197 case "resource", "data":
198
199 content, _, contentDiags := block.Body.PartialContent(resourceSchema)
200 diags = append(diags, contentDiags...)
201
202 typeName := block.Labels[0]
203 name := block.Labels[1]
204
205 r := &Resource{
206 Type: typeName,
207 Name: name,
208 Pos: sourcePosHCL(block.DefRange),
209 }
210
211 var resourcesMap map[string]*Resource
212
213 switch block.Type {
214 case "resource":
215 r.Mode = ManagedResourceMode
216 resourcesMap = mod.ManagedResources
217 case "data":
218 r.Mode = DataResourceMode
219 resourcesMap = mod.DataResources
220 }
221
222 key := r.MapKey()
223
224 resourcesMap[key] = r
225
226 if attr, defined := content.Attributes["provider"]; defined {
227 // New style here is to provide this as a naked traversal
228 // expression, but we also support quoted references for
229 // older configurations that predated this convention.
230 traversal, travDiags := hcl.AbsTraversalForExpr(attr.Expr)
231 if travDiags.HasErrors() {
232 traversal = nil // in case we got any partial results
233
234 // Fall back on trying to parse as a string
235 var travStr string
236 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &travStr)
237 if !valDiags.HasErrors() {
238 var strDiags hcl.Diagnostics
239 traversal, strDiags = hclsyntax.ParseTraversalAbs([]byte(travStr), "", hcl.Pos{})
240 if strDiags.HasErrors() {
241 traversal = nil
242 }
243 }
244 }
245
246 // If we get out here with a nil traversal then we didn't
247 // succeed in processing the input.
248 if len(traversal) > 0 {
249 providerName := traversal.RootName()
250 alias := ""
251 if len(traversal) > 1 {
252 if getAttr, ok := traversal[1].(hcl.TraverseAttr); ok {
253 alias = getAttr.Name
254 }
255 }
256 r.Provider = ProviderRef{
257 Name: providerName,
258 Alias: alias,
259 }
260 } else {
261 diags = append(diags, &hcl.Diagnostic{
262 Severity: hcl.DiagError,
263 Summary: "Invalid provider reference",
264 Detail: "Provider argument requires a provider name followed by an optional alias, like \"aws.foo\".",
265 Subject: attr.Expr.Range().Ptr(),
266 })
267 }
268 } else {
269 // If provider _isn't_ set then we'll infer it from the
270 // resource type.
271 r.Provider = ProviderRef{
272 Name: resourceTypeDefaultProviderName(r.Type),
273 }
274 }
275
276 case "module":
277
278 content, _, contentDiags := block.Body.PartialContent(moduleCallSchema)
279 diags = append(diags, contentDiags...)
280
281 name := block.Labels[0]
282 mc := &ModuleCall{
283 Name: block.Labels[0],
284 Pos: sourcePosHCL(block.DefRange),
285 }
286
287 // check if this is overriding an existing module
288 var origSource string
289 if origMod, exists := mod.ModuleCalls[name]; exists {
290 origSource = origMod.Source
291 }
292
293 mod.ModuleCalls[name] = mc
294
295 if attr, defined := content.Attributes["source"]; defined {
296 var source string
297 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &source)
298 diags = append(diags, valDiags...)
299 mc.Source = source
300 }
301
302 if mc.Source == "" {
303 mc.Source = origSource
304 }
305
306 if attr, defined := content.Attributes["version"]; defined {
307 var version string
308 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &version)
309 diags = append(diags, valDiags...)
310 mc.Version = version
311 }
312
313 default:
314 // Should never happen because our cases above should be
315 // exhaustive for our schema.
316 panic(fmt.Errorf("unhandled block type %q", block.Type))
317 }
318 }
319 }
320
321 return mod, diagnosticsHCL(diags)
322 }