awslabs.terraform-mcp-server 0.0.1__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.
- awslabs/__init__.py +2 -0
- awslabs/terraform_mcp_server/__init__.py +3 -0
- awslabs/terraform_mcp_server/impl/resources/__init__.py +11 -0
- awslabs/terraform_mcp_server/impl/resources/terraform_aws_provider_resources_listing.py +52 -0
- awslabs/terraform_mcp_server/impl/resources/terraform_awscc_provider_resources_listing.py +55 -0
- awslabs/terraform_mcp_server/impl/tools/__init__.py +15 -0
- awslabs/terraform_mcp_server/impl/tools/execute_terraform_command.py +206 -0
- awslabs/terraform_mcp_server/impl/tools/run_checkov_scan.py +359 -0
- awslabs/terraform_mcp_server/impl/tools/search_aws_provider_docs.py +677 -0
- awslabs/terraform_mcp_server/impl/tools/search_awscc_provider_docs.py +627 -0
- awslabs/terraform_mcp_server/impl/tools/search_specific_aws_ia_modules.py +444 -0
- awslabs/terraform_mcp_server/impl/tools/utils.py +558 -0
- awslabs/terraform_mcp_server/models/__init__.py +27 -0
- awslabs/terraform_mcp_server/models/models.py +260 -0
- awslabs/terraform_mcp_server/scripts/generate_aws_provider_resources.py +1224 -0
- awslabs/terraform_mcp_server/scripts/generate_awscc_provider_resources.py +1020 -0
- awslabs/terraform_mcp_server/scripts/scrape_aws_terraform_best_practices.py +129 -0
- awslabs/terraform_mcp_server/server.py +329 -0
- awslabs/terraform_mcp_server/static/AWSCC_PROVIDER_RESOURCES.md +3125 -0
- awslabs/terraform_mcp_server/static/AWS_PROVIDER_RESOURCES.md +3833 -0
- awslabs/terraform_mcp_server/static/AWS_TERRAFORM_BEST_PRACTICES.md +2523 -0
- awslabs/terraform_mcp_server/static/MCP_INSTRUCTIONS.md +126 -0
- awslabs/terraform_mcp_server/static/TERRAFORM_WORKFLOW_GUIDE.md +198 -0
- awslabs/terraform_mcp_server/static/__init__.py +22 -0
- awslabs/terraform_mcp_server/tests/__init__.py +1 -0
- awslabs/terraform_mcp_server/tests/run_tests.sh +35 -0
- awslabs/terraform_mcp_server/tests/test_parameter_annotations.py +207 -0
- awslabs/terraform_mcp_server/tests/test_tool_implementations.py +309 -0
- awslabs_terraform_mcp_server-0.0.1.dist-info/METADATA +97 -0
- awslabs_terraform_mcp_server-0.0.1.dist-info/RECORD +32 -0
- awslabs_terraform_mcp_server-0.0.1.dist-info/WHEEL +4 -0
- awslabs_terraform_mcp_server-0.0.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"""Implementation of specific AWS-IA module search tool for four key modules."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import re
|
|
5
|
+
import requests
|
|
6
|
+
import time
|
|
7
|
+
import traceback
|
|
8
|
+
from .utils import (
|
|
9
|
+
clean_description,
|
|
10
|
+
extract_outputs_from_readme,
|
|
11
|
+
get_github_release_details,
|
|
12
|
+
get_submodules,
|
|
13
|
+
get_variables_tf,
|
|
14
|
+
)
|
|
15
|
+
from awslabs.terraform_mcp_server.models import ModuleSearchResult, SubmoduleInfo
|
|
16
|
+
from loguru import logger
|
|
17
|
+
from typing import Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Define the specific modules we want to check
|
|
21
|
+
SPECIFIC_MODULES = [
|
|
22
|
+
{'namespace': 'aws-ia', 'name': 'bedrock', 'provider': 'aws'},
|
|
23
|
+
{'namespace': 'aws-ia', 'name': 'opensearch-serverless', 'provider': 'aws'},
|
|
24
|
+
{'namespace': 'aws-ia', 'name': 'sagemaker-endpoint', 'provider': 'aws'},
|
|
25
|
+
{'namespace': 'aws-ia', 'name': 'serverless-streamlit-app', 'provider': 'aws'},
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def get_module_details(namespace: str, name: str, provider: str = 'aws') -> Dict:
|
|
30
|
+
"""Fetch detailed information about a specific Terraform module.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
namespace: The module namespace (e.g., aws-ia)
|
|
34
|
+
name: The module name (e.g., vpc)
|
|
35
|
+
provider: The provider (default: aws)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dictionary containing module details including README content and submodules
|
|
39
|
+
"""
|
|
40
|
+
logger.info(f'Fetching details for module {namespace}/{name}/{provider}')
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
# Get basic module info via API
|
|
44
|
+
details_url = f'https://registry.terraform.io/v1/modules/{namespace}/{name}/{provider}'
|
|
45
|
+
logger.debug(f'Making API request to: {details_url}')
|
|
46
|
+
|
|
47
|
+
response = requests.get(details_url)
|
|
48
|
+
response.raise_for_status()
|
|
49
|
+
|
|
50
|
+
details = response.json()
|
|
51
|
+
logger.debug(
|
|
52
|
+
f'Received module details. Status code: {response.status_code}, Content size: {len(response.text)} bytes'
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Debug log the version info we initially have
|
|
56
|
+
initial_version = details.get('latest_version', 'unknown')
|
|
57
|
+
if 'latest' in details and 'version' in details['latest']:
|
|
58
|
+
initial_version = details['latest']['version']
|
|
59
|
+
logger.debug(f'Initial version from primary API: {initial_version}')
|
|
60
|
+
|
|
61
|
+
# Add additional API call to get the latest version if not in details
|
|
62
|
+
if 'latest' not in details or 'version' not in details.get('latest', {}):
|
|
63
|
+
versions_url = f'{details_url}/versions'
|
|
64
|
+
logger.debug(f'Making API request to get versions: {versions_url}')
|
|
65
|
+
|
|
66
|
+
versions_response = requests.get(versions_url)
|
|
67
|
+
logger.debug(f'Versions API response code: {versions_response.status_code}')
|
|
68
|
+
|
|
69
|
+
if versions_response.status_code == 200:
|
|
70
|
+
versions_data = versions_response.json()
|
|
71
|
+
logger.debug(
|
|
72
|
+
f'Received versions data with {len(versions_data.get("modules", []))} module versions'
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if versions_data.get('modules') and len(versions_data['modules']) > 0:
|
|
76
|
+
latest_version = versions_data['modules'][0].get('version', '')
|
|
77
|
+
details['latest_version'] = latest_version
|
|
78
|
+
logger.debug(f'Updated latest version to: {latest_version}')
|
|
79
|
+
else:
|
|
80
|
+
logger.debug('No modules found in versions response')
|
|
81
|
+
else:
|
|
82
|
+
logger.debug(
|
|
83
|
+
f'Failed to fetch versions. Status code: {versions_response.status_code}'
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
logger.debug('Latest version already available in primary API response')
|
|
87
|
+
|
|
88
|
+
# Try to get README content and version details, starting with direct API if available
|
|
89
|
+
readme_content = None
|
|
90
|
+
version_details = None
|
|
91
|
+
version_from_github = ''
|
|
92
|
+
|
|
93
|
+
# APPROACH 1: Try to see if the registry API provides README content directly
|
|
94
|
+
logger.debug('APPROACH 1: Checking for README content in API response')
|
|
95
|
+
if 'readme' in details and details['readme']:
|
|
96
|
+
readme_content = details['readme']
|
|
97
|
+
logger.info(
|
|
98
|
+
f'Found README content directly in API response: {len(readme_content)} chars'
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# APPROACH 2: Try using the GitHub repo URL for README content and version details
|
|
102
|
+
if 'source' in details:
|
|
103
|
+
source_url = details.get('source')
|
|
104
|
+
# Properly validate GitHub URL using regex to ensure it's actually from github.com domain
|
|
105
|
+
if isinstance(source_url, str) and re.match(r'https://github.com/', source_url):
|
|
106
|
+
logger.info(f'Found GitHub source URL: {source_url}')
|
|
107
|
+
|
|
108
|
+
# Extract GitHub owner and repo
|
|
109
|
+
github_parts = re.match(r'https://github.com/([^/]+)/([^/]+)', source_url)
|
|
110
|
+
if github_parts:
|
|
111
|
+
owner, repo = github_parts.groups()
|
|
112
|
+
logger.info(f'Extracted GitHub repo: {owner}/{repo}')
|
|
113
|
+
|
|
114
|
+
# Get version details from GitHub
|
|
115
|
+
github_version_info = await get_github_release_details(owner, repo)
|
|
116
|
+
version_details = github_version_info['details']
|
|
117
|
+
version_from_github = github_version_info['version']
|
|
118
|
+
|
|
119
|
+
if version_from_github:
|
|
120
|
+
logger.info(f'Found version from GitHub: {version_from_github}')
|
|
121
|
+
details['latest_version'] = version_from_github
|
|
122
|
+
|
|
123
|
+
# Get variables.tf content and parsed variables
|
|
124
|
+
variables_content, variables = await get_variables_tf(owner, repo, 'main')
|
|
125
|
+
if variables_content and variables:
|
|
126
|
+
logger.info(f'Found variables.tf with {len(variables)} variables')
|
|
127
|
+
details['variables_content'] = variables_content
|
|
128
|
+
details['variables'] = [var.dict() for var in variables]
|
|
129
|
+
else:
|
|
130
|
+
# Try master branch as fallback if main didn't work
|
|
131
|
+
variables_content, variables = await get_variables_tf(
|
|
132
|
+
owner, repo, 'master'
|
|
133
|
+
)
|
|
134
|
+
if variables_content and variables:
|
|
135
|
+
logger.info(
|
|
136
|
+
f'Found variables.tf in master branch with {len(variables)} variables'
|
|
137
|
+
)
|
|
138
|
+
details['variables_content'] = variables_content
|
|
139
|
+
details['variables'] = [var.dict() for var in variables]
|
|
140
|
+
|
|
141
|
+
# If README content not already found, try fetching it from GitHub
|
|
142
|
+
if not readme_content:
|
|
143
|
+
logger.debug(
|
|
144
|
+
f'APPROACH 2: Fetching README from GitHub source: {source_url}'
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Convert HTTPS URL to raw content URL
|
|
148
|
+
try:
|
|
149
|
+
# Try main branch first, then fall back to master if needed
|
|
150
|
+
found_readme_branch = None
|
|
151
|
+
for branch in ['main', 'master']:
|
|
152
|
+
raw_readme_url = f'https://raw.githubusercontent.com/{owner}/{repo}/{branch}/README.md'
|
|
153
|
+
logger.debug(f'Trying to fetch README from: {raw_readme_url}')
|
|
154
|
+
|
|
155
|
+
readme_response = requests.get(raw_readme_url)
|
|
156
|
+
if readme_response.status_code == 200:
|
|
157
|
+
readme_content = readme_response.text
|
|
158
|
+
found_readme_branch = branch
|
|
159
|
+
logger.info(
|
|
160
|
+
f'Successfully fetched README from GitHub ({branch}): {len(readme_content)} chars'
|
|
161
|
+
)
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
# Look for submodules now that we have identified the main branch
|
|
165
|
+
if found_readme_branch:
|
|
166
|
+
logger.info(
|
|
167
|
+
f'Fetching submodules using {found_readme_branch} branch'
|
|
168
|
+
)
|
|
169
|
+
start_time = time.time()
|
|
170
|
+
submodules = await get_submodules(owner, repo, found_readme_branch)
|
|
171
|
+
if submodules:
|
|
172
|
+
logger.info(
|
|
173
|
+
f'Found {len(submodules)} submodules in {time.time() - start_time:.2f} seconds'
|
|
174
|
+
)
|
|
175
|
+
details['submodules'] = [
|
|
176
|
+
submodule.dict() for submodule in submodules
|
|
177
|
+
]
|
|
178
|
+
else:
|
|
179
|
+
logger.info('No submodules found')
|
|
180
|
+
else:
|
|
181
|
+
# Try both main branches for submodules if readme wasn't found
|
|
182
|
+
for branch in ['main', 'master']:
|
|
183
|
+
logger.debug(f'Trying {branch} branch for submodules')
|
|
184
|
+
start_time = time.time()
|
|
185
|
+
submodules = await get_submodules(owner, repo, branch)
|
|
186
|
+
if submodules:
|
|
187
|
+
logger.info(
|
|
188
|
+
f'Found {len(submodules)} submodules in {branch} branch in {time.time() - start_time:.2f} seconds'
|
|
189
|
+
)
|
|
190
|
+
details['submodules'] = [
|
|
191
|
+
submodule.dict() for submodule in submodules
|
|
192
|
+
]
|
|
193
|
+
break
|
|
194
|
+
except Exception as ex:
|
|
195
|
+
logger.error(f'Error fetching README from GitHub: {ex}')
|
|
196
|
+
logger.debug(f'Stack trace: {traceback.format_exc()}')
|
|
197
|
+
|
|
198
|
+
# Process content we've gathered
|
|
199
|
+
|
|
200
|
+
# Add readme_content to details if available
|
|
201
|
+
if readme_content:
|
|
202
|
+
logger.info(f'Successfully extracted README content ({len(readme_content)} chars)')
|
|
203
|
+
logger.debug(f'First 100 characters of README: {readme_content[:100]}...')
|
|
204
|
+
|
|
205
|
+
# Extract outputs from README content
|
|
206
|
+
outputs = extract_outputs_from_readme(readme_content)
|
|
207
|
+
if outputs:
|
|
208
|
+
logger.info(f'Extracted {len(outputs)} outputs from README')
|
|
209
|
+
details['outputs'] = outputs
|
|
210
|
+
else:
|
|
211
|
+
logger.info('No outputs found in README')
|
|
212
|
+
|
|
213
|
+
# Trim if too large
|
|
214
|
+
if len(readme_content) > 8000:
|
|
215
|
+
logger.debug(
|
|
216
|
+
f'README content exceeds 8000 characters ({len(readme_content)}), truncating...'
|
|
217
|
+
)
|
|
218
|
+
readme_content = readme_content[:8000] + '...\n[README truncated due to length]'
|
|
219
|
+
logger.debug('README content truncated')
|
|
220
|
+
|
|
221
|
+
details['readme_content'] = readme_content
|
|
222
|
+
else:
|
|
223
|
+
logger.warning('No README content found through any method')
|
|
224
|
+
|
|
225
|
+
# Add version details if available
|
|
226
|
+
if version_details:
|
|
227
|
+
logger.info('Adding version details to response')
|
|
228
|
+
logger.debug(f'Version details: {version_details}')
|
|
229
|
+
details['version_details'] = version_details
|
|
230
|
+
|
|
231
|
+
return details
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.error(f'Error fetching module details: {e}')
|
|
235
|
+
# Add stack trace for debugging
|
|
236
|
+
logger.debug(f'Stack trace: {traceback.format_exc()}')
|
|
237
|
+
return {}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
async def get_specific_module_info(module_info: Dict[str, str]) -> Optional[ModuleSearchResult]:
|
|
241
|
+
"""Get detailed information about a specific module.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
module_info: Dictionary with namespace, name, and provider of the module
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
ModuleSearchResult object with module details or None if module not found
|
|
248
|
+
"""
|
|
249
|
+
namespace = module_info['namespace']
|
|
250
|
+
name = module_info['name']
|
|
251
|
+
provider = module_info['provider']
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
# First, check if the module exists
|
|
255
|
+
details_url = f'https://registry.terraform.io/v1/modules/{namespace}/{name}/{provider}'
|
|
256
|
+
response = requests.get(details_url)
|
|
257
|
+
|
|
258
|
+
if response.status_code != 200:
|
|
259
|
+
logger.warning(
|
|
260
|
+
f'Module {namespace}/{name}/{provider} not found (status code: {response.status_code})'
|
|
261
|
+
)
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
module_data = response.json()
|
|
265
|
+
|
|
266
|
+
# Get the description and clean it
|
|
267
|
+
description = module_data.get('description', 'No description available')
|
|
268
|
+
cleaned_description = clean_description(description)
|
|
269
|
+
|
|
270
|
+
# Create the basic result
|
|
271
|
+
result = ModuleSearchResult(
|
|
272
|
+
name=name,
|
|
273
|
+
namespace=namespace,
|
|
274
|
+
provider=provider,
|
|
275
|
+
version=module_data.get('latest_version', 'unknown'),
|
|
276
|
+
url=f'https://registry.terraform.io/modules/{namespace}/{name}/{provider}',
|
|
277
|
+
description=cleaned_description,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Get detailed information including README
|
|
281
|
+
details = await get_module_details(namespace, name, provider)
|
|
282
|
+
|
|
283
|
+
if details:
|
|
284
|
+
# Update the version if we got a better one from the details
|
|
285
|
+
if 'latest_version' in details:
|
|
286
|
+
result.version = details['latest_version']
|
|
287
|
+
|
|
288
|
+
# Add version details if available
|
|
289
|
+
if 'version_details' in details:
|
|
290
|
+
result.version_details = details['version_details']
|
|
291
|
+
|
|
292
|
+
# Get README content
|
|
293
|
+
if 'readme_content' in details and details['readme_content']:
|
|
294
|
+
result.readme_content = details['readme_content']
|
|
295
|
+
|
|
296
|
+
# Get input and output counts if available
|
|
297
|
+
if 'root' in details and 'inputs' in details['root']:
|
|
298
|
+
result.input_count = len(details['root']['inputs'])
|
|
299
|
+
|
|
300
|
+
if 'root' in details and 'outputs' in details['root']:
|
|
301
|
+
result.output_count = len(details['root']['outputs'])
|
|
302
|
+
|
|
303
|
+
# Add submodules if available
|
|
304
|
+
if 'submodules' in details and details['submodules']:
|
|
305
|
+
submodules = [
|
|
306
|
+
SubmoduleInfo(**submodule_data) for submodule_data in details['submodules']
|
|
307
|
+
]
|
|
308
|
+
result.submodules = submodules
|
|
309
|
+
|
|
310
|
+
# Add variables information if available
|
|
311
|
+
if 'variables' in details and details['variables']:
|
|
312
|
+
from awslabs.terraform_mcp_server.models import TerraformVariable
|
|
313
|
+
|
|
314
|
+
variables = [TerraformVariable(**var_data) for var_data in details['variables']]
|
|
315
|
+
result.variables = variables
|
|
316
|
+
|
|
317
|
+
# Add variables.tf content if available
|
|
318
|
+
if 'variables_content' in details and details['variables_content']:
|
|
319
|
+
result.variables_content = details['variables_content']
|
|
320
|
+
|
|
321
|
+
# Add outputs from README if available
|
|
322
|
+
if 'outputs' in details and details['outputs']:
|
|
323
|
+
from awslabs.terraform_mcp_server.models import TerraformOutput
|
|
324
|
+
|
|
325
|
+
outputs = [
|
|
326
|
+
TerraformOutput(name=output['name'], description=output.get('description'))
|
|
327
|
+
for output in details['outputs']
|
|
328
|
+
]
|
|
329
|
+
result.outputs = outputs
|
|
330
|
+
# Update output_count if not already set
|
|
331
|
+
if result.output_count is None:
|
|
332
|
+
result.output_count = len(outputs)
|
|
333
|
+
|
|
334
|
+
return result
|
|
335
|
+
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.error(f'Error getting info for module {namespace}/{name}/{provider}: {e}')
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
async def search_specific_aws_ia_modules_impl(query: str) -> List[ModuleSearchResult]:
|
|
342
|
+
"""Search for specific AWS-IA Terraform modules.
|
|
343
|
+
|
|
344
|
+
This tool checks for information about four specific AWS-IA modules:
|
|
345
|
+
- aws-ia/bedrock/aws - Amazon Bedrock module for generative AI applications
|
|
346
|
+
- aws-ia/opensearch-serverless/aws - OpenSearch Serverless collection for vector search
|
|
347
|
+
- aws-ia/sagemaker-endpoint/aws - SageMaker endpoint deployment module
|
|
348
|
+
- aws-ia/serverless-streamlit-app/aws - Serverless Streamlit application deployment
|
|
349
|
+
|
|
350
|
+
It returns detailed information about these modules, including their README content,
|
|
351
|
+
variables.tf content, and submodules when available.
|
|
352
|
+
|
|
353
|
+
The search is performed across module names, descriptions, README content, and variable
|
|
354
|
+
definitions. This allows you to find modules based on their functionality or specific
|
|
355
|
+
configuration options.
|
|
356
|
+
|
|
357
|
+
The implementation fetches module information directly from the Terraform Registry API
|
|
358
|
+
and GitHub repositories to ensure the most up-to-date information. Results include
|
|
359
|
+
comprehensive details about each module's structure, configuration options, and usage examples.
|
|
360
|
+
|
|
361
|
+
Examples:
|
|
362
|
+
- To get information about all four modules:
|
|
363
|
+
search_specific_aws_ia_modules_impl(query='')
|
|
364
|
+
|
|
365
|
+
- To find modules related to Bedrock:
|
|
366
|
+
search_specific_aws_ia_modules_impl(query='bedrock')
|
|
367
|
+
|
|
368
|
+
- To find modules related to vector search:
|
|
369
|
+
search_specific_aws_ia_modules_impl(query='vector search')
|
|
370
|
+
|
|
371
|
+
- To find modules with specific configuration options:
|
|
372
|
+
search_specific_aws_ia_modules_impl(query='endpoint_name')
|
|
373
|
+
|
|
374
|
+
Parameters:
|
|
375
|
+
query: Optional search term to filter modules (empty returns all four modules)
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
A list of matching modules with their details, including:
|
|
379
|
+
- Basic module information (name, namespace, version)
|
|
380
|
+
- Module documentation (README content)
|
|
381
|
+
- Input and output parameter counts
|
|
382
|
+
- Variables from variables.tf with descriptions and default values
|
|
383
|
+
- Submodules information
|
|
384
|
+
- Version details and release information
|
|
385
|
+
"""
|
|
386
|
+
logger.info(f"Searching for specific AWS-IA modules with query: '{query}'")
|
|
387
|
+
|
|
388
|
+
tasks = []
|
|
389
|
+
|
|
390
|
+
# Create tasks for fetching module information
|
|
391
|
+
for module_info in SPECIFIC_MODULES:
|
|
392
|
+
tasks.append(get_specific_module_info(module_info))
|
|
393
|
+
|
|
394
|
+
# Run all tasks concurrently
|
|
395
|
+
module_results = await asyncio.gather(*tasks)
|
|
396
|
+
|
|
397
|
+
# Filter out None results (modules not found)
|
|
398
|
+
module_results = [result for result in module_results if result is not None]
|
|
399
|
+
|
|
400
|
+
# If query is provided, filter results
|
|
401
|
+
if query and query.strip():
|
|
402
|
+
query_terms = query.lower().split()
|
|
403
|
+
filtered_results = []
|
|
404
|
+
|
|
405
|
+
for result in module_results:
|
|
406
|
+
# Check if any query term is in the module name, description, readme, or variables
|
|
407
|
+
matches = False
|
|
408
|
+
|
|
409
|
+
# Build search text from module details and variables
|
|
410
|
+
search_text = (
|
|
411
|
+
f'{result.name} {result.description} {result.readme_content or ""}'.lower()
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# Add variables information to search text if available
|
|
415
|
+
if result.variables:
|
|
416
|
+
for var in result.variables:
|
|
417
|
+
var_text = f'{var.name} {var.type or ""} {var.description or ""}'
|
|
418
|
+
search_text += f' {var_text.lower()}'
|
|
419
|
+
|
|
420
|
+
# Add variables.tf content to search text if available
|
|
421
|
+
if result.variables_content:
|
|
422
|
+
search_text += f' {result.variables_content.lower()}'
|
|
423
|
+
|
|
424
|
+
# Add outputs information to search text if available
|
|
425
|
+
if result.outputs:
|
|
426
|
+
for output in result.outputs:
|
|
427
|
+
output_text = f'{output.name} {output.description or ""}'
|
|
428
|
+
search_text += f' {output_text.lower()}'
|
|
429
|
+
|
|
430
|
+
for term in query_terms:
|
|
431
|
+
if term in search_text:
|
|
432
|
+
matches = True
|
|
433
|
+
break
|
|
434
|
+
|
|
435
|
+
if matches:
|
|
436
|
+
filtered_results.append(result)
|
|
437
|
+
|
|
438
|
+
logger.info(
|
|
439
|
+
f"Found {len(filtered_results)} modules matching query '{query}' out of {len(module_results)} total modules"
|
|
440
|
+
)
|
|
441
|
+
return filtered_results
|
|
442
|
+
else:
|
|
443
|
+
logger.info(f'Returning all {len(module_results)} specific modules (no query filter)')
|
|
444
|
+
return module_results
|