Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow attribute values to come from resource metadata #399

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,24 @@ dynamic_references = {
template = Template(template_content, dynamic_references=dynamic_references)
```

There are cases where the default behaviour of our `GetAtt` implementation may not be sufficient and you need a more accurate returned value. When unit testing there are no real AWS resources created, and cloud-radar does not attempt to realistically generate attribute values - a string is always returned. This works good enough most of the time, but there are some cases where if you are attempting to apply intrinsic functions against the attribute value it needs to be more correct. When this occurs, you can add Metadata to the template to provide test values to use.

```
Resources:
MediaPackageV2Channel:
Type: AWS::MediaPackageV2::Channel
Metadata:
Cloud-Radar:
attribute-values:
# Default behaviour of a string is not good enough here, the attribute value is expected to be a List.
IngestEndpointUrls:
- http://one.example.com
- http://two.example.com
Properties:
ChannelGroupName: dev_video_1
ChannelName: !Sub ${AWS::StackName}-MediaPackageChannel
```

A real unit testing example using Pytest can be seen [here](./tests/test_cf/test_examples/test_unit.py)

</details>
Expand Down
13 changes: 12 additions & 1 deletion src/cloud_radar/cf/unit/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,18 @@ def get_att(template: "Template", values: Any) -> str:
if resource_name not in template.template["Resources"]:
raise KeyError(f"Fn::GetAtt - Resource {resource_name} not found in template.")

return f"{resource_name}.{att_name}"
# Get the resource definition
resource = template.template["Resources"][resource_name]

# Check if there is a value in the resource Metadata for this attribute.
# If the attribute requested is in the metadata, return it.
# Otherwise use the string value of "{resource_name}.{att_name}"

metadata = resource.get("Metadata", {})
cloud_radar_metadata = metadata.get("Cloud-Radar", {})
attribute_values = cloud_radar_metadata.get("attribute-values", {})

return attribute_values.get(att_name, f"{resource_name}.{att_name}")


def get_azs(_t: "Template", region: Any) -> List[str]:
Expand Down
36 changes: 36 additions & 0 deletions tests/templates/test_media_getatt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
AWSTemplateFormatVersion: 2010-09-09
Description: "Basic template to check GetAtt behaviours"

Resources:
MediaPackageV2Channel:
Type: AWS::MediaPackageV2::Channel
Metadata:
Cloud-Radar:
attribute-values:
# When unit testing there are no real AWS resources created, and cloud-radar
# does not attempt to realistically generate attribute values - a string is always
# returned. This works good enough most of the time, but there are some cases where
# if you are attempting to apply intrinsic functions against the attribute value
# it needs to be more correct.
#
# In this case, the attribute value is expected to be a List, not a string.
IngestEndpointUrls:
- http://one.example.com
- http://two.example.com
Properties:
ChannelGroupName: dev_video_1
ChannelName: !Sub ${AWS::StackName}-MediaPackageChannel

Outputs:
ChannelArn:
Description: The ARN of the MediaPackageV2 Channel.
Value: !GetAtt MediaPackageV2Channel.Arn
ChannelCreatedAt:
Description: The creation timestamp of the MediaPackageV2 Channel.
Value: !GetAtt MediaPackageV2Channel.CreatedAt
ChannelIngestEndpointUrl1:
Description: The first IngestEndpointUrl of the MediaPackageV2 Channel.
Value: !Select [0, !GetAtt MediaPackageV2Channel.IngestEndpointUrls]
ChannelIngestEndpointUrl2:
Description: The second IngestEndpointUrl of the MediaPackageV2 Channel.
Value: !Select [1, !GetAtt MediaPackageV2Channel.IngestEndpointUrls]
3 changes: 2 additions & 1 deletion tests/test_cf/test_e2e/test_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ def test_constructor(template_dir, default_params):

assert stack.config.config.project.regions[0] == "us-east-1"

assert stack.config.config.project.parameters == {}
# Assert either empty or None at the start
assert not stack.config.config.project.parameters

stack = Stack(str(template))

Expand Down
29 changes: 29 additions & 0 deletions tests/test_cf/test_unit/test_functions_get_att.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pathlib import Path

import pytest

from cloud_radar.cf.unit._template import Template

"""Tests that the GetAtt function can use attribute values defined in a template."""


@pytest.fixture
def template():
template_path = Path(__file__).parent / "../../templates/test_media_getatt.yaml"

return Template.from_yaml(template_path.resolve(), {})


def test_outputs(template: Template):
stack = template.create_stack()

# These two outputs are expected to use values which came from the metadata override
stack.get_output("ChannelIngestEndpointUrl1").assert_value_is(
"http://one.example.com"
)
stack.get_output("ChannelIngestEndpointUrl2").assert_value_is(
"http://two.example.com"
)

# This attribute will use the default format
stack.get_output("ChannelArn").assert_value_is("MediaPackageV2Channel.Arn")
Loading