Skip to content

Commit b8a37dc

Browse files
ec2: get attributes from launch template when starting instances
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7ac979b commit b8a37dc

2 files changed

Lines changed: 236 additions & 2 deletions

File tree

moto/ec2/models/instances.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,29 @@ def run_instances(
773773
The KeyPair-parameter can be validated, to see if it is a known key-pair.
774774
Enable this validation by setting the environment variable `MOTO_ENABLE_KEYPAIR_VALIDATION=true`
775775
"""
776+
if kwargs.get("launch_template"):
777+
tmpl = self._get_template_from_args(kwargs["launch_template"]).data
778+
779+
if user_data is None and (template_user_data := tmpl.get("UserData")):
780+
user_data = template_user_data
781+
if kwargs.get("is_instance_type_default") and (
782+
template_instance_type := tmpl.get("InstanceType")
783+
):
784+
kwargs["instance_type"] = template_instance_type
785+
kwargs["is_instance_type_default"] = False
786+
if not kwargs.get("key_name") and (
787+
template_key_name := tmpl.get("KeyName")
788+
):
789+
kwargs["key_name"] = template_key_name
790+
if not kwargs.get("security_group_ids") and (
791+
template_sg_ids := tmpl.get("SecurityGroupIds")
792+
):
793+
kwargs["security_group_ids"] = template_sg_ids
794+
if not security_group_names and (
795+
template_sgs := tmpl.get("SecurityGroups")
796+
):
797+
security_group_names = template_sgs
798+
776799
location_type = "availability-zone" if kwargs.get("placement") else "region"
777800
default_region = "us-east-1"
778801
if settings.ENABLE_KEYPAIR_VALIDATION:

tests/test_ec2/test_instances.py

Lines changed: 213 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3158,15 +3158,28 @@ def _verify_instance_tags(
31583158
assert tags[key] == value
31593159

31603160

3161-
def _run_instance_from_template(ec2_client, template_id, version=None):
3162-
launch_template_spec = {"LaunchTemplateId": template_id}
3161+
def _run_instance_from_template(
3162+
ec2_client,
3163+
template_id=None,
3164+
template_name=None,
3165+
version=None,
3166+
**instance_kwargs,
3167+
):
3168+
launch_template_spec = {}
3169+
if template_id:
3170+
launch_template_spec["LaunchTemplateId"] = template_id
3171+
elif template_name:
3172+
launch_template_spec["LaunchTemplateName"] = template_name
3173+
else:
3174+
raise ValueError("Either template_id or template_name must be provided.")
31633175
if version:
31643176
launch_template_spec["Version"] = version
31653177

31663178
instance = ec2_client.run_instances(
31673179
MinCount=1,
31683180
MaxCount=1,
31693181
LaunchTemplate=launch_template_spec,
3182+
**instance_kwargs,
31703183
)["Instances"][0]
31713184
return instance
31723185

@@ -3334,3 +3347,201 @@ def test_create_instance_from_launch_template_latest_and_default_version(
33343347
finally:
33353348
# Clean up launch template
33363349
ec2_client.delete_launch_template(LaunchTemplateId=template_id)
3350+
3351+
3352+
_LT_USER_DATA_SCRIPT = b"#!/bin/bash\necho from-template"
3353+
_LT_USER_DATA_B64 = base64.b64encode(_LT_USER_DATA_SCRIPT).decode()
3354+
3355+
3356+
@ec2_aws_verified(
3357+
create_launch_template=True,
3358+
launch_template_data={"InstanceType": "t2.micro", "UserData": _LT_USER_DATA_B64},
3359+
)
3360+
@pytest.mark.aws_verified
3361+
def test_run_instances__user_data_from_launch_template(
3362+
ec2_client=None, launch_template_name=None
3363+
):
3364+
"""UserData in a launch template is applied when not supplied in RunInstances."""
3365+
ami_id = _get_ami_id(ec2_client)
3366+
instance = _run_instance_from_template(
3367+
ec2_client, template_name=launch_template_name, ImageId=ami_id
3368+
)
3369+
instance_id = instance["InstanceId"]
3370+
try:
3371+
attr = ec2_client.describe_instance_attribute(
3372+
InstanceId=instance_id, Attribute="userData"
3373+
)
3374+
assert attr["UserData"]["Value"] == _LT_USER_DATA_B64
3375+
finally:
3376+
ec2_client.terminate_instances(InstanceIds=[instance_id])
3377+
3378+
3379+
@ec2_aws_verified(
3380+
create_launch_template=True,
3381+
launch_template_data={"InstanceType": "t2.micro", "UserData": _LT_USER_DATA_B64},
3382+
)
3383+
@pytest.mark.aws_verified
3384+
def test_run_instances__user_data_override_takes_priority(
3385+
ec2_client=None, launch_template_name=None
3386+
):
3387+
"""Explicit UserData in RunInstances overrides the launch template value."""
3388+
ami_id = _get_ami_id(ec2_client)
3389+
request_script = "#!/bin/bash\necho from-request"
3390+
expected_b64 = base64.b64encode(request_script.encode()).decode()
3391+
3392+
instance = _run_instance_from_template(
3393+
ec2_client,
3394+
template_name=launch_template_name,
3395+
ImageId=ami_id,
3396+
UserData=request_script,
3397+
)
3398+
instance_id = instance["InstanceId"]
3399+
try:
3400+
attr = ec2_client.describe_instance_attribute(
3401+
InstanceId=instance_id, Attribute="userData"
3402+
)
3403+
assert attr["UserData"]["Value"] == expected_b64
3404+
finally:
3405+
ec2_client.terminate_instances(InstanceIds=[instance_id])
3406+
3407+
3408+
@ec2_aws_verified(
3409+
create_launch_template=True,
3410+
launch_template_data={"InstanceType": "t2.micro"},
3411+
)
3412+
@pytest.mark.aws_verified
3413+
def test_run_instances__instance_type_from_launch_template(
3414+
ec2_client=None, launch_template_name=None
3415+
):
3416+
"""InstanceType in a launch template is applied when not supplied in RunInstances."""
3417+
ami_id = _get_ami_id(ec2_client)
3418+
instance = _run_instance_from_template(
3419+
ec2_client, template_name=launch_template_name, ImageId=ami_id
3420+
)
3421+
instance_id = instance["InstanceId"]
3422+
try:
3423+
assert instance["InstanceType"] == "t2.micro"
3424+
attr = ec2_client.describe_instance_attribute(
3425+
InstanceId=instance_id, Attribute="instanceType"
3426+
)
3427+
assert attr["InstanceType"]["Value"] == "t2.micro"
3428+
describe_instances = ec2_client.describe_instances(InstanceIds=[instance_id])
3429+
assert (
3430+
describe_instances["Reservations"][0]["Instances"][0]["InstanceType"]
3431+
== "t2.micro"
3432+
)
3433+
finally:
3434+
ec2_client.terminate_instances(InstanceIds=[instance_id])
3435+
3436+
3437+
@ec2_aws_verified(
3438+
create_launch_template=True,
3439+
launch_template_data={"InstanceType": "t2.micro"},
3440+
)
3441+
@pytest.mark.aws_verified
3442+
def test_run_instances__instance_type_override_takes_priority(
3443+
ec2_client=None, launch_template_name=None
3444+
):
3445+
"""Explicit InstanceType in RunInstances overrides the launch template value."""
3446+
ami_id = _get_ami_id(ec2_client)
3447+
resp = ec2_client.run_instances(
3448+
MinCount=1,
3449+
MaxCount=1,
3450+
ImageId=ami_id,
3451+
LaunchTemplate={"LaunchTemplateName": launch_template_name},
3452+
InstanceType="t2.small",
3453+
)
3454+
instance_id = resp["Instances"][0]["InstanceId"]
3455+
try:
3456+
assert resp["Instances"][0]["InstanceType"] == "t2.small"
3457+
attr = ec2_client.describe_instance_attribute(
3458+
InstanceId=instance_id, Attribute="instanceType"
3459+
)
3460+
assert attr["InstanceType"]["Value"] == "t2.small"
3461+
describe_instances = ec2_client.describe_instances(InstanceIds=[instance_id])
3462+
assert (
3463+
describe_instances["Reservations"][0]["Instances"][0]["InstanceType"]
3464+
== "t2.small"
3465+
)
3466+
finally:
3467+
ec2_client.terminate_instances(InstanceIds=[instance_id])
3468+
3469+
3470+
@ec2_aws_verified()
3471+
@pytest.mark.aws_verified
3472+
def test_run_instances__key_name_from_launch_template(ec2_client=None):
3473+
"""KeyName in a launch template is applied when not supplied in RunInstances."""
3474+
ami_id = _get_ami_id(ec2_client)
3475+
key_name = f"test-key-{str(uuid4())[0:8]}"
3476+
lt_name = str(uuid4())
3477+
ec2_client.create_key_pair(KeyName=key_name)
3478+
ec2_client.create_launch_template(
3479+
LaunchTemplateName=lt_name,
3480+
LaunchTemplateData={"InstanceType": "t2.micro", "KeyName": key_name},
3481+
)
3482+
instance = _run_instance_from_template(
3483+
ec2_client, template_name=lt_name, ImageId=ami_id
3484+
)
3485+
instance_id = instance["InstanceId"]
3486+
try:
3487+
assert instance["KeyName"] == key_name
3488+
describe_instances = ec2_client.describe_instances(InstanceIds=[instance_id])
3489+
assert (
3490+
describe_instances["Reservations"][0]["Instances"][0]["KeyName"] == key_name
3491+
)
3492+
finally:
3493+
ec2_client.terminate_instances(InstanceIds=[instance_id])
3494+
ec2_client.delete_launch_template(LaunchTemplateName=lt_name)
3495+
ec2_client.delete_key_pair(KeyName=key_name)
3496+
3497+
3498+
@ec2_aws_verified()
3499+
@pytest.mark.aws_verified
3500+
def test_run_instances__security_group_ids_from_launch_template(ec2_client=None):
3501+
"""SecurityGroupIds in a launch template are applied when not supplied in RunInstances."""
3502+
ami_id = _get_ami_id(ec2_client)
3503+
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
3504+
subnet_id = ec2_client.create_subnet(
3505+
VpcId=vpc_id,
3506+
CidrBlock="10.0.1.0/24",
3507+
AvailabilityZone=ec2_client.meta.region_name + "a",
3508+
)["Subnet"]["SubnetId"]
3509+
sg_id = ec2_client.create_security_group(
3510+
GroupName=f"test-sg-{str(uuid4())[0:6]}",
3511+
Description="test",
3512+
VpcId=vpc_id,
3513+
)["GroupId"]
3514+
lt_name = str(uuid4())
3515+
ec2_client.create_launch_template(
3516+
LaunchTemplateName=lt_name,
3517+
LaunchTemplateData={
3518+
"InstanceType": "t2.micro",
3519+
"SecurityGroupIds": [sg_id],
3520+
},
3521+
)
3522+
instance = _run_instance_from_template(
3523+
ec2_client, template_name=lt_name, ImageId=ami_id, SubnetId=subnet_id
3524+
)
3525+
instance_id = instance["InstanceId"]
3526+
try:
3527+
instance_sg_ids = [g["GroupId"] for g in instance["SecurityGroups"]]
3528+
assert sg_id in instance_sg_ids
3529+
attr = ec2_client.describe_instance_attribute(
3530+
InstanceId=instance_id, Attribute="groupSet"
3531+
)
3532+
attr_sg_ids = [g["GroupId"] for g in attr["Groups"]]
3533+
assert sg_id in attr_sg_ids
3534+
describe_instances = ec2_client.describe_instances(InstanceIds=[instance_id])
3535+
instance_sg_ids = [
3536+
g["GroupId"]
3537+
for g in describe_instances["Reservations"][0]["Instances"][0][
3538+
"SecurityGroups"
3539+
]
3540+
]
3541+
assert sg_id in instance_sg_ids
3542+
finally:
3543+
ec2_client.terminate_instances(InstanceIds=[instance_id])
3544+
ec2_client.delete_launch_template(LaunchTemplateName=lt_name)
3545+
ec2_client.delete_security_group(GroupId=sg_id)
3546+
ec2_client.delete_subnet(SubnetId=subnet_id)
3547+
ec2_client.delete_vpc(VpcId=vpc_id)

0 commit comments

Comments
 (0)