awslabs.terraform-mcp-server 1.0.14__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.
- awslabs/__init__.py +17 -0
- awslabs/terraform_mcp_server/__init__.py +17 -0
- awslabs/terraform_mcp_server/impl/resources/__init__.py +25 -0
- awslabs/terraform_mcp_server/impl/resources/terraform_aws_provider_resources_listing.py +66 -0
- awslabs/terraform_mcp_server/impl/resources/terraform_awscc_provider_resources_listing.py +69 -0
- awslabs/terraform_mcp_server/impl/tools/__init__.py +33 -0
- awslabs/terraform_mcp_server/impl/tools/execute_terraform_command.py +223 -0
- awslabs/terraform_mcp_server/impl/tools/execute_terragrunt_command.py +320 -0
- awslabs/terraform_mcp_server/impl/tools/run_checkov_scan.py +376 -0
- awslabs/terraform_mcp_server/impl/tools/search_aws_provider_docs.py +691 -0
- awslabs/terraform_mcp_server/impl/tools/search_awscc_provider_docs.py +641 -0
- awslabs/terraform_mcp_server/impl/tools/search_specific_aws_ia_modules.py +458 -0
- awslabs/terraform_mcp_server/impl/tools/search_user_provided_module.py +349 -0
- awslabs/terraform_mcp_server/impl/tools/utils.py +572 -0
- awslabs/terraform_mcp_server/models/__init__.py +49 -0
- awslabs/terraform_mcp_server/models/models.py +381 -0
- awslabs/terraform_mcp_server/scripts/generate_aws_provider_resources.py +1240 -0
- awslabs/terraform_mcp_server/scripts/generate_awscc_provider_resources.py +1039 -0
- awslabs/terraform_mcp_server/scripts/scrape_aws_terraform_best_practices.py +143 -0
- awslabs/terraform_mcp_server/server.py +440 -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 +142 -0
- awslabs/terraform_mcp_server/static/TERRAFORM_WORKFLOW_GUIDE.md +330 -0
- awslabs/terraform_mcp_server/static/__init__.py +38 -0
- awslabs_terraform_mcp_server-1.0.14.dist-info/METADATA +166 -0
- awslabs_terraform_mcp_server-1.0.14.dist-info/RECORD +30 -0
- awslabs_terraform_mcp_server-1.0.14.dist-info/WHEEL +4 -0
- awslabs_terraform_mcp_server-1.0.14.dist-info/entry_points.txt +2 -0
awslabs/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# This file is part of the awslabs namespace.
|
|
16
|
+
# It is intentionally minimal to support PEP 420 namespace packages.
|
|
17
|
+
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""awslabs.terraform-mcp-server"""
|
|
16
|
+
|
|
17
|
+
__version__ = '1.0.14'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Resource implementations for the Terraform expert."""
|
|
16
|
+
|
|
17
|
+
from .terraform_aws_provider_resources_listing import terraform_aws_provider_assets_listing_impl
|
|
18
|
+
from .terraform_awscc_provider_resources_listing import (
|
|
19
|
+
terraform_awscc_provider_resources_listing_impl,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
'terraform_aws_provider_assets_listing_impl',
|
|
24
|
+
'terraform_awscc_provider_resources_listing_impl',
|
|
25
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Implementation for terraform_aws_provider_resources_listing resource."""
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
from loguru import logger
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Configure logger for enhanced diagnostics with stacktraces
|
|
23
|
+
logger.configure(
|
|
24
|
+
handlers=[
|
|
25
|
+
{
|
|
26
|
+
'sink': sys.stderr,
|
|
27
|
+
'backtrace': True,
|
|
28
|
+
'diagnose': True,
|
|
29
|
+
'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>',
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Path to the static markdown file
|
|
35
|
+
STATIC_RESOURCES_PATH = (
|
|
36
|
+
Path(__file__).parent.parent.parent / 'static' / 'AWS_PROVIDER_RESOURCES.md'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def terraform_aws_provider_assets_listing_impl() -> str:
|
|
41
|
+
"""Generate a comprehensive listing of AWS provider resources and data sources.
|
|
42
|
+
|
|
43
|
+
This implementation reads from a pre-generated static markdown file instead of
|
|
44
|
+
scraping the web in real-time. The static file should be generated using the
|
|
45
|
+
generate_aws_provider_resources.py script.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A markdown formatted string with categorized resources and data sources
|
|
49
|
+
"""
|
|
50
|
+
logger.info('Loading AWS provider resources listing from static file')
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
# Check if the static file exists
|
|
54
|
+
if STATIC_RESOURCES_PATH.exists():
|
|
55
|
+
# Read the static file content
|
|
56
|
+
with open(STATIC_RESOURCES_PATH, 'r', encoding='utf-8') as f:
|
|
57
|
+
content = f.read()
|
|
58
|
+
logger.info('Successfully loaded AWS Provider asset list')
|
|
59
|
+
return content
|
|
60
|
+
else:
|
|
61
|
+
# Send error if static file does not exist
|
|
62
|
+
logger.debug(f"Static assets list file not found at '{STATIC_RESOURCES_PATH}'")
|
|
63
|
+
raise Exception('Static assets list file not found')
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f'Error generating AWS provider assets listing: {e}')
|
|
66
|
+
return f'# AWS Provider Assets Listing\n\nError generating listing: {str(e)}'
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Implementation for terraform_awscc_provider_resources_listing resource."""
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
from loguru import logger
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Configure logger for enhanced diagnostics with stacktraces
|
|
23
|
+
logger.configure(
|
|
24
|
+
handlers=[
|
|
25
|
+
{
|
|
26
|
+
'sink': sys.stderr,
|
|
27
|
+
'backtrace': True,
|
|
28
|
+
'diagnose': True,
|
|
29
|
+
'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>',
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Path to the static markdown file
|
|
35
|
+
STATIC_RESOURCES_PATH = (
|
|
36
|
+
Path(__file__).parent.parent.parent / 'static' / 'AWSCC_PROVIDER_RESOURCES.md'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def terraform_awscc_provider_resources_listing_impl() -> str:
|
|
41
|
+
"""Generate a comprehensive listing of AWSCC provider resources and data sources.
|
|
42
|
+
|
|
43
|
+
This implementation reads from a pre-generated static markdown file instead of
|
|
44
|
+
scraping the web in real-time. The static file should be generated using the
|
|
45
|
+
generate_awscc_provider_resources.py script.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A markdown formatted string with categorized resources and data sources
|
|
49
|
+
"""
|
|
50
|
+
logger.info('Loading AWSCC provider resources listing from static file')
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
# Check if the static file exists
|
|
54
|
+
if STATIC_RESOURCES_PATH.exists():
|
|
55
|
+
# Read the static file content
|
|
56
|
+
with open(STATIC_RESOURCES_PATH, 'r', encoding='utf-8') as f:
|
|
57
|
+
content = f.read()
|
|
58
|
+
|
|
59
|
+
logger.info(
|
|
60
|
+
f'Successfully loaded AWSCC provider resources from {STATIC_RESOURCES_PATH}'
|
|
61
|
+
)
|
|
62
|
+
return content
|
|
63
|
+
else:
|
|
64
|
+
# Send error if static file does not exist
|
|
65
|
+
logger.debug(f"Static assets list file not found at '{STATIC_RESOURCES_PATH}'")
|
|
66
|
+
raise Exception('Static assets list file not found')
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.error(f'Error generating AWSCC provider resources listing: {e}')
|
|
69
|
+
return f'# AWSCC Provider Resources Listing\n\nError generating listing: {str(e)}'
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Tool implementations for the terraform MCP server."""
|
|
16
|
+
|
|
17
|
+
from .search_user_provided_module import search_user_provided_module_impl
|
|
18
|
+
from .execute_terraform_command import execute_terraform_command_impl
|
|
19
|
+
from .execute_terragrunt_command import execute_terragrunt_command_impl
|
|
20
|
+
from .search_aws_provider_docs import search_aws_provider_docs_impl
|
|
21
|
+
from .search_awscc_provider_docs import search_awscc_provider_docs_impl
|
|
22
|
+
from .search_specific_aws_ia_modules import search_specific_aws_ia_modules_impl
|
|
23
|
+
from .run_checkov_scan import run_checkov_scan_impl
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
'search_user_provided_module_impl',
|
|
27
|
+
'execute_terraform_command_impl',
|
|
28
|
+
'execute_terragrunt_command_impl',
|
|
29
|
+
'search_aws_provider_docs_impl',
|
|
30
|
+
'search_awscc_provider_docs_impl',
|
|
31
|
+
'search_specific_aws_ia_modules_impl',
|
|
32
|
+
'run_checkov_scan_impl',
|
|
33
|
+
]
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Implementation of Terraform command execution tool."""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import subprocess
|
|
21
|
+
from awslabs.terraform_mcp_server.impl.tools.utils import get_dangerous_patterns
|
|
22
|
+
from awslabs.terraform_mcp_server.models import TerraformExecutionRequest, TerraformExecutionResult
|
|
23
|
+
from loguru import logger
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def execute_terraform_command_impl(
|
|
27
|
+
request: TerraformExecutionRequest,
|
|
28
|
+
) -> TerraformExecutionResult:
|
|
29
|
+
"""Execute Terraform workflow commands against an AWS account.
|
|
30
|
+
|
|
31
|
+
This tool runs Terraform commands (init, plan, validate, apply, destroy) in the
|
|
32
|
+
specified working directory, with optional variables and region settings.
|
|
33
|
+
|
|
34
|
+
Parameters:
|
|
35
|
+
request: Details about the Terraform command to execute
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
A TerraformExecutionResult object containing command output and status
|
|
39
|
+
"""
|
|
40
|
+
logger.info(f"Executing 'terraform {request.command}' in {request.working_directory}")
|
|
41
|
+
|
|
42
|
+
# Helper function to clean output text
|
|
43
|
+
def clean_output_text(text: str) -> str:
|
|
44
|
+
"""Clean output text by removing or replacing problematic Unicode characters.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
text: The text to clean
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Cleaned text with ASCII-friendly replacements
|
|
51
|
+
"""
|
|
52
|
+
if not text:
|
|
53
|
+
return text
|
|
54
|
+
|
|
55
|
+
# First remove ANSI escape sequences (color codes, cursor movement)
|
|
56
|
+
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
57
|
+
text = ansi_escape.sub('', text)
|
|
58
|
+
|
|
59
|
+
# Remove C0 and C1 control characters (except common whitespace)
|
|
60
|
+
control_chars = re.compile(r'[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]')
|
|
61
|
+
text = control_chars.sub('', text)
|
|
62
|
+
|
|
63
|
+
# Replace HTML entities
|
|
64
|
+
html_entities = {
|
|
65
|
+
'->': '->', # Replace HTML arrow
|
|
66
|
+
'<': '<', # Less than
|
|
67
|
+
'>': '>', # Greater than
|
|
68
|
+
'&': '&', # Ampersand
|
|
69
|
+
}
|
|
70
|
+
for entity, replacement in html_entities.items():
|
|
71
|
+
text = text.replace(entity, replacement)
|
|
72
|
+
|
|
73
|
+
# Replace box-drawing and other special Unicode characters with ASCII equivalents
|
|
74
|
+
unicode_chars = {
|
|
75
|
+
'\u2500': '-', # Horizontal line
|
|
76
|
+
'\u2502': '|', # Vertical line
|
|
77
|
+
'\u2514': '+', # Up and right
|
|
78
|
+
'\u2518': '+', # Up and left
|
|
79
|
+
'\u2551': '|', # Double vertical
|
|
80
|
+
'\u2550': '-', # Double horizontal
|
|
81
|
+
'\u2554': '+', # Double down and right
|
|
82
|
+
'\u2557': '+', # Double down and left
|
|
83
|
+
'\u255a': '+', # Double up and right
|
|
84
|
+
'\u255d': '+', # Double up and left
|
|
85
|
+
'\u256c': '+', # Double cross
|
|
86
|
+
'\u2588': '#', # Full block
|
|
87
|
+
'\u25cf': '*', # Black circle
|
|
88
|
+
'\u2574': '-', # Left box drawing
|
|
89
|
+
'\u2576': '-', # Right box drawing
|
|
90
|
+
'\u2577': '|', # Down box drawing
|
|
91
|
+
'\u2575': '|', # Up box drawing
|
|
92
|
+
}
|
|
93
|
+
for char, replacement in unicode_chars.items():
|
|
94
|
+
text = text.replace(char, replacement)
|
|
95
|
+
|
|
96
|
+
return text
|
|
97
|
+
|
|
98
|
+
# Set environment variables for AWS region if provided
|
|
99
|
+
env = os.environ.copy()
|
|
100
|
+
if request.aws_region:
|
|
101
|
+
env['AWS_REGION'] = request.aws_region
|
|
102
|
+
|
|
103
|
+
# Security check for command injection
|
|
104
|
+
allowed_commands = ['init', 'plan', 'validate', 'apply', 'destroy']
|
|
105
|
+
if request.command not in allowed_commands:
|
|
106
|
+
logger.error(f'Invalid Terraform command: {request.command}')
|
|
107
|
+
return TerraformExecutionResult(
|
|
108
|
+
command=f'terraform {request.command}',
|
|
109
|
+
status='error',
|
|
110
|
+
error_message=f'Invalid Terraform command: {request.command}. Allowed commands are: {", ".join(allowed_commands)}',
|
|
111
|
+
working_directory=request.working_directory,
|
|
112
|
+
outputs=None,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Check for potentially dangerous characters or command injection attempts
|
|
116
|
+
dangerous_patterns = get_dangerous_patterns()
|
|
117
|
+
logger.debug(f'Checking for {len(dangerous_patterns)} dangerous patterns')
|
|
118
|
+
|
|
119
|
+
for pattern in dangerous_patterns:
|
|
120
|
+
if request.variables:
|
|
121
|
+
# Check if the pattern is in any of the variable values
|
|
122
|
+
for var_name, var_value in request.variables.items():
|
|
123
|
+
if pattern in str(var_value) or pattern in str(var_name):
|
|
124
|
+
logger.error(
|
|
125
|
+
f'Potentially dangerous pattern detected in variable {var_name}: {pattern}'
|
|
126
|
+
)
|
|
127
|
+
return TerraformExecutionResult(
|
|
128
|
+
command=f'terraform {request.command}',
|
|
129
|
+
status='error',
|
|
130
|
+
error_message=f"Security violation: Potentially dangerous pattern '{pattern}' detected in variable '{var_name}'",
|
|
131
|
+
working_directory=request.working_directory,
|
|
132
|
+
outputs=None,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Build the command
|
|
136
|
+
cmd = ['terraform', request.command]
|
|
137
|
+
|
|
138
|
+
# Add auto-approve flag for apply and destroy commands to make them non-interactive
|
|
139
|
+
if request.command in ['apply', 'destroy']:
|
|
140
|
+
logger.info(f'Adding -auto-approve flag to {request.command} command')
|
|
141
|
+
cmd.append('-auto-approve')
|
|
142
|
+
|
|
143
|
+
# Add variables only for commands that accept them (plan, apply, destroy)
|
|
144
|
+
if request.command in ['plan', 'apply', 'destroy'] and request.variables:
|
|
145
|
+
logger.info(f'Adding {len(request.variables)} variables to {request.command} command')
|
|
146
|
+
for key, value in request.variables.items():
|
|
147
|
+
cmd.append(f'-var={key}={value}')
|
|
148
|
+
|
|
149
|
+
# Execute command
|
|
150
|
+
try:
|
|
151
|
+
# nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit
|
|
152
|
+
# Safe: Command is validated against allowlist, variables are checked for dangerous patterns,
|
|
153
|
+
# working_directory is user-controlled but subprocess uses cwd parameter (not shell injection)
|
|
154
|
+
process = subprocess.run( # noqa: B603 - Safe: allowlisted commands, validated variables, no shell injection
|
|
155
|
+
cmd, cwd=request.working_directory, capture_output=True, text=True, env=env
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Prepare the result
|
|
159
|
+
stdout = process.stdout
|
|
160
|
+
stderr = process.stderr if process.stderr else ''
|
|
161
|
+
|
|
162
|
+
# Clean output text if requested
|
|
163
|
+
if request.strip_ansi:
|
|
164
|
+
logger.debug('Cleaning command output text (ANSI codes and control characters)')
|
|
165
|
+
stdout = clean_output_text(stdout)
|
|
166
|
+
stderr = clean_output_text(stderr)
|
|
167
|
+
|
|
168
|
+
result = {
|
|
169
|
+
'command': f'terraform {request.command}',
|
|
170
|
+
'status': 'success' if process.returncode == 0 else 'error',
|
|
171
|
+
'return_code': process.returncode,
|
|
172
|
+
'stdout': stdout,
|
|
173
|
+
'stderr': stderr,
|
|
174
|
+
'working_directory': request.working_directory,
|
|
175
|
+
'outputs': None,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Get outputs if this was a successful apply command
|
|
179
|
+
if request.command == 'apply' and process.returncode == 0:
|
|
180
|
+
try:
|
|
181
|
+
logger.info('Getting Terraform outputs')
|
|
182
|
+
output_process = subprocess.run( # noqa: B603 - Safe: hardcoded terraform output command with no user input
|
|
183
|
+
['terraform', 'output', '-json'],
|
|
184
|
+
cwd=request.working_directory,
|
|
185
|
+
capture_output=True,
|
|
186
|
+
text=True,
|
|
187
|
+
env=env,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if output_process.returncode == 0 and output_process.stdout:
|
|
191
|
+
# Get output and clean it if needed
|
|
192
|
+
output_stdout = output_process.stdout
|
|
193
|
+
if request.strip_ansi:
|
|
194
|
+
output_stdout = clean_output_text(output_stdout)
|
|
195
|
+
|
|
196
|
+
# Parse the JSON output
|
|
197
|
+
raw_outputs = json.loads(output_stdout)
|
|
198
|
+
|
|
199
|
+
# Process outputs to extract values from complex structure
|
|
200
|
+
processed_outputs = {}
|
|
201
|
+
for key, value in raw_outputs.items():
|
|
202
|
+
# Terraform outputs in JSON format have a nested structure
|
|
203
|
+
# with 'value', 'type', and sometimes 'sensitive'
|
|
204
|
+
if isinstance(value, dict) and 'value' in value:
|
|
205
|
+
processed_outputs[key] = value['value']
|
|
206
|
+
else:
|
|
207
|
+
processed_outputs[key] = value
|
|
208
|
+
|
|
209
|
+
result['outputs'] = processed_outputs
|
|
210
|
+
logger.info(f'Extracted {len(processed_outputs)} Terraform outputs')
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.warning(f'Failed to get Terraform outputs: {e}')
|
|
213
|
+
|
|
214
|
+
# Return the output
|
|
215
|
+
return TerraformExecutionResult(**result)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
return TerraformExecutionResult(
|
|
218
|
+
command=f'terraform {request.command}',
|
|
219
|
+
status='error',
|
|
220
|
+
error_message=str(e),
|
|
221
|
+
working_directory=request.working_directory,
|
|
222
|
+
outputs=None,
|
|
223
|
+
)
|