Repository: getmoto/moto
Pull request #7135 (2023-12-18 18:00:29)

EC2: launch templates created by CloudFormation have a generated name if not provided
## Purpose

Fixes a bug in the creation of launch templates via CloudFormation. If your template has more than one ***unnamed*** launch template, creation will fail with

> botocore.exceptions.ClientError: An error occurred (InvalidLaunchTemplateName.AlreadyExistsException) when calling the CreateStack operation: Launch template name already in use.

This is because moto's implementation expects that the resource have a `LaunchTemplateName` property even though [it is optional](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-launchtemplate.html#cfn-ec2-launchtemplate-launchtemplatename).

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/7135?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`d44d05c`)](https://app.codecov.io/gh/getmoto/moto/commit/d44d05c4a1639f52af665974e301152c3ce29a3e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) 95.87% compared to head [(`d36d311`)](https://app.codecov.io/gh/getmoto/moto/pull/7135?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) 95.88%.


<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #7135   +/-   ##
=======================================
  Coverage   95.87%   95.88%           
=======================================
  Files         834      834           
  Lines       82233    82237    +4     
=======================================
+ Hits        78844    78850    +6     
+ Misses       3389     3387    -2     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/7135/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/7135/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `35.79% <25.00%> (-0.01%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/7135/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `95.82% <100.00%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/7135?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).


Patch:
diff --git a/moto/ec2/models/launch_templates.py b/moto/ec2/models/launch_templates.py
index 1e4a63f34..16db8d79d 100644
--- a/moto/ec2/models/launch_templates.py
+++ b/moto/ec2/models/launch_templates.py
@@ -13,6 +13,7 @@ from ..utils import (
     convert_tag_spec,
     generic_filter,
     random_launch_template_id,
+    random_launch_template_name,
     utc_date_and_time,
 )
 from .core import TaggedEC2Resource
@@ -141,6 +142,9 @@ class LaunchTemplate(TaggedEC2Resource, CloudFormationModel):
             properties.get("TagSpecifications", {}), tag_key="Tags"
         )
 
+        if name is None:
+            name = random_launch_template_name()
+
         launch_template = backend.create_launch_template(
             name, description, data, tag_spec
         )
diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py
index 08638ac37..821712af8 100644
--- a/moto/ec2/utils.py
+++ b/moto/ec2/utils.py
@@ -233,6 +233,10 @@ def random_launch_template_id() -> str:
     return random_id(prefix=EC2_RESOURCE_TO_PREFIX["launch-template"], size=17)
 
 
+def random_launch_template_name() -> str:
+    return f"LaunchTemplate_{random_resource_id(size=12)}"
+
+
 def random_iam_instance_profile_association_id() -> str:
     return random_id(prefix=EC2_RESOURCE_TO_PREFIX["iam-instance-profile-association"])
 


Test patch:
diff --git a/tests/test_ec2/test_launch_templates_cloudformation.py b/tests/test_ec2/test_launch_templates_cloudformation.py
index 71ce94a98..ede997e20 100644
--- a/tests/test_ec2/test_launch_templates_cloudformation.py
+++ b/tests/test_ec2/test_launch_templates_cloudformation.py
@@ -245,3 +245,55 @@ def test_asg_with_default_launch_template_version():
         == launch_template_name
     )
     assert autoscaling_group["LaunchTemplate"]["Version"] == "1"
+
+
+@mock_autoscaling
+@mock_cloudformation
+@mock_ec2
+def test_two_launch_templates():
+    cf_client = boto3.client("cloudformation", region_name="us-west-1")
+    ec2_client = boto3.client("ec2", region_name="us-west-1")
+
+    stack_name = str(uuid4())
+
+    template_json = json.dumps(
+        {
+            "AWSTemplateFormatVersion": "2010-09-09",
+            "Description": "AWS CloudFormation Template to create two LaunchTemplate",
+            "Resources": {
+                "LaunchTemplate0": {
+                    "Type": "AWS::EC2::LaunchTemplate",
+                    "Properties": {
+                        "LaunchTemplateData": {
+                            "ImageId": EXAMPLE_AMI_ID,
+                            "InstanceType": "t3.small",
+                            "UserData": "",
+                        },
+                    },
+                },
+                "LaunchTemplate1": {
+                    "Type": "AWS::EC2::LaunchTemplate",
+                    "Properties": {
+                        "LaunchTemplateData": {
+                            "ImageId": EXAMPLE_AMI_ID,
+                            "InstanceType": "t3.medium",
+                            "UserData": "",
+                        },
+                    },
+                },
+            },
+        }
+    )
+
+    cf_client.create_stack(
+        StackName=stack_name,
+        TemplateBody=template_json,
+        Capabilities=["CAPABILITY_NAMED_IAM"],
+        OnFailure="DELETE",
+    )
+
+    launch_templates = ec2_client.describe_launch_templates()
+    assert (
+        launch_templates["LaunchTemplates"][0]["LaunchTemplateName"]
+        != launch_templates["LaunchTemplates"][1]["LaunchTemplateName"]
+    )