awslabs.ccapi-mcp-server 1.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.ccapi-mcp-server might be problematic. Click here for more details.

Files changed (28) hide show
  1. awslabs/__init__.py +16 -0
  2. awslabs/ccapi_mcp_server/__init__.py +17 -0
  3. awslabs/ccapi_mcp_server/aws_client.py +62 -0
  4. awslabs/ccapi_mcp_server/cloud_control_utils.py +120 -0
  5. awslabs/ccapi_mcp_server/context.py +37 -0
  6. awslabs/ccapi_mcp_server/errors.py +67 -0
  7. awslabs/ccapi_mcp_server/iac_generator.py +203 -0
  8. awslabs/ccapi_mcp_server/impl/__init__.py +13 -0
  9. awslabs/ccapi_mcp_server/impl/tools/__init__.py +13 -0
  10. awslabs/ccapi_mcp_server/impl/tools/explanation.py +325 -0
  11. awslabs/ccapi_mcp_server/impl/tools/infrastructure_generation.py +70 -0
  12. awslabs/ccapi_mcp_server/impl/tools/resource_operations.py +367 -0
  13. awslabs/ccapi_mcp_server/impl/tools/security_scanning.py +223 -0
  14. awslabs/ccapi_mcp_server/impl/tools/session_management.py +221 -0
  15. awslabs/ccapi_mcp_server/impl/utils/__init__.py +13 -0
  16. awslabs/ccapi_mcp_server/impl/utils/validation.py +64 -0
  17. awslabs/ccapi_mcp_server/infrastructure_generator.py +160 -0
  18. awslabs/ccapi_mcp_server/models/__init__.py +13 -0
  19. awslabs/ccapi_mcp_server/models/models.py +118 -0
  20. awslabs/ccapi_mcp_server/schema_manager.py +219 -0
  21. awslabs/ccapi_mcp_server/server.py +733 -0
  22. awslabs/ccapi_mcp_server/static/__init__.py +13 -0
  23. awslabs_ccapi_mcp_server-1.0.1.dist-info/METADATA +656 -0
  24. awslabs_ccapi_mcp_server-1.0.1.dist-info/RECORD +28 -0
  25. awslabs_ccapi_mcp_server-1.0.1.dist-info/WHEEL +4 -0
  26. awslabs_ccapi_mcp_server-1.0.1.dist-info/entry_points.txt +2 -0
  27. awslabs_ccapi_mcp_server-1.0.1.dist-info/licenses/LICENSE +175 -0
  28. awslabs_ccapi_mcp_server-1.0.1.dist-info/licenses/NOTICE +2 -0
awslabs/__init__.py ADDED
@@ -0,0 +1,16 @@
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.
@@ -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.ccapi-mcp-server"""
16
+
17
+ __version__ = '1.0.1'
@@ -0,0 +1,62 @@
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
+ import botocore.config
16
+ from awslabs.ccapi_mcp_server import __version__
17
+ from awslabs.ccapi_mcp_server.errors import ClientError
18
+ from boto3 import Session
19
+ from os import environ
20
+
21
+
22
+ session_config = botocore.config.Config(
23
+ user_agent_extra=f'ccapi-mcp-server/{__version__}',
24
+ )
25
+
26
+
27
+ def get_aws_client(service_name, region_name=None):
28
+ """Create and return an AWS service client using boto3's default credential chain.
29
+
30
+ Args:
31
+ service_name: AWS service name (e.g., 'cloudcontrol', 'logs', 'marketplace-catalog')
32
+ region_name: AWS region name (defaults to boto3's default region resolution)
33
+
34
+ Returns:
35
+ Boto3 client for the specified service
36
+ """
37
+ # Handle FieldInfo objects (from pydantic)
38
+ if hasattr(region_name, 'default') and region_name is not None:
39
+ region_name = region_name.default
40
+
41
+ # AWS Region Resolution Order (highest to lowest priority):
42
+ # 1. region_name argument passed to this function
43
+ # 2. AWS_REGION environment variable
44
+ # 3. region setting in ~/.aws/config for the active profile
45
+ # 4. "us-east-1" as the final fallback
46
+ #
47
+ # This follows boto3's standard region resolution chain, ensuring consistent
48
+ # behavior with other AWS tools and SDKs.
49
+ profile_name = environ.get('AWS_PROFILE')
50
+ session = Session(profile_name=profile_name) if profile_name else Session()
51
+
52
+ try:
53
+ return session.client(service_name, region_name=region_name, config=session_config)
54
+ except Exception as e:
55
+ if 'ExpiredToken' in str(e):
56
+ raise ClientError('Your AWS credentials have expired. Please refresh them.')
57
+ elif 'NoCredentialProviders' in str(e):
58
+ raise ClientError(
59
+ 'No AWS credentials found. Please configure credentials using environment variables or AWS configuration.'
60
+ )
61
+ else:
62
+ raise ClientError('Got an error when loading your client.')
@@ -0,0 +1,120 @@
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
+ from awslabs.ccapi_mcp_server.errors import ClientError
16
+ from typing import Any, Dict
17
+
18
+
19
+ def add_default_tags(properties: Dict, schema: Dict) -> Dict:
20
+ """Add default tags to resource properties. Always tries to add tags - let AWS reject if unsupported."""
21
+ # Return empty dict when properties is None or empty dict {}
22
+ # This prevents processing invalid/missing resource properties
23
+ if not properties:
24
+ return {}
25
+
26
+ properties_with_tags = properties.copy()
27
+
28
+ # Always try to add tags - don't check schema since it can be unreliable
29
+ # Ensure Tags array exists
30
+ if 'Tags' not in properties_with_tags:
31
+ properties_with_tags['Tags'] = []
32
+
33
+ tags = properties_with_tags['Tags']
34
+ # Add default tags if they don't exist
35
+ managed_by_exists = any(tag.get('Key') == 'MANAGED_BY' for tag in tags)
36
+ source_exists = any(tag.get('Key') == 'MCP_SERVER_SOURCE_CODE' for tag in tags)
37
+ version_exists = any(tag.get('Key') == 'MCP_SERVER_VERSION' for tag in tags)
38
+
39
+ if not managed_by_exists:
40
+ tags.append({'Key': 'MANAGED_BY', 'Value': 'CCAPI-MCP-SERVER'})
41
+ if not source_exists:
42
+ tags.append(
43
+ {
44
+ 'Key': 'MCP_SERVER_SOURCE_CODE',
45
+ 'Value': 'https://github.com/awslabs/mcp/tree/main/src/ccapi-mcp-server',
46
+ }
47
+ )
48
+ if not version_exists:
49
+ from awslabs.ccapi_mcp_server import __version__
50
+
51
+ tags.append({'Key': 'MCP_SERVER_VERSION', 'Value': __version__})
52
+
53
+ properties_with_tags['Tags'] = tags
54
+
55
+ return properties_with_tags
56
+
57
+
58
+ def validate_patch(patch_document: Any):
59
+ """A best effort check that makes sure that the format of a patch document is valid before sending it to CloudControl."""
60
+ if not isinstance(patch_document, list):
61
+ raise ClientError('Patch document must be a list')
62
+
63
+ for patch_op in patch_document:
64
+ if not isinstance(patch_op, dict):
65
+ raise ClientError('Each patch operation must be a dictionary')
66
+ if 'op' not in patch_op:
67
+ raise ClientError("Each patch operation must include an 'op' field")
68
+ if patch_op['op'] not in ['add', 'remove', 'replace', 'move', 'copy', 'test']:
69
+ raise ClientError(
70
+ f"Operation '{patch_op['op']}' is not supported. Must be one of: add, remove, replace, move, copy, test"
71
+ )
72
+ if 'path' not in patch_op:
73
+ raise ClientError("Each patch operation must include a 'path' field")
74
+ # Value is required for add, replace, and test operations
75
+ if patch_op['op'] in ['add', 'replace', 'test'] and 'value' not in patch_op:
76
+ raise ClientError(f"The '{patch_op['op']}' operation requires a 'value' field")
77
+ # From is required for move and copy operations
78
+ if patch_op['op'] in ['move', 'copy'] and 'from' not in patch_op:
79
+ raise ClientError(f"The '{patch_op['op']}' operation requires a 'from' field")
80
+
81
+
82
+ def progress_event(response_event, hooks_events) -> Dict[str, Any]:
83
+ """Map a CloudControl API response to a standard output format for the MCP."""
84
+ response = {
85
+ 'status': response_event['OperationStatus'],
86
+ 'resource_type': response_event['TypeName'],
87
+ 'is_complete': response_event['OperationStatus'] == 'SUCCESS'
88
+ or response_event['OperationStatus'] == 'FAILED',
89
+ 'request_token': response_event['RequestToken'],
90
+ }
91
+
92
+ if response_event.get('Identifier', None):
93
+ response['identifier'] = response_event['Identifier']
94
+ if response_event.get('ResourceModel', None):
95
+ response['resource_info'] = response_event['ResourceModel']
96
+ if response_event.get('ErrorCode', None):
97
+ response['error_code'] = response_event['ErrorCode']
98
+ if response_event.get('EventTime', None):
99
+ response['event_time'] = response_event['EventTime']
100
+ if response_event.get('RetryAfter', None):
101
+ response['retry_after'] = response_event['RetryAfter']
102
+
103
+ # CloudControl returns a list of hooks events which may also contain a message which should
104
+ # take precedent over the status message returned from CloudControl directly
105
+ hooks_status_message = None
106
+ if hooks_events:
107
+ failed_hook_event_messages = (
108
+ hook_event['HookStatusMessage']
109
+ for hook_event in hooks_events
110
+ if hook_event.get('HookStatus', None) == 'HOOK_COMPLETE_FAILED'
111
+ or hook_event.get('HookStatus', None) == 'HOOK_FAILED'
112
+ )
113
+ hooks_status_message = next(failed_hook_event_messages, None)
114
+
115
+ if hooks_status_message:
116
+ response['status_message'] = hooks_status_message
117
+ elif response_event.get('StatusMessage', None):
118
+ response['status_message'] = response_event['StatusMessage']
119
+
120
+ return response
@@ -0,0 +1,37 @@
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
+ from awslabs.ccapi_mcp_server.errors import ServerError
16
+
17
+
18
+ class Context:
19
+ """A singleton which includes context for the MCP server such as startup parameters."""
20
+
21
+ _instance = None
22
+
23
+ def __init__(self, readonly_mode: bool):
24
+ """Initializes the context."""
25
+ self._readonly_mode = readonly_mode
26
+
27
+ @classmethod
28
+ def readonly_mode(cls) -> bool:
29
+ """If a the server was started up with the argument --readonly True, this will be set to True."""
30
+ if cls._instance is None:
31
+ raise ServerError('Context was not initialized')
32
+ return cls._instance._readonly_mode
33
+
34
+ @classmethod
35
+ def initialize(cls, readonly_mode: bool):
36
+ """Create the singleton instance of the type."""
37
+ cls._instance = cls(readonly_mode)
@@ -0,0 +1,67 @@
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
+
16
+ def handle_aws_api_error(e: Exception) -> Exception:
17
+ """Handle AWS API errors using boto3's built-in exception information.
18
+
19
+ Leverages boto3's structured error handling as documented at:
20
+ https://boto3.amazonaws.com/v1/documentation/api/latest/guide/error-handling.html
21
+
22
+ Args:
23
+ e: The exception that was raised
24
+
25
+ Returns:
26
+ Standardized ClientError with AWS error details
27
+ """
28
+ # Import boto3 exceptions for proper type checking
29
+ from botocore.exceptions import ClientError as BotocoreClientError
30
+ from botocore.exceptions import NoCredentialsError, PartialCredentialsError
31
+
32
+ # Handle boto3's structured exceptions directly
33
+ if isinstance(e, BotocoreClientError):
34
+ # boto3 ClientError already has structured error information
35
+ error_code = e.response['Error']['Code']
36
+ error_message = e.response['Error']['Message']
37
+ return ClientError(f'AWS API Error ({error_code}): {error_message}')
38
+ elif isinstance(e, NoCredentialsError):
39
+ return ClientError('AWS credentials not found. Please configure your AWS credentials.')
40
+ elif isinstance(e, PartialCredentialsError):
41
+ return ClientError(
42
+ 'Incomplete AWS credentials. Please check your AWS credential configuration.'
43
+ )
44
+ else:
45
+ # Fallback for other exceptions
46
+ return ClientError(f'An error occurred: {str(e)}')
47
+
48
+
49
+ class ClientError(Exception):
50
+ """An error that indicates that the request was malformed or incorrect in some way. There was no issue on the server side."""
51
+
52
+ def __init__(self, message):
53
+ """Call super and set message."""
54
+ # Call the base class constructor with the parameters it needs
55
+ super().__init__(message)
56
+ self.type = 'client'
57
+ self.message = message
58
+
59
+
60
+ class ServerError(Exception):
61
+ """An error that indicates that there was an issue processing the request."""
62
+
63
+ def __init__(self, message: str):
64
+ """Initialize ServerError with message."""
65
+ super().__init__(message)
66
+ self.type = 'server'
67
+ self.message = message
@@ -0,0 +1,203 @@
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
+ """CloudFormation IaC Generator tool implementation."""
16
+
17
+ import os
18
+ from awslabs.ccapi_mcp_server.aws_client import get_aws_client
19
+ from awslabs.ccapi_mcp_server.errors import ClientError, handle_aws_api_error
20
+ from typing import Dict, List, Optional
21
+
22
+
23
+ async def create_template(
24
+ template_name: Optional[str] = None,
25
+ resources: Optional[List[Dict[str, str]]] = None,
26
+ output_format: str = 'YAML',
27
+ deletion_policy: str = 'RETAIN',
28
+ update_replace_policy: str = 'RETAIN',
29
+ template_id: Optional[str] = None,
30
+ save_to_file: Optional[str] = None,
31
+ region_name: Optional[str] = None,
32
+ ) -> Dict:
33
+ """Create a CloudFormation template from existing resources using the IaC Generator API.
34
+
35
+ This function handles three main scenarios:
36
+ 1. Starting a new template generation process
37
+ 2. Checking the status of an existing template generation process
38
+ 3. Retrieving a generated template
39
+
40
+ Args:
41
+ template_name: Name for the generated template
42
+ resources: List of resources to include in the template, each with 'ResourceType' and 'ResourceIdentifier'
43
+ output_format: Output format for the template (JSON or YAML)
44
+ deletion_policy: Default DeletionPolicy for resources in the template (RETAIN, DELETE, or SNAPSHOT)
45
+ update_replace_policy: Default UpdateReplacePolicy for resources in the template (RETAIN, DELETE, or SNAPSHOT)
46
+ template_id: ID of an existing template generation process to check status or retrieve template
47
+ save_to_file: Path to save the generated template to a file
48
+ region_name: AWS region name
49
+
50
+ Returns:
51
+ A dictionary containing information about the template generation process or the generated template
52
+ """
53
+ # Validate parameters
54
+ if not template_id and not template_name:
55
+ raise ClientError('Either template_name or template_id must be provided')
56
+
57
+ if output_format not in ['JSON', 'YAML']:
58
+ raise ClientError("output_format must be either 'JSON' or 'YAML'")
59
+
60
+ if deletion_policy not in ['RETAIN', 'DELETE', 'SNAPSHOT']:
61
+ raise ClientError("deletion_policy must be one of 'RETAIN', 'DELETE', or 'SNAPSHOT'")
62
+
63
+ if update_replace_policy not in ['RETAIN', 'DELETE', 'SNAPSHOT']:
64
+ raise ClientError("update_replace_policy must be one of 'RETAIN', 'DELETE', or 'SNAPSHOT'")
65
+
66
+ # Get CloudFormation client
67
+ cfn_client = get_aws_client('cloudformation', region_name)
68
+
69
+ # Case 1: Check status or retrieve template for an existing template generation process
70
+ if template_id:
71
+ return await _handle_existing_template(
72
+ cfn_client, template_id, save_to_file, output_format
73
+ )
74
+
75
+ # Case 2: Start a new template generation process
76
+ return await _start_template_generation(
77
+ cfn_client, template_name, resources, deletion_policy, update_replace_policy
78
+ )
79
+
80
+
81
+ async def _start_template_generation(
82
+ cfn_client,
83
+ template_name: str | None,
84
+ resources: Optional[List[Dict[str, str]]],
85
+ deletion_policy: str,
86
+ update_replace_policy: str,
87
+ ) -> Dict:
88
+ """Start a new template generation process.
89
+
90
+ Args:
91
+ cfn_client: Boto3 CloudFormation client
92
+ template_name: Name for the generated template
93
+ resources: List of resources to include in the template
94
+ output_format: Output format for the template (JSON or YAML)
95
+ deletion_policy: DeletionPolicy for resources in the template
96
+ update_replace_policy: UpdateReplacePolicy for resources in the template
97
+
98
+ Returns:
99
+ A dictionary containing information about the template generation process
100
+ """
101
+ # Prepare parameters for the API call
102
+ params = {
103
+ 'GeneratedTemplateName': template_name,
104
+ 'TemplateConfiguration': {
105
+ 'DeletionPolicy': deletion_policy,
106
+ 'UpdateReplacePolicy': update_replace_policy,
107
+ },
108
+ }
109
+
110
+ # Add resources if provided
111
+ if resources:
112
+ resource_identifiers = []
113
+ for resource in resources:
114
+ if 'ResourceType' not in resource or 'ResourceIdentifier' not in resource:
115
+ raise ClientError(
116
+ "Each resource must have 'ResourceType' and 'ResourceIdentifier'"
117
+ )
118
+ resource_identifiers.append(
119
+ {
120
+ 'ResourceType': resource['ResourceType'],
121
+ 'ResourceIdentifier': resource['ResourceIdentifier'],
122
+ }
123
+ )
124
+ params['Resources'] = resource_identifiers
125
+
126
+ # Call the API
127
+ try:
128
+ response = cfn_client.create_generated_template(**params)
129
+ return {
130
+ 'status': 'INITIATED',
131
+ 'template_id': response['GeneratedTemplateId'],
132
+ 'message': 'Template generation initiated. Use the template_id to check status.',
133
+ }
134
+ except Exception as e:
135
+ raise handle_aws_api_error(e)
136
+
137
+
138
+ async def _handle_existing_template(
139
+ cfn_client, template_id: str, save_to_file: Optional[str], output_format: str = 'YAML'
140
+ ) -> Dict:
141
+ """Handle an existing template generation process - check status or retrieve template.
142
+
143
+ Args:
144
+ cfn_client: Boto3 CloudFormation client
145
+ template_id: ID of the template generation process
146
+ save_to_file: Path to save the generated template to a file
147
+ output_format: Format of generated template. Either JSON or YAML
148
+
149
+ Returns:
150
+ A dictionary containing information about the template generation process or the generated template
151
+ """
152
+ # Check the status of the template generation process
153
+ try:
154
+ status_response = cfn_client.describe_generated_template(GeneratedTemplateName=template_id)
155
+
156
+ status = status_response['Status']
157
+
158
+ # Return status information if the template is not yet complete
159
+ if status != 'COMPLETE':
160
+ return {
161
+ 'status': status,
162
+ 'template_id': template_id,
163
+ 'message': f'Template generation {status.lower()}.',
164
+ }
165
+
166
+ # If the template is complete, retrieve it
167
+ template_response = cfn_client.get_generated_template(
168
+ GeneratedTemplateName=template_id, Format=output_format
169
+ )
170
+
171
+ template_content = template_response['TemplateBody']
172
+ resources = status_response.get('ResourceIdentifiers', [])
173
+
174
+ # Save the template to a file if requested
175
+ file_path = None
176
+ if save_to_file:
177
+ try:
178
+ # Ensure the directory exists
179
+ os.makedirs(os.path.dirname(os.path.abspath(save_to_file)), exist_ok=True)
180
+
181
+ # Write the template to the file
182
+ with open(save_to_file, 'w') as f:
183
+ f.write(template_content)
184
+ file_path = save_to_file
185
+ except Exception as e:
186
+ raise ClientError(f'Failed to save template to file: {str(e)}')
187
+
188
+ # Return the template and related information
189
+ result = {
190
+ 'status': 'COMPLETED',
191
+ 'template_id': template_id,
192
+ 'template': template_content,
193
+ 'resources': resources,
194
+ 'message': 'Template generation completed.',
195
+ }
196
+
197
+ if file_path:
198
+ result['file_path'] = file_path
199
+
200
+ return result
201
+
202
+ except Exception as e:
203
+ raise handle_aws_api_error(e)
@@ -0,0 +1,13 @@
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.
@@ -0,0 +1,13 @@
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.