Spaces:
Sleeping
Sleeping
Create app.py
Browse filesThe Terraform MCP server
app.py
ADDED
|
@@ -0,0 +1,1919 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"",
|
| 2 |
+
"# 6. Clean up test",
|
| 3 |
+
"terraform destroy",
|
| 4 |
+
"cd ../..",
|
| 5 |
+
"",
|
| 6 |
+
"# 7. Tag version",
|
| 7 |
+
"git tag v1.0.0",
|
| 8 |
+
"git push --tags"
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
class TerraformModuleConverter:
|
| 12 |
+
"""Convert Terraform configurations to reusable modules"""
|
| 13 |
+
|
| 14 |
+
@staticmethod
|
| 15 |
+
def extract_variables(config: str) -> str:
|
| 16 |
+
"""Extract hardcoded values and convert to variables"""
|
| 17 |
+
variables = []
|
| 18 |
+
|
| 19 |
+
# Common patterns to extract
|
| 20 |
+
patterns = {
|
| 21 |
+
r'"(t[23]\.[a-z]+)"': ('instance_type', 'string', 'EC2 instance type'),
|
| 22 |
+
r'"(\d+\.\d+\.\d+\.\d+/\d+)"': ('cidr_block', 'string', 'CIDR block'),
|
| 23 |
+
r'"(us-[a-z]+-\d+[a-z]?)"': ('aws_region', 'string', 'AWS region'),
|
| 24 |
+
r'"(eastus|westus|centralus)"': ('location', 'string', 'Azure location'),
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
for pattern, (var_name, var_type, description) in patterns.items():
|
| 28 |
+
if re.search(pattern, config):
|
| 29 |
+
variables.append(f'''variable "{var_name}" {{
|
| 30 |
+
description = "{description}"
|
| 31 |
+
type = {var_type}
|
| 32 |
+
default = # Add appropriate default
|
| 33 |
+
}}''')
|
| 34 |
+
|
| 35 |
+
return '\n\n'.join(variables)
|
| 36 |
+
|
| 37 |
+
@staticmethod
|
| 38 |
+
def generate_outputs(config: str, resource_name: str) -> str:
|
| 39 |
+
"""Generate outputs for a module"""
|
| 40 |
+
outputs = []
|
| 41 |
+
|
| 42 |
+
# Extract resource types from config
|
| 43 |
+
resources = re.findall(r'resource\s+"([^"]+)"\s+"([^"]+)"', config)
|
| 44 |
+
modules = re.findall(r'module\s+"([^"]+)"\s*\{', config)
|
| 45 |
+
|
| 46 |
+
for resource_type, name in resources:
|
| 47 |
+
if 'aws_instance' in resource_type:
|
| 48 |
+
outputs.extend([
|
| 49 |
+
f'output "{name}_id" {{\n description = "ID of the EC2 instance"\n value = {resource_type}.{name}.id\n}}',
|
| 50 |
+
f'output "{name}_public_ip" {{\n description = "Public IP of the instance"\n value = {resource_type}.{name}.public_ip\n}}'
|
| 51 |
+
])
|
| 52 |
+
elif 'aws_s3_bucket' in resource_type:
|
| 53 |
+
outputs.extend([
|
| 54 |
+
f'output "{name}_name" {{\n description = "Name of the S3 bucket"\n value = {resource_type}.{name}.id\n}}',
|
| 55 |
+
f'output "{name}_arn" {{\n description = "ARN of the S3 bucket"\n value = {resource_type}.{name}.arn\n}}'
|
| 56 |
+
])
|
| 57 |
+
|
| 58 |
+
for module_name in modules:
|
| 59 |
+
outputs.append(f'output "{module_name}_outputs" {{\n description = "All outputs from {module_name} module"\n value = module.{module_name}\n}}')
|
| 60 |
+
|
| 61 |
+
return '\n\n'.join(outputs)
|
| 62 |
+
|
| 63 |
+
# ============================================================================
|
| 64 |
+
# MCP SERVER IMPLEMENTATION
|
| 65 |
+
# ============================================================================
|
| 66 |
+
|
| 67 |
+
class CleanTerraformMCPServer:
|
| 68 |
+
"""Clean Terraform MCP Server - focused on code generation and validation"""
|
| 69 |
+
|
| 70 |
+
def __init__(self):
|
| 71 |
+
self.server = Server("terraform-mcp-server")
|
| 72 |
+
self.code_generator = TerraformCodeGenerator()
|
| 73 |
+
self.validator = TerraformValidator()
|
| 74 |
+
self.workflows = TerraformWorkflows()
|
| 75 |
+
self.module_converter = TerraformModuleConverter()
|
| 76 |
+
self._setup_handlers()
|
| 77 |
+
|
| 78 |
+
def _setup_handlers(self):
|
| 79 |
+
"""Setup MCP server handlers"""
|
| 80 |
+
|
| 81 |
+
@self.server.list_tools()
|
| 82 |
+
async def handle_list_tools() -> list[types.Tool]:
|
| 83 |
+
"""List all available Terraform tools"""
|
| 84 |
+
return [
|
| 85 |
+
types.Tool(
|
| 86 |
+
name="generate_terraform_config",
|
| 87 |
+
description="Generate complete Terraform configuration for infrastructure resources",
|
| 88 |
+
inputSchema={
|
| 89 |
+
"type": "object",
|
| 90 |
+
"properties": {
|
| 91 |
+
"resource_type": {
|
| 92 |
+
"type": "string",
|
| 93 |
+
"description": "Type of infrastructure resource",
|
| 94 |
+
"enum": ["vpc", "ec2", "s3", "eks", "rds", "vnet", "compute", "aks"]
|
| 95 |
+
},
|
| 96 |
+
"provider": {
|
| 97 |
+
"type": "string",
|
| 98 |
+
"description": "Cloud provider",
|
| 99 |
+
"enum": ["aws", "azurerm", "google"]
|
| 100 |
+
},
|
| 101 |
+
"name": {
|
| 102 |
+
"type": "string",
|
| 103 |
+
"description": "Name for the infrastructure resources"
|
| 104 |
+
},
|
| 105 |
+
"options": {
|
| 106 |
+
"type": "object",
|
| 107 |
+
"description": "Resource-specific configuration options",
|
| 108 |
+
"properties": {
|
| 109 |
+
"cidr": {"type": "string", "description": "CIDR block for VPC"},
|
| 110 |
+
"azs": {"type": "array", "items": {"type": "string"}, "description": "Availability zones"},
|
| 111 |
+
"instance_type": {"type": "string", "description": "EC2 instance type"},
|
| 112 |
+
"key_pair_name": {"type": "string", "description": "SSH key pair name"},
|
| 113 |
+
"enable_nat_gateway": {"type": "boolean", "description": "Enable NAT gateway"},
|
| 114 |
+
"versioning": {"type": "boolean", "description": "Enable S3 versioning"},
|
| 115 |
+
"encryption": {"type": "boolean", "description": "Enable encryption"},
|
| 116 |
+
"node_instance_type": {"type": "string", "description": "EKS node instance type"},
|
| 117 |
+
"min_nodes": {"type": "integer", "description": "Minimum nodes"},
|
| 118 |
+
"max_nodes": {"type": "integer", "description": "Maximum nodes"},
|
| 119 |
+
"desired_nodes": {"type": "integer", "description": "Desired nodes"},
|
| 120 |
+
"kubernetes_version": {"type": "string", "description": "Kubernetes version"}
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
},
|
| 124 |
+
"required": ["resource_type", "provider", "name"]
|
| 125 |
+
}
|
| 126 |
+
),
|
| 127 |
+
types.Tool(
|
| 128 |
+
name="validate_terraform_config",
|
| 129 |
+
description="Validate Terraform configuration syntax and best practices",
|
| 130 |
+
inputSchema={
|
| 131 |
+
"type": "object",
|
| 132 |
+
"properties": {
|
| 133 |
+
"config_content": {
|
| 134 |
+
"type": "string",
|
| 135 |
+
"description": "Terraform configuration content to validate"
|
| 136 |
+
},
|
| 137 |
+
"check_best_practices": {
|
| 138 |
+
"type": "boolean",
|
| 139 |
+
"description": "Include best practices validation",
|
| 140 |
+
"default": True
|
| 141 |
+
},
|
| 142 |
+
"strict_mode": {
|
| 143 |
+
"type": "boolean",
|
| 144 |
+
"description": "Enable strict validation rules",
|
| 145 |
+
"default": False
|
| 146 |
+
}
|
| 147 |
+
},
|
| 148 |
+
"required": ["config_content"]
|
| 149 |
+
}
|
| 150 |
+
),
|
| 151 |
+
types.Tool(
|
| 152 |
+
name="get_deployment_workflow",
|
| 153 |
+
description="Generate step-by-step Terraform deployment workflow",
|
| 154 |
+
inputSchema={
|
| 155 |
+
"type": "object",
|
| 156 |
+
"properties": {
|
| 157 |
+
"workflow_type": {
|
| 158 |
+
"type": "string",
|
| 159 |
+
"description": "Type of deployment workflow",
|
| 160 |
+
"enum": ["basic", "production", "module_development"],
|
| 161 |
+
"default": "basic"
|
| 162 |
+
},
|
| 163 |
+
"backend_type": {
|
| 164 |
+
"type": "string",
|
| 165 |
+
"description": "Backend storage type",
|
| 166 |
+
"enum": ["local", "s3", "azurerm", "gcs"],
|
| 167 |
+
"default": "local"
|
| 168 |
+
},
|
| 169 |
+
"environment": {
|
| 170 |
+
"type": "string",
|
| 171 |
+
"description": "Target environment",
|
| 172 |
+
"enum": ["development", "staging", "production"],
|
| 173 |
+
"default": "development"
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
),
|
| 178 |
+
types.Tool(
|
| 179 |
+
name="convert_to_module",
|
| 180 |
+
description="Convert Terraform configuration to reusable module structure",
|
| 181 |
+
inputSchema={
|
| 182 |
+
"type": "object",
|
| 183 |
+
"properties": {
|
| 184 |
+
"config_content": {
|
| 185 |
+
"type": "string",
|
| 186 |
+
"description": "Terraform configuration to convert"
|
| 187 |
+
},
|
| 188 |
+
"module_name": {
|
| 189 |
+
"type": "string",
|
| 190 |
+
"description": "Name for the module"
|
| 191 |
+
},
|
| 192 |
+
"extract_variables": {
|
| 193 |
+
"type": "boolean",
|
| 194 |
+
"description": "Extract hardcoded values as variables",
|
| 195 |
+
"default": True
|
| 196 |
+
},
|
| 197 |
+
"generate_examples": {
|
| 198 |
+
"type": "boolean",
|
| 199 |
+
"description": "Generate usage examples",
|
| 200 |
+
"default": True
|
| 201 |
+
}
|
| 202 |
+
},
|
| 203 |
+
"required": ["config_content", "module_name"]
|
| 204 |
+
}
|
| 205 |
+
),
|
| 206 |
+
types.Tool(
|
| 207 |
+
name="format_terraform_code",
|
| 208 |
+
description="Format and standardize Terraform code",
|
| 209 |
+
inputSchema={
|
| 210 |
+
"type": "object",
|
| 211 |
+
"properties": {
|
| 212 |
+
"config_content": {
|
| 213 |
+
"type": "string",
|
| 214 |
+
"description": "Terraform configuration to format"
|
| 215 |
+
},
|
| 216 |
+
"sort_blocks": {
|
| 217 |
+
"type": "boolean",
|
| 218 |
+
"description": "Sort resource blocks alphabetically",
|
| 219 |
+
"default": True
|
| 220 |
+
}
|
| 221 |
+
},
|
| 222 |
+
"required": ["config_content"]
|
| 223 |
+
}
|
| 224 |
+
),
|
| 225 |
+
types.Tool(
|
| 226 |
+
name="generate_terraform_docs",
|
| 227 |
+
description="Generate documentation for Terraform configuration",
|
| 228 |
+
inputSchema={
|
| 229 |
+
"type": "object",
|
| 230 |
+
"properties": {
|
| 231 |
+
"config_content": {
|
| 232 |
+
"type": "string",
|
| 233 |
+
"description": "Terraform configuration to document"
|
| 234 |
+
},
|
| 235 |
+
"module_name": {
|
| 236 |
+
"type": "string",
|
| 237 |
+
"description": "Name of the module"
|
| 238 |
+
},
|
| 239 |
+
"include_examples": {
|
| 240 |
+
"type": "boolean",
|
| 241 |
+
"description": "Include usage examples",
|
| 242 |
+
"default": True
|
| 243 |
+
}
|
| 244 |
+
},
|
| 245 |
+
"required": ["config_content"]
|
| 246 |
+
}
|
| 247 |
+
)
|
| 248 |
+
]
|
| 249 |
+
|
| 250 |
+
@self.server.call_tool()
|
| 251 |
+
async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
|
| 252 |
+
"""Handle tool calls"""
|
| 253 |
+
try:
|
| 254 |
+
if name == "generate_terraform_config":
|
| 255 |
+
return await self._handle_generate_config(arguments)
|
| 256 |
+
elif name == "validate_terraform_config":
|
| 257 |
+
return await self._handle_validate_config(arguments)
|
| 258 |
+
elif name == "get_deployment_workflow":
|
| 259 |
+
return await self._handle_deployment_workflow(arguments)
|
| 260 |
+
elif name == "convert_to_module":
|
| 261 |
+
return await self._handle_convert_to_module(arguments)
|
| 262 |
+
elif name == "format_terraform_code":
|
| 263 |
+
return await self._handle_format_code(arguments)
|
| 264 |
+
elif name == "generate_terraform_docs":
|
| 265 |
+
return await self._handle_generate_docs(arguments)
|
| 266 |
+
else:
|
| 267 |
+
return [types.TextContent(type="text", text=f"❌ Unknown tool: {name}")]
|
| 268 |
+
|
| 269 |
+
except Exception as e:
|
| 270 |
+
logger.error(f"Tool execution error: {e}")
|
| 271 |
+
return [types.TextContent(type="text", text=f"❌ Error executing {name}: {str(e)}")]
|
| 272 |
+
|
| 273 |
+
async def _handle_generate_config(self, args: dict) -> list[types.TextContent]:
|
| 274 |
+
"""Handle Terraform configuration generation"""
|
| 275 |
+
resource_type = args.get("resource_type")
|
| 276 |
+
provider = args.get("provider")
|
| 277 |
+
name = args.get("name")
|
| 278 |
+
options = args.get("options", {})
|
| 279 |
+
|
| 280 |
+
# Generate configuration based on resource type
|
| 281 |
+
if resource_type == "vpc" and provider == "aws":
|
| 282 |
+
config = self.code_generator.generate_vpc_config(name, options)
|
| 283 |
+
elif resource_type == "ec2" and provider == "aws":
|
| 284 |
+
config = self.code_generator.generate_ec2_config(name, options)
|
| 285 |
+
elif resource_type == "s3" and provider == "aws":
|
| 286 |
+
config = self.code_generator.generate_s3_config(name, options)
|
| 287 |
+
elif resource_type == "eks" and provider == "aws":
|
| 288 |
+
config = self.code_generator.generate_eks_config(name, options)
|
| 289 |
+
elif resource_type == "vnet" and provider == "azurerm":
|
| 290 |
+
config = self.code_generator.generate_azure_vnet_config(name, options)
|
| 291 |
+
else:
|
| 292 |
+
return [types.TextContent(type="text", text=f"❌ Configuration generation for {resource_type} on {provider} not yet implemented")]
|
| 293 |
+
|
| 294 |
+
# Basic validation
|
| 295 |
+
validation_issues = self.validator.validate_syntax(config)
|
| 296 |
+
best_practices = self.validator.validate_best_practices(config)
|
| 297 |
+
|
| 298 |
+
response = f"""# 🚀 Terraform Configuration Generated
|
| 299 |
+
|
| 300 |
+
## 📋 Summary
|
| 301 |
+
- **Resource Type**: {resource_type.upper()}
|
| 302 |
+
- **Provider**: {provider}
|
| 303 |
+
- **Name**: {name}
|
| 304 |
+
- **Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 305 |
+
|
| 306 |
+
## 🔧 Configuration
|
| 307 |
+
|
| 308 |
+
```hcl
|
| 309 |
+
{config}
|
| 310 |
+
```
|
| 311 |
+
|
| 312 |
+
## ✅ Validation Results
|
| 313 |
+
"""
|
| 314 |
+
|
| 315 |
+
if validation_issues:
|
| 316 |
+
response += "### Issues Found:\n"
|
| 317 |
+
for issue in validation_issues:
|
| 318 |
+
response += f"- {issue}\n"
|
| 319 |
+
else:
|
| 320 |
+
response += "- ✅ No syntax issues found\n"
|
| 321 |
+
|
| 322 |
+
if best_practices:
|
| 323 |
+
response += "\n### Best Practice Suggestions:\n"
|
| 324 |
+
for suggestion in best_practices:
|
| 325 |
+
response += f"- {suggestion}\n"
|
| 326 |
+
|
| 327 |
+
response += f"""
|
| 328 |
+
|
| 329 |
+
## 🚀 Next Steps
|
| 330 |
+
|
| 331 |
+
1. Save configuration to `main.tf`
|
| 332 |
+
2. Run `terraform init` to initialize
|
| 333 |
+
3. Run `terraform plan` to review changes
|
| 334 |
+
4. Run `terraform apply` to create resources
|
| 335 |
+
|
| 336 |
+
## 📦 Recommended Module Structure
|
| 337 |
+
|
| 338 |
+
```
|
| 339 |
+
{name}-infrastructure/
|
| 340 |
+
├── main.tf # Main configuration
|
| 341 |
+
├── variables.tf # Input variables
|
| 342 |
+
├── outputs.tf # Output values
|
| 343 |
+
├── versions.tf # Provider versions
|
| 344 |
+
└── README.md # Documentation
|
| 345 |
+
```
|
| 346 |
+
"""
|
| 347 |
+
|
| 348 |
+
return [types.TextContent(type="text", text=response)]
|
| 349 |
+
|
| 350 |
+
async def _handle_validate_config(self, args: dict) -> list[types.TextContent]:
|
| 351 |
+
"""Handle configuration validation"""
|
| 352 |
+
config_content = args.get("config_content")
|
| 353 |
+
check_best_practices = args.get("check_best_practices", True)
|
| 354 |
+
strict_mode = args.get("strict_mode", False)
|
| 355 |
+
|
| 356 |
+
# Syntax validation
|
| 357 |
+
syntax_issues = self.validator.validate_syntax(config_content)
|
| 358 |
+
|
| 359 |
+
# Best practices validation
|
| 360 |
+
best_practice_suggestions = []
|
| 361 |
+
if check_best_practices:
|
| 362 |
+
best_practice_suggestions = self.validator.validate_best_practices(config_content)
|
| 363 |
+
|
| 364 |
+
response = "# 📋 Terraform Configuration Validation\n\n"
|
| 365 |
+
|
| 366 |
+
# Syntax validation results
|
| 367 |
+
response += "## ✅ Syntax Validation\n\n"
|
| 368 |
+
if not syntax_issues:
|
| 369 |
+
response += "✅ **No syntax issues found**\n\n"
|
| 370 |
+
else:
|
| 371 |
+
response += f"Found {len(syntax_issues)} syntax issues:\n\n"
|
| 372 |
+
for issue in syntax_issues:
|
| 373 |
+
response += f"- {issue}\n"
|
| 374 |
+
response += "\n"
|
| 375 |
+
|
| 376 |
+
# Best practices results
|
| 377 |
+
if check_best_practices:
|
| 378 |
+
response += "## 💡 Best Practices Review\n\n"
|
| 379 |
+
if not best_practice_suggestions:
|
| 380 |
+
response += "✅ **Configuration follows best practices**\n\n"
|
| 381 |
+
else:
|
| 382 |
+
response += f"Found {len(best_practice_suggestions)} suggestions:\n\n"
|
| 383 |
+
for suggestion in best_practice_suggestions:
|
| 384 |
+
response += f"- {suggestion}\n"
|
| 385 |
+
response += "\n"
|
| 386 |
+
|
| 387 |
+
# Overall score
|
| 388 |
+
total_issues = len(syntax_issues)
|
| 389 |
+
total_suggestions = len(best_practice_suggestions) if check_best_practices else 0
|
| 390 |
+
|
| 391 |
+
if total_issues == 0 and total_suggestions == 0:
|
| 392 |
+
response += "## 🏆 Overall Assessment\n\n"
|
| 393 |
+
response += "**Excellent!** Your configuration is clean and follows best practices.\n"
|
| 394 |
+
elif total_issues == 0:
|
| 395 |
+
response += "## ✅ Overall Assessment\n\n"
|
| 396 |
+
response += "**Good!** No syntax errors, but consider the suggestions above.\n"
|
| 397 |
+
else:
|
| 398 |
+
response += "## ⚠️ Overall Assessment\n\n"
|
| 399 |
+
response += "**Needs attention.** Please address the syntax issues before deployment.\n"
|
| 400 |
+
|
| 401 |
+
response += "\n## 🔧 Recommended Actions\n\n"
|
| 402 |
+
if total_issues > 0:
|
| 403 |
+
response += "1. **Fix syntax issues** - Address all syntax problems first\n"
|
| 404 |
+
response += "2. **Run terraform validate** - Validate with Terraform CLI\n"
|
| 405 |
+
response += "3. **Run terraform fmt** - Format the code consistently\n"
|
| 406 |
+
if check_best_practices and total_suggestions > 0:
|
| 407 |
+
response += "4. **Review best practices** - Consider implementing suggestions\n"
|
| 408 |
+
response += "5. **Test thoroughly** - Always test in development environment first\n"
|
| 409 |
+
|
| 410 |
+
return [types.TextContent(type="text", text=response)]
|
| 411 |
+
|
| 412 |
+
async def _handle_deployment_workflow(self, args: dict) -> list[types.TextContent]:
|
| 413 |
+
"""Handle deployment workflow generation"""
|
| 414 |
+
workflow_type = args.get("workflow_type", "basic")
|
| 415 |
+
backend_type = args.get("backend_type", "local")
|
| 416 |
+
environment = args.get("environment", "development")
|
| 417 |
+
|
| 418 |
+
if workflow_type == "basic":
|
| 419 |
+
commands = self.workflows.get_basic_workflow()
|
| 420 |
+
elif workflow_type == "production":
|
| 421 |
+
commands = self.workflows.get_production_workflow(backend_type)
|
| 422 |
+
elif workflow_type == "module_development":
|
| 423 |
+
commands = self.workflows.get_module_workflow()
|
| 424 |
+
else:
|
| 425 |
+
return [types.TextContent(type="text", text=f"❌ Unknown workflow type: {workflow_type}")]
|
| 426 |
+
|
| 427 |
+
response = f"""# 🚀 Terraform Deployment Workflow
|
| 428 |
+
|
| 429 |
+
## ��� Configuration
|
| 430 |
+
- **Workflow Type**: {workflow_type.title()}
|
| 431 |
+
- **Backend**: {backend_type.upper()}
|
| 432 |
+
- **Environment**: {environment.title()}
|
| 433 |
+
- **Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 434 |
+
|
| 435 |
+
## 🔄 Commands
|
| 436 |
+
|
| 437 |
+
```bash
|
| 438 |
+
{chr(10).join(commands)}
|
| 439 |
+
```
|
| 440 |
+
|
| 441 |
+
## 🛡️ Security Considerations
|
| 442 |
+
|
| 443 |
+
"""
|
| 444 |
+
|
| 445 |
+
if environment == "production":
|
| 446 |
+
response += """### Production Environment
|
| 447 |
+
- ✅ Use remote state storage
|
| 448 |
+
- ✅ Enable state locking
|
| 449 |
+
- ✅ Implement approval workflows
|
| 450 |
+
- ✅ Run security scans
|
| 451 |
+
- ✅ Backup state files regularly
|
| 452 |
+
- ✅ Use least privilege access
|
| 453 |
+
- ✅ Monitor all changes
|
| 454 |
+
|
| 455 |
+
"""
|
| 456 |
+
|
| 457 |
+
response += """### General Security
|
| 458 |
+
- 🔒 Never commit sensitive data to version control
|
| 459 |
+
- 🔒 Use environment variables for secrets
|
| 460 |
+
- 🔒 Enable encryption for state files
|
| 461 |
+
- 🔒 Implement proper IAM policies
|
| 462 |
+
- 🔒 Regular security reviews
|
| 463 |
+
|
| 464 |
+
## 📚 Additional Resources
|
| 465 |
+
|
| 466 |
+
- [Terraform Best Practices](https://www.terraform.io/docs/cloud/guides/recommended-practices/index.html)
|
| 467 |
+
- [State Management](https://www.terraform.io/docs/language/state/index.html)
|
| 468 |
+
- [Security Guidelines](https://www.terraform.io/docs/cloud/guides/recommended-practices/part1.html)
|
| 469 |
+
"""
|
| 470 |
+
|
| 471 |
+
return [types.TextContent(type="text", text=response)]
|
| 472 |
+
|
| 473 |
+
async def _handle_convert_to_module(self, args: dict) -> list[types.TextContent]:
|
| 474 |
+
"""Handle module conversion"""
|
| 475 |
+
config_content = args.get("config_content")
|
| 476 |
+
module_name = args.get("module_name")
|
| 477 |
+
extract_variables = args.get("extract_variables", True)
|
| 478 |
+
generate_examples = args.get("generate_examples", True)
|
| 479 |
+
|
| 480 |
+
response = f"""# 📦 Module Conversion: {module_name}
|
| 481 |
+
|
| 482 |
+
Converting Terraform configuration to a reusable module structure.
|
| 483 |
+
|
| 484 |
+
## 📁 Module Structure
|
| 485 |
+
|
| 486 |
+
```
|
| 487 |
+
modules/{module_name}/
|
| 488 |
+
├── main.tf # Main configuration
|
| 489 |
+
├── variables.tf # Input variables
|
| 490 |
+
├── outputs.tf # Output values
|
| 491 |
+
├── versions.tf # Provider requirements
|
| 492 |
+
├── README.md # Documentation
|
| 493 |
+
└── examples/
|
| 494 |
+
└── basic/
|
| 495 |
+
├── main.tf # Example usage
|
| 496 |
+
└── README.md # Example documentation
|
| 497 |
+
```
|
| 498 |
+
|
| 499 |
+
"""
|
| 500 |
+
|
| 501 |
+
# Generate variables.tf
|
| 502 |
+
if extract_variables:
|
| 503 |
+
variables = self.module_converter.extract_variables(config_content)
|
| 504 |
+
response += f"""## variables.tf
|
| 505 |
+
|
| 506 |
+
```hcl
|
| 507 |
+
{variables}
|
| 508 |
+
|
| 509 |
+
variable "tags" {{
|
| 510 |
+
description = "A map of tags to assign to the resource"
|
| 511 |
+
type = map(string)
|
| 512 |
+
default = {{}}
|
| 513 |
+
}}
|
| 514 |
+
```
|
| 515 |
+
|
| 516 |
+
"""
|
| 517 |
+
|
| 518 |
+
# Generate main.tf (modified config)
|
| 519 |
+
response += f"""## main.tf
|
| 520 |
+
|
| 521 |
+
```hcl
|
| 522 |
+
{config_content}
|
| 523 |
+
```
|
| 524 |
+
|
| 525 |
+
"""
|
| 526 |
+
|
| 527 |
+
# Generate outputs.tf
|
| 528 |
+
outputs = self.module_converter.generate_outputs(config_content, module_name)
|
| 529 |
+
response += f"""## outputs.tf
|
| 530 |
+
|
| 531 |
+
```hcl
|
| 532 |
+
{outputs}
|
| 533 |
+
```
|
| 534 |
+
|
| 535 |
+
"""
|
| 536 |
+
|
| 537 |
+
# Generate versions.tf
|
| 538 |
+
response += f"""## versions.tf
|
| 539 |
+
|
| 540 |
+
```hcl
|
| 541 |
+
terraform {{
|
| 542 |
+
required_version = ">= 1.0"
|
| 543 |
+
|
| 544 |
+
required_providers {{
|
| 545 |
+
aws = {{
|
| 546 |
+
source = "hashicorp/aws"
|
| 547 |
+
version = ">= 5.0"
|
| 548 |
+
}}
|
| 549 |
+
}}
|
| 550 |
+
}}
|
| 551 |
+
```
|
| 552 |
+
|
| 553 |
+
"""
|
| 554 |
+
|
| 555 |
+
# Generate example usage
|
| 556 |
+
if generate_examples:
|
| 557 |
+
response += f"""## examples/basic/main.tf
|
| 558 |
+
|
| 559 |
+
```hcl
|
| 560 |
+
module "{module_name}" {{
|
| 561 |
+
source = "../../"
|
| 562 |
+
|
| 563 |
+
project_name = "example"
|
| 564 |
+
environment = "dev"
|
| 565 |
+
|
| 566 |
+
# Add module-specific variables here
|
| 567 |
+
|
| 568 |
+
tags = {{
|
| 569 |
+
Environment = "development"
|
| 570 |
+
Project = "example"
|
| 571 |
+
}}
|
| 572 |
+
}}
|
| 573 |
+
|
| 574 |
+
output "{module_name}_outputs" {{
|
| 575 |
+
description = "All outputs from the {module_name} module"
|
| 576 |
+
value = module.{module_name}
|
| 577 |
+
}}
|
| 578 |
+
```
|
| 579 |
+
|
| 580 |
+
"""
|
| 581 |
+
|
| 582 |
+
# Generate README.md
|
| 583 |
+
response += f"""## README.md
|
| 584 |
+
|
| 585 |
+
```markdown
|
| 586 |
+
# {module_name.title()} Module
|
| 587 |
+
|
| 588 |
+
This module creates {module_name} infrastructure resources.
|
| 589 |
+
|
| 590 |
+
## Usage
|
| 591 |
+
|
| 592 |
+
```hcl
|
| 593 |
+
module "{module_name}" {{
|
| 594 |
+
source = "path/to/module"
|
| 595 |
+
|
| 596 |
+
project_name = "my-project"
|
| 597 |
+
environment = "production"
|
| 598 |
+
|
| 599 |
+
tags = {{
|
| 600 |
+
Environment = "production"
|
| 601 |
+
Team = "infrastructure"
|
| 602 |
+
}}
|
| 603 |
+
}}
|
| 604 |
+
```
|
| 605 |
+
|
| 606 |
+
## Requirements
|
| 607 |
+
|
| 608 |
+
| Name | Version |
|
| 609 |
+
|------|---------|
|
| 610 |
+
| terraform | >= 1.0 |
|
| 611 |
+
| aws | >= 5.0 |
|
| 612 |
+
|
| 613 |
+
## Providers
|
| 614 |
+
|
| 615 |
+
| Name | Version |
|
| 616 |
+
|------|---------|
|
| 617 |
+
| aws | >= 5.0 |
|
| 618 |
+
|
| 619 |
+
## Resources
|
| 620 |
+
|
| 621 |
+
[List of resources created by this module]
|
| 622 |
+
|
| 623 |
+
## Inputs
|
| 624 |
+
|
| 625 |
+
[Generated from variables.tf]
|
| 626 |
+
|
| 627 |
+
## Outputs
|
| 628 |
+
|
| 629 |
+
[Generated from outputs.tf]
|
| 630 |
+
```
|
| 631 |
+
|
| 632 |
+
## 🚀 Next Steps
|
| 633 |
+
|
| 634 |
+
1. **Create module directory structure**
|
| 635 |
+
2. **Save each file in appropriate location**
|
| 636 |
+
3. **Test module with examples**
|
| 637 |
+
4. **Add validation rules to variables**
|
| 638 |
+
5. **Update documentation**
|
| 639 |
+
6. **Version tag for release**
|
| 640 |
+
|
| 641 |
+
## 🧪 Testing the Module
|
| 642 |
+
|
| 643 |
+
```bash
|
| 644 |
+
# Navigate to example
|
| 645 |
+
cd examples/basic
|
| 646 |
+
|
| 647 |
+
# Initialize and test
|
| 648 |
+
terraform init
|
| 649 |
+
terraform plan
|
| 650 |
+
terraform apply
|
| 651 |
+
|
| 652 |
+
# Clean up
|
| 653 |
+
terraform destroy
|
| 654 |
+
```
|
| 655 |
+
"""
|
| 656 |
+
|
| 657 |
+
return [types.TextContent(type="text", text=response)]
|
| 658 |
+
|
| 659 |
+
async def _handle_format_code(self, args: dict) -> list[types.TextContent]:
|
| 660 |
+
"""Handle code formatting"""
|
| 661 |
+
config_content = args.get("config_content")
|
| 662 |
+
sort_blocks = args.get("sort_blocks", True)
|
| 663 |
+
|
| 664 |
+
# Basic formatting improvements
|
| 665 |
+
formatted_config = config_content
|
| 666 |
+
|
| 667 |
+
# Normalize indentation (2 spaces)
|
| 668 |
+
lines = formatted_config.split('\n')
|
| 669 |
+
formatted_lines = []
|
| 670 |
+
indent_level = 0
|
| 671 |
+
|
| 672 |
+
for line in lines:
|
| 673 |
+
stripped = line.strip()
|
| 674 |
+
|
| 675 |
+
# Decrease indent for closing braces
|
| 676 |
+
if stripped == '}':
|
| 677 |
+
indent_level = max(0, indent_level - 1)
|
| 678 |
+
|
| 679 |
+
# Add indentation
|
| 680 |
+
if stripped:
|
| 681 |
+
formatted_lines.append(' ' * indent_level + stripped)
|
| 682 |
+
else:
|
| 683 |
+
formatted_lines.append('')
|
| 684 |
+
|
| 685 |
+
# Increase indent for opening braces
|
| 686 |
+
if stripped.endswith('{'):
|
| 687 |
+
indent_level += 1
|
| 688 |
+
|
| 689 |
+
formatted_config = '\n'.join(formatted_lines)
|
| 690 |
+
|
| 691 |
+
response = f"""# 🎨 Terraform Code Formatting
|
| 692 |
+
|
| 693 |
+
## ✨ Formatted Configuration
|
| 694 |
+
|
| 695 |
+
```hcl
|
| 696 |
+
{formatted_config}
|
| 697 |
+
```
|
| 698 |
+
|
| 699 |
+
## 📋 Formatting Applied
|
| 700 |
+
|
| 701 |
+
- ✅ Normalized indentation (2 spaces)
|
| 702 |
+
- ✅ Consistent spacing
|
| 703 |
+
- ✅ Proper block alignment
|
| 704 |
+
- ✅ Standard Terraform formatting
|
| 705 |
+
|
| 706 |
+
## 🔧 Additional Formatting Tips
|
| 707 |
+
|
| 708 |
+
1. **Use `terraform fmt`** - Run the official formatter
|
| 709 |
+
2. **Configure editor** - Set up auto-formatting in your IDE
|
| 710 |
+
3. **Pre-commit hooks** - Automatically format before commits
|
| 711 |
+
4. **Consistent naming** - Use lowercase with underscores
|
| 712 |
+
5. **Group related blocks** - Keep similar resources together
|
| 713 |
+
|
| 714 |
+
## 📐 Style Guidelines
|
| 715 |
+
|
| 716 |
+
```hcl
|
| 717 |
+
# Good: Consistent resource naming
|
| 718 |
+
resource "aws_instance" "web_server" {{
|
| 719 |
+
ami = data.aws_ami.ubuntu.id
|
| 720 |
+
instance_type = var.instance_type
|
| 721 |
+
|
| 722 |
+
tags = {{
|
| 723 |
+
Name = "web-server"
|
| 724 |
+
}}
|
| 725 |
+
}}
|
| 726 |
+
|
| 727 |
+
# Good: Aligned arguments
|
| 728 |
+
variable "instance_type" {{
|
| 729 |
+
description = "EC2 instance type"
|
| 730 |
+
type = string
|
| 731 |
+
default = "t3.micro"
|
| 732 |
+
}}
|
| 733 |
+
```
|
| 734 |
+
"""
|
| 735 |
+
|
| 736 |
+
return [types.TextContent(type="text", text=response)]
|
| 737 |
+
|
| 738 |
+
async def _handle_generate_docs(self, args: dict) -> list[types.TextContent]:
|
| 739 |
+
"""Handle documentation generation"""
|
| 740 |
+
config_content = args.get("config_content")
|
| 741 |
+
module_name = args.get("module_name", "terraform-module")
|
| 742 |
+
include_examples = args.get("include_examples", True)
|
| 743 |
+
|
| 744 |
+
# Extract information from config
|
| 745 |
+
resources = re.findall(r'resource\s+"([^"]+)"\s+"([^"]+)"', config_content)
|
| 746 |
+
variables = re.findall(r'variable\s+"([^"]+)"\s*\{', config_content)
|
| 747 |
+
outputs = re.findall(r'output\s+"([^"]+)"\s*\{', config_content)
|
| 748 |
+
modules = re.findall(r'module\s+"([^"]+)"\s*\{', config_content)
|
| 749 |
+
|
| 750 |
+
response = f"""# 📚 Terraform Documentation: {module_name}
|
| 751 |
+
|
| 752 |
+
## 📋 Module Overview
|
| 753 |
+
|
| 754 |
+
This Terraform configuration manages infrastructure resources with the following components:
|
| 755 |
+
|
| 756 |
+
"""
|
| 757 |
+
|
| 758 |
+
# Resources section
|
| 759 |
+
if resources:
|
| 760 |
+
response += f"""## 🏗️ Resources Created
|
| 761 |
+
|
| 762 |
+
| Resource Type | Name | Description |
|
| 763 |
+
|---------------|------|-------------|
|
| 764 |
+
"""
|
| 765 |
+
for resource_type, name in resources:
|
| 766 |
+
response += f"| `{resource_type}` | `{name}` | {resource_type.replace('_', ' ').title()} resource |\n"
|
| 767 |
+
response += "\n"
|
| 768 |
+
|
| 769 |
+
# Modules section
|
| 770 |
+
if modules:
|
| 771 |
+
response += f"""## 📦 Modules Used
|
| 772 |
+
|
| 773 |
+
| Module Name | Description |
|
| 774 |
+
|-------------|-------------|
|
| 775 |
+
"""
|
| 776 |
+
for module in modules:
|
| 777 |
+
response += f"| `{module}` | External module for {module.replace('_', ' ')} |\n"
|
| 778 |
+
response += "\n"
|
| 779 |
+
|
| 780 |
+
# Variables section
|
| 781 |
+
if variables:
|
| 782 |
+
response += f"""## 📥 Input Variables
|
| 783 |
+
|
| 784 |
+
| Name | Description | Type | Default |
|
| 785 |
+
|------|-------------|------|---------|
|
| 786 |
+
"""
|
| 787 |
+
for var in variables:
|
| 788 |
+
response += f"| `{var}` | Description for {var} | `string` | `null` |\n"
|
| 789 |
+
response += "\n"
|
| 790 |
+
|
| 791 |
+
# Outputs section
|
| 792 |
+
if outputs:
|
| 793 |
+
response += f"""## 📤 Outputs
|
| 794 |
+
|
| 795 |
+
| Name | Description |
|
| 796 |
+
|------|-------------|
|
| 797 |
+
"""
|
| 798 |
+
for output in outputs:
|
| 799 |
+
response += f"| `{output}` | Output description for {output} |\n"
|
| 800 |
+
response += "\n"
|
| 801 |
+
|
| 802 |
+
# Usage examples
|
| 803 |
+
if include_examples:
|
| 804 |
+
response += f"""## 🚀 Usage Examples
|
| 805 |
+
|
| 806 |
+
### Basic Usage
|
| 807 |
+
|
| 808 |
+
```hcl
|
| 809 |
+
module "{module_name}" {{
|
| 810 |
+
source = "./modules/{module_name}"
|
| 811 |
+
|
| 812 |
+
# Required variables
|
| 813 |
+
project_name = "my-project"
|
| 814 |
+
environment = "production"
|
| 815 |
+
|
| 816 |
+
# Optional variables with custom values
|
| 817 |
+
# Add your variable overrides here
|
| 818 |
+
|
| 819 |
+
tags = {{
|
| 820 |
+
Environment = "production"
|
| 821 |
+
Team = "infrastructure"
|
| 822 |
+
Project = "my-project"
|
| 823 |
+
}}
|
| 824 |
+
}}
|
| 825 |
+
```
|
| 826 |
+
|
| 827 |
+
### Advanced Usage
|
| 828 |
+
|
| 829 |
+
```hcl
|
| 830 |
+
module "{module_name}" {{
|
| 831 |
+
source = "./modules/{module_name}"
|
| 832 |
+
|
| 833 |
+
project_name = "advanced-project"
|
| 834 |
+
environment = "production"
|
| 835 |
+
|
| 836 |
+
# Advanced configuration options
|
| 837 |
+
# Customize based on your needs
|
| 838 |
+
|
| 839 |
+
tags = {{
|
| 840 |
+
Environment = "production"
|
| 841 |
+
Team = "platform"
|
| 842 |
+
CostCenter = "engineering"
|
| 843 |
+
Owner = "platform-team"
|
| 844 |
+
}}
|
| 845 |
+
}}
|
| 846 |
+
|
| 847 |
+
# Use module outputs
|
| 848 |
+
output "module_outputs" {{
|
| 849 |
+
description = "All outputs from {module_name} module"
|
| 850 |
+
value = module.{module_name}
|
| 851 |
+
}}
|
| 852 |
+
```
|
| 853 |
+
|
| 854 |
+
"""
|
| 855 |
+
|
| 856 |
+
# Requirements section
|
| 857 |
+
response += f"""## 📋 Requirements
|
| 858 |
+
|
| 859 |
+
| Name | Version |
|
| 860 |
+
|------|---------|
|
| 861 |
+
| terraform | >= 1.0 |
|
| 862 |
+
| aws | >= 5.0 |
|
| 863 |
+
|
| 864 |
+
## 🔧 Installation & Setup
|
| 865 |
+
|
| 866 |
+
1. **Clone or download** this module
|
| 867 |
+
2. **Configure variables** in `terraform.tfvars`
|
| 868 |
+
3. **Initialize Terraform**: `terraform init`
|
| 869 |
+
4. **Plan deployment**: `terraform plan`
|
| 870 |
+
5. **Apply configuration**: `terraform apply`
|
| 871 |
+
|
| 872 |
+
## 🧪 Testing
|
| 873 |
+
|
| 874 |
+
```bash
|
| 875 |
+
# Validate configuration
|
| 876 |
+
terraform validate
|
| 877 |
+
|
| 878 |
+
# Format code
|
| 879 |
+
terraform fmt
|
| 880 |
+
|
| 881 |
+
# Plan deployment
|
| 882 |
+
terraform plan
|
| 883 |
+
|
| 884 |
+
# Apply in test environment
|
| 885 |
+
terraform apply -var="environment=test"
|
| 886 |
+
```
|
| 887 |
+
|
| 888 |
+
## 🛡️ Security Considerations
|
| 889 |
+
|
| 890 |
+
- Review all security groups and IAM policies
|
| 891 |
+
- Enable encryption for all storage resources
|
| 892 |
+
- Use least privilege access principles
|
| 893 |
+
- Regular security audits and updates
|
| 894 |
+
|
| 895 |
+
## 📞 Support
|
| 896 |
+
|
| 897 |
+
For questions or issues:
|
| 898 |
+
- Check the documentation
|
| 899 |
+
- Review example configurations
|
| 900 |
+
- Submit issues via your project's issue tracker
|
| 901 |
+
|
| 902 |
+
## 📜 License
|
| 903 |
+
|
| 904 |
+
[Add your license information here]
|
| 905 |
+
"""
|
| 906 |
+
|
| 907 |
+
return [types.TextContent(type="text", text=response)]
|
| 908 |
+
|
| 909 |
+
# ============================================================================
|
| 910 |
+
# MAIN ENTRY POINT FOR HF SPACES
|
| 911 |
+
# ============================================================================
|
| 912 |
+
|
| 913 |
+
async def main():
|
| 914 |
+
"""Main function for running the MCP server"""
|
| 915 |
+
|
| 916 |
+
# Initialize the clean MCP server
|
| 917 |
+
terraform_server = CleanTerraformMCPServer()
|
| 918 |
+
|
| 919 |
+
# Log startup information
|
| 920 |
+
logger.info("🚀 Clean Terraform MCP Server starting...")
|
| 921 |
+
logger.info("🔧 Available tools: generate_terraform_config, validate_terraform_config, get_deployment_workflow, convert_to_module, format_terraform_code, generate_terraform_docs")
|
| 922 |
+
logger.info("📦 No external dependencies - pure Terraform tooling")
|
| 923 |
+
|
| 924 |
+
# Run the server with stdio transport for HF Spaces/MCP clients
|
| 925 |
+
async with stdio_server() as (read_stream, write_stream):
|
| 926 |
+
await terraform_server.server.run(
|
| 927 |
+
read_stream,
|
| 928 |
+
write_stream,
|
| 929 |
+
InitializationOptions(
|
| 930 |
+
server_name="terraform-mcp-server",
|
| 931 |
+
server_version="1.0.0",
|
| 932 |
+
capabilities=terraform_server.server.get_capabilities(
|
| 933 |
+
notification_options=NotificationOptions(),
|
| 934 |
+
experimental_capabilities={}
|
| 935 |
+
)
|
| 936 |
+
)
|
| 937 |
+
)
|
| 938 |
+
|
| 939 |
+
if __name__ == "__main__":
|
| 940 |
+
asyncio.run(main())#!/usr/bin/env python3
|
| 941 |
+
"""
|
| 942 |
+
Clean Terraform MCP Server
|
| 943 |
+
Single responsibility: Terraform code generation, validation, and workflows
|
| 944 |
+
No external dependencies beyond MCP and standard library
|
| 945 |
+
"""
|
| 946 |
+
|
| 947 |
+
import asyncio
|
| 948 |
+
import json
|
| 949 |
+
import re
|
| 950 |
+
import os
|
| 951 |
+
from typing import Dict, List, Optional, Any
|
| 952 |
+
from dataclasses import dataclass
|
| 953 |
+
from datetime import datetime
|
| 954 |
+
import logging
|
| 955 |
+
|
| 956 |
+
# MCP Server imports
|
| 957 |
+
import mcp.types as types
|
| 958 |
+
from mcp.server.models import InitializationOptions
|
| 959 |
+
from mcp.server import NotificationOptions, Server
|
| 960 |
+
from mcp.server.stdio import stdio_server
|
| 961 |
+
|
| 962 |
+
# Configure logging
|
| 963 |
+
logging.basicConfig(level=logging.INFO)
|
| 964 |
+
logger = logging.getLogger(__name__)
|
| 965 |
+
|
| 966 |
+
# ============================================================================
|
| 967 |
+
# TERRAFORM CODE TEMPLATES AND GENERATORS
|
| 968 |
+
# ============================================================================
|
| 969 |
+
|
| 970 |
+
class TerraformTemplates:
|
| 971 |
+
"""Static Terraform code templates for different resource types"""
|
| 972 |
+
|
| 973 |
+
@staticmethod
|
| 974 |
+
def get_provider_block(provider: str, version: str = None) -> str:
|
| 975 |
+
"""Generate provider configuration block"""
|
| 976 |
+
versions = {
|
| 977 |
+
"aws": "~> 5.0",
|
| 978 |
+
"azurerm": "~> 3.0",
|
| 979 |
+
"google": "~> 4.0",
|
| 980 |
+
"kubernetes": "~> 2.0"
|
| 981 |
+
}
|
| 982 |
+
|
| 983 |
+
version = version or versions.get(provider, "latest")
|
| 984 |
+
|
| 985 |
+
if provider == "aws":
|
| 986 |
+
return f'''terraform {{
|
| 987 |
+
required_version = ">= 1.0"
|
| 988 |
+
required_providers {{
|
| 989 |
+
aws = {{
|
| 990 |
+
source = "hashicorp/aws"
|
| 991 |
+
version = "{version}"
|
| 992 |
+
}}
|
| 993 |
+
}}
|
| 994 |
+
}}
|
| 995 |
+
|
| 996 |
+
provider "aws" {{
|
| 997 |
+
region = var.aws_region
|
| 998 |
+
|
| 999 |
+
default_tags {{
|
| 1000 |
+
tags = {{
|
| 1001 |
+
Project = var.project_name
|
| 1002 |
+
ManagedBy = "Terraform"
|
| 1003 |
+
Environment = var.environment
|
| 1004 |
+
}}
|
| 1005 |
+
}}
|
| 1006 |
+
}}'''
|
| 1007 |
+
|
| 1008 |
+
elif provider == "azurerm":
|
| 1009 |
+
return f'''terraform {{
|
| 1010 |
+
required_version = ">= 1.0"
|
| 1011 |
+
required_providers {{
|
| 1012 |
+
azurerm = {{
|
| 1013 |
+
source = "hashicorp/azurerm"
|
| 1014 |
+
version = "{version}"
|
| 1015 |
+
}}
|
| 1016 |
+
}}
|
| 1017 |
+
}}
|
| 1018 |
+
|
| 1019 |
+
provider "azurerm" {{
|
| 1020 |
+
features {{}}
|
| 1021 |
+
}}'''
|
| 1022 |
+
|
| 1023 |
+
elif provider == "kubernetes":
|
| 1024 |
+
return f'''terraform {{
|
| 1025 |
+
required_version = ">= 1.0"
|
| 1026 |
+
required_providers {{
|
| 1027 |
+
kubernetes = {{
|
| 1028 |
+
source = "hashicorp/kubernetes"
|
| 1029 |
+
version = "{version}"
|
| 1030 |
+
}}
|
| 1031 |
+
}}
|
| 1032 |
+
}}
|
| 1033 |
+
|
| 1034 |
+
provider "kubernetes" {{
|
| 1035 |
+
config_path = "~/.kube/config"
|
| 1036 |
+
}}'''
|
| 1037 |
+
|
| 1038 |
+
else:
|
| 1039 |
+
return f'''terraform {{
|
| 1040 |
+
required_version = ">= 1.0"
|
| 1041 |
+
required_providers {{
|
| 1042 |
+
{provider} = {{
|
| 1043 |
+
source = "hashicorp/{provider}"
|
| 1044 |
+
version = "{version}"
|
| 1045 |
+
}}
|
| 1046 |
+
}}
|
| 1047 |
+
}}
|
| 1048 |
+
|
| 1049 |
+
provider "{provider}" {{
|
| 1050 |
+
# Configure the {provider} provider
|
| 1051 |
+
}}'''
|
| 1052 |
+
|
| 1053 |
+
@staticmethod
|
| 1054 |
+
def get_common_variables(provider: str) -> str:
|
| 1055 |
+
"""Generate common variables for a provider"""
|
| 1056 |
+
if provider == "aws":
|
| 1057 |
+
return '''variable "aws_region" {
|
| 1058 |
+
description = "AWS region"
|
| 1059 |
+
type = string
|
| 1060 |
+
default = "us-west-2"
|
| 1061 |
+
}
|
| 1062 |
+
|
| 1063 |
+
variable "project_name" {
|
| 1064 |
+
description = "Name of the project"
|
| 1065 |
+
type = string
|
| 1066 |
+
}
|
| 1067 |
+
|
| 1068 |
+
variable "environment" {
|
| 1069 |
+
description = "Environment name"
|
| 1070 |
+
type = string
|
| 1071 |
+
default = "dev"
|
| 1072 |
+
|
| 1073 |
+
validation {
|
| 1074 |
+
condition = contains(["dev", "staging", "prod"], var.environment)
|
| 1075 |
+
error_message = "Environment must be dev, staging, or prod."
|
| 1076 |
+
}
|
| 1077 |
+
}'''
|
| 1078 |
+
|
| 1079 |
+
elif provider == "azurerm":
|
| 1080 |
+
return '''variable "location" {
|
| 1081 |
+
description = "Azure region"
|
| 1082 |
+
type = string
|
| 1083 |
+
default = "East US"
|
| 1084 |
+
}
|
| 1085 |
+
|
| 1086 |
+
variable "project_name" {
|
| 1087 |
+
description = "Name of the project"
|
| 1088 |
+
type = string
|
| 1089 |
+
}
|
| 1090 |
+
|
| 1091 |
+
variable "environment" {
|
| 1092 |
+
description = "Environment name"
|
| 1093 |
+
type = string
|
| 1094 |
+
default = "dev"
|
| 1095 |
+
|
| 1096 |
+
validation {
|
| 1097 |
+
condition = contains(["dev", "staging", "prod"], var.environment)
|
| 1098 |
+
error_message = "Environment must be dev, staging, or prod."
|
| 1099 |
+
}
|
| 1100 |
+
}'''
|
| 1101 |
+
|
| 1102 |
+
else:
|
| 1103 |
+
return '''variable "project_name" {
|
| 1104 |
+
description = "Name of the project"
|
| 1105 |
+
type = string
|
| 1106 |
+
}
|
| 1107 |
+
|
| 1108 |
+
variable "environment" {
|
| 1109 |
+
description = "Environment name"
|
| 1110 |
+
type = string
|
| 1111 |
+
default = "dev"
|
| 1112 |
+
}'''
|
| 1113 |
+
|
| 1114 |
+
class TerraformCodeGenerator:
|
| 1115 |
+
"""Pure Terraform code generation without external dependencies"""
|
| 1116 |
+
|
| 1117 |
+
def __init__(self):
|
| 1118 |
+
self.templates = TerraformTemplates()
|
| 1119 |
+
|
| 1120 |
+
def generate_vpc_config(self, name: str, options: Dict) -> str:
|
| 1121 |
+
"""Generate AWS VPC configuration"""
|
| 1122 |
+
cidr = options.get("cidr", "10.0.0.0/16")
|
| 1123 |
+
azs = options.get("azs", ["us-west-2a", "us-west-2b"])
|
| 1124 |
+
enable_nat = options.get("enable_nat_gateway", True)
|
| 1125 |
+
|
| 1126 |
+
return f'''# {name} VPC Infrastructure Configuration
|
| 1127 |
+
{self.templates.get_provider_block("aws")}
|
| 1128 |
+
|
| 1129 |
+
{self.templates.get_common_variables("aws")}
|
| 1130 |
+
|
| 1131 |
+
variable "vpc_cidr" {{
|
| 1132 |
+
description = "CIDR block for VPC"
|
| 1133 |
+
type = string
|
| 1134 |
+
default = "{cidr}"
|
| 1135 |
+
|
| 1136 |
+
validation {{
|
| 1137 |
+
condition = can(cidrhost(var.vpc_cidr, 0))
|
| 1138 |
+
error_message = "VPC CIDR must be a valid IPv4 CIDR block."
|
| 1139 |
+
}}
|
| 1140 |
+
}}
|
| 1141 |
+
|
| 1142 |
+
# VPC Module
|
| 1143 |
+
module "{name}_vpc" {{
|
| 1144 |
+
source = "terraform-aws-modules/vpc/aws"
|
| 1145 |
+
|
| 1146 |
+
name = "${{var.environment}}-{name}-vpc"
|
| 1147 |
+
cidr = var.vpc_cidr
|
| 1148 |
+
|
| 1149 |
+
azs = {azs}
|
| 1150 |
+
public_subnets = [for k, v in {azs} : cidrsubnet(var.vpc_cidr, 8, k)]
|
| 1151 |
+
private_subnets = [for k, v in {azs} : cidrsubnet(var.vpc_cidr, 8, k + 10)]
|
| 1152 |
+
|
| 1153 |
+
enable_nat_gateway = {str(enable_nat).lower()}
|
| 1154 |
+
enable_vpn_gateway = false
|
| 1155 |
+
enable_dns_hostnames = true
|
| 1156 |
+
enable_dns_support = true
|
| 1157 |
+
|
| 1158 |
+
public_subnet_tags = {{
|
| 1159 |
+
Type = "Public"
|
| 1160 |
+
Tier = "Web"
|
| 1161 |
+
}}
|
| 1162 |
+
|
| 1163 |
+
private_subnet_tags = {{
|
| 1164 |
+
Type = "Private"
|
| 1165 |
+
Tier = "Application"
|
| 1166 |
+
}}
|
| 1167 |
+
|
| 1168 |
+
tags = {{
|
| 1169 |
+
Name = "${{var.environment}}-{name}-vpc"
|
| 1170 |
+
}}
|
| 1171 |
+
}}
|
| 1172 |
+
|
| 1173 |
+
# Outputs
|
| 1174 |
+
output "vpc_id" {{
|
| 1175 |
+
description = "ID of the VPC"
|
| 1176 |
+
value = module.{name}_vpc.vpc_id
|
| 1177 |
+
}}
|
| 1178 |
+
|
| 1179 |
+
output "vpc_cidr_block" {{
|
| 1180 |
+
description = "CIDR block of the VPC"
|
| 1181 |
+
value = module.{name}_vpc.vpc_cidr_block
|
| 1182 |
+
}}
|
| 1183 |
+
|
| 1184 |
+
output "public_subnets" {{
|
| 1185 |
+
description = "List of IDs of public subnets"
|
| 1186 |
+
value = module.{name}_vpc.public_subnets
|
| 1187 |
+
}}
|
| 1188 |
+
|
| 1189 |
+
output "private_subnets" {{
|
| 1190 |
+
description = "List of IDs of private subnets"
|
| 1191 |
+
value = module.{name}_vpc.private_subnets
|
| 1192 |
+
}}
|
| 1193 |
+
|
| 1194 |
+
output "nat_gateway_ips" {{
|
| 1195 |
+
description = "List of public Elastic IPs for NAT Gateway"
|
| 1196 |
+
value = module.{name}_vpc.nat_public_ips
|
| 1197 |
+
}}'''
|
| 1198 |
+
|
| 1199 |
+
def generate_ec2_config(self, name: str, options: Dict) -> str:
|
| 1200 |
+
"""Generate AWS EC2 configuration"""
|
| 1201 |
+
instance_type = options.get("instance_type", "t3.micro")
|
| 1202 |
+
key_pair = options.get("key_pair_name", f"{name}-key")
|
| 1203 |
+
ami_filter = options.get("ami_filter", "amzn2-ami-hvm-*-x86_64-gp2")
|
| 1204 |
+
|
| 1205 |
+
return f'''# {name} EC2 Instance Configuration
|
| 1206 |
+
{self.templates.get_provider_block("aws")}
|
| 1207 |
+
|
| 1208 |
+
{self.templates.get_common_variables("aws")}
|
| 1209 |
+
|
| 1210 |
+
variable "instance_type" {{
|
| 1211 |
+
description = "EC2 instance type"
|
| 1212 |
+
type = string
|
| 1213 |
+
default = "{instance_type}"
|
| 1214 |
+
|
| 1215 |
+
validation {{
|
| 1216 |
+
condition = contains([
|
| 1217 |
+
"t3.nano", "t3.micro", "t3.small", "t3.medium", "t3.large",
|
| 1218 |
+
"t3.xlarge", "t3.2xlarge", "m5.large", "m5.xlarge"
|
| 1219 |
+
], var.instance_type)
|
| 1220 |
+
error_message = "Instance type must be a valid instance type."
|
| 1221 |
+
}}
|
| 1222 |
+
}}
|
| 1223 |
+
|
| 1224 |
+
variable "key_pair_name" {{
|
| 1225 |
+
description = "Name of the AWS key pair"
|
| 1226 |
+
type = string
|
| 1227 |
+
default = "{key_pair}"
|
| 1228 |
+
}}
|
| 1229 |
+
|
| 1230 |
+
# Data sources
|
| 1231 |
+
data "aws_ami" "app_ami" {{
|
| 1232 |
+
most_recent = true
|
| 1233 |
+
owners = ["amazon"]
|
| 1234 |
+
|
| 1235 |
+
filter {{
|
| 1236 |
+
name = "name"
|
| 1237 |
+
values = ["{ami_filter}"]
|
| 1238 |
+
}}
|
| 1239 |
+
|
| 1240 |
+
filter {{
|
| 1241 |
+
name = "virtualization-type"
|
| 1242 |
+
values = ["hvm"]
|
| 1243 |
+
}}
|
| 1244 |
+
}}
|
| 1245 |
+
|
| 1246 |
+
data "aws_vpc" "default" {{
|
| 1247 |
+
default = true
|
| 1248 |
+
}}
|
| 1249 |
+
|
| 1250 |
+
data "aws_subnets" "default" {{
|
| 1251 |
+
filter {{
|
| 1252 |
+
name = "vpc-id"
|
| 1253 |
+
values = [data.aws_vpc.default.id]
|
| 1254 |
+
}}
|
| 1255 |
+
}}
|
| 1256 |
+
|
| 1257 |
+
# EC2 Instance Module
|
| 1258 |
+
module "{name}_instance" {{
|
| 1259 |
+
source = "terraform-aws-modules/ec2-instance/aws"
|
| 1260 |
+
|
| 1261 |
+
name = "{name}-instance"
|
| 1262 |
+
|
| 1263 |
+
instance_type = var.instance_type
|
| 1264 |
+
ami = data.aws_ami.app_ami.id
|
| 1265 |
+
key_name = var.key_pair_name
|
| 1266 |
+
monitoring = true
|
| 1267 |
+
vpc_security_group_ids = [aws_security_group.{name}_sg.id]
|
| 1268 |
+
subnet_id = data.aws_subnets.default.ids[0]
|
| 1269 |
+
|
| 1270 |
+
create_iam_instance_profile = true
|
| 1271 |
+
iam_role_description = "IAM role for {name} EC2 instance"
|
| 1272 |
+
iam_role_policies = {{
|
| 1273 |
+
AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
|
| 1274 |
+
}}
|
| 1275 |
+
|
| 1276 |
+
user_data_base64 = base64encode(local.user_data)
|
| 1277 |
+
|
| 1278 |
+
root_block_device = [
|
| 1279 |
+
{{
|
| 1280 |
+
encrypted = true
|
| 1281 |
+
volume_type = "gp3"
|
| 1282 |
+
throughput = 200
|
| 1283 |
+
volume_size = 20
|
| 1284 |
+
tags = {{
|
| 1285 |
+
Name = "{name}-root-block"
|
| 1286 |
+
}}
|
| 1287 |
+
}},
|
| 1288 |
+
]
|
| 1289 |
+
|
| 1290 |
+
tags = {{
|
| 1291 |
+
Name = "{name}-instance"
|
| 1292 |
+
}}
|
| 1293 |
+
}}
|
| 1294 |
+
|
| 1295 |
+
# Security Group
|
| 1296 |
+
resource "aws_security_group" "{name}_sg" {{
|
| 1297 |
+
name_prefix = "{name}-sg"
|
| 1298 |
+
description = "Security group for {name} instance"
|
| 1299 |
+
vpc_id = data.aws_vpc.default.id
|
| 1300 |
+
|
| 1301 |
+
ingress {{
|
| 1302 |
+
description = "SSH"
|
| 1303 |
+
from_port = 22
|
| 1304 |
+
to_port = 22
|
| 1305 |
+
protocol = "tcp"
|
| 1306 |
+
cidr_blocks = ["10.0.0.0/8"]
|
| 1307 |
+
}}
|
| 1308 |
+
|
| 1309 |
+
ingress {{
|
| 1310 |
+
description = "HTTP"
|
| 1311 |
+
from_port = 80
|
| 1312 |
+
to_port = 80
|
| 1313 |
+
protocol = "tcp"
|
| 1314 |
+
cidr_blocks = ["0.0.0.0/0"]
|
| 1315 |
+
}}
|
| 1316 |
+
|
| 1317 |
+
ingress {{
|
| 1318 |
+
description = "HTTPS"
|
| 1319 |
+
from_port = 443
|
| 1320 |
+
to_port = 443
|
| 1321 |
+
protocol = "tcp"
|
| 1322 |
+
cidr_blocks = ["0.0.0.0/0"]
|
| 1323 |
+
}}
|
| 1324 |
+
|
| 1325 |
+
egress {{
|
| 1326 |
+
from_port = 0
|
| 1327 |
+
to_port = 0
|
| 1328 |
+
protocol = "-1"
|
| 1329 |
+
cidr_blocks = ["0.0.0.0/0"]
|
| 1330 |
+
}}
|
| 1331 |
+
|
| 1332 |
+
tags = {{
|
| 1333 |
+
Name = "{name}-security-group"
|
| 1334 |
+
}}
|
| 1335 |
+
}}
|
| 1336 |
+
|
| 1337 |
+
# User data script
|
| 1338 |
+
locals {{
|
| 1339 |
+
user_data = <<-EOT
|
| 1340 |
+
#!/bin/bash
|
| 1341 |
+
yum update -y
|
| 1342 |
+
yum install -y httpd
|
| 1343 |
+
systemctl start httpd
|
| 1344 |
+
systemctl enable httpd
|
| 1345 |
+
echo "<h1>Hello from {name}</h1>" > /var/www/html/index.html
|
| 1346 |
+
|
| 1347 |
+
# Install CloudWatch agent
|
| 1348 |
+
wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
|
| 1349 |
+
rpm -U ./amazon-cloudwatch-agent.rpm
|
| 1350 |
+
EOT
|
| 1351 |
+
}}
|
| 1352 |
+
|
| 1353 |
+
# Outputs
|
| 1354 |
+
output "instance_id" {{
|
| 1355 |
+
description = "ID of the EC2 instance"
|
| 1356 |
+
value = module.{name}_instance.id
|
| 1357 |
+
}}
|
| 1358 |
+
|
| 1359 |
+
output "instance_public_ip" {{
|
| 1360 |
+
description = "Public IP address of the EC2 instance"
|
| 1361 |
+
value = module.{name}_instance.public_ip
|
| 1362 |
+
}}
|
| 1363 |
+
|
| 1364 |
+
output "instance_private_ip" {{
|
| 1365 |
+
description = "Private IP address of the EC2 instance"
|
| 1366 |
+
value = module.{name}_instance.private_ip
|
| 1367 |
+
}}
|
| 1368 |
+
|
| 1369 |
+
output "security_group_id" {{
|
| 1370 |
+
description = "ID of the security group"
|
| 1371 |
+
value = aws_security_group.{name}_sg.id
|
| 1372 |
+
}}'''
|
| 1373 |
+
|
| 1374 |
+
def generate_s3_config(self, name: str, options: Dict) -> str:
|
| 1375 |
+
"""Generate AWS S3 bucket configuration"""
|
| 1376 |
+
versioning = options.get("versioning", True)
|
| 1377 |
+
encryption = options.get("encryption", True)
|
| 1378 |
+
public_access = options.get("block_public_access", True)
|
| 1379 |
+
|
| 1380 |
+
return f'''# {name} S3 Bucket Configuration
|
| 1381 |
+
{self.templates.get_provider_block("aws")}
|
| 1382 |
+
|
| 1383 |
+
{self.templates.get_common_variables("aws")}
|
| 1384 |
+
|
| 1385 |
+
variable "bucket_name" {{
|
| 1386 |
+
description = "Name of the S3 bucket (will be prefixed with random string)"
|
| 1387 |
+
type = string
|
| 1388 |
+
default = "{name}-bucket"
|
| 1389 |
+
}}
|
| 1390 |
+
|
| 1391 |
+
variable "enable_versioning" {{
|
| 1392 |
+
description = "Enable versioning for the S3 bucket"
|
| 1393 |
+
type = bool
|
| 1394 |
+
default = {str(versioning).lower()}
|
| 1395 |
+
}}
|
| 1396 |
+
|
| 1397 |
+
# Random suffix for bucket name uniqueness
|
| 1398 |
+
resource "random_string" "bucket_suffix" {{
|
| 1399 |
+
length = 8
|
| 1400 |
+
special = false
|
| 1401 |
+
upper = false
|
| 1402 |
+
}}
|
| 1403 |
+
|
| 1404 |
+
# S3 Bucket Module
|
| 1405 |
+
module "{name}_s3_bucket" {{
|
| 1406 |
+
source = "terraform-aws-modules/s3-bucket/aws"
|
| 1407 |
+
|
| 1408 |
+
bucket = "${{var.bucket_name}}-${{random_string.bucket_suffix.result}}"
|
| 1409 |
+
|
| 1410 |
+
# Versioning
|
| 1411 |
+
versioning = {{
|
| 1412 |
+
enabled = var.enable_versioning
|
| 1413 |
+
}}
|
| 1414 |
+
|
| 1415 |
+
# Server-side encryption
|
| 1416 |
+
server_side_encryption_configuration = {{
|
| 1417 |
+
rule = {{
|
| 1418 |
+
apply_server_side_encryption_by_default = {{
|
| 1419 |
+
sse_algorithm = "AES256"
|
| 1420 |
+
}}
|
| 1421 |
+
}}
|
| 1422 |
+
}}
|
| 1423 |
+
|
| 1424 |
+
# Public access block
|
| 1425 |
+
block_public_acls = {str(public_access).lower()}
|
| 1426 |
+
block_public_policy = {str(public_access).lower()}
|
| 1427 |
+
ignore_public_acls = {str(public_access).lower()}
|
| 1428 |
+
restrict_public_buckets = {str(public_access).lower()}
|
| 1429 |
+
|
| 1430 |
+
# Lifecycle configuration
|
| 1431 |
+
lifecycle_configuration = {{
|
| 1432 |
+
rule = [
|
| 1433 |
+
{{
|
| 1434 |
+
id = "delete_incomplete_multipart_uploads"
|
| 1435 |
+
status = "Enabled"
|
| 1436 |
+
|
| 1437 |
+
abort_incomplete_multipart_upload = {{
|
| 1438 |
+
days_after_initiation = 7
|
| 1439 |
+
}}
|
| 1440 |
+
}},
|
| 1441 |
+
{{
|
| 1442 |
+
id = "transition_to_ia"
|
| 1443 |
+
status = "Enabled"
|
| 1444 |
+
|
| 1445 |
+
transition = [
|
| 1446 |
+
{{
|
| 1447 |
+
days = 30
|
| 1448 |
+
storage_class = "STANDARD_IA"
|
| 1449 |
+
}},
|
| 1450 |
+
{{
|
| 1451 |
+
days = 90
|
| 1452 |
+
storage_class = "GLACIER"
|
| 1453 |
+
}}
|
| 1454 |
+
]
|
| 1455 |
+
}}
|
| 1456 |
+
]
|
| 1457 |
+
}}
|
| 1458 |
+
|
| 1459 |
+
tags = {{
|
| 1460 |
+
Name = "${{var.bucket_name}}-${{random_string.bucket_suffix.result}}"
|
| 1461 |
+
}}
|
| 1462 |
+
}}
|
| 1463 |
+
|
| 1464 |
+
# Bucket policy for additional security
|
| 1465 |
+
resource "aws_s3_bucket_policy" "{name}_bucket_policy" {{
|
| 1466 |
+
bucket = module.{name}_s3_bucket.s3_bucket_id
|
| 1467 |
+
|
| 1468 |
+
policy = jsonencode({{
|
| 1469 |
+
Version = "2012-10-17"
|
| 1470 |
+
Statement = [
|
| 1471 |
+
{{
|
| 1472 |
+
Sid = "DenyInsecureConnections"
|
| 1473 |
+
Effect = "Deny"
|
| 1474 |
+
Principal = "*"
|
| 1475 |
+
Action = "s3:*"
|
| 1476 |
+
Resource = [
|
| 1477 |
+
module.{name}_s3_bucket.s3_bucket_arn,
|
| 1478 |
+
"${{module.{name}_s3_bucket.s3_bucket_arn}}/*"
|
| 1479 |
+
]
|
| 1480 |
+
Condition = {{
|
| 1481 |
+
Bool = {{
|
| 1482 |
+
"aws:SecureTransport" = "false"
|
| 1483 |
+
}}
|
| 1484 |
+
}}
|
| 1485 |
+
}}
|
| 1486 |
+
]
|
| 1487 |
+
}})
|
| 1488 |
+
}}
|
| 1489 |
+
|
| 1490 |
+
# Outputs
|
| 1491 |
+
output "bucket_name" {{
|
| 1492 |
+
description = "Name of the S3 bucket"
|
| 1493 |
+
value = module.{name}_s3_bucket.s3_bucket_id
|
| 1494 |
+
}}
|
| 1495 |
+
|
| 1496 |
+
output "bucket_arn" {{
|
| 1497 |
+
description = "ARN of the S3 bucket"
|
| 1498 |
+
value = module.{name}_s3_bucket.s3_bucket_arn
|
| 1499 |
+
}}
|
| 1500 |
+
|
| 1501 |
+
output "bucket_domain_name" {{
|
| 1502 |
+
description = "Bucket domain name"
|
| 1503 |
+
value = module.{name}_s3_bucket.s3_bucket_bucket_domain_name
|
| 1504 |
+
}}
|
| 1505 |
+
|
| 1506 |
+
output "bucket_regional_domain_name" {{
|
| 1507 |
+
description = "Bucket regional domain name"
|
| 1508 |
+
value = module.{name}_s3_bucket.s3_bucket_bucket_regional_domain_name
|
| 1509 |
+
}}'''
|
| 1510 |
+
|
| 1511 |
+
def generate_eks_config(self, name: str, options: Dict) -> str:
|
| 1512 |
+
"""Generate AWS EKS cluster configuration"""
|
| 1513 |
+
node_instance_type = options.get("node_instance_type", "t3.medium")
|
| 1514 |
+
min_nodes = options.get("min_nodes", 1)
|
| 1515 |
+
max_nodes = options.get("max_nodes", 3)
|
| 1516 |
+
desired_nodes = options.get("desired_nodes", 2)
|
| 1517 |
+
k8s_version = options.get("kubernetes_version", "1.28")
|
| 1518 |
+
|
| 1519 |
+
return f'''# {name} EKS Cluster Configuration
|
| 1520 |
+
{self.templates.get_provider_block("aws")}
|
| 1521 |
+
|
| 1522 |
+
{self.templates.get_common_variables("aws")}
|
| 1523 |
+
|
| 1524 |
+
variable "cluster_name" {{
|
| 1525 |
+
description = "Name of the EKS cluster"
|
| 1526 |
+
type = string
|
| 1527 |
+
default = "{name}-eks-cluster"
|
| 1528 |
+
}}
|
| 1529 |
+
|
| 1530 |
+
variable "kubernetes_version" {{
|
| 1531 |
+
description = "Kubernetes version"
|
| 1532 |
+
type = string
|
| 1533 |
+
default = "{k8s_version}"
|
| 1534 |
+
}}
|
| 1535 |
+
|
| 1536 |
+
variable "node_instance_type" {{
|
| 1537 |
+
description = "Instance type for EKS nodes"
|
| 1538 |
+
type = string
|
| 1539 |
+
default = "{node_instance_type}"
|
| 1540 |
+
}}
|
| 1541 |
+
|
| 1542 |
+
variable "node_group_min_size" {{
|
| 1543 |
+
description = "Minimum number of nodes"
|
| 1544 |
+
type = number
|
| 1545 |
+
default = {min_nodes}
|
| 1546 |
+
}}
|
| 1547 |
+
|
| 1548 |
+
variable "node_group_max_size" {{
|
| 1549 |
+
description = "Maximum number of nodes"
|
| 1550 |
+
type = number
|
| 1551 |
+
default = {max_nodes}
|
| 1552 |
+
}}
|
| 1553 |
+
|
| 1554 |
+
variable "node_group_desired_size" {{
|
| 1555 |
+
description = "Desired number of nodes"
|
| 1556 |
+
type = number
|
| 1557 |
+
default = {desired_nodes}
|
| 1558 |
+
}}
|
| 1559 |
+
|
| 1560 |
+
# Data sources
|
| 1561 |
+
data "aws_availability_zones" "available" {{
|
| 1562 |
+
filter {{
|
| 1563 |
+
name = "opt-in-status"
|
| 1564 |
+
values = ["opt-in-not-required"]
|
| 1565 |
+
}}
|
| 1566 |
+
}}
|
| 1567 |
+
|
| 1568 |
+
data "aws_caller_identity" "current" {{}}
|
| 1569 |
+
|
| 1570 |
+
# VPC for EKS
|
| 1571 |
+
module "vpc" {{
|
| 1572 |
+
source = "terraform-aws-modules/vpc/aws"
|
| 1573 |
+
|
| 1574 |
+
name = "${{var.cluster_name}}-vpc"
|
| 1575 |
+
cidr = "10.0.0.0/16"
|
| 1576 |
+
|
| 1577 |
+
azs = slice(data.aws_availability_zones.available.names, 0, 3)
|
| 1578 |
+
public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
|
| 1579 |
+
private_subnets = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
|
| 1580 |
+
|
| 1581 |
+
enable_nat_gateway = true
|
| 1582 |
+
single_nat_gateway = true
|
| 1583 |
+
enable_dns_hostnames = true
|
| 1584 |
+
enable_dns_support = true
|
| 1585 |
+
|
| 1586 |
+
public_subnet_tags = {{
|
| 1587 |
+
"kubernetes.io/role/elb" = "1"
|
| 1588 |
+
}}
|
| 1589 |
+
|
| 1590 |
+
private_subnet_tags = {{
|
| 1591 |
+
"kubernetes.io/role/internal-elb" = "1"
|
| 1592 |
+
}}
|
| 1593 |
+
|
| 1594 |
+
tags = {{
|
| 1595 |
+
"kubernetes.io/cluster/${{var.cluster_name}}" = "shared"
|
| 1596 |
+
}}
|
| 1597 |
+
}}
|
| 1598 |
+
|
| 1599 |
+
# EKS Cluster
|
| 1600 |
+
module "eks" {{
|
| 1601 |
+
source = "terraform-aws-modules/eks/aws"
|
| 1602 |
+
|
| 1603 |
+
cluster_name = var.cluster_name
|
| 1604 |
+
cluster_version = var.kubernetes_version
|
| 1605 |
+
|
| 1606 |
+
vpc_id = module.vpc.vpc_id
|
| 1607 |
+
subnet_ids = module.vpc.private_subnets
|
| 1608 |
+
cluster_endpoint_public_access = true
|
| 1609 |
+
|
| 1610 |
+
cluster_addons = {{
|
| 1611 |
+
coredns = {{
|
| 1612 |
+
most_recent = true
|
| 1613 |
+
}}
|
| 1614 |
+
kube-proxy = {{
|
| 1615 |
+
most_recent = true
|
| 1616 |
+
}}
|
| 1617 |
+
vpc-cni = {{
|
| 1618 |
+
most_recent = true
|
| 1619 |
+
}}
|
| 1620 |
+
aws-ebs-csi-driver = {{
|
| 1621 |
+
most_recent = true
|
| 1622 |
+
}}
|
| 1623 |
+
}}
|
| 1624 |
+
|
| 1625 |
+
eks_managed_node_groups = {{
|
| 1626 |
+
main = {{
|
| 1627 |
+
name = "${{var.cluster_name}}-nodes"
|
| 1628 |
+
|
| 1629 |
+
instance_types = [var.node_instance_type]
|
| 1630 |
+
|
| 1631 |
+
min_size = var.node_group_min_size
|
| 1632 |
+
max_size = var.node_group_max_size
|
| 1633 |
+
desired_size = var.node_group_desired_size
|
| 1634 |
+
|
| 1635 |
+
disk_size = 50
|
| 1636 |
+
|
| 1637 |
+
labels = {{
|
| 1638 |
+
Environment = var.environment
|
| 1639 |
+
NodeGroup = "main"
|
| 1640 |
+
}}
|
| 1641 |
+
|
| 1642 |
+
tags = {{
|
| 1643 |
+
Name = "${{var.cluster_name}}-node"
|
| 1644 |
+
}}
|
| 1645 |
+
}}
|
| 1646 |
+
}}
|
| 1647 |
+
|
| 1648 |
+
# Cluster access entry
|
| 1649 |
+
access_entries = {{
|
| 1650 |
+
admin = {{
|
| 1651 |
+
kubernetes_groups = []
|
| 1652 |
+
principal_arn = data.aws_caller_identity.current.arn
|
| 1653 |
+
|
| 1654 |
+
policy_associations = {{
|
| 1655 |
+
admin = {{
|
| 1656 |
+
policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
|
| 1657 |
+
access_scope = {{
|
| 1658 |
+
type = "cluster"
|
| 1659 |
+
}}
|
| 1660 |
+
}}
|
| 1661 |
+
}}
|
| 1662 |
+
}}
|
| 1663 |
+
}}
|
| 1664 |
+
|
| 1665 |
+
tags = {{
|
| 1666 |
+
Environment = var.environment
|
| 1667 |
+
Terraform = "true"
|
| 1668 |
+
}}
|
| 1669 |
+
}}
|
| 1670 |
+
|
| 1671 |
+
# Outputs
|
| 1672 |
+
output "cluster_endpoint" {{
|
| 1673 |
+
description = "Endpoint for EKS control plane"
|
| 1674 |
+
value = module.eks.cluster_endpoint
|
| 1675 |
+
}}
|
| 1676 |
+
|
| 1677 |
+
output "cluster_security_group_id" {{
|
| 1678 |
+
description = "Security group ID attached to the cluster control plane"
|
| 1679 |
+
value = module.eks.cluster_security_group_id
|
| 1680 |
+
}}
|
| 1681 |
+
|
| 1682 |
+
output "cluster_name" {{
|
| 1683 |
+
description = "Kubernetes Cluster Name"
|
| 1684 |
+
value = module.eks.cluster_name
|
| 1685 |
+
}}
|
| 1686 |
+
|
| 1687 |
+
output "cluster_arn" {{
|
| 1688 |
+
description = "The Amazon Resource Name (ARN) of the cluster"
|
| 1689 |
+
value = module.eks.cluster_arn
|
| 1690 |
+
}}
|
| 1691 |
+
|
| 1692 |
+
output "cluster_certificate_authority_data" {{
|
| 1693 |
+
description = "Base64 encoded certificate data required to communicate with the cluster"
|
| 1694 |
+
value = module.eks.cluster_certificate_authority_data
|
| 1695 |
+
}}
|
| 1696 |
+
|
| 1697 |
+
output "configure_kubectl" {{
|
| 1698 |
+
description = "Configure kubectl: make sure you're logged in with the correct AWS profile and run the following command to update your kubeconfig"
|
| 1699 |
+
value = "aws eks --region ${{var.aws_region}} update-kubeconfig --name ${{module.eks.cluster_name}}"
|
| 1700 |
+
}}
|
| 1701 |
+
|
| 1702 |
+
output "vpc_id" {{
|
| 1703 |
+
description = "ID of the VPC where the cluster security group belongs"
|
| 1704 |
+
value = module.vpc.vpc_id
|
| 1705 |
+
}}'''
|
| 1706 |
+
|
| 1707 |
+
def generate_azure_vnet_config(self, name: str, options: Dict) -> str:
|
| 1708 |
+
"""Generate Azure Virtual Network configuration"""
|
| 1709 |
+
address_space = options.get("address_space", ["10.0.0.0/16"])
|
| 1710 |
+
|
| 1711 |
+
return f'''# {name} Azure Virtual Network Configuration
|
| 1712 |
+
{self.templates.get_provider_block("azurerm")}
|
| 1713 |
+
|
| 1714 |
+
{self.templates.get_common_variables("azurerm")}
|
| 1715 |
+
|
| 1716 |
+
variable "address_space" {{
|
| 1717 |
+
description = "Address space for the virtual network"
|
| 1718 |
+
type = list(string)
|
| 1719 |
+
default = {address_space}
|
| 1720 |
+
}}
|
| 1721 |
+
|
| 1722 |
+
# Resource Group
|
| 1723 |
+
resource "azurerm_resource_group" "{name}_rg" {{
|
| 1724 |
+
name = "${{var.environment}}-{name}-rg"
|
| 1725 |
+
location = var.location
|
| 1726 |
+
|
| 1727 |
+
tags = {{
|
| 1728 |
+
Environment = var.environment
|
| 1729 |
+
Project = var.project_name
|
| 1730 |
+
}}
|
| 1731 |
+
}}
|
| 1732 |
+
|
| 1733 |
+
# Virtual Network Module
|
| 1734 |
+
module "{name}_vnet" {{
|
| 1735 |
+
source = "Azure/vnet/azurerm"
|
| 1736 |
+
|
| 1737 |
+
resource_group_name = azurerm_resource_group.{name}_rg.name
|
| 1738 |
+
location = azurerm_resource_group.{name}_rg.location
|
| 1739 |
+
vnet_name = "${{var.environment}}-{name}-vnet"
|
| 1740 |
+
address_space = var.address_space
|
| 1741 |
+
|
| 1742 |
+
subnet_prefixes = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
|
| 1743 |
+
subnet_names = ["subnet1", "subnet2", "subnet3"]
|
| 1744 |
+
|
| 1745 |
+
tags = {{
|
| 1746 |
+
Environment = var.environment
|
| 1747 |
+
Project = var.project_name
|
| 1748 |
+
}}
|
| 1749 |
+
}}
|
| 1750 |
+
|
| 1751 |
+
# Outputs
|
| 1752 |
+
output "vnet_id" {{
|
| 1753 |
+
description = "The ID of the Virtual Network"
|
| 1754 |
+
value = module.{name}_vnet.vnet_id
|
| 1755 |
+
}}
|
| 1756 |
+
|
| 1757 |
+
output "vnet_name" {{
|
| 1758 |
+
description = "The name of the Virtual Network"
|
| 1759 |
+
value = module.{name}_vnet.vnet_name
|
| 1760 |
+
}}
|
| 1761 |
+
|
| 1762 |
+
output "subnet_ids" {{
|
| 1763 |
+
description = "The IDs of the subnets"
|
| 1764 |
+
value = module.{name}_vnet.vnet_subnets
|
| 1765 |
+
}}
|
| 1766 |
+
|
| 1767 |
+
output "resource_group_name" {{
|
| 1768 |
+
description = "The name of the resource group"
|
| 1769 |
+
value = azurerm_resource_group.{name}_rg.name
|
| 1770 |
+
}}'''
|
| 1771 |
+
|
| 1772 |
+
# ============================================================================
|
| 1773 |
+
# TERRAFORM VALIDATION AND UTILITIES
|
| 1774 |
+
# ============================================================================
|
| 1775 |
+
|
| 1776 |
+
class TerraformValidator:
|
| 1777 |
+
"""Terraform configuration validation utilities"""
|
| 1778 |
+
|
| 1779 |
+
@staticmethod
|
| 1780 |
+
def validate_syntax(config: str) -> List[str]:
|
| 1781 |
+
"""Basic Terraform syntax validation"""
|
| 1782 |
+
issues = []
|
| 1783 |
+
|
| 1784 |
+
# Check for terraform block
|
| 1785 |
+
if not re.search(r'terraform\s*\{', config):
|
| 1786 |
+
issues.append("⚠️ Missing terraform {} block")
|
| 1787 |
+
|
| 1788 |
+
# Check for provider block
|
| 1789 |
+
if not re.search(r'provider\s+"[^"]+"\s*\{', config):
|
| 1790 |
+
issues.append("⚠️ Missing provider configuration")
|
| 1791 |
+
|
| 1792 |
+
# Check for version constraints
|
| 1793 |
+
if not re.search(r'version\s*=\s*"[^"]+"', config):
|
| 1794 |
+
issues.append("⚠️ Missing provider version constraints")
|
| 1795 |
+
|
| 1796 |
+
# Check resource naming conventions
|
| 1797 |
+
resources = re.findall(r'resource\s+"([^"]+)"\s+"([^"]+)"', config)
|
| 1798 |
+
for resource_type, resource_name in resources:
|
| 1799 |
+
if re.search(r'[A-Z-]', resource_name):
|
| 1800 |
+
issues.append(f"⚠️ Resource '{resource_name}' should use lowercase with underscores")
|
| 1801 |
+
|
| 1802 |
+
# Check for variable descriptions
|
| 1803 |
+
variables = re.findall(r'variable\s+"([^"]+)"\s*\{', config)
|
| 1804 |
+
for var_name in variables:
|
| 1805 |
+
var_block = re.search(f'variable\\s+"{var_name}"\\s*\\{{([^}}]+)\\}}', config, re.DOTALL)
|
| 1806 |
+
if var_block and 'description' not in var_block.group(1):
|
| 1807 |
+
issues.append(f"💡 Variable '{var_name}' missing description")
|
| 1808 |
+
|
| 1809 |
+
return issues
|
| 1810 |
+
|
| 1811 |
+
@staticmethod
|
| 1812 |
+
def validate_best_practices(config: str) -> List[str]:
|
| 1813 |
+
"""Check Terraform best practices"""
|
| 1814 |
+
suggestions = []
|
| 1815 |
+
|
| 1816 |
+
# Check for tags
|
| 1817 |
+
if 'tags' not in config.lower():
|
| 1818 |
+
suggestions.append("💡 Consider adding tags to resources")
|
| 1819 |
+
|
| 1820 |
+
# Check for remote state
|
| 1821 |
+
if 'backend' not in config:
|
| 1822 |
+
suggestions.append("💡 Consider using remote state storage")
|
| 1823 |
+
|
| 1824 |
+
# Check for data sources vs hardcoded values
|
| 1825 |
+
if re.search(r'ami-[a-f0-9]+', config):
|
| 1826 |
+
suggestions.append("💡 Consider using data sources instead of hardcoded AMI IDs")
|
| 1827 |
+
|
| 1828 |
+
# Check for variable validation
|
| 1829 |
+
if 'variable' in config and 'validation' not in config:
|
| 1830 |
+
suggestions.append("💡 Consider adding validation rules to variables")
|
| 1831 |
+
|
| 1832 |
+
# Check for outputs
|
| 1833 |
+
if 'resource' in config and 'output' not in config:
|
| 1834 |
+
suggestions.append("💡 Consider adding outputs for important resource attributes")
|
| 1835 |
+
|
| 1836 |
+
return suggestions
|
| 1837 |
+
|
| 1838 |
+
class TerraformWorkflows:
|
| 1839 |
+
"""Terraform deployment workflow generators"""
|
| 1840 |
+
|
| 1841 |
+
@staticmethod
|
| 1842 |
+
def get_basic_workflow() -> List[str]:
|
| 1843 |
+
"""Get basic Terraform workflow commands"""
|
| 1844 |
+
return [
|
| 1845 |
+
"terraform init",
|
| 1846 |
+
"terraform validate",
|
| 1847 |
+
"terraform fmt",
|
| 1848 |
+
"terraform plan",
|
| 1849 |
+
"terraform apply",
|
| 1850 |
+
"terraform output"
|
| 1851 |
+
]
|
| 1852 |
+
|
| 1853 |
+
@staticmethod
|
| 1854 |
+
def get_production_workflow(backend_type: str = "s3") -> List[str]:
|
| 1855 |
+
"""Get production-ready workflow commands"""
|
| 1856 |
+
commands = [
|
| 1857 |
+
f"# Production Terraform Workflow with {backend_type.upper()} backend",
|
| 1858 |
+
"",
|
| 1859 |
+
"# 1. Initialize with backend configuration",
|
| 1860 |
+
"terraform init",
|
| 1861 |
+
"",
|
| 1862 |
+
"# 2. Validate configuration",
|
| 1863 |
+
"terraform validate",
|
| 1864 |
+
"",
|
| 1865 |
+
"# 3. Format code",
|
| 1866 |
+
"terraform fmt",
|
| 1867 |
+
"",
|
| 1868 |
+
"# 4. Security scan (optional)",
|
| 1869 |
+
"# tfsec .",
|
| 1870 |
+
"",
|
| 1871 |
+
"# 5. Plan and save",
|
| 1872 |
+
"terraform plan -out=tfplan",
|
| 1873 |
+
"",
|
| 1874 |
+
"# 6. Review plan output carefully",
|
| 1875 |
+
"terraform show tfplan",
|
| 1876 |
+
"",
|
| 1877 |
+
"# 7. Apply saved plan",
|
| 1878 |
+
"terraform apply tfplan",
|
| 1879 |
+
"",
|
| 1880 |
+
"# 8. Verify outputs",
|
| 1881 |
+
"terraform output",
|
| 1882 |
+
"",
|
| 1883 |
+
"# 9. Clean up plan file",
|
| 1884 |
+
"rm tfplan"
|
| 1885 |
+
]
|
| 1886 |
+
|
| 1887 |
+
if backend_type == "s3":
|
| 1888 |
+
commands.insert(4, "# Configure S3 backend if not done")
|
| 1889 |
+
commands.insert(5, "# terraform init -backend-config=backend.hcl")
|
| 1890 |
+
commands.insert(6, "")
|
| 1891 |
+
|
| 1892 |
+
return commands
|
| 1893 |
+
|
| 1894 |
+
@staticmethod
|
| 1895 |
+
def get_module_workflow() -> List[str]:
|
| 1896 |
+
"""Get module development workflow"""
|
| 1897 |
+
return [
|
| 1898 |
+
"# Module Development Workflow",
|
| 1899 |
+
"",
|
| 1900 |
+
"# 1. Initialize module",
|
| 1901 |
+
"terraform init",
|
| 1902 |
+
"",
|
| 1903 |
+
"# 2. Validate module",
|
| 1904 |
+
"terraform validate",
|
| 1905 |
+
"",
|
| 1906 |
+
"# 3. Format module code",
|
| 1907 |
+
"terraform fmt -recursive",
|
| 1908 |
+
"",
|
| 1909 |
+
"# 4. Generate documentation",
|
| 1910 |
+
"# terraform-docs markdown . > README.md",
|
| 1911 |
+
"",
|
| 1912 |
+
"# 5. Test module (in examples/ directory)",
|
| 1913 |
+
"cd examples/basic",
|
| 1914 |
+
"terraform init",
|
| 1915 |
+
"terraform plan",
|
| 1916 |
+
"",
|
| 1917 |
+
"# 6. Clean up test",
|
| 1918 |
+
"terraform destroy",
|
| 1919 |
+
"cd ../.
|