diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load.go | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load.go b/vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load.go new file mode 100644 index 0000000..2d13fe1 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load.go | |||
@@ -0,0 +1,130 @@ | |||
1 | package tfconfig | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "io/ioutil" | ||
6 | "path/filepath" | ||
7 | "strings" | ||
8 | |||
9 | "github.com/hashicorp/hcl2/hcl" | ||
10 | ) | ||
11 | |||
12 | // LoadModule reads the directory at the given path and attempts to interpret | ||
13 | // it as a Terraform module. | ||
14 | func LoadModule(dir string) (*Module, Diagnostics) { | ||
15 | |||
16 | // For broad compatibility here we actually have two separate loader | ||
17 | // codepaths. The main one uses the new HCL parser and API and is intended | ||
18 | // for configurations from Terraform 0.12 onwards (though will work for | ||
19 | // many older configurations too), but we'll also fall back on one that | ||
20 | // uses the _old_ HCL implementation so we can deal with some edge-cases | ||
21 | // that are not valid in new HCL. | ||
22 | |||
23 | module, diags := loadModule(dir) | ||
24 | if diags.HasErrors() { | ||
25 | // Try using the legacy HCL parser and see if we fare better. | ||
26 | legacyModule, legacyDiags := loadModuleLegacyHCL(dir) | ||
27 | if !legacyDiags.HasErrors() { | ||
28 | legacyModule.init(legacyDiags) | ||
29 | return legacyModule, legacyDiags | ||
30 | } | ||
31 | } | ||
32 | |||
33 | module.init(diags) | ||
34 | return module, diags | ||
35 | } | ||
36 | |||
37 | // IsModuleDir checks if the given path contains terraform configuration files. | ||
38 | // This allows the caller to decide how to handle directories that do not have tf files. | ||
39 | func IsModuleDir(dir string) bool { | ||
40 | primaryPaths, _ := dirFiles(dir) | ||
41 | if len(primaryPaths) == 0 { | ||
42 | return false | ||
43 | } | ||
44 | return true | ||
45 | } | ||
46 | |||
47 | func (m *Module) init(diags Diagnostics) { | ||
48 | // Fill in any additional provider requirements that are implied by | ||
49 | // resource configurations, to avoid the caller from needing to apply | ||
50 | // this logic itself. Implied requirements don't have version constraints, | ||
51 | // but we'll make sure the requirement value is still non-nil in this | ||
52 | // case so callers can easily recognize it. | ||
53 | for _, r := range m.ManagedResources { | ||
54 | if _, exists := m.RequiredProviders[r.Provider.Name]; !exists { | ||
55 | m.RequiredProviders[r.Provider.Name] = []string{} | ||
56 | } | ||
57 | } | ||
58 | for _, r := range m.DataResources { | ||
59 | if _, exists := m.RequiredProviders[r.Provider.Name]; !exists { | ||
60 | m.RequiredProviders[r.Provider.Name] = []string{} | ||
61 | } | ||
62 | } | ||
63 | |||
64 | // We redundantly also reference the diagnostics from inside the module | ||
65 | // object, primarily so that we can easily included in JSON-serialized | ||
66 | // versions of the module object. | ||
67 | m.Diagnostics = diags | ||
68 | } | ||
69 | |||
70 | func dirFiles(dir string) (primary []string, diags hcl.Diagnostics) { | ||
71 | infos, err := ioutil.ReadDir(dir) | ||
72 | if err != nil { | ||
73 | diags = append(diags, &hcl.Diagnostic{ | ||
74 | Severity: hcl.DiagError, | ||
75 | Summary: "Failed to read module directory", | ||
76 | Detail: fmt.Sprintf("Module directory %s does not exist or cannot be read.", dir), | ||
77 | }) | ||
78 | return | ||
79 | } | ||
80 | |||
81 | var override []string | ||
82 | for _, info := range infos { | ||
83 | if info.IsDir() { | ||
84 | // We only care about files | ||
85 | continue | ||
86 | } | ||
87 | |||
88 | name := info.Name() | ||
89 | ext := fileExt(name) | ||
90 | if ext == "" || isIgnoredFile(name) { | ||
91 | continue | ||
92 | } | ||
93 | |||
94 | baseName := name[:len(name)-len(ext)] // strip extension | ||
95 | isOverride := baseName == "override" || strings.HasSuffix(baseName, "_override") | ||
96 | |||
97 | fullPath := filepath.Join(dir, name) | ||
98 | if isOverride { | ||
99 | override = append(override, fullPath) | ||
100 | } else { | ||
101 | primary = append(primary, fullPath) | ||
102 | } | ||
103 | } | ||
104 | |||
105 | // We are assuming that any _override files will be logically named, | ||
106 | // and processing the files in alphabetical order. Primaries first, then overrides. | ||
107 | primary = append(primary, override...) | ||
108 | |||
109 | return | ||
110 | } | ||
111 | |||
112 | // fileExt returns the Terraform configuration extension of the given | ||
113 | // path, or a blank string if it is not a recognized extension. | ||
114 | func fileExt(path string) string { | ||
115 | if strings.HasSuffix(path, ".tf") { | ||
116 | return ".tf" | ||
117 | } else if strings.HasSuffix(path, ".tf.json") { | ||
118 | return ".tf.json" | ||
119 | } else { | ||
120 | return "" | ||
121 | } | ||
122 | } | ||
123 | |||
124 | // isIgnoredFile returns true if the given filename (which must not have a | ||
125 | // directory path ahead of it) should be ignored as e.g. an editor swap file. | ||
126 | func isIgnoredFile(name string) bool { | ||
127 | return strings.HasPrefix(name, ".") || // Unix-like hidden files | ||
128 | strings.HasSuffix(name, "~") || // vim | ||
129 | strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#") // emacs | ||
130 | } | ||