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

Add support for generating a StackId when rendering a template #397

Open
wants to merge 2 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
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ The default values for pseudo parameters:
| **NoValue** | "" |
| **Partition** | "aws" |
| Region | "us-east-1" |
| **StackId** | "" |
| **StackName** | "" |
| StackId | (generated based on other values) |
| StackName | "my-cloud-radar-stack" |
| **URLSuffix** | "amazonaws.com" |
_Note: Bold variables are not fully implemented yet see the [Roadmap](#roadmap)_

Expand Down Expand Up @@ -307,7 +307,6 @@ A real functional testing example using Pytest can be seen [here](./tests/test_c
### Unit
- Add full functionality to pseudo variables.
* Variables like `Partition`, `URLSuffix` should change if the region changes.
* Variables like `StackName` and `StackId` should have a better default than ""
- Handle References to resources that shouldn't exist.
* It's currently possible that a `!Ref` to a Resource stays in the final template even if that resource is later removed because of a conditional.

Expand Down
19 changes: 17 additions & 2 deletions src/cloud_radar/cf/unit/_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json
import re
import uuid
from pathlib import Path
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Union

Expand All @@ -26,8 +27,8 @@ class Template:
NoValue: str = "" # Not yet implemented
Partition: str = "aws" # Other regions not implemented
Region: str = "us-east-1"
StackId: str = "" # Not yet implemented
StackName: str = "" # Not yet implemented
StackId: str = "" # If left blank this will be generated
StackName: str = "my-cloud-radar-stack"
URLSuffix: str = "amazonaws.com" # Other regions not implemented

def __init__(
Expand Down Expand Up @@ -310,6 +311,19 @@ def remove_condtional_resources(self, template: Dict[str, Any]) -> Dict[str, Any

return template

# If the StackId variable is not set, generate a value for it
def _get_populated_stack_id(self) -> str:
if not Template.StackId:
# Not explicitly set, generate a value
unique_uuid = uuid.uuid4()

return (
f"arn:{Template.Partition}:cloudformation:{self.Region}:"
f"{Template.AccountId}:stack/{Template.StackName}/{unique_uuid}"
)

return Template.StackId

def create_stack(
self,
params: Optional[Dict[str, str]] = None,
Expand All @@ -318,6 +332,7 @@ def create_stack(
):
if region:
self.Region = region
self.StackId = self._get_populated_stack_id()

self.render(params, parameters_file=parameters_file)

Expand Down
16 changes: 16 additions & 0 deletions tests/templates/test_stackid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
AWSTemplateFormatVersion: 2010-09-09
Description: "Creates an S3 bucket to store logs."

Resources:
UniqueBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub
- 'my-test-${stack_region}-${uniqifier}-bucket'
- # AWS::StackId has this format
# arn:aws:cloudformation:us-west-2:123456789012:stack/teststack/51af3dc0-da77-11e4-872e-1234567db123
# Trying to capture the last piece after the '-'
# As stack name could contain "-"s, split on the "/"s first
uniqifier: !Select [ 4, !Split [ "-", !Select [ 2, !Split [ "/", !Ref AWS::StackId ] ] ] ]
# Usually you would refer to AWS:::Region, but trying to test StackId creation works as expected
stack_region: !Select [ 3, !Split [":", !Ref AWS::StackId]]
73 changes: 73 additions & 0 deletions tests/test_cf/test_unit/test_stack_stackid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Test case that verifies that generation of the value for AWS::StackId works as expected

from pathlib import Path

import pytest

from cloud_radar.cf.unit._template import Template


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

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


def test_function_populated_var(template):
expected_value = "arn:aws:cloudformation:us-west-2:123456789012:stack/teststack/51af3dc0-da77-11e4-872e-1234567db123"
Template.StackId = expected_value

actual_value = template._get_populated_stack_id()
assert actual_value == expected_value


def test_function_blank_var(template):
Template.StackId = ""

actual_value = template._get_populated_stack_id()
# Check all except the UUID
assert actual_value.startswith(
f"arn:{Template.Partition}:cloudformation:{Template.Region}:{Template.AccountId}:stack/{Template.StackName}/"
)

# Check the UUID part looks UUID like
unique_uuid = actual_value.split("/")[2]
assert 5 == len(unique_uuid.split("-"))


def test_template_blank_var_stack_region(template):
Template.StackId = ""

stack = template.create_stack({}, region="eu-west-1")

bucket = stack.get_resource("UniqueBucket")
bucket_name = bucket.get_property_value("BucketName")

assert len(bucket_name) == 37
assert bucket_name[:18] == "my-test-eu-west-1-"
assert bucket_name[30:] == "-bucket"


def test_template_blank_var_global_region(template):
Template.StackId = ""

stack = template.create_stack({})

bucket = stack.get_resource("UniqueBucket")
bucket_name = bucket.get_property_value("BucketName")

assert len(bucket_name) == 37
assert bucket_name[:18] == "my-test-us-east-1-"
assert bucket_name[30:] == "-bucket"


def test_template_populated_var(template):
Template.StackId = "arn:aws:cloudformation:us-west-2:123456789012:stack/teststack/51af3dc0-da77-11e4-872e-1234567db123"

stack = template.create_stack({})

bucket = stack.get_resource("UniqueBucket")
bucket_name = bucket.get_property_value("BucketName")

assert "my-test-us-west-2-1234567db123-bucket" == bucket_name
Loading