7 legacyhcl "github.com/hashicorp/hcl"
8 legacyast "github.com/hashicorp/hcl/hcl/ast"
11 func loadModuleLegacyHCL(dir string) (*Module, Diagnostics) {
12 // This implementation is intentionally more quick-and-dirty than the
13 // main loader. In particular, it doesn't bother to keep careful track
14 // of multiple error messages because we always fall back on returning
15 // the main parser's error message if our fallback parsing produces
16 // an error, and thus the errors here are not seen by the end-caller.
19 primaryPaths, diags := dirFiles(dir)
20 if diags.HasErrors() {
21 return mod, diagnosticsHCL(diags)
24 for _, filename := range primaryPaths {
25 src, err := ioutil.ReadFile(filename)
27 return mod, diagnosticsErrorf("Error reading %s: %s", filename, err)
30 hclRoot, err := legacyhcl.Parse(string(src))
32 return mod, diagnosticsErrorf("Error parsing %s: %s", filename, err)
35 list, ok := hclRoot.Node.(*legacyast.ObjectList)
37 return mod, diagnosticsErrorf("Error parsing %s: no root object", filename)
40 for _, item := range list.Filter("terraform").Items {
41 if len(item.Keys) > 0 {
42 item = &legacyast.ObjectItem{
43 Val: &legacyast.ObjectType{
44 List: &legacyast.ObjectList{
45 Items: []*legacyast.ObjectItem{item},
51 type TerraformBlock struct {
52 RequiredVersion string `hcl:"required_version"`
54 var block TerraformBlock
55 err = legacyhcl.DecodeObject(&block, item.Val)
57 return nil, diagnosticsErrorf("terraform block: %s", err)
60 if block.RequiredVersion != "" {
61 mod.RequiredCore = append(mod.RequiredCore, block.RequiredVersion)
65 if vars := list.Filter("variable"); len(vars.Items) > 0 {
66 vars = vars.Children()
67 type VariableBlock struct {
68 Type string `hcl:"type"`
71 Fields []string `hcl:",decodedFields"`
74 for _, item := range vars.Items {
75 unwrapLegacyHCLObjectKeysFromJSON(item, 1)
77 if len(item.Keys) != 1 {
78 return nil, diagnosticsErrorf("variable block at %s has no label", item.Pos())
81 name := item.Keys[0].Token.Value().(string)
83 var block VariableBlock
84 err := legacyhcl.DecodeObject(&block, item.Val)
86 return nil, diagnosticsErrorf("invalid variable block at %s: %s", item.Pos(), err)
89 // Clean up legacy HCL decoding ambiguity by unwrapping list of maps
90 if ms, ok := block.Default.([]map[string]interface{}); ok {
91 def := make(map[string]interface{})
92 for _, m := range ms {
103 Description: block.Description,
104 Default: block.Default,
105 Pos: sourcePosLegacyHCL(item.Pos(), filename),
107 if _, exists := mod.Variables[name]; exists {
108 return nil, diagnosticsErrorf("duplicate variable block for %q", name)
110 mod.Variables[name] = v
115 if outputs := list.Filter("output"); len(outputs.Items) > 0 {
116 outputs = outputs.Children()
117 type OutputBlock struct {
121 for _, item := range outputs.Items {
122 unwrapLegacyHCLObjectKeysFromJSON(item, 1)
124 if len(item.Keys) != 1 {
125 return nil, diagnosticsErrorf("output block at %s has no label", item.Pos())
128 name := item.Keys[0].Token.Value().(string)
130 var block OutputBlock
131 err := legacyhcl.DecodeObject(&block, item.Val)
133 return nil, diagnosticsErrorf("invalid output block at %s: %s", item.Pos(), err)
138 Description: block.Description,
139 Pos: sourcePosLegacyHCL(item.Pos(), filename),
141 if _, exists := mod.Outputs[name]; exists {
142 return nil, diagnosticsErrorf("duplicate output block for %q", name)
144 mod.Outputs[name] = o
148 for _, blockType := range []string{"resource", "data"} {
149 if resources := list.Filter(blockType); len(resources.Items) > 0 {
150 resources = resources.Children()
151 type ResourceBlock struct {
155 for _, item := range resources.Items {
156 unwrapLegacyHCLObjectKeysFromJSON(item, 2)
158 if len(item.Keys) != 2 {
159 return nil, diagnosticsErrorf("resource block at %s has wrong label count", item.Pos())
162 typeName := item.Keys[0].Token.Value().(string)
163 name := item.Keys[1].Token.Value().(string)
164 var mode ResourceMode
165 var rMap map[string]*Resource
168 mode = ManagedResourceMode
169 rMap = mod.ManagedResources
171 mode = DataResourceMode
172 rMap = mod.DataResources
175 var block ResourceBlock
176 err := legacyhcl.DecodeObject(&block, item.Val)
178 return nil, diagnosticsErrorf("invalid resource block at %s: %s", item.Pos(), err)
181 var providerName, providerAlias string
182 if dotPos := strings.IndexByte(block.Provider, '.'); dotPos != -1 {
183 providerName = block.Provider[:dotPos]
184 providerAlias = block.Provider[dotPos+1:]
186 providerName = block.Provider
188 if providerName == "" {
189 providerName = resourceTypeDefaultProviderName(typeName)
196 Provider: ProviderRef{
198 Alias: providerAlias,
200 Pos: sourcePosLegacyHCL(item.Pos(), filename),
203 if _, exists := rMap[key]; exists {
204 return nil, diagnosticsErrorf("duplicate resource block for %q", key)
212 if moduleCalls := list.Filter("module"); len(moduleCalls.Items) > 0 {
213 moduleCalls = moduleCalls.Children()
214 type ModuleBlock struct {
219 for _, item := range moduleCalls.Items {
220 unwrapLegacyHCLObjectKeysFromJSON(item, 1)
222 if len(item.Keys) != 1 {
223 return nil, diagnosticsErrorf("module block at %s has no label", item.Pos())
226 name := item.Keys[0].Token.Value().(string)
228 var block ModuleBlock
229 err := legacyhcl.DecodeObject(&block, item.Val)
231 return nil, diagnosticsErrorf("module block at %s: %s", item.Pos(), err)
236 Source: block.Source,
237 Version: block.Version,
238 Pos: sourcePosLegacyHCL(item.Pos(), filename),
240 // it's possible this module call is from an override file
241 if origMod, exists := mod.ModuleCalls[name]; exists {
243 mc.Source = origMod.Source
246 mod.ModuleCalls[name] = mc
250 if providerConfigs := list.Filter("provider"); len(providerConfigs.Items) > 0 {
251 providerConfigs = providerConfigs.Children()
252 type ProviderBlock struct {
256 for _, item := range providerConfigs.Items {
257 unwrapLegacyHCLObjectKeysFromJSON(item, 1)
259 if len(item.Keys) != 1 {
260 return nil, diagnosticsErrorf("provider block at %s has no label", item.Pos())
263 name := item.Keys[0].Token.Value().(string)
265 var block ProviderBlock
266 err := legacyhcl.DecodeObject(&block, item.Val)
268 return nil, diagnosticsErrorf("invalid provider block at %s: %s", item.Pos(), err)
271 if block.Version != "" {
272 mod.RequiredProviders[name] = append(mod.RequiredProviders[name], block.Version)
275 // Even if there wasn't an explicit version required, we still
276 // need an entry in our map to signal the unversioned dependency.
277 if _, exists := mod.RequiredProviders[name]; !exists {
278 mod.RequiredProviders[name] = []string{}
288 // unwrapLegacyHCLObjectKeysFromJSON cleans up an edge case that can occur when
289 // parsing JSON as input: if we're parsing JSON then directly nested
290 // items will show up as additional "keys".
292 // For objects that expect a fixed number of keys, this breaks the
293 // decoding process. This function unwraps the object into what it would've
294 // looked like if it came directly from HCL by specifying the number of keys
299 // { "foo": { "baz": {} } }
301 // Will show up with Keys being: []string{"foo", "baz"}
302 // when we really just want the first two. This function will fix this.
303 func unwrapLegacyHCLObjectKeysFromJSON(item *legacyast.ObjectItem, depth int) {
304 if len(item.Keys) > depth && item.Keys[0].Token.JSON {
305 for len(item.Keys) > depth {
306 // Pop off the last key
308 key := item.Keys[n-1]
310 item.Keys = item.Keys[:n-1]
312 // Wrap our value in a list
313 item.Val = &legacyast.ObjectType{
314 List: &legacyast.ObjectList{
315 Items: []*legacyast.ObjectItem{
316 &legacyast.ObjectItem{
317 Keys: []*legacyast.ObjectKey{key},