tfplan-report 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: tfplan-report
3
+ Version: 0.1.0
4
+ Summary: Generate Markdown reports from Terraform plans
5
+ Author: Aswin
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Aswin-00/tfreadme.git
8
+ Keywords: terraform,terraform-plan,iac,devops,aws,cloud,markdown,report,automation,cli
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development
21
+ Classifier: Topic :: Software Development :: Build Tools
22
+ Classifier: Topic :: System :: Systems Administration
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: tabulate>=0.9.0
27
+
28
+ # Terraform Plan Report Generator
29
+
30
+ Generate clean, human-readable Markdown reports from Terraform plan files.
31
+
32
+ This utility converts the output of `terraform show -json` into a structured Markdown report that is easier to review during infrastructure changes, pull requests, and CI/CD pipelines.
33
+
34
+ ## Features
35
+
36
+ * Generate Markdown reports from Terraform plan files
37
+ * Summary of resource actions
38
+
39
+ * CREATE
40
+ * UPDATE
41
+ * DELETE
42
+ * REPLACE
43
+ * Resource type summary
44
+ * Friendly resource name detection
45
+ * Resource ID extraction
46
+ * Terraform resource address listing
47
+ * Collapsible sections for improved readability
48
+ * Lightweight and dependency-minimal
49
+
50
+ ## Example Workflow
51
+
52
+ ```text
53
+ terraform plan -out=tfplan
54
+
55
+
56
+ terraform show -json tfplan
57
+
58
+
59
+ Terraform Plan Report
60
+
61
+
62
+ README_TFPLAN.md
63
+ ```
64
+
65
+ ## Installation
66
+
67
+ Clone the repository:
68
+
69
+ ```bash
70
+ git clone https://github.com/<your-username>/tfplan-report.git
71
+ cd tfplan-report
72
+ ```
73
+
74
+ Install the package:
75
+
76
+ ```bash
77
+ pip install .
78
+ ```
79
+
80
+ Or install the dependency manually:
81
+
82
+ ```bash
83
+ pip install tabulate
84
+ ```
85
+
86
+ ## Requirements
87
+
88
+ * Python 3.9+
89
+ * Terraform CLI
90
+ * tabulate
91
+
92
+ ## Usage
93
+
94
+ Generate a Terraform plan:
95
+
96
+ ```bash
97
+ terraform plan -out=tfplan
98
+ ```
99
+
100
+ Generate the Markdown report:
101
+
102
+ ```bash
103
+ tfreadme tfplan
104
+ ```
105
+
106
+ or
107
+
108
+ ```bash
109
+ python tfreadme.py tfplan
110
+ ```
111
+
112
+ The tool generates:
113
+
114
+ ```text
115
+ README_TFPLAN.md
116
+ ```
117
+
118
+ ## Sample Output
119
+
120
+ ### Plan Summary
121
+
122
+ | Action | Count |
123
+ | ------- | ----: |
124
+ | CREATE | 12 |
125
+ | UPDATE | 5 |
126
+ | DELETE | 2 |
127
+ | REPLACE | 1 |
128
+
129
+ ### Resource Type Summary
130
+
131
+ | Resource Type | Count |
132
+ | ------------------ | ----: |
133
+ | aws_instance | 8 |
134
+ | aws_security_group | 4 |
135
+ | aws_iam_role | 2 |
136
+
137
+ ### CREATE
138
+
139
+ | Name | Type | Resource ID | Terraform Address |
140
+ | ---------- | ------------ | ----------- | ----------------- |
141
+ | web-server | aws_instance | N/A | aws_instance.web |
142
+
143
+ ## Project Structure
144
+
145
+ ```text
146
+ .
147
+ ├── pyproject.toml
148
+ ├── requirements.txt
149
+ ├── tfreadme.py
150
+ └── README.md
151
+ ```
152
+
153
+ ## How It Works
154
+
155
+ The tool:
156
+
157
+ 1. Reads a Terraform binary plan file
158
+ 2. Executes `terraform show -json`
159
+ 3. Parses the JSON output
160
+ 4. Classifies resource actions
161
+ 5. Groups resources by operation
162
+ 6. Generates a clean Markdown report
163
+
164
+ ## Current Capabilities
165
+
166
+ * Parse Terraform plan files
167
+ * Generate Markdown summaries
168
+ * Count resources by action
169
+ * Count resources by type
170
+ * Extract resource names
171
+ * Extract resource IDs
172
+ * Display Terraform addresses
173
+ * Produce review-friendly output
174
+
175
+
176
+ ## Technologies Used
177
+
178
+ * Python
179
+ * Terraform
180
+ * JSON
181
+ * tabulate
182
+
183
+
184
+ ## License
185
+
186
+ MIT License
187
+
188
+ ## Author
189
+
190
+ Developed by **Aswin** as a practical DevOps automation tool to simplify Terraform plan reviews and improve infrastructure change visibility.
@@ -0,0 +1,6 @@
1
+ tfreadme.py,sha256=1gv36a6AhOLx7HWcH55vcpSateO3suYrCDJtCbXeD5c,5585
2
+ tfplan_report-0.1.0.dist-info/METADATA,sha256=TK9o0bPE0x_Do2HeLk44w0B-BCdoy3xWCkMY6bil9Nw,3931
3
+ tfplan_report-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
4
+ tfplan_report-0.1.0.dist-info/entry_points.txt,sha256=Ob_yWc8oWMYHxl0HJCZZnfRbPHikQBb2pSKN5EXHgA8,43
5
+ tfplan_report-0.1.0.dist-info/top_level.txt,sha256=mBhBoR1u_S4yL4XdRVnvWgx9RpUYYFKOrFRgzGsw7o0,9
6
+ tfplan_report-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tfreadme = tfreadme:main
@@ -0,0 +1 @@
1
+ tfreadme
tfreadme.py ADDED
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import json
4
+ import sys
5
+ from collections import defaultdict
6
+ from tabulate import tabulate
7
+ import subprocess
8
+ import argparse
9
+
10
+
11
+ OUTPUT_FILE = "README_TFPLAN.md"
12
+ def get_resource_name(change):
13
+ """
14
+ Extract a friendly name from before/after state.
15
+ """
16
+ candidates = [
17
+ change.get("after", {}),
18
+ change.get("before", {})
19
+ ]
20
+
21
+ for obj in candidates:
22
+ if not isinstance(obj, dict):
23
+ continue
24
+
25
+ if obj.get("name"):
26
+ return str(obj["name"])
27
+
28
+ tags = obj.get("tags", {})
29
+ if isinstance(tags, dict) and tags.get("Name"):
30
+ return str(tags["Name"])
31
+
32
+ tags_all = obj.get("tags_all", {})
33
+ if isinstance(tags_all, dict) and tags_all.get("Name"):
34
+ return str(tags_all["Name"])
35
+
36
+ return "N/A"
37
+
38
+
39
+ def get_resource_id(change: dict) -> str:
40
+ for obj in (
41
+ change.get("before"),
42
+ change.get("after"),
43
+ ):
44
+ if isinstance(obj, dict):
45
+ if obj.get("id"):
46
+ return str(obj["id"])
47
+
48
+ return "N/A"
49
+
50
+
51
+ def classify_action(actions):
52
+ actions = set(actions)
53
+
54
+ if actions == {"create"}:
55
+ return "CREATE"
56
+
57
+ if actions == {"update"}:
58
+ return "UPDATE"
59
+
60
+ if actions == {"delete"}:
61
+ return "DELETE"
62
+
63
+ if actions == {"create", "delete"}:
64
+ return "REPLACE"
65
+
66
+ return ",".join(sorted(actions)).upper()
67
+
68
+ def load_plan(plan_file: str) -> dict:
69
+ """
70
+ Load a Terraform binary plan using `terraform show -json`
71
+ and return it as a Python dictionary.
72
+ """
73
+ try:
74
+ result = subprocess.run(
75
+ ["terraform", "show", "-json", plan_file],
76
+ capture_output=True,
77
+ text=True,
78
+ check=True,
79
+ )
80
+
81
+ return json.loads(result.stdout)
82
+
83
+ except subprocess.CalledProcessError as e:
84
+ raise RuntimeError(
85
+ f"terraform show failed:\n{e.stderr}"
86
+ ) from e
87
+
88
+ except json.JSONDecodeError as e:
89
+ raise RuntimeError(
90
+ "Failed to parse terraform JSON output."
91
+ ) from e
92
+
93
+
94
+ def build_report(plan):
95
+ grouped = defaultdict(list)
96
+ summary = defaultdict(int)
97
+ type_summary = defaultdict(int)
98
+
99
+ resource_changes = plan.get("resource_changes", [])
100
+
101
+ for rc in resource_changes:
102
+ change = rc.get("change", {})
103
+ actions = change.get("actions", [])
104
+
105
+ action = classify_action(actions)
106
+
107
+ resource = {
108
+ "name": get_resource_name(change),
109
+ "type": rc.get("type", "N/A"),
110
+ "address": rc.get("address", "N/A"),
111
+ "id": get_resource_id(change),
112
+ }
113
+
114
+ grouped[action].append(resource)
115
+
116
+ summary[action] += 1
117
+ type_summary[resource["type"]] += 1
118
+
119
+ md = []
120
+
121
+ md.append("# Terraform Plan Report")
122
+ md.append("")
123
+ md.append("Generated from `terraform show -json` output.")
124
+ md.append("")
125
+
126
+ # Summary
127
+ md.append("## Plan Summary")
128
+ md.append("")
129
+
130
+ summary_rows = [
131
+ [action, count]
132
+ for action, count in sorted(summary.items())
133
+ ]
134
+
135
+ md.append(
136
+ tabulate(
137
+ summary_rows,
138
+ headers=["Action", "Count"],
139
+ tablefmt="github"
140
+ )
141
+ )
142
+
143
+ md.append("")
144
+ md.append(
145
+ f"**Total Resource Changes:** {sum(summary.values())}"
146
+ )
147
+ md.append("")
148
+
149
+ # Resource Type Summary
150
+ md.append("## Resource Type Summary")
151
+ md.append("")
152
+
153
+ type_rows = sorted(
154
+ type_summary.items(),
155
+ key=lambda x: x[1],
156
+ reverse=True
157
+ )
158
+
159
+ md.append(
160
+ tabulate(
161
+ type_rows,
162
+ headers=["Resource Type", "Count"],
163
+ tablefmt="github"
164
+ )
165
+ )
166
+
167
+ md.append("")
168
+
169
+ action_order = [
170
+ "CREATE",
171
+ "UPDATE",
172
+ "DELETE",
173
+ "REPLACE"
174
+ ]
175
+
176
+ for action in action_order:
177
+
178
+ if action not in grouped:
179
+ continue
180
+
181
+ resources = sorted(
182
+ grouped[action],
183
+ key=lambda x: x["address"]
184
+ )
185
+
186
+ md.append(
187
+ f"<details open>"
188
+ )
189
+ md.append(
190
+ f"<summary><strong>{action} ({len(resources)})</strong></summary>"
191
+ )
192
+ md.append("")
193
+ md.append("")
194
+
195
+ rows = []
196
+
197
+ for r in resources:
198
+ rows.append([
199
+ r["name"],
200
+ r["type"],
201
+ r["id"],
202
+ r["address"]
203
+ ])
204
+
205
+ md.append(
206
+ tabulate(
207
+ rows,
208
+ headers=[
209
+ "Name",
210
+ "Type",
211
+ "Resource ID",
212
+ "Terraform Address"
213
+ ],
214
+ tablefmt="github"
215
+ )
216
+ )
217
+
218
+ md.append("")
219
+ md.append("</details>")
220
+ md.append("")
221
+
222
+ return "\n".join(md)
223
+
224
+
225
+ def main():
226
+ parser = argparse.ArgumentParser(
227
+ description="Generate a Markdown report from a Terraform plan."
228
+ )
229
+
230
+ parser.add_argument(
231
+ "plan",
232
+ help="Terraform binary plan file (e.g. tfplan)"
233
+ )
234
+
235
+ parser.add_argument(
236
+ "-o",
237
+ "--output",
238
+ default="README_TFPLAN.md",
239
+ help="Output markdown file (default: README_TFPLAN.md)"
240
+ )
241
+
242
+ args = parser.parse_args()
243
+
244
+ try:
245
+ plan = load_plan(args.plan)
246
+
247
+ report = build_report(plan)
248
+
249
+ with open(args.output, "w", encoding="utf-8") as f:
250
+ f.write(report)
251
+
252
+ print(f"Report generated: {args.output}")
253
+
254
+ except Exception as e:
255
+ print(f"Error: {e}")
256
+ sys.exit(1)
257
+
258
+
259
+
260
+
261
+ if __name__ == "__main__":
262
+ main()