awslabs.terraform-mcp-server 0.0.2__py3-none-any.whl → 0.0.7__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.

Potentially problematic release.


This version of awslabs.terraform-mcp-server might be problematic. Click here for more details.

@@ -1,5 +1,6 @@
1
1
  """Tool implementations for the terraform MCP server."""
2
2
 
3
+ from .search_user_provided_module import search_user_provided_module_impl
3
4
  from .execute_terraform_command import execute_terraform_command_impl
4
5
  from .search_aws_provider_docs import search_aws_provider_docs_impl
5
6
  from .search_awscc_provider_docs import search_awscc_provider_docs_impl
@@ -7,6 +8,7 @@ from .search_specific_aws_ia_modules import search_specific_aws_ia_modules_impl
7
8
  from .run_checkov_scan import run_checkov_scan_impl
8
9
 
9
10
  __all__ = [
11
+ 'search_user_provided_module_impl',
10
12
  'execute_terraform_command_impl',
11
13
  'search_aws_provider_docs_impl',
12
14
  'search_awscc_provider_docs_impl',
@@ -0,0 +1,346 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4
+ # with the License. A copy of the License is located at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
9
+ # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
10
+ # and limitations under the License.
11
+ """Implementation of user provided module from the Terraform registry search tool."""
12
+
13
+ import re
14
+ import requests
15
+ import traceback
16
+ from awslabs.terraform_mcp_server.impl.tools.utils import (
17
+ clean_description,
18
+ extract_outputs_from_readme,
19
+ get_github_release_details,
20
+ get_variables_tf,
21
+ )
22
+ from awslabs.terraform_mcp_server.models import (
23
+ SearchUserProvidedModuleRequest,
24
+ SearchUserProvidedModuleResult,
25
+ TerraformOutput,
26
+ TerraformVariable,
27
+ )
28
+ from loguru import logger
29
+ from typing import Any, Dict, Optional, Tuple
30
+ from urllib.parse import urlparse
31
+
32
+
33
+ async def search_user_provided_module_impl(
34
+ request: SearchUserProvidedModuleRequest,
35
+ ) -> SearchUserProvidedModuleResult:
36
+ """Analyze a Terraform module from the registry.
37
+
38
+ This tool takes a Terraform registry module URL and analyzes its input variables,
39
+ output variables, README, and other details to provide comprehensive information
40
+ about the module.
41
+
42
+ Parameters:
43
+ request: Details about the Terraform module to analyze
44
+
45
+ Returns:
46
+ A SearchUserProvidedModuleResult object containing module information
47
+ """
48
+ logger.info(f'Analyzing Terraform module: {request.module_url}')
49
+
50
+ try:
51
+ # Parse the module URL to extract namespace, name, and provider
52
+ module_parts = parse_module_url(request.module_url)
53
+ if not module_parts:
54
+ return SearchUserProvidedModuleResult(
55
+ status='error',
56
+ module_name='unknown',
57
+ module_url=request.module_url,
58
+ module_version='unknown',
59
+ module_description='',
60
+ variables=[],
61
+ outputs=[],
62
+ readme_content=None,
63
+ error_message=f'Invalid module URL format: {request.module_url}. Expected format: [namespace]/[name]/[provider] or registry.terraform.io/[namespace]/[name]/[provider]',
64
+ )
65
+
66
+ namespace, name, provider = module_parts
67
+
68
+ # Fetch module details from Terraform Registry
69
+ module_details = await get_module_details(namespace, name, provider, request.version)
70
+ if not module_details:
71
+ return SearchUserProvidedModuleResult(
72
+ status='error',
73
+ module_name=name,
74
+ module_url=request.module_url,
75
+ module_version=request.version or 'latest',
76
+ module_description='',
77
+ variables=[],
78
+ outputs=[],
79
+ readme_content=None,
80
+ error_message=f'Failed to fetch module details from Terraform Registry: {request.module_url}',
81
+ )
82
+
83
+ # Extract module information
84
+ module_version = module_details.get('version', request.version or 'latest')
85
+ module_description = clean_description(module_details.get('description', ''))
86
+ readme_content = module_details.get('readme_content', '')
87
+
88
+ # Get variables and outputs
89
+ variables = []
90
+ outputs = []
91
+
92
+ # Extract variables from module details
93
+ if 'variables' in module_details and module_details['variables']:
94
+ variables = [TerraformVariable(**var_data) for var_data in module_details['variables']]
95
+ elif 'root' in module_details and 'inputs' in module_details['root']:
96
+ # Extract from registry API format
97
+ for var_name, var_data in module_details['root']['inputs'].items():
98
+ variables.append(
99
+ TerraformVariable(
100
+ name=var_name,
101
+ type=var_data.get('type', ''),
102
+ description=var_data.get('description', ''),
103
+ default=var_data.get('default'),
104
+ required=var_data.get('required', True),
105
+ )
106
+ )
107
+
108
+ # Extract outputs from module details
109
+ if 'outputs' in module_details and module_details['outputs']:
110
+ outputs = [
111
+ TerraformOutput(name=output['name'], description=output.get('description', ''))
112
+ for output in module_details['outputs']
113
+ ]
114
+ elif 'root' in module_details and 'outputs' in module_details['root']:
115
+ # Extract from registry API format
116
+ for output_name, output_data in module_details['root']['outputs'].items():
117
+ outputs.append(
118
+ TerraformOutput(
119
+ name=output_name,
120
+ description=output_data.get('description', ''),
121
+ )
122
+ )
123
+ elif readme_content:
124
+ # Try to extract outputs from README
125
+ extracted_outputs = extract_outputs_from_readme(readme_content)
126
+ if extracted_outputs:
127
+ outputs = [
128
+ TerraformOutput(name=output['name'], description=output.get('description', ''))
129
+ for output in extracted_outputs
130
+ ]
131
+
132
+ # Create the result
133
+ result = SearchUserProvidedModuleResult(
134
+ status='success',
135
+ module_name=name,
136
+ module_url=request.module_url,
137
+ module_version=module_version,
138
+ module_description=module_description,
139
+ variables=variables,
140
+ outputs=outputs,
141
+ readme_content=readme_content,
142
+ error_message=None,
143
+ )
144
+
145
+ return result
146
+
147
+ except Exception as e:
148
+ logger.error(f'Error analyzing Terraform module: {e}')
149
+ logger.debug(f'Stack trace: {traceback.format_exc()}')
150
+ return SearchUserProvidedModuleResult(
151
+ status='error',
152
+ module_name=request.module_url.split('/')[-2]
153
+ if '/' in request.module_url
154
+ else 'unknown',
155
+ module_url=request.module_url,
156
+ module_version=request.version or 'latest',
157
+ module_description='',
158
+ variables=[],
159
+ outputs=[],
160
+ readme_content=None,
161
+ error_message=f'Error analyzing Terraform module: {str(e)}',
162
+ )
163
+
164
+
165
+ def parse_module_url(module_url: str) -> Optional[Tuple[str, str, str]]:
166
+ """Parse a Terraform module URL to extract namespace, name, and provider.
167
+
168
+ Args:
169
+ module_url: The module URL or identifier (e.g., "hashicorp/consul/aws" or "registry.terraform.io/hashicorp/consul/aws")
170
+
171
+ Returns:
172
+ Tuple containing (namespace, name, provider) or None if invalid format
173
+ """
174
+ # First, handle registry.terraform.io URLs (with or without scheme)
175
+ parsed_url = None
176
+
177
+ # If URL has a scheme (http://, https://)
178
+ if '://' in module_url:
179
+ parsed_url = urlparse(module_url)
180
+ # For URLs without scheme, add a dummy scheme to enable proper URL parsing
181
+ else:
182
+ parsed_url = urlparse(f'https://{module_url}')
183
+
184
+ # Check if this is a registry.terraform.io URL
185
+ if parsed_url.netloc == 'registry.terraform.io':
186
+ # Extract path and remove leading slash
187
+ path = parsed_url.path.lstrip('/')
188
+ parts = path.split('/')
189
+ else:
190
+ # Simple module path format (namespace/name/provider)
191
+ parts = module_url.split('/')
192
+
193
+ # Ensure we have at least namespace/name/provider
194
+ if len(parts) < 3:
195
+ return None
196
+
197
+ namespace = parts[0]
198
+ name = parts[1]
199
+ provider = parts[2]
200
+
201
+ return namespace, name, provider
202
+
203
+
204
+ async def get_module_details(
205
+ namespace: str, name: str, provider: str, version: Optional[str] = None
206
+ ) -> Dict[str, Any]:
207
+ """Fetch detailed information about a Terraform module from the registry.
208
+
209
+ Args:
210
+ namespace: The module namespace (e.g., hashicorp)
211
+ name: The module name (e.g., consul)
212
+ provider: The provider (e.g., aws)
213
+ version: Optional specific version to fetch
214
+
215
+ Returns:
216
+ Dictionary containing module details
217
+ """
218
+ logger.info(f'Fetching details for module {namespace}/{name}/{provider}')
219
+
220
+ try:
221
+ # Get basic module info via API
222
+ details_url = f'https://registry.terraform.io/v1/modules/{namespace}/{name}/{provider}'
223
+ if version:
224
+ details_url += f'/{version}'
225
+
226
+ logger.debug(f'Making API request to: {details_url}')
227
+
228
+ response = requests.get(details_url)
229
+ response.raise_for_status()
230
+
231
+ details = response.json()
232
+ logger.debug(
233
+ f'Received module details. Status code: {response.status_code}, Content size: {len(response.text)} bytes'
234
+ )
235
+
236
+ # Get the version
237
+ module_version = version or details.get('version', '')
238
+ if not module_version and 'latest' in details and 'version' in details['latest']:
239
+ module_version = details['latest']['version']
240
+
241
+ # Try to get README content and version details
242
+ readme_content = None
243
+ version_details = None
244
+
245
+ # APPROACH 1: Try to see if the registry API provides README content directly
246
+ logger.debug('Checking for README content in API response')
247
+ if 'readme' in details and details['readme']:
248
+ readme_content = details['readme']
249
+ logger.info(
250
+ f'Found README content directly in API response: {len(readme_content)} chars'
251
+ )
252
+
253
+ # APPROACH 2: Try using the GitHub repo URL for README content and version details
254
+ if 'source' in details:
255
+ source_url = details.get('source')
256
+ # Validate GitHub URL using regex
257
+ if isinstance(source_url, str) and re.match(r'https://github.com/', source_url):
258
+ logger.info(f'Found GitHub source URL: {source_url}')
259
+
260
+ # Extract GitHub owner and repo
261
+ github_parts = re.match(r'https://github.com/([^/]+)/([^/]+)', source_url)
262
+ if github_parts:
263
+ owner, repo = github_parts.groups()
264
+ logger.info(f'Extracted GitHub repo: {owner}/{repo}')
265
+
266
+ # Get version details from GitHub
267
+ github_version_info = await get_github_release_details(owner, repo)
268
+ version_details = github_version_info['details']
269
+ version_from_github = github_version_info['version']
270
+
271
+ if version_from_github:
272
+ logger.info(f'Found version from GitHub: {version_from_github}')
273
+ if not module_version:
274
+ module_version = version_from_github
275
+
276
+ # Get variables.tf content and parsed variables
277
+ variables_content, variables = await get_variables_tf(owner, repo, 'main')
278
+ if variables_content and variables:
279
+ logger.info(f'Found variables.tf with {len(variables)} variables')
280
+ details['variables_content'] = variables_content
281
+ details['variables'] = [var.dict() for var in variables]
282
+ else:
283
+ # Try master branch as fallback
284
+ variables_content, variables = await get_variables_tf(
285
+ owner, repo, 'master'
286
+ )
287
+ if variables_content and variables:
288
+ logger.info(
289
+ f'Found variables.tf in master branch with {len(variables)} variables'
290
+ )
291
+ details['variables_content'] = variables_content
292
+ details['variables'] = [var.dict() for var in variables]
293
+
294
+ # If README content not already found, try fetching it from GitHub
295
+ if not readme_content:
296
+ logger.debug(f'Fetching README from GitHub source: {source_url}')
297
+
298
+ # Try main branch first, then fall back to master if needed
299
+ for branch in ['main', 'master']:
300
+ raw_readme_url = f'https://raw.githubusercontent.com/{owner}/{repo}/{branch}/README.md'
301
+ logger.debug(f'Trying to fetch README from: {raw_readme_url}')
302
+
303
+ readme_response = requests.get(raw_readme_url)
304
+ if readme_response.status_code == 200:
305
+ readme_content = readme_response.text
306
+ logger.info(
307
+ f'Successfully fetched README from GitHub ({branch}): {len(readme_content)} chars'
308
+ )
309
+ break
310
+
311
+ # Add readme_content to details if available
312
+ if readme_content:
313
+ logger.info(f'Successfully extracted README content ({len(readme_content)} chars)')
314
+
315
+ # Extract outputs from README content
316
+ outputs = extract_outputs_from_readme(readme_content)
317
+ if outputs:
318
+ logger.info(f'Extracted {len(outputs)} outputs from README')
319
+ details['outputs'] = outputs
320
+
321
+ # Trim if too large
322
+ if len(readme_content) > 8000:
323
+ logger.debug(
324
+ f'README content exceeds 8000 characters ({len(readme_content)}), truncating...'
325
+ )
326
+ readme_content = readme_content[:8000] + '...\n[README truncated due to length]'
327
+ logger.debug('README content truncated')
328
+
329
+ details['readme_content'] = readme_content
330
+ else:
331
+ logger.warning('No README content found through any method')
332
+
333
+ # Add version details if available
334
+ if version_details:
335
+ logger.info('Adding version details to response')
336
+ details['version_details'] = version_details
337
+
338
+ # Add version to details
339
+ details['version'] = module_version
340
+
341
+ return details
342
+
343
+ except Exception as e:
344
+ logger.error(f'Error fetching module details: {e}')
345
+ logger.debug(f'Stack trace: {traceback.format_exc()}')
346
+ return {}
@@ -10,6 +10,8 @@ from .models import (
10
10
  CheckovScanResult,
11
11
  TerraformVariable,
12
12
  TerraformOutput,
13
+ SearchUserProvidedModuleRequest,
14
+ SearchUserProvidedModuleResult,
13
15
  )
14
16
 
15
17
  __all__ = [
@@ -24,4 +26,6 @@ __all__ = [
24
26
  'CheckovScanResult',
25
27
  'TerraformVariable',
26
28
  'TerraformOutput',
29
+ 'SearchUserProvidedModuleRequest',
30
+ 'SearchUserProvidedModuleResult',
27
31
  ]
@@ -258,3 +258,47 @@ class CheckovScanResult(BaseModel):
258
258
  )
259
259
  summary: Dict[str, Any] = Field({}, description='Summary of scan results')
260
260
  raw_output: Optional[str] = Field(None, description='Raw output from Checkov')
261
+
262
+
263
+ class SearchUserProvidedModuleRequest(BaseModel):
264
+ """Request model for searching user-provided Terraform modules.
265
+
266
+ Attributes:
267
+ module_url: URL of the Terraform module in the registry (e.g., 'hashicorp/consul/aws').
268
+ version: Optional specific version of the module to analyze.
269
+ variables: Optional dictionary of variables to use when analyzing the module.
270
+ """
271
+
272
+ module_url: str = Field(
273
+ ..., description='URL or identifier of the Terraform module (e.g., "hashicorp/consul/aws")'
274
+ )
275
+ version: Optional[str] = Field(None, description='Specific version of the module to analyze')
276
+ variables: Optional[Dict[str, Any]] = Field(
277
+ None, description='Variables to use when analyzing the module'
278
+ )
279
+
280
+
281
+ class SearchUserProvidedModuleResult(BaseModel):
282
+ """Result model for searching user-provided Terraform modules.
283
+
284
+ Attributes:
285
+ status: Execution status (success/error).
286
+ module_name: Name of the analyzed module.
287
+ module_url: URL of the module in the registry.
288
+ module_version: Version of the module that was analyzed.
289
+ module_description: Description of the module.
290
+ variables: List of variables defined by the module.
291
+ outputs: List of outputs provided by the module.
292
+ readme_content: The README content of the module.
293
+ error_message: Optional error message if execution failed.
294
+ """
295
+
296
+ status: Literal['success', 'error']
297
+ module_name: str
298
+ module_url: str
299
+ module_version: str
300
+ module_description: str
301
+ variables: List[TerraformVariable] = Field([], description='Variables defined by the module')
302
+ outputs: List[TerraformOutput] = Field([], description='Outputs provided by the module')
303
+ readme_content: Optional[str] = Field(None, description='README content of the module')
304
+ error_message: Optional[str] = Field(None, description='Error message if execution failed')
@@ -12,11 +12,14 @@ from awslabs.terraform_mcp_server.impl.tools import (
12
12
  search_aws_provider_docs_impl,
13
13
  search_awscc_provider_docs_impl,
14
14
  search_specific_aws_ia_modules_impl,
15
+ search_user_provided_module_impl,
15
16
  )
16
17
  from awslabs.terraform_mcp_server.models import (
17
18
  CheckovScanRequest,
18
19
  CheckovScanResult,
19
20
  ModuleSearchResult,
21
+ SearchUserProvidedModuleRequest,
22
+ SearchUserProvidedModuleResult,
20
23
  TerraformAWSCCProviderDocsResult,
21
24
  TerraformAWSProviderDocsResult,
22
25
  TerraformExecutionRequest,
@@ -29,7 +32,7 @@ from awslabs.terraform_mcp_server.static import (
29
32
  )
30
33
  from mcp.server.fastmcp import FastMCP
31
34
  from pydantic import Field
32
- from typing import Dict, List, Literal, Optional
35
+ from typing import Any, Dict, List, Literal, Optional
33
36
 
34
37
 
35
38
  mcp = FastMCP(
@@ -264,6 +267,54 @@ async def run_checkov_scan(
264
267
  return await run_checkov_scan_impl(request)
265
268
 
266
269
 
270
+ @mcp.tool(name='SearchUserProvidedModule')
271
+ async def search_user_provided_module(
272
+ module_url: str = Field(
273
+ ..., description='URL or identifier of the Terraform module (e.g., "hashicorp/consul/aws")'
274
+ ),
275
+ version: Optional[str] = Field(None, description='Specific version of the module to analyze'),
276
+ variables: Optional[Dict[str, Any]] = Field(
277
+ None, description='Variables to use when analyzing the module'
278
+ ),
279
+ ) -> SearchUserProvidedModuleResult:
280
+ """Search for a user-provided Terraform registry module and understand its inputs, outputs, and usage.
281
+
282
+ This tool takes a Terraform registry module URL and analyzes its input variables,
283
+ output variables, README, and other details to provide comprehensive information
284
+ about the module.
285
+
286
+ The module URL should be in the format "namespace/name/provider" (e.g., "hashicorp/consul/aws")
287
+ or "registry.terraform.io/namespace/name/provider".
288
+
289
+ Examples:
290
+ - To search for the HashiCorp Consul module:
291
+ search_user_provided_module(module_url='hashicorp/consul/aws')
292
+
293
+ - To search for a specific version of a module:
294
+ search_user_provided_module(module_url='terraform-aws-modules/vpc/aws', version='3.14.0')
295
+
296
+ - To search for a module with specific variables:
297
+ search_user_provided_module(
298
+ module_url='terraform-aws-modules/eks/aws',
299
+ variables={'cluster_name': 'my-cluster', 'vpc_id': 'vpc-12345'}
300
+ )
301
+
302
+ Parameters:
303
+ module_url: URL or identifier of the Terraform module (e.g., "hashicorp/consul/aws")
304
+ version: Optional specific version of the module to analyze
305
+ variables: Optional dictionary of variables to use when analyzing the module
306
+
307
+ Returns:
308
+ A SearchUserProvidedModuleResult object containing module information
309
+ """
310
+ request = SearchUserProvidedModuleRequest(
311
+ module_url=module_url,
312
+ version=version,
313
+ variables=variables,
314
+ )
315
+ return await search_user_provided_module_impl(request)
316
+
317
+
267
318
  # * Resources
268
319
  @mcp.resource(
269
320
  name='terraform_development_workflow',
@@ -70,6 +70,10 @@ When implementing specific AWS resources (only after confirming no suitable AWS-
70
70
  3. `SearchSpecificAwsIaModules`
71
71
  * Use for specialized AI/ML infrastructure needs
72
72
  * Returns details for supported AWS-IA modules
73
+ 4. `SearchUserProvidedModule`
74
+ * Analyze any Terraform Registry module by URL or identifier
75
+ * Extract input variables, output variables, and README content
76
+ * Understand module usage and configuration options
73
77
 
74
78
  ### Command Execution Tools
75
79
 
@@ -108,6 +112,9 @@ The AWSCC provider (Cloud Control API-based) offers:
108
112
  - "Is this VPC configuration secure? Let's scan it with Checkov."
109
113
  - "Find documentation for awscc_lambda_function to ensure we're using the preferred provider."
110
114
  - "We need a Bedrock implementation for RAG. Let's search for AWS-IA modules that can help."
115
+ - "Use the terraform-aws-modules/vpc/aws module to implement a VPC"
116
+ - "Search for the hashicorp/consul/aws module and explain how to use it"
117
+ - "What variables are required for the terraform-aws-modules/eks/aws module?"
111
118
 
112
119
  ## Best Practices
113
120
 
@@ -83,6 +83,11 @@ flowchart TD
83
83
  * FIRST check for specialized AWS-IA modules (`SearchSpecificAwsIaModules` tool)
84
84
  * If no suitable module exists, THEN use AWSCC provider resources (`SearchAwsccProviderDocs` tool)
85
85
  * ONLY fall back to traditional AWS provider (`SearchAwsProviderDocs` tool) when the above options don't meet requirements
86
+ - When a user provides a specific Terraform Registry module to use:
87
+ * Use the `SearchUserProvidedModule` tool to analyze the module
88
+ * Extract input variables, output variables, and README content
89
+ * Understand module usage and configuration options
90
+ * Provide guidance on how to use the module correctly
86
91
  - MCP Resources and tools to consult:
87
92
  - Resources
88
93
  - *terraform_development_workflow* to consult this guide and to use it to ensure you're following the development workflow correctly
@@ -91,6 +96,7 @@ flowchart TD
91
96
  - *terraform_aws_provider_resources_listing* for available AWS resources
92
97
  - Tools
93
98
  - *SearchSpecificAwsIaModules* tool to check for specialized AWS-IA modules first (Bedrock, OpenSearch Serverless, SageMaker, Streamlit)
99
+ - *SearchUserProvidedModule* tool to analyze any Terraform Registry module provided by the user
94
100
  - *SearchAwsccProviderDocs* tool to look up specific Cloud Control API resources
95
101
  - *SearchAwsProviderDocs* tool to look up specific resource documentation
96
102
  2. Validate Code
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awslabs.terraform-mcp-server
3
- Version: 0.0.2
3
+ Version: 0.0.7
4
4
  Summary: An AWS Labs Model Context Protocol (MCP) server for terraform
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: beautifulsoup4>=4.12.0
@@ -45,6 +45,12 @@ MCP server for Terraform on AWS best practices, infrastructure as code patterns,
45
45
  - SageMaker endpoint deployment for ML model hosting
46
46
  - Serverless Streamlit application deployment for AI interfaces
47
47
 
48
+ - **Terraform Registry Module Analysis** - Analyze Terraform Registry modules
49
+ - Search for modules by URL or identifier
50
+ - Extract input variables, output variables, and README content
51
+ - Understand module usage and configuration options
52
+ - Analyze module structure and dependencies
53
+
48
54
  - **Terraform Workflow Execution** - Run Terraform commands directly
49
55
  - Initialize, plan, validate, apply, and destroy operations
50
56
  - Pass variables and specify AWS regions
@@ -1,32 +1,29 @@
1
1
  awslabs/__init__.py,sha256=4zfFn3N0BkvQmMTAIvV_QAbKp6GWzrwaUN17YeRoChM,115
2
2
  awslabs/terraform_mcp_server/__init__.py,sha256=a-zIkwClerA84_XGykBggO4w5kf8f85EapnWnbAH01c,58
3
- awslabs/terraform_mcp_server/server.py,sha256=3N5A5eP6YB0YqCw6nbekzpXctd724rgtu--6zqvFlfA,13626
3
+ awslabs/terraform_mcp_server/server.py,sha256=ZyX_w3oath8l2T3tH63EyH5J_l93LZGCxW2HCeL4t2s,15787
4
4
  awslabs/terraform_mcp_server/impl/resources/__init__.py,sha256=bxqHGDtuwWq8w-21lT5GzOuxBqkmnUvW6cnSA36ve3A,388
5
5
  awslabs/terraform_mcp_server/impl/resources/terraform_aws_provider_resources_listing.py,sha256=t_guGQ44yvip9u2cONN2cWc3fXk6HyQGPu5DDP5YEkI,1973
6
6
  awslabs/terraform_mcp_server/impl/resources/terraform_awscc_provider_resources_listing.py,sha256=RoBSe-p5OyLYWNYa7aGx2f4NWyjap1wNrj8F4o8_PSo,2060
7
- awslabs/terraform_mcp_server/impl/tools/__init__.py,sha256=TRlf5cGGVKfofMGwX_T8YXvnq8Evcyl0AzabiwIZyFc,601
7
+ awslabs/terraform_mcp_server/impl/tools/__init__.py,sha256=4D7D6cYF98qj4lltiO5UOHNFRl8Gfg7Zv9n-wRlOj3o,715
8
8
  awslabs/terraform_mcp_server/impl/tools/execute_terraform_command.py,sha256=_fPxC2wT23oWa9XaAoUEV1j5lK7Xpi7MqBAq85fLNdU,8507
9
9
  awslabs/terraform_mcp_server/impl/tools/run_checkov_scan.py,sha256=oS6qgUPPti7lpa2VNsUwPbswiq2hgpe49fggVUI_PfM,13435
10
10
  awslabs/terraform_mcp_server/impl/tools/search_aws_provider_docs.py,sha256=w7NX4oQwsCeVQPj8YZzvXvXcox8drzZimW3mRL8Kd84,29049
11
11
  awslabs/terraform_mcp_server/impl/tools/search_awscc_provider_docs.py,sha256=fY8eRQ1j6oB4XyfAoUC6mTUXNkoFaY0vsmMhV6w0NR8,27297
12
12
  awslabs/terraform_mcp_server/impl/tools/search_specific_aws_ia_modules.py,sha256=Kny5iharA1Wj-9tnhoPHntu4qM9n-2mC2dB6TXwhqfk,20148
13
+ awslabs/terraform_mcp_server/impl/tools/search_user_provided_module.py,sha256=6d_QHnDwpGicMsmQmpfs0hzTtf-XAVXZBNUsBnkfxtw,14491
13
14
  awslabs/terraform_mcp_server/impl/tools/utils.py,sha256=GB1OuhYgrg00AzKfhgaUpQwbVBK5D56GQLcm60NHd1c,20500
14
- awslabs/terraform_mcp_server/models/__init__.py,sha256=L7nlJJ9g_J7tuYw_BD_XarEcwH3WonrIoOBZcD5FC7A,651
15
- awslabs/terraform_mcp_server/models/models.py,sha256=ZVn0gFlqjYTD0mNvlvXTilNhk2o9Mse9oUEBbl38Cbo,10685
15
+ awslabs/terraform_mcp_server/models/__init__.py,sha256=-kCRp9lGMz0-SXv0XvXP48_dVCeMbQIJqkxLTqXlmhk,801
16
+ awslabs/terraform_mcp_server/models/models.py,sha256=oQutL78FdbXXQgekXjp8Dhotq-A-wBJLylQAHRMyNs0,12614
16
17
  awslabs/terraform_mcp_server/scripts/generate_aws_provider_resources.py,sha256=v8t4UL8KwIoqVYz25Xd8wiJJ2cwKmHvxaF0_ZpUI0b4,56714
17
18
  awslabs/terraform_mcp_server/scripts/generate_awscc_provider_resources.py,sha256=FN2QOYefeEcyy-JXl1v3hLEz2YSBNRJB_9i182DdWiA,48722
18
19
  awslabs/terraform_mcp_server/scripts/scrape_aws_terraform_best_practices.py,sha256=t_eFbciBwZXdK6pNkkcp4X7VPR4vXyXkmE_YLhm6Xr4,3783
19
20
  awslabs/terraform_mcp_server/static/AWSCC_PROVIDER_RESOURCES.md,sha256=I_vu3dWXzd9Pxcd7tkdxFQs97wtuunyoXdJCAkCUXnE,440270
20
21
  awslabs/terraform_mcp_server/static/AWS_PROVIDER_RESOURCES.md,sha256=OMboscC0Ov6O3U3K1uqrzRzx18nq_5AsJzITc5-CA8Y,303030
21
22
  awslabs/terraform_mcp_server/static/AWS_TERRAFORM_BEST_PRACTICES.md,sha256=cftJ9y2nZ0kMendoV6WBlQFsNw-QnGnmF6dR88eoYdA,87665
22
- awslabs/terraform_mcp_server/static/MCP_INSTRUCTIONS.md,sha256=k6XQFgab3_W7_g0AZNXdyxyrzA2RjOzP9DbXS_9FiP4,6010
23
- awslabs/terraform_mcp_server/static/TERRAFORM_WORKFLOW_GUIDE.md,sha256=qvqtaikyyQ_XHL-LyU_BDos1ym2kKBR7ljMZtLn3FAs,9322
23
+ awslabs/terraform_mcp_server/static/MCP_INSTRUCTIONS.md,sha256=P82tv9KJJxhoG5UFKaG16nQS5a0ud6Fcvg_BUypz-z0,6445
24
+ awslabs/terraform_mcp_server/static/TERRAFORM_WORKFLOW_GUIDE.md,sha256=1RrSF7NH0BNVnQTZFwPhuaWsxElmCcwvl07iLOnO2gw,9768
24
25
  awslabs/terraform_mcp_server/static/__init__.py,sha256=J5JGKYybg48XyBi2hepC101RDNFBxSXZS5YGvj0tql8,549
25
- awslabs/terraform_mcp_server/tests/__init__.py,sha256=eWyaXCIPzCC8_4kXuKnK9s79gg6_J3XYoAtQzkNesWI,45
26
- awslabs/terraform_mcp_server/tests/run_tests.sh,sha256=u6GUSQffYJyn0RkAd4qeqkczYc_cf-lL3kC4a1YYjB4,1068
27
- awslabs/terraform_mcp_server/tests/test_parameter_annotations.py,sha256=7DrC-idSQmrSAAPLHQ09V3sU7a_8HkfCrOnqJn2SQeo,8361
28
- awslabs/terraform_mcp_server/tests/test_tool_implementations.py,sha256=Uh8vyFJJ9ti14hkmQDkWJEtGUJvFR3IZ0P90ez0LiMY,11641
29
- awslabs_terraform_mcp_server-0.0.2.dist-info/METADATA,sha256=wNYoFnJY-jLzLtJuCwGHQ8kxrIUC9uEp8D4BvYWxQiY,4344
30
- awslabs_terraform_mcp_server-0.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
- awslabs_terraform_mcp_server-0.0.2.dist-info/entry_points.txt,sha256=jCTPQeUJ74jpIDcYwVHQCk-y0n0ehRFFerh_Qm4ZU1c,90
32
- awslabs_terraform_mcp_server-0.0.2.dist-info/RECORD,,
26
+ awslabs_terraform_mcp_server-0.0.7.dist-info/METADATA,sha256=FelC966-qfJFl-RXXsRb-pNp8oHC1e9WUNqjtDIs4no,4633
27
+ awslabs_terraform_mcp_server-0.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
+ awslabs_terraform_mcp_server-0.0.7.dist-info/entry_points.txt,sha256=jCTPQeUJ74jpIDcYwVHQCk-y0n0ehRFFerh_Qm4ZU1c,90
29
+ awslabs_terraform_mcp_server-0.0.7.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- """Test package for terraform_mcp_server."""
@@ -1,35 +0,0 @@
1
- #!/bin/bash
2
- # Script to run the Terraform MCP server tests
3
-
4
- SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
5
- PROJECT_ROOT="$SCRIPT_DIR/../../.."
6
-
7
- # Set PYTHONPATH to include the project root
8
- export PYTHONPATH="$PROJECT_ROOT:$PYTHONPATH"
9
-
10
- # Function to run a test module
11
- run_test() {
12
- echo "Running $1..."
13
- cd "$PROJECT_ROOT"
14
- python -m awslabs.terraform_mcp_server.tests.$1
15
- echo "Test completed: $1"
16
- }
17
-
18
- # Get the test name from the first argument, default to all tests
19
- TEST_NAME=$1
20
- if [ -z "$TEST_NAME" ]; then
21
- echo "=== Running All Tests ==="
22
- run_test "test_parameter_annotations"
23
- run_test "test_tool_implementations"
24
- elif [ "$TEST_NAME" == "params" ]; then
25
- run_test "test_parameter_annotations"
26
- elif [ "$TEST_NAME" == "tools" ]; then
27
- run_test "test_tool_implementations"
28
- else
29
- echo "Unknown test: $TEST_NAME"
30
- echo "Usage: $0 [params|tools]"
31
- echo " params - Run parameter annotation tests"
32
- echo " tools - Run tool implementation tests"
33
- echo " (no args) - Run all tests"
34
- exit 1
35
- fi
@@ -1,207 +0,0 @@
1
- """Test script for verifying parameter annotations in MCP tools."""
2
-
3
- import json
4
- import sys
5
- from awslabs.terraform_mcp_server.server import mcp
6
- from pathlib import Path
7
-
8
-
9
- # Add project root to path to allow importing the server
10
- project_root = str(Path(__file__).parent.parent.parent.parent)
11
- if project_root not in sys.path:
12
- sys.path.insert(0, project_root)
13
-
14
-
15
- def print_tool_parameters():
16
- """Print the parameters for each tool after annotations are added."""
17
- tool_names = [
18
- 'SearchAwsProviderDocs',
19
- 'ExecuteTerraformCommand',
20
- 'SearchAwsccProviderDocs',
21
- 'SearchSpecificAwsIaModules',
22
- 'RunCheckovScan',
23
- ]
24
-
25
- print('\n=== Current Tool Parameter Schemas ===\n')
26
- for tool_name in tool_names:
27
- try:
28
- tool = mcp._tool_manager.get_tool(tool_name)
29
- if tool is None:
30
- print(f'Tool {tool_name} not found')
31
- continue
32
-
33
- if not hasattr(tool, 'parameters') or tool.parameters is None:
34
- print(f'Tool {tool_name} has no parameters schema')
35
- continue
36
-
37
- print(f'=== {tool_name} Parameters Schema ===')
38
- print(json.dumps(tool.parameters, indent=2))
39
- print('\n')
40
- except Exception as e:
41
- print(f'Error getting tool {tool_name}: {e}')
42
-
43
-
44
- def add_parameter_annotations():
45
- """Add parameter annotations to the MCP tools."""
46
- print('Adding parameter annotations to MCP tools...\n')
47
-
48
- # Add parameter descriptions for SearchAwsProviderDocs
49
- search_tool = mcp._tool_manager.get_tool('SearchAwsProviderDocs')
50
- if (
51
- search_tool is not None
52
- and hasattr(search_tool, 'parameters')
53
- and search_tool.parameters is not None
54
- ):
55
- if (
56
- 'properties' in search_tool.parameters
57
- and 'asset_name' in search_tool.parameters['properties']
58
- ):
59
- search_tool.parameters['properties']['asset_name']['description'] = (
60
- 'Name of the AWS service (asset) to look for (e.g., "aws_s3_bucket", "aws_lambda_function")'
61
- )
62
- if (
63
- 'properties' in search_tool.parameters
64
- and 'asset_type' in search_tool.parameters['properties']
65
- ):
66
- search_tool.parameters['properties']['asset_type']['description'] = (
67
- "Type of documentation to search - 'resource', 'data_source', or 'both' (default)"
68
- )
69
-
70
- # Add parameter descriptions for SearchAwsccProviderDocs
71
- awscc_docs_tool = mcp._tool_manager.get_tool('SearchAwsccProviderDocs')
72
- if (
73
- awscc_docs_tool is not None
74
- and hasattr(awscc_docs_tool, 'parameters')
75
- and awscc_docs_tool.parameters is not None
76
- ):
77
- if (
78
- 'properties' in awscc_docs_tool.parameters
79
- and 'asset_name' in awscc_docs_tool.parameters['properties']
80
- ):
81
- awscc_docs_tool.parameters['properties']['asset_name']['description'] = (
82
- 'Name of the AWSCC service (asset) to look for (e.g., awscc_s3_bucket, awscc_lambda_function)'
83
- )
84
- if (
85
- 'properties' in awscc_docs_tool.parameters
86
- and 'asset_type' in awscc_docs_tool.parameters['properties']
87
- ):
88
- awscc_docs_tool.parameters['properties']['asset_type']['description'] = (
89
- "Type of documentation to search - 'resource', 'data_source', or 'both' (default)"
90
- )
91
-
92
- # Add parameter descriptions for SearchSpecificAwsIaModules
93
- modules_tool = mcp._tool_manager.get_tool('SearchSpecificAwsIaModules')
94
- if (
95
- modules_tool is not None
96
- and hasattr(modules_tool, 'parameters')
97
- and modules_tool.parameters is not None
98
- ):
99
- if (
100
- 'properties' in modules_tool.parameters
101
- and 'query' in modules_tool.parameters['properties']
102
- ):
103
- modules_tool.parameters['properties']['query']['description'] = (
104
- 'Optional search term to filter modules (empty returns all four modules)'
105
- )
106
-
107
- # Add parameter descriptions for ExecuteTerraformCommand
108
- terraform_tool = mcp._tool_manager.get_tool('ExecuteTerraformCommand')
109
- if (
110
- terraform_tool is not None
111
- and hasattr(terraform_tool, 'parameters')
112
- and terraform_tool.parameters is not None
113
- ):
114
- if (
115
- 'properties' in terraform_tool.parameters
116
- and 'request' in terraform_tool.parameters['properties']
117
- ):
118
- terraform_tool.parameters['properties']['request']['description'] = (
119
- 'Details about the Terraform command to execute'
120
- )
121
-
122
- # Since request is a complex object with nested properties, update its schema
123
- if (
124
- 'properties' in terraform_tool.parameters['properties']['request']
125
- and 'properties'
126
- in terraform_tool.parameters['properties']['request']['properties']
127
- ):
128
- props = terraform_tool.parameters['properties']['request']['properties']
129
- if 'command' in props:
130
- props['command']['description'] = (
131
- 'Terraform command to execute (init, plan, validate, apply, destroy)'
132
- )
133
- if 'working_directory' in props:
134
- props['working_directory']['description'] = (
135
- 'Directory containing Terraform files'
136
- )
137
- if 'variables' in props:
138
- props['variables']['description'] = 'Terraform variables to pass'
139
- if 'aws_region' in props:
140
- props['aws_region']['description'] = 'AWS region to use'
141
- if 'strip_ansi' in props:
142
- props['strip_ansi']['description'] = (
143
- 'Whether to strip ANSI color codes from output'
144
- )
145
-
146
- # Add parameter descriptions for RunCheckovScan
147
- checkov_scan_tool = mcp._tool_manager.get_tool('RunCheckovScan')
148
- if (
149
- checkov_scan_tool is not None
150
- and hasattr(checkov_scan_tool, 'parameters')
151
- and checkov_scan_tool.parameters is not None
152
- ):
153
- if (
154
- 'properties' in checkov_scan_tool.parameters
155
- and 'request' in checkov_scan_tool.parameters['properties']
156
- ):
157
- checkov_scan_tool.parameters['properties']['request']['description'] = (
158
- 'Details about the Checkov scan to execute'
159
- )
160
-
161
- # Since request is a complex object with nested properties, update its schema
162
- if (
163
- 'properties' in checkov_scan_tool.parameters['properties']['request']
164
- and 'properties'
165
- in checkov_scan_tool.parameters['properties']['request']['properties']
166
- ):
167
- props = checkov_scan_tool.parameters['properties']['request']['properties']
168
- if 'working_directory' in props:
169
- props['working_directory']['description'] = (
170
- 'Directory containing Terraform files to scan'
171
- )
172
- if 'framework' in props:
173
- props['framework']['description'] = (
174
- 'Framework to scan (terraform, cloudformation, etc.)'
175
- )
176
- if 'check_ids' in props:
177
- props['check_ids']['description'] = (
178
- 'Optional list of specific check IDs to run'
179
- )
180
- if 'skip_check_ids' in props:
181
- props['skip_check_ids']['description'] = 'Optional list of check IDs to skip'
182
- if 'output_format' in props:
183
- props['output_format']['description'] = (
184
- 'Format for scan results (default: json)'
185
- )
186
-
187
- print('Parameter annotations added successfully.\n')
188
-
189
-
190
- def main():
191
- """Run the parameter annotation test."""
192
- print('=== Terraform MCP Parameter Annotation Test ===\n')
193
-
194
- # Print original parameter schemas
195
- print('Original parameter schemas:')
196
- print_tool_parameters()
197
-
198
- # Add parameter annotations
199
- add_parameter_annotations()
200
-
201
- # Print updated parameter schemas
202
- print('Updated parameter schemas:')
203
- print_tool_parameters()
204
-
205
-
206
- if __name__ == '__main__':
207
- main()
@@ -1,309 +0,0 @@
1
- """Test script for Terraform MCP server implementation functions."""
2
-
3
- import asyncio
4
- import json
5
- import sys
6
- from awslabs.terraform_mcp_server.impl.tools.search_aws_provider_docs import (
7
- search_aws_provider_docs_impl,
8
- )
9
- from awslabs.terraform_mcp_server.impl.tools.search_awscc_provider_docs import (
10
- search_awscc_provider_docs_impl,
11
- )
12
- from awslabs.terraform_mcp_server.impl.tools.search_specific_aws_ia_modules import (
13
- search_specific_aws_ia_modules_impl,
14
- )
15
- from loguru import logger
16
- from typing import Any
17
-
18
-
19
- # Configure logger for enhanced diagnostics with stacktraces
20
- logger.configure(
21
- handlers=[
22
- {
23
- 'sink': sys.stderr,
24
- 'backtrace': True,
25
- 'diagnose': True,
26
- 'format': '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>',
27
- }
28
- ]
29
- )
30
-
31
-
32
- def print_aws_provider_results(results):
33
- """Print formatted results data using the provided logger.
34
-
35
- Args:
36
- results: List of result objects containing asset information
37
- logger: Logger object to use for output
38
- """
39
- logger.info(f'Found {len(results)} results')
40
-
41
- for i, result in enumerate(results):
42
- logger.info(f'\nResult {i + 1}:')
43
- logger.info(f' Asset Name: {result.asset_name}')
44
- logger.info(f' Asset Type: {result.asset_type}')
45
- logger.info(f' URL: {result.url}')
46
-
47
- # Handle description
48
- if result.description:
49
- description_preview = (
50
- result.description[:50] + '...'
51
- if len(result.description) > 50
52
- else result.description
53
- )
54
- logger.info(f' Description: {description_preview}')
55
- else:
56
- logger.info(' No description')
57
-
58
- # Handle example usage
59
- if result.example_usage:
60
- logger.info(f' Example Usage: {len(result.example_usage)} found')
61
-
62
- # Handle arguments
63
- if result.arguments:
64
- logger.info(f' Arguments: {len(result.arguments)} found')
65
-
66
- # Handle attributes
67
- if result.attributes:
68
- logger.info(f' Attributes: {len(result.attributes)} found')
69
-
70
-
71
- def print_awscc_provider_results(results):
72
- """Print formatted results data using the provided logger.
73
-
74
- Args:
75
- results: List of result objects containing asset information
76
- logger: Logger object to use for output
77
- """
78
- logger.info(f'Found {len(results)} results')
79
-
80
- for i, result in enumerate(results):
81
- logger.info(f'\nResult {i + 1}:')
82
- logger.info(f' Asset Name: {result.asset_name}')
83
- logger.info(f' Asset Type: {result.asset_type}')
84
- logger.info(f' URL: {result.url}')
85
-
86
- # Handle description
87
- if result.description:
88
- description_preview = (
89
- result.description[:50] + '...'
90
- if len(result.description) > 50
91
- else result.description
92
- )
93
- logger.info(f' Description: {description_preview}')
94
- else:
95
- logger.info(' No description')
96
-
97
- # Handle example usage
98
- if result.example_usage:
99
- logger.info(f' Example Usage: {len(result.example_usage)} found')
100
-
101
- # Handle schema arguments
102
- if result.schema_arguments:
103
- logger.info(f' Schema arguments: {len(result.schema_arguments)} found')
104
-
105
-
106
- async def test_search_aws_provider_docs():
107
- """Test the AWS provider docs search function."""
108
- logger.info('=== Testing search_aws_provider_docs_impl ===')
109
-
110
- # Test case 1: Common resource with just 1 example snippet
111
- logger.info('**********---Test case 1: Searching for aws_s3_bucket as a resource---**********')
112
- results = await search_aws_provider_docs_impl('aws_s3_bucket', 'resource')
113
- print_aws_provider_results(results)
114
-
115
- # Test case 2: Common resource with multiple example snippets
116
- logger.info(
117
- '**********---Test case 2: Searching for aws_api_gateway_rest_api as a resource---**********'
118
- )
119
- results = await search_aws_provider_docs_impl('api_gateway_rest_api', 'resource')
120
- print_aws_provider_results(results)
121
-
122
- # Test case 3: Common resource with multiple example snippets and multiple arguments in subsections
123
- logger.info(
124
- '**********---Test case 3: Searching for aws_lambda_function as a resource---**********'
125
- )
126
- results = await search_aws_provider_docs_impl('aws_lambda_function', 'resource')
127
- print_aws_provider_results(results)
128
-
129
- # Test case 4: Specifying data source as asset type
130
- logger.info(
131
- '**********---Test case 4: Searching for aws_lambda_function as a data source ---**********'
132
- )
133
- results = await search_aws_provider_docs_impl('aws_lambda_function', 'data_source')
134
- print_aws_provider_results(results)
135
-
136
- # Test case 5: Searching for both kinds
137
- logger.info('**********---Test case 5: Searching for aws_dynamodb_table as both ---**********')
138
- results = await search_aws_provider_docs_impl('aws_dynamodb_table', 'both')
139
- print_aws_provider_results(results)
140
-
141
- # Test case 6: Non-existent resource
142
- logger.info('**********---Test case 6: Searching for non-existent resource---**********')
143
- results = await search_aws_provider_docs_impl('aws_nonexistent_resource')
144
- print_aws_provider_results(results)
145
-
146
-
147
- async def test_search_awscc_provider_docs():
148
- """Test the AWSCC provider docs search function."""
149
- logger.info('\n=== Testing search_awscc_provider_docs_impl ===')
150
-
151
- # Test case 1: Common resource
152
- logger.info(
153
- '**********---Test case 1: Searching for awscc_apigateway_api_key as a resource---**********'
154
- )
155
- results = await search_awscc_provider_docs_impl('awscc_apigateway_api_key', 'resource')
156
- print_awscc_provider_results(results)
157
-
158
- # Test case 2: Resource with attribute
159
- logger.info(
160
- '**********---Test case 2: Searching for awscc_apigateway_api_key as a data source---**********'
161
- )
162
- results = await search_awscc_provider_docs_impl('awscc_apigateway_api_key', 'data_source')
163
- print_awscc_provider_results(results)
164
-
165
- # Test case 3: lambda_function resource
166
- logger.info(
167
- '**********---Test case 7: Searching for lambda_function as a resource---**********'
168
- )
169
- results = await search_awscc_provider_docs_impl('lambda_function', 'resource')
170
- print_awscc_provider_results(results)
171
-
172
- # Test case 4: Searching for both kinds
173
- logger.info(
174
- '**********---Test case 4: Searching for lambda_function as both kinds---**********'
175
- )
176
- results = await search_awscc_provider_docs_impl('awscc_lambda_function', 'both')
177
- print_awscc_provider_results(results)
178
-
179
- # Test case 5: Non-existent resource
180
- logger.info('**********---Test case 5: Searching for non-existent resource---**********')
181
- results = await search_awscc_provider_docs_impl('awscc_nonexistent_resource')
182
- print_awscc_provider_results(results)
183
-
184
-
185
- async def test_search_specific_aws_ia_modules():
186
- """Test the AWS IA modules search function."""
187
- logger.info('\n=== Testing search_specific_aws_ia_modules_impl ===')
188
-
189
- # Test case 1: Search all modules
190
- logger.info('Test case 1: Searching all AWS IA modules')
191
- results = await search_specific_aws_ia_modules_impl('')
192
-
193
- logger.info(f'Found {len(results)} modules')
194
- for i, result in enumerate(results):
195
- logger.info(f'\nModule {i + 1}:')
196
- logger.info(f' Name: {result.name}')
197
- logger.info(f' Namespace: {result.namespace}')
198
- logger.info(
199
- f' Description: {result.description[:100]}...'
200
- if result.description
201
- else ' No description'
202
- )
203
- logger.info(f' URL: {result.url}')
204
-
205
- # Test case 2: Search with query
206
- logger.info("\nTest case 2: Searching for 'bedrock' modules")
207
- results = await search_specific_aws_ia_modules_impl('bedrock')
208
-
209
- logger.info(f'Found {len(results)} modules')
210
- for i, result in enumerate(results):
211
- logger.info(f'\nModule {i + 1}:')
212
- logger.info(f' Name: {result.name}')
213
- logger.info(f' Namespace: {result.namespace}')
214
- logger.info(
215
- f' Description: {result.description[:100]}...'
216
- if result.description
217
- else ' No description'
218
- )
219
-
220
-
221
- async def test_execute_terraform_command():
222
- """Test the Terraform command execution function.
223
-
224
- Note: This test requires a valid Terraform configuration in a temporary directory.
225
- Skip this test if you don't have a valid Terraform configuration to test with.
226
- """
227
- logger.info('\n=== Testing execute_terraform_command_impl ===')
228
- logger.info('Skipping actual execution as it requires a valid Terraform configuration.')
229
- logger.info('To test this function, you would need to:')
230
- logger.info('1. Create a temporary directory with valid Terraform files')
231
- logger.info('2. Run terraform init, plan, etc. on those files')
232
-
233
- # Example of how you would call it (commented out)
234
- """
235
- request = TerraformExecutionRequest(
236
- command="validate",
237
- working_directory="/path/to/terraform/config",
238
- variables={"environment": "test"},
239
- aws_region="us-west-2",
240
- strip_ansi=True
241
- )
242
-
243
- result = await execute_terraform_command_impl(request)
244
- logger.info(f"Command: {result.command}")
245
- logger.info(f"Status: {result.status}")
246
- logger.info(f"Return Code: {result.return_code}")
247
- if result.stdout:
248
- logger.info(f"Stdout: {result.stdout[:100]}...")
249
- if result.stderr:
250
- logger.info(f"Stderr: {result.stderr[:100]}...")
251
- """
252
-
253
-
254
- async def test_run_checkov_scan():
255
- """Test the Checkov scan function.
256
-
257
- Note: This test requires a valid Terraform configuration in a temporary directory.
258
- Skip this test if you don't have a valid Terraform configuration to test with.
259
- """
260
- logger.info('\n=== Testing run_checkov_scan_impl ===')
261
- logger.info('Skipping actual execution as it requires a valid Terraform configuration.')
262
- logger.info('To test this function, you would need to:')
263
- logger.info('1. Create a temporary directory with valid Terraform files')
264
- logger.info('2. Run Checkov on those files')
265
-
266
- # Example of how you would call it (commented out)
267
- """
268
- request = CheckovScanRequest(
269
- working_directory="/path/to/terraform/config",
270
- framework="terraform",
271
- output_format="json"
272
- )
273
-
274
- result = await run_checkov_scan_impl(request)
275
- logger.info(f"Status: {result.status}")
276
- logger.info(f"Return Code: {result.return_code}")
277
- logger.info(f"Found {len(result.vulnerabilities)} vulnerabilities")
278
- for i, vuln in enumerate(result.vulnerabilities[:3]): # Show first 3 only
279
- logger.info(f"\nVulnerability {i+1}:")
280
- logger.info(f" ID: {vuln.id}")
281
- logger.info(f" Resource: {vuln.resource}")
282
- logger.info(f" Description: {vuln.description[:100]}..." if vuln.description else " No description")
283
- """
284
-
285
-
286
- def format_json(obj: Any) -> str:
287
- """Format an object as pretty JSON."""
288
- if hasattr(obj, 'model_dump'):
289
- # For Pydantic v2
290
- data = obj.model_dump()
291
- elif hasattr(obj, 'dict'):
292
- # For Pydantic v1
293
- data = obj.dict()
294
- else:
295
- data = obj
296
- return json.dumps(data, indent=2, default=str)
297
-
298
-
299
- async def main():
300
- """Run all tests."""
301
- try:
302
- await test_search_aws_provider_docs()
303
- await test_search_awscc_provider_docs()
304
- except Exception as e:
305
- logger.exception(f'Error running tests: {e}')
306
-
307
-
308
- if __name__ == '__main__':
309
- asyncio.run(main())