8 "github.com/hashicorp/hcl2/hcl/hclsyntax"
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"
16 func loadModule(dir string) (*Module, Diagnostics) {
18 primaryPaths, diags := dirFiles(dir)
20 parser := hclparse.NewParser()
22 for _, filename := range primaryPaths {
24 var fileDiags hcl.Diagnostics
25 if strings.HasSuffix(filename, ".json") {
26 file, fileDiags = parser.ParseJSONFile(filename)
28 file, fileDiags = parser.ParseHCLFile(filename)
30 diags = append(diags, fileDiags...)
35 content, _, contentDiags := file.Body.PartialContent(rootSchema)
36 diags = append(diags, contentDiags...)
38 for _, block := range content.Blocks {
42 content, _, contentDiags := block.Body.PartialContent(terraformBlockSchema)
43 diags = append(diags, contentDiags...)
45 if attr, defined := content.Attributes["required_version"]; defined {
47 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &version)
48 diags = append(diags, valDiags...)
49 if !valDiags.HasErrors() {
50 mod.RequiredCore = append(mod.RequiredCore, version)
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...)
60 for name, attr := range attrs {
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)
71 content, _, contentDiags := block.Body.PartialContent(variableSchema)
72 diags = append(diags, contentDiags...)
74 name := block.Labels[0]
77 Pos: sourcePosHCL(block.DefRange),
80 mod.Variables[name] = v
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.
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.
96 var typeExprAsStr string
97 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &typeExprAsStr)
98 if !valDiags.HasErrors() {
99 typeExpr = typeExprAsStr
102 rng := attr.Expr.Range()
103 sourceFilename := rng.Filename
104 source, exists := parser.Sources()[sourceFilename]
106 typeExpr = string(rng.SliceBytes(source))
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,
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
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
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())
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))
145 err = json.Unmarshal(valJSON, &def)
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))
157 content, _, contentDiags := block.Body.PartialContent(outputSchema)
158 diags = append(diags, contentDiags...)
160 name := block.Labels[0]
163 Pos: sourcePosHCL(block.DefRange),
166 mod.Outputs[name] = o
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
177 content, _, contentDiags := block.Body.PartialContent(providerConfigSchema)
178 diags = append(diags, contentDiags...)
180 name := block.Labels[0]
182 if attr, defined := content.Attributes["version"]; defined {
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)
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{}
197 case "resource", "data":
199 content, _, contentDiags := block.Body.PartialContent(resourceSchema)
200 diags = append(diags, contentDiags...)
202 typeName := block.Labels[0]
203 name := block.Labels[1]
208 Pos: sourcePosHCL(block.DefRange),
211 var resourcesMap map[string]*Resource
215 r.Mode = ManagedResourceMode
216 resourcesMap = mod.ManagedResources
218 r.Mode = DataResourceMode
219 resourcesMap = mod.DataResources
224 resourcesMap[key] = r
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
234 // Fall back on trying to parse as a 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() {
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()
251 if len(traversal) > 1 {
252 if getAttr, ok := traversal[1].(hcl.TraverseAttr); ok {
256 r.Provider = ProviderRef{
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(),
269 // If provider _isn't_ set then we'll infer it from the
271 r.Provider = ProviderRef{
272 Name: resourceTypeDefaultProviderName(r.Type),
278 content, _, contentDiags := block.Body.PartialContent(moduleCallSchema)
279 diags = append(diags, contentDiags...)
281 name := block.Labels[0]
283 Name: block.Labels[0],
284 Pos: sourcePosHCL(block.DefRange),
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
293 mod.ModuleCalls[name] = mc
295 if attr, defined := content.Attributes["source"]; defined {
297 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &source)
298 diags = append(diags, valDiags...)
303 mc.Source = origSource
306 if attr, defined := content.Attributes["version"]; defined {
308 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &version)
309 diags = append(diags, valDiags...)
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))
321 return mod, diagnosticsHCL(diags)