|
1 | 1 | package tf |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "os" |
| 5 | + "path" |
4 | 6 | "slices" |
| 7 | + "strings" |
5 | 8 |
|
6 | 9 | "github.com/gruntwork-io/terragrunt/internal/errors" |
7 | | - "github.com/hashicorp/terraform-config-inspect/tfconfig" |
| 10 | + "github.com/hashicorp/hcl/v2" |
| 11 | + "github.com/hashicorp/hcl/v2/hclparse" |
8 | 12 | ) |
9 | 13 |
|
10 | 14 | const ( |
@@ -129,49 +133,75 @@ var ( |
129 | 133 | } |
130 | 134 | ) |
131 | 135 |
|
132 | | -// diagnosticDoesNotAffectModuleVariables tells you if a diagnostic can be ignored for the purpose of extracting |
133 | | -// variables defined in a module. |
134 | | -func diagnosticDoesNotAffectModuleVariables(d tfconfig.Diagnostic) bool { |
135 | | - ignorableErrors := []tfconfig.Diagnostic{ |
136 | | - // These two occur when a module block uses a variable in the `version` or `source` fields. |
137 | | - // Terraform doesn't support this, but OpenTofu does. Either way our ability to extract variables is unaffected. |
138 | | - // |
139 | | - // What we really need is an OpenTofu version of terraform-config-inspect. This may work for now but as / if |
140 | | - // syntax continues to diverge we may run into other issues. |
141 | | - {Summary: "Variables not allowed", Detail: "Variables may not be used here."}, |
142 | | - {Summary: "Unsuitable value type", Detail: "Unsuitable value: value must be known"}, |
143 | | - } |
144 | | - if d.Severity != tfconfig.DiagError { |
145 | | - return true |
| 136 | +// ModuleVariables will return all the variables defined in the downloaded terraform modules, taking into |
| 137 | +// account all the generated sources. This function will return the required and optional variables separately. |
| 138 | +func ModuleVariables(modulePath string) ([]string, []string, error) { |
| 139 | + parser := hclparse.NewParser() |
| 140 | + |
| 141 | + files, err := os.ReadDir(modulePath) |
| 142 | + if err != nil { |
| 143 | + return nil, nil, err |
146 | 144 | } |
147 | 145 |
|
148 | | - i := slices.IndexFunc(ignorableErrors, func(ie tfconfig.Diagnostic) bool { |
149 | | - return ie.Summary == d.Summary && ie.Detail == d.Detail |
150 | | - }) |
| 146 | + hclFiles := []*hcl.File{} |
| 147 | + allDiags := hcl.Diagnostics{} |
151 | 148 |
|
152 | | - return i != -1 |
153 | | -} |
| 149 | + for _, file := range files { |
| 150 | + if file.IsDir() { |
| 151 | + continue |
| 152 | + } |
154 | 153 |
|
155 | | -// ModuleVariables will return all the variables defined in the downloaded terraform modules, taking into |
156 | | -// account all the generated sources. This function will return the required and optional variables separately. |
157 | | -func ModuleVariables(modulePath string) ([]string, []string, error) { |
158 | | - module, diags := tfconfig.LoadModule(modulePath) |
| 154 | + parseFunc := parser.ParseHCLFile |
| 155 | + |
| 156 | + suffix := "" |
| 157 | + for s := range strings.SplitSeq(file.Name(), ".") { |
| 158 | + suffix = s |
| 159 | + } |
159 | 160 |
|
160 | | - diags = slices.DeleteFunc(diags, diagnosticDoesNotAffectModuleVariables) |
161 | | - if diags.HasErrors() { |
162 | | - return nil, nil, errors.New(diags) |
| 161 | + if suffix == "json" { |
| 162 | + parseFunc = parser.ParseJSONFile |
| 163 | + } |
| 164 | + |
| 165 | + if !(slices.Contains([]string{"tf", "tofu", "json"}, suffix)) { |
| 166 | + continue |
| 167 | + } |
| 168 | + |
| 169 | + file, parseDiags := parseFunc(path.Join(modulePath, file.Name())) |
| 170 | + |
| 171 | + hclFiles = append(hclFiles, file) |
| 172 | + allDiags = append(allDiags, parseDiags...) |
163 | 173 | } |
164 | 174 |
|
165 | | - required := []string{} |
166 | | - optional := []string{} |
| 175 | + body := hcl.MergeFiles(hclFiles) |
167 | 176 |
|
168 | | - for _, variable := range module.Variables { |
169 | | - if variable.Required { |
170 | | - required = append(required, variable.Name) |
| 177 | + varsSchema := &hcl.BodySchema{ |
| 178 | + Blocks: []hcl.BlockHeaderSchema{ |
| 179 | + { |
| 180 | + Type: "variable", |
| 181 | + LabelNames: []string{"name"}, |
| 182 | + }, |
| 183 | + }, |
| 184 | + } |
| 185 | + |
| 186 | + varsContent, _, contentDiags := body.PartialContent(varsSchema) |
| 187 | + allDiags = append(allDiags, contentDiags...) |
| 188 | + optional, required := []string{}, []string{} |
| 189 | + |
| 190 | + for _, b := range varsContent.Blocks { |
| 191 | + name := b.Labels[0] |
| 192 | + attributes, attrDiags := b.Body.JustAttributes() |
| 193 | + |
| 194 | + allDiags = append(allDiags, attrDiags...) |
| 195 | + if _, ok := attributes["default"]; ok { |
| 196 | + optional = append(optional, name) |
171 | 197 | } else { |
172 | | - optional = append(optional, variable.Name) |
| 198 | + required = append(required, name) |
173 | 199 | } |
174 | 200 | } |
175 | 201 |
|
| 202 | + if allDiags.HasErrors() { |
| 203 | + return nil, nil, errors.New(allDiags) |
| 204 | + } |
| 205 | + |
176 | 206 | return required, optional, nil |
177 | 207 | } |
0 commit comments