94d35af520
This change introduces a checklist similar to IMPLEMENTATION_COVERAGE.md to document moto's adherence to the full CloudFormation specification. The script (invoked via `make cloudformation_coverage`) finds any moto models that implement `moto.core.common_models.CloudFormationModel` and checks to see if all CRUD methods are implemented. For `has_cfn_attr`, it also checks to see if all of the attributes exposed by `Fn::GetAtt` are implemented. It does not check to see if `physical_resource_id` is implemented because as far as I can tell, the published AWS spec doesn't include that information: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html A more aggressive approach would be to remove default implementations from all the abstract methods and allow Python to throw for any unimplemented method. But that would obviously break a lot of things. Instead, I think the checklist represents good documentation for users about what they can and can't expect moto to handle when parsing their CloudFormation templates. It also serves as a nice to-do list of small improvements for contributors to add (and I'll likely add a few myself). Many of these would be particularly good "first issues" for first time contributors because in general, these methods just call existing methods.
112 lines
3.6 KiB
Python
Executable File
112 lines
3.6 KiB
Python
Executable File
#!/usr/bin/env python
|
|
import importlib
|
|
import json
|
|
import mock
|
|
import requests
|
|
|
|
import moto
|
|
|
|
# Populate CloudFormationModel.__subclasses__()
|
|
moto.mock_all()
|
|
|
|
|
|
def check(condition):
|
|
if bool(condition):
|
|
return "x"
|
|
else:
|
|
return " "
|
|
|
|
|
|
def is_implemented(model, method_name):
|
|
# method_name in model.__dict__ will be True if the method
|
|
# exists on the model and False if it's only inherited from
|
|
# CloudFormationModel.
|
|
return hasattr(model, method_name) and method_name in model.__dict__
|
|
|
|
|
|
class CloudFormationChecklist:
|
|
def __init__(self, resource_name, schema):
|
|
self.resource_name = resource_name
|
|
self.schema = schema
|
|
|
|
def __str__(self):
|
|
missing_attrs_checklist = "\n".join(
|
|
[f" - [ ] {attr}" for attr in self.missing_attrs]
|
|
)
|
|
report = (
|
|
f"- {self.resource_name}:\n"
|
|
f" - [{check(self.creatable)}] create implemented\n"
|
|
f" - [{check(self.updatable)}] update implemented\n"
|
|
f" - [{check(self.deletable)}] delete implemented\n"
|
|
f" - [{check(not self.missing_attrs)}] Fn::GetAtt implemented\n"
|
|
) + missing_attrs_checklist
|
|
|
|
return report.strip()
|
|
|
|
@property
|
|
def service_name(self):
|
|
return self.resource_name.split("::")[1].lower()
|
|
|
|
@property
|
|
def model_name(self):
|
|
return self.resource_name.split("::")[2]
|
|
|
|
@property
|
|
def moto_model(self):
|
|
for subclass in moto.core.common_models.CloudFormationModel.__subclasses__():
|
|
subclass_service = subclass.__module__.split(".")[1]
|
|
subclass_model = subclass.__name__
|
|
|
|
if subclass_service == self.service_name and subclass_model in (
|
|
self.model_name,
|
|
"Fake" + self.model_name,
|
|
):
|
|
return subclass
|
|
|
|
@property
|
|
def expected_attrs(self):
|
|
return list(self.schema.get("Attributes", {}).keys())
|
|
|
|
@property
|
|
def missing_attrs(self):
|
|
missing_attrs = []
|
|
for attr in self.expected_attrs:
|
|
try:
|
|
# TODO: Change the actual abstract method to return False
|
|
with mock.patch(
|
|
"moto.core.common_models.CloudFormationModel.has_cfn_attr",
|
|
return_value=False,
|
|
):
|
|
if not self.moto_model.has_cfn_attr(attr):
|
|
missing_attrs.append(attr)
|
|
except:
|
|
missing_attrs.append(attr)
|
|
return missing_attrs
|
|
|
|
@property
|
|
def creatable(self):
|
|
return is_implemented(self.moto_model, "create_from_cloudformation_json")
|
|
|
|
@property
|
|
def updatable(self):
|
|
return is_implemented(self.moto_model, "update_from_cloudformation_json")
|
|
|
|
@property
|
|
def deletable(self):
|
|
return is_implemented(self.moto_model, "delete_from_cloudformation_json")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html
|
|
cfn_spec = requests.get(
|
|
"https://dnwj8swjjbsbt.cloudfront.net/latest/gzip/CloudFormationResourceSpecification.json"
|
|
).json()
|
|
for resource_name, schema in sorted(cfn_spec["ResourceTypes"].items()):
|
|
checklist = CloudFormationChecklist(resource_name, schema)
|
|
# Only print checklists for models that implement CloudFormationModel;
|
|
# otherwise the checklist is very long and mostly empty because there
|
|
# are so many niche AWS services and resources that moto doesn't
|
|
# implement yet.
|
|
if checklist.moto_model:
|
|
print(checklist)
|