Skip to content

Commit

Permalink
CloudFormation: Implement Fn::GetAtt for AWS::EC2::LaunchTemplate (#7121
Browse files Browse the repository at this point in the history
)
  • Loading branch information
skinitimski committed Dec 17, 2023
1 parent 2b077a2 commit 692b475
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 5 deletions.
8 changes: 3 additions & 5 deletions moto/cloudformation/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,11 +740,9 @@ def build_resource_diff(self, other_template: Dict[str, Any]) -> Dict[str, Any]:
new = other_template["Resources"]

resource_names_by_action = {
"Add": set(new) - set(old),
"Modify": set(
name for name in new if name in old and new[name] != old[name]
),
"Remove": set(old) - set(new),
"Add": [name for name in new if name not in old],
"Modify": [name for name in new if name in old and new[name] != old[name]],
"Remove": [name for name in old if name not in new],
}

return resource_names_by_action
Expand Down
19 changes: 19 additions & 0 deletions moto/ec2/models/launch_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,25 @@ def delete_from_cloudformation_json( # type: ignore[misc]

backend.delete_launch_template(name, None)

@classmethod
def has_cfn_attr(cls, attr: str) -> bool:
return attr in [
"DefaultVersionNumber",
"LaunchTemplateId",
"LatestVersionNumber",
]

def get_cfn_attribute(self, attribute_name: str) -> str:
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException

if attribute_name == "DefaultVersionNumber":
return str(self.default_version_number)
if attribute_name == "LaunchTemplateId":
return self.id
if attribute_name == "LatestVersionNumber":
return str(self.latest_version_number)
raise UnformattedGetAttTemplateException()


class LaunchTemplateBackend:
def __init__(self) -> None:
Expand Down
247 changes: 247 additions & 0 deletions tests/test_ec2/test_launch_templates_cloudformation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import json
from uuid import uuid4

import boto3

from moto import mock_autoscaling, mock_cloudformation, mock_ec2
from tests import EXAMPLE_AMI_ID, EXAMPLE_AMI_ID2


@mock_autoscaling
@mock_cloudformation
@mock_ec2
def test_asg_with_latest_launch_template_version():
cf_client = boto3.client("cloudformation", region_name="us-west-1")
ec2 = boto3.resource("ec2", region_name="us-west-1")
vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
autoscaling_client = boto3.client("autoscaling", region_name="us-west-1")

subnet1 = ec2.create_subnet(
CidrBlock="10.0.1.0/24", VpcId=vpc.id, AvailabilityZone="us-west-1a"
)

subnet2 = ec2.create_subnet(
CidrBlock="10.0.2.0/24", VpcId=vpc.id, AvailabilityZone="us-west-1b"
)

autoscaling_group_name = str(uuid4())

stack_name = str(uuid4())
launch_template_name = str(uuid4())[0:6]

version_attribute = "LatestVersionNumber"

template_json = json.dumps(
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation Template to create an ASG group with LaunchTemplate",
"Resources": {
"LaunchTemplate": {
"Type": "AWS::EC2::LaunchTemplate",
"Properties": {
"LaunchTemplateName": launch_template_name,
"LaunchTemplateData": {
"ImageId": EXAMPLE_AMI_ID,
"InstanceType": "t3.small",
"UserData": "",
},
},
},
"AutoScalingGroup": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"AutoScalingGroupName": autoscaling_group_name,
"VPCZoneIdentifier": [subnet1.id],
"LaunchTemplate": {
"LaunchTemplateId": {"Ref": "LaunchTemplate"},
"Version": {
"Fn::GetAtt": ["LaunchTemplate", version_attribute]
},
},
"MinSize": 1,
"MaxSize": 1,
},
},
},
}
)

cf_client.create_stack(
StackName=stack_name,
TemplateBody=template_json,
Capabilities=["CAPABILITY_NAMED_IAM"],
OnFailure="DELETE",
)

template_json = json.dumps(
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation Template to create an ASG group with LaunchTemplate",
"Resources": {
"LaunchTemplate": {
"Type": "AWS::EC2::LaunchTemplate",
"Properties": {
"LaunchTemplateName": launch_template_name,
"LaunchTemplateData": {
"ImageId": EXAMPLE_AMI_ID2,
"InstanceType": "t3.medium",
"UserData": "",
},
},
},
"AutoScalingGroup": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"AutoScalingGroupName": autoscaling_group_name,
"VPCZoneIdentifier": [subnet2.id],
"LaunchTemplate": {
"LaunchTemplateId": {"Ref": "LaunchTemplate"},
"Version": {
"Fn::GetAtt": ["LaunchTemplate", version_attribute]
},
},
"MinSize": 1,
"MaxSize": 2,
},
},
},
}
)

cf_client.update_stack(
StackName=stack_name,
TemplateBody=template_json,
Capabilities=["CAPABILITY_NAMED_IAM"],
)

autoscaling_group = autoscaling_client.describe_auto_scaling_groups(
AutoScalingGroupNames=[
autoscaling_group_name,
]
)["AutoScalingGroups"][0]

assert (
autoscaling_group["LaunchTemplate"]["LaunchTemplateName"]
== launch_template_name
)
assert autoscaling_group["LaunchTemplate"]["Version"] == "2"


@mock_autoscaling
@mock_cloudformation
@mock_ec2
def test_asg_with_default_launch_template_version():
cf_client = boto3.client("cloudformation", region_name="us-west-1")
ec2 = boto3.resource("ec2", region_name="us-west-1")
vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
autoscaling_client = boto3.client("autoscaling", region_name="us-west-1")

subnet1 = ec2.create_subnet(
CidrBlock="10.0.1.0/24", VpcId=vpc.id, AvailabilityZone="us-west-1a"
)

subnet2 = ec2.create_subnet(
CidrBlock="10.0.2.0/24", VpcId=vpc.id, AvailabilityZone="us-west-1b"
)

autoscaling_group_name = str(uuid4())

stack_name = str(uuid4())
launch_template_name = str(uuid4())[0:6]

version_attribute = "DefaultVersionNumber"

template_json = json.dumps(
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation Template to create an ASG group with LaunchTemplate",
"Resources": {
"LaunchTemplate": {
"Type": "AWS::EC2::LaunchTemplate",
"Properties": {
"LaunchTemplateName": launch_template_name,
"LaunchTemplateData": {
"ImageId": EXAMPLE_AMI_ID,
"InstanceType": "t3.small",
"UserData": "",
},
},
},
"AutoScalingGroup": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"AutoScalingGroupName": autoscaling_group_name,
"VPCZoneIdentifier": [subnet1.id],
"LaunchTemplate": {
"LaunchTemplateId": {"Ref": "LaunchTemplate"},
"Version": {
"Fn::GetAtt": ["LaunchTemplate", version_attribute]
},
},
"MinSize": 1,
"MaxSize": 1,
},
},
},
}
)

cf_client.create_stack(
StackName=stack_name,
TemplateBody=template_json,
Capabilities=["CAPABILITY_NAMED_IAM"],
OnFailure="DELETE",
)

template_json = json.dumps(
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation Template to create an ASG group with LaunchTemplate",
"Resources": {
"LaunchTemplate": {
"Type": "AWS::EC2::LaunchTemplate",
"Properties": {
"LaunchTemplateName": launch_template_name,
"LaunchTemplateData": {
"ImageId": EXAMPLE_AMI_ID2,
"InstanceType": "t3.medium",
"UserData": "",
},
},
},
"AutoScalingGroup": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"AutoScalingGroupName": autoscaling_group_name,
"VPCZoneIdentifier": [subnet2.id],
"LaunchTemplate": {
"LaunchTemplateId": {"Ref": "LaunchTemplate"},
"Version": {
"Fn::GetAtt": ["LaunchTemplate", version_attribute]
},
},
"MinSize": 1,
"MaxSize": 2,
},
},
},
}
)

cf_client.update_stack(
StackName=stack_name,
TemplateBody=template_json,
Capabilities=["CAPABILITY_NAMED_IAM"],
)

autoscaling_group = autoscaling_client.describe_auto_scaling_groups(
AutoScalingGroupNames=[
autoscaling_group_name,
]
)["AutoScalingGroups"][0]

assert (
autoscaling_group["LaunchTemplate"]["LaunchTemplateName"]
== launch_template_name
)
assert autoscaling_group["LaunchTemplate"]["Version"] == "1"

0 comments on commit 692b475

Please sign in to comment.