Skip to content

Commit

Permalink
add globals to model and able to resolve it
Browse files Browse the repository at this point in the history
  • Loading branch information
Ramon committed Nov 24, 2023
1 parent 912798e commit 1cb12f3
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 2 deletions.
25 changes: 23 additions & 2 deletions pycfmodel/model/cf_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ class CFModel(CustomModel):
Properties:
- AWSTemplateFormatVersion
- AWSTemplateFormatVersion: The AWS CloudFormation template version that the template conforms to.
- Conditions: Conditions that control behaviour of the template.
- Description: Description for the template.
- Globals: TODO
- Mappings: A 3 level mapping of keys and associated values.
- Metadata: Additional information about the template.
- Outputs: Output values of the template.
Expand All @@ -30,11 +31,14 @@ class CFModel(CustomModel):
- Transform: For serverless applications, specifies the version of the AWS Serverless Application Model (AWS SAM) to use.
More info at [AWS Docs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-anatomy.html)
More info for Globals at [AWS Globals Docs](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html)
"""

AWSTemplateFormatVersion: Optional[date]
Conditions: Optional[Dict] = {}
Description: Optional[str] = None
Globals: Optional[Dict] = {}
Mappings: Optional[Dict[str, Dict[str, Dict[str, Any]]]] = {}
Metadata: Optional[Dict[str, Any]] = None
Outputs: Optional[Dict[str, Dict[str, Union[str, Dict]]]] = {}
Expand Down Expand Up @@ -84,14 +88,23 @@ def resolve(self, extra_params=None) -> "CFModel":
{key: _extended_bool(resolve(value, extended_parameters, self.Mappings, resolved_conditions))}
)

globals_cf = dict_value.pop("Globals", {})
resolved_globals = {
key: resolve(value, extended_parameters, self.Mappings, resolved_conditions)
for key, value in globals_cf.items()
}

resources = dict_value.pop("Resources")
resolved_resources = {
key: resolve(value, extended_parameters, self.Mappings, resolved_conditions)
for key, value in resources.items()
if value.get("Condition") is None
or (value.get("Condition") is not None and resolved_conditions.get(value["Condition"], True))
}
return CFModel(**dict_value, Conditions=resolved_conditions, Resources=resolved_resources)

return CFModel(
**dict_value, Conditions=resolved_conditions, Resources=resolved_resources, Globals=resolved_globals
)

def expand_actions(self) -> "CFModel":
"""
Expand Down Expand Up @@ -134,3 +147,11 @@ def resources_filtered_by_type(
if isinstance(resource, allowed_resource_classes) or resource.Type in allowed_types:
result[resource_name] = resource
return result

def is_sam_model(self) -> bool:
transform = self.Transform
if self.Transform is None:
return False
if isinstance(transform, str):
transform = [transform]
return any(macro.startswith("AWS::Serverless") for macro in transform)
62 changes: 62 additions & 0 deletions tests/test_cf_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,55 @@ def model():
"Resources": {"Logical ID": {"Type": "Resource type", "Properties": {"foo": "bar"}}},
"Rules": {},
"Outputs": {},
"Globals": {},
}
)


@pytest.fixture()
def model_single_transform():
return CFModel(
**{
"AWSTemplateFormatVersion": "2012-12-12",
"Description": "",
"Metadata": {},
"Parameters": {},
"Mappings": {},
"Conditions": {},
"Transform": "AWS::Serverless-2016-10-31",
"Resources": {"Logical ID": {"Type": "Resource type", "Properties": {"foo": "bar"}}},
"Rules": {},
"Outputs": {},
}
)


@pytest.fixture()
def model_no_transform():
return CFModel(
**{
"AWSTemplateFormatVersion": "2012-12-12",
"Description": "",
"Metadata": {},
"Parameters": {},
"Mappings": {},
"Conditions": {},
"Resources": {"Logical ID": {"Type": "Resource type", "Properties": {"foo": "bar"}}},
"Rules": {},
"Outputs": {},
}
)


@pytest.fixture()
def model_with_empty_globals():
return CFModel(
**{
"AWSTemplateFormatVersion": "2012-12-12",
"Globals": {},
"Parameters": {},
"Transform": "AWS::Serverless-2016-10-31",
"Resources": {"Logical ID": {"Type": "AWS::Dummy::Dummy", "Properties": {"foo": "bar"}}},
}
)

Expand All @@ -26,6 +75,7 @@ def test_basic_json(model: CFModel):
assert type(model).__name__ == "CFModel"
assert len(model.Resources) == 1
assert model.Transform == ["MyMacro", "AWS::Serverless"]
assert model.Globals == {}


def test_resources_filtered_by_type():
Expand Down Expand Up @@ -61,3 +111,15 @@ def test_transform_handles_string():

def test_resolve_model(model):
assert model.resolve() == model


@pytest.mark.parametrize(
"model_fixture,is_sam_model",
[("model", True), ("model_single_transform", True), ("model_no_transform", False)],
)
def test_transform_is_of_type_sam_model(model_fixture, is_sam_model, request):
assert request.getfixturevalue(model_fixture).is_sam_model() is is_sam_model


def test_model_with_empty_globals_is_able_to_resolve_to_empty_dict(model_with_empty_globals):
assert model_with_empty_globals.Globals == {}
22 changes: 22 additions & 0 deletions tests/test_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,3 +815,25 @@ def test_resolve_find_in_map_for_bool_values_in_map(params, expected_resolved_va

result = resolve_find_in_map(function_body=function_body, params=params, mappings=mappings, conditions={})
assert result == expected_resolved_value


def test_resolve_globals_with_values_and_referencing_parameters():
"""
This test aims to be able to solve these type of templates:
https://github.com/Skyscanner/cfripper/issues/259#issuecomment-1824485673
"""
template = {
"AWSTemplateFormatVersion": "2012-12-12",
"Transform": "AWS::Serverless-2016-10-31",
"Parameters": {
"ProjectName": {"Type": "String", "Default": "my-project"},
"Environment": {"Type": "String", "Default": "development"},
},
"Globals": {"Function": {"Tags": {"Env": {"Ref": "Environment"}, "Project": {"Ref": "ProjectName"}}}},
"Resources": {"MySNSTopic": {"Type": "AWS::SNS::Topic"}},
}

model = parse(template).resolve()

assert model.Globals.get("Function").get("Tags").get("Env") == "development"
assert model.Globals.get("Function").get("Tags").get("Project") == "my-project"

0 comments on commit 1cb12f3

Please sign in to comment.