Skip to content

Commit 81f352f

Browse files
committed
Do our own 'parsing' of files the real parser couldn't handle
1 parent b24399c commit 81f352f

File tree

2 files changed

+101
-1
lines changed

2 files changed

+101
-1
lines changed
+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""
2+
Fallback parsing for hcl files
3+
4+
We only need limited information from Terraform Modules:
5+
- The required_version constraint
6+
- The backend type
7+
- A list of sensitive variable names
8+
- The backend configuration for remote backends and cloud blocks
9+
10+
The easiest way to get this information is to parse the HCL files directly.
11+
This doesn't always work if our parser fails, or the files are malformed.
12+
13+
This fallback 'parser' does the stupidest thing that might work to get the information we need.
14+
15+
TODO: The backend configuration is not yet implemented.
16+
"""
17+
18+
import re
19+
from pathlib import Path
20+
from typing import Optional
21+
22+
from github_actions.debug import debug
23+
24+
25+
def get_required_version(body: str) -> Optional[str]:
26+
"""Get the required_version constraint string from a tf file"""
27+
28+
if version := re.search(r'required_version\s*=\s*"(.+)"', body):
29+
return version.group(1)
30+
31+
def get_backend_type(body: str) -> Optional[str]:
32+
"""Get the backend type from a tf file"""
33+
34+
if backend := re.search(r'backend\s*"(.+)"', body):
35+
return backend.group(1)
36+
37+
if backend := re.search(r'backend\s+(.*)\s*{', body):
38+
return backend.group(1).strip()
39+
40+
if re.search(r'cloud\s+\{', body):
41+
return 'cloud'
42+
43+
def get_sensitive_variables(body: str) -> list[str]:
44+
"""Get the sensitive variable names from a tf file"""
45+
46+
variables = []
47+
48+
found = False
49+
50+
for line in reversed(body.splitlines()):
51+
if re.search(r'sensitive\s*=\s*true', line, re.IGNORECASE) or re.search(r'sensitive\s*=\s*"true"', line, re.IGNORECASE):
52+
found = True
53+
continue
54+
55+
if found and (variable := re.search(r'variable\s*"(.+)"', line)):
56+
variables.append(variable.group(1))
57+
found = False
58+
59+
if found and (variable := re.search(r'variable\s+(.+)\{', line)):
60+
variables.append(variable.group(1))
61+
found = False
62+
63+
return variables
64+
65+
def parse(path: Path) -> dict:
66+
debug(f'Attempting to parse {path} with fallback parser')
67+
body = path.read_text()
68+
69+
module = {}
70+
71+
if constraint := get_required_version(body):
72+
module['terraform'] = [{
73+
'required_version': constraint
74+
}]
75+
76+
if backend_type := get_backend_type(body):
77+
if 'terraform' not in module:
78+
module['terraform'] = []
79+
80+
if backend_type == 'cloud':
81+
module['terraform'].append({'cloud': [{}]})
82+
else:
83+
module['terraform'].append({'backend': [{backend_type:{}}]})
84+
85+
if sensitive_variables := get_sensitive_variables(body):
86+
module['variable'] = []
87+
for variable in sensitive_variables:
88+
module['variable'].append({
89+
variable: {
90+
'sensitive': True
91+
}
92+
})
93+
94+
return module
95+
96+
if __name__ == '__main__':
97+
from pprint import pprint
98+
pprint(parse(Path('tests/workflows/test-validate/hard-parse/main.tf')))

image/src/terraform/hcl.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@
88
from pathlib import Path
99

1010
from github_actions.debug import debug
11+
import terraform.fallback_parser
1112

1213

1314
def try_load(path: Path) -> dict:
1415
try:
1516
with open(path) as f:
1617
return hcl2.load(f)
1718
except Exception as e:
19+
debug(f'Failed to load {path}')
1820
debug(str(e))
19-
return {}
21+
return terraform.fallback_parser.parse(Path(path))
2022

2123

2224
def is_loadable(path: Path) -> bool:

0 commit comments

Comments
 (0)