104 lines
3.4 KiB
Python
104 lines
3.4 KiB
Python
import json
|
|
import threading
|
|
from typing import Any, Dict
|
|
|
|
from moto import settings
|
|
from moto.awslambda import lambda_backends
|
|
from moto.core import CloudFormationModel
|
|
from moto.moto_api._internal import mock_random
|
|
|
|
|
|
class CustomModel(CloudFormationModel):
|
|
def __init__(
|
|
self, region_name: str, request_id: str, logical_id: str, resource_name: str
|
|
):
|
|
self.region_name = region_name
|
|
self.request_id = request_id
|
|
self.logical_id = logical_id
|
|
self.resource_name = resource_name
|
|
self.data: Dict[str, Any] = dict()
|
|
self._finished = False
|
|
|
|
def set_data(self, data: Dict[str, Any]) -> None:
|
|
self.data = data
|
|
self._finished = True
|
|
|
|
def is_created(self) -> bool:
|
|
return self._finished
|
|
|
|
@property
|
|
def physical_resource_id(self) -> str:
|
|
return self.resource_name
|
|
|
|
@staticmethod
|
|
def cloudformation_type() -> str:
|
|
return "?"
|
|
|
|
@classmethod
|
|
def create_from_cloudformation_json( # type: ignore[misc]
|
|
cls,
|
|
resource_name: str,
|
|
cloudformation_json: Dict[str, Any],
|
|
account_id: str,
|
|
region_name: str,
|
|
**kwargs: Any,
|
|
) -> "CustomModel":
|
|
logical_id = kwargs["LogicalId"]
|
|
stack_id = kwargs["StackId"]
|
|
resource_type = kwargs["ResourceType"]
|
|
properties = cloudformation_json["Properties"]
|
|
service_token = properties["ServiceToken"]
|
|
|
|
backend = lambda_backends[account_id][region_name]
|
|
fn = backend.get_function(service_token)
|
|
|
|
request_id = str(mock_random.uuid4())
|
|
|
|
custom_resource = CustomModel(
|
|
region_name, request_id, logical_id, resource_name
|
|
)
|
|
|
|
from moto.cloudformation import cloudformation_backends
|
|
|
|
stack = cloudformation_backends[account_id][region_name].get_stack(stack_id)
|
|
stack.add_custom_resource(custom_resource)
|
|
|
|
# A request will be send to this URL to indicate success/failure
|
|
# This request will be coming from inside a Docker container
|
|
# Note that, in order to reach the Moto host, the Moto-server should be listening on 0.0.0.0
|
|
#
|
|
# Alternative: Maybe we should let the user pass in a container-name where Moto is running?
|
|
# Similar to how we know for sure that the container in our CI is called 'motoserver'
|
|
host = f"{settings.moto_server_host()}:{settings.moto_server_port()}"
|
|
response_url = (
|
|
f"{host}/cloudformation_{region_name}/cfnresponse?stack={stack_id}"
|
|
)
|
|
|
|
event = {
|
|
"RequestType": "Create",
|
|
"ServiceToken": service_token,
|
|
"ResponseURL": response_url,
|
|
"StackId": stack_id,
|
|
"RequestId": request_id,
|
|
"LogicalResourceId": logical_id,
|
|
"ResourceType": resource_type,
|
|
"ResourceProperties": properties,
|
|
}
|
|
|
|
invoke_thread = threading.Thread(
|
|
target=fn.invoke, args=(json.dumps(event), {}, {})
|
|
)
|
|
invoke_thread.start()
|
|
|
|
return custom_resource
|
|
|
|
@classmethod
|
|
def has_cfn_attr(cls, attr: str) -> bool: # pylint: disable=unused-argument
|
|
# We don't know which attributes are supported for third-party resources
|
|
return True
|
|
|
|
def get_cfn_attribute(self, attribute_name: str) -> Any:
|
|
if attribute_name in self.data:
|
|
return self.data[attribute_name]
|
|
return None
|