Skip to content

Commit

Permalink
fix(naming): removed compliance type convention checks
Browse files Browse the repository at this point in the history
  • Loading branch information
dhutchison committed Nov 16, 2023
1 parent a468d1f commit 9bb4722
Show file tree
Hide file tree
Showing 3 changed files with 0 additions and 281 deletions.
56 changes: 0 additions & 56 deletions examples/unit/naming/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,59 +62,3 @@ If a resource was incorrectly named and did not match the expected pattern, this
```
E AssertionError: Resource 'rFileSystem' tag 'Name' value 'my-test-xx-west-3-vol' did not match expected pattern '^[a-z]*-vol$'.
```


# Advanced Naming Convention Checks

With the previous examples you can easily target individual resources to check their names meet expectations, but adding a test like this for every single resource in templates across your organisation is monotonous and time consuming. It is common for organisations to define conventions like all S3 buckets should have a naming convention like `{company name}-{AWS region}-{some name}`. This example covers the more advanced case for doing resource type to pattern conventions. This example is taken from the `test_naming_conventions` method of [test_naming_advanced.py](./test_naming_advanced.py).


```python
# Ideally this dict would be coming from a common library
# function that you have shared between all your test cases
# (assuming consistency is the goal).
#
# This defines all the resource types we want to check, the pattern to match,
# and either the details of the Tag or Property that the name is held in.
type_patterns = {
"AWS::EFS::FileSystem": {
"Tag": "Name",
# This TagProperty is optional. The default is 'Tags',
# but some resources use a different property name for
# their tags.
"TagProperty": "FileSystemTags",
"Pattern": r"^[a-z0-9-]*-vol$",
},
"AWS::S3::Bucket": {
"Property": "BucketName",
"Pattern": r"^[a-z0-9-]*-xx-west-3[a-z0-9-]*$",
},
"AWS::S3::BucketPolicy": {
# The BucketPolicy type does not support custom names. If we do not
# want to set fail_on_missing_type=False when we call the assertion
# below then we need to include this type in the dict, and set it
# not to be checked.
# This approach ensures that types do not slip through unintentionally.
"Check": False
},
}

stack.assert_resource_type_property_value_conventions(type_patterns)
```

In this example our object containing our naming conventions includes three types, showing the different types of configuration that can be used:

1. `AWS::EFS::FileSystem` - A resource type which holds it's name in the tag `Name`, using a non-standard tag property `FileSystemTags`
2. `AWS::S3::Bucket` - A resource type which holds it's name in a property called `BucketName`
3. `AWS::S3::BucketPolicy` - A resource type which does not have a custom name, so is configured not to perform any pattern checking. This is required to be configured unless `fail_on_missing_type=False` is supplied to the `assert_resource_type_property_value_conventions` method.


If a resource is encountered that did not match the defined pattern, you would get a test failure with an error like this:
```
E AssertionError: Resource 'rFileSystem' tag 'Name' value 'my-test-xx-west-3-vol' did not match expected pattern '^[a-z0-9-]*-FAIL$'.
```

The default behaviour is for `fail_on_missing_type` to be True, so if a resource is encountered with a type not defined in `type_patterns`, an assertion error like the following will be raised.
```
E AssertionError: Resource 'rFileSystem' has type 'AWS::EFS::FileSystem' which is not included in the supplied type_patterns.
```
123 changes: 0 additions & 123 deletions examples/unit/naming/test_naming_advanced.py

This file was deleted.

102 changes: 0 additions & 102 deletions src/cloud_radar/cf/unit/_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,105 +189,3 @@ def get_output(self, output_name: str):
output = Output(output_name, output_data)

return output

def assert_resource_type_property_value_conventions(
self, type_patterns: dict, fail_on_missing_type: bool = True
):
"""
This method will perform assertions on all resources in the stack to check
that a property or tag value matches a regex pattern.
This is commonly used to ensure that resources match naming conventions. The
test_naming_advanced.py example file shows how to use this.
The type_patterns dict uses CloudFormation resource types as the keys,
with the values being an object defining if it should be checked (default
True), the pattern to compare against, and the details of the Property
or Tag to get the value for.
For a type which uses a property:
{
"AWS::S3::Bucket": {
"Property": "BucketName",
"Pattern": r"^[a-z0-9-]*-xx-west-3[a-z0-9-]*$",
}
}
For a type which uses a tag:
{
"AWS::EFS::FileSystem": {
"Tag": "Name",
# This TagProperty is optional. The default is 'Tags',
# but some resources use a different property name for
# their tags.
"TagProperty": "FileSystemTags",
"Pattern": r"^[a-z0-9-]*-vol$",
}
}
For a type which should not be checked:
{
"AWS::S3::BucketPolicy": {
# The BucketPolicy type does not support custom names. If we do not
# want to set fail_on_missing_type=False when we call the assertion
# below then we need to include this type in the dict, and set it
# not to be checked.
# This approach ensures that types do not slip through unintentionally.
"Check": False
}
}
Args:
type_patterns (dict): A dict with the key being the resource type,
and the value an object, in one of the formats
specified above.
fail_on_missing_type (bool): If this is set to true, an assertion error
will be thrown if the stack contains a
resource of a type that is not defined in
the type_patterns dict.
"""

for resource_name in self.data.get("Resources", {}):
resource_value = self.get_resource(resource_name)

naming_convention = type_patterns.get(resource_value.get("Type"))
resource_type = resource_value.get_type_value()

assert naming_convention is not None or not fail_on_missing_type, (
f"Resource '{resource_value.name}' has type '{resource_type}' "
f"which is not included in the supplied type_patterns."
)

if naming_convention is not None and naming_convention.get("Check", True):
# Only proceed further if this type is to be checked.
# This might be set to False where a resource cannot have
# a custom name

assert "Pattern" in naming_convention, (
f"Naming convention definition for {resource_type} did not contain"
" a 'Pattern', and 'Check' was not set to False."
)
pattern = naming_convention["Pattern"]

if "Property" in naming_convention:
# The name is held in a top level property
resource_value.assert_property_value_matches_pattern(
naming_convention["Property"], pattern
)
elif "Tag" in naming_convention:
# The name is held in a tag. We can also look in the configuration
# for a custom tag property, as not all resources use Tag
resource_value.assert_tag_value_matches_pattern(
naming_convention["Tag"],
pattern,
naming_convention.get("TagProperty", "Tags"),
)
else:
raise AssertionError(
(
f"Naming convention definition for {resource_type} did not "
f"contain one of 'Property' or 'Tag', and 'Check' was not "
"set to False."
)
)

0 comments on commit 9bb4722

Please sign in to comment.