diff --git a/docs/docs/services/cf.rst b/docs/docs/services/cf.rst index b3e227189..86d7797bc 100644 --- a/docs/docs/services/cf.rst +++ b/docs/docs/services/cf.rst @@ -121,6 +121,8 @@ Please let us know if you'd like support for a resource not yet listed here. +---------------------------------------+--------+--------+--------+ - [ ] Id | | | | | | | +---------------------------------------+--------+--------+--------+----------------------------------------+ + |AWS::EMR::Cluster | x | | | - [ ] MasterPublicDNS | + +---------------------------------------+--------+--------+--------+----------------------------------------+ |AWS::Events::Archive | x | x | | - [x] Arn | +---------------------------------------+--------+--------+--------+----------------------------------------+ |AWS::Events::EventBus | x | x | x | - [x] Arn | diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index 8b0eaa608..239cc886d 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -33,8 +33,6 @@ from moto.awslambda import models as lambda_models # noqa # pylint: disable=al from moto.batch import models as batch_models # noqa # pylint: disable=all from moto.cloudformation.custom_model import CustomModel from moto.cloudwatch import models as cw_models # noqa # pylint: disable=all - -# End ugly list of imports from moto.core.common_models import CloudFormationModel from moto.datapipeline import models as data_models # noqa # pylint: disable=all from moto.dynamodb import models as ddb_models # noqa # pylint: disable=all @@ -45,6 +43,7 @@ from moto.ecs import models as ecs_models # noqa # pylint: disable=all from moto.efs import models as efs_models # noqa # pylint: disable=all from moto.elb import models as elb_models # noqa # pylint: disable=all from moto.elbv2 import models as elbv2_models # noqa # pylint: disable=all +from moto.emr import models as emr_models # noqa # pylint: disable=all from moto.events import models as events_models # noqa # pylint: disable=all from moto.iam import models as iam_models # noqa # pylint: disable=all from moto.kinesis import models as kinesis_models # noqa # pylint: disable=all @@ -62,6 +61,7 @@ from moto.ssm import models as ssm_models # noqa # pylint: disable=all from moto.ssm import ssm_backends from moto.stepfunctions import models as sfn_models # noqa # pylint: disable=all +# End ugly list of imports from .exceptions import ( ExportNotFound, MissingParameterError, diff --git a/moto/emr/models.py b/moto/emr/models.py index fa0d7100e..58f65613c 100644 --- a/moto/emr/models.py +++ b/moto/emr/models.py @@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Tuple from dateutil.parser import parse as dtparse from moto.core.base_backend import BackendDict, BaseBackend -from moto.core.common_models import BaseModel +from moto.core.common_models import BaseModel, CloudFormationModel from moto.emr.exceptions import ( InvalidRequestException, ResourceNotFoundException, @@ -151,7 +151,7 @@ class FakeStep(BaseModel): self.start_datetime = datetime.now(timezone.utc) -class FakeCluster(BaseModel): +class FakeCluster(CloudFormationModel): def __init__( self, emr_backend: "ElasticMapReduceBackend", @@ -384,6 +384,71 @@ class FakeCluster(BaseModel): def set_visibility(self, visibility: str) -> None: self.visible_to_all_users = visibility + @property + def physical_resource_id(self) -> str: + return self.id + + @staticmethod + def cloudformation_type() -> str: + return "AWS::EMR::Cluster" + + @classmethod + def has_cfn_attr(cls, attr: str) -> bool: + return attr in ["Id"] + + def get_cfn_attribute(self, attribute_name: str) -> str: + from moto.cloudformation.exceptions import UnformattedGetAttTemplateException + + if attribute_name == "Id": + return self.id + raise UnformattedGetAttTemplateException() + + @classmethod + def create_from_cloudformation_json( # type: ignore[misc] + cls, + resource_name: str, + cloudformation_json: Any, + account_id: str, + region_name: str, + **kwargs: Any, + ) -> "FakeCluster": + + properties = cloudformation_json["Properties"] + + instance_attrs = properties.get("Instances", {}) + instance_attrs["ec2_subnet_id"] = instance_attrs.get("Ec2SubnetId") + instance_attrs["emr_managed_master_security_group"] = instance_attrs.get( + "EmrManagedMasterSecurityGroup" + ) + instance_attrs["emr_managed_slave_security_group"] = instance_attrs.get( + "EmrManagedSlaveSecurityGroup" + ) + instance_attrs["service_access_security_group"] = instance_attrs.get( + "ServiceAccessSecurityGroup" + ) + instance_attrs["additional_master_security_groups"] = instance_attrs.get( + "AdditionalMasterSecurityGroups", [] + ) + instance_attrs["additional_slave_security_groups"] = instance_attrs.get( + "AdditionalSlaveSecurityGroups", [] + ) + + emr_backend: ElasticMapReduceBackend = emr_backends[account_id][region_name] + cluster = emr_backend.run_job_flow( + name=properties["Name"], + log_uri=properties.get("LogUri"), + job_flow_role=properties["JobFlowRole"], + service_role=properties["ServiceRole"], + steps=[], + instance_attrs=instance_attrs, + kerberos_attributes=properties.get("KerberosAttributes", {}), + release_label=properties.get("ReleaseLabel"), + custom_ami_id=properties.get("CustomAmiId"), + ) + tags = {item["Key"]: item["Value"] for item in properties.get("Tags", [])} + cluster.add_tags(tags) + return cluster + class FakeSecurityConfiguration(BaseModel): def __init__(self, name: str, security_configuration: str): diff --git a/moto/kms/models.py b/moto/kms/models.py index f567a9c58..d12d9834a 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -233,11 +233,11 @@ class Key(CloudFormationModel): policy=properties["KeyPolicy"], key_usage="ENCRYPT_DECRYPT", key_spec="SYMMETRIC_DEFAULT", - description=properties["Description"], + description=properties.get("Description"), tags=properties.get("Tags", []), ) - key.key_rotation_status = properties["EnableKeyRotation"] - key.enabled = properties["Enabled"] + key.key_rotation_status = properties.get("EnableKeyRotation", False) + key.enabled = properties.get("Enabled", True) return key diff --git a/tests/test_emr/test_emr_cloudformation.py b/tests/test_emr/test_emr_cloudformation.py new file mode 100644 index 000000000..3bebfd22a --- /dev/null +++ b/tests/test_emr/test_emr_cloudformation.py @@ -0,0 +1,620 @@ +import json + +import boto3 + +from moto import mock_aws +from tests import EXAMPLE_AMI_ID + +template = """{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "The AWS CloudFormation template for this Serverless application", + "Resources": { + "Cluster1": { + "Type" : "AWS::EMR::Cluster", + "Properties" : { + "Instances" : { + "CoreInstanceGroup": { + "InstanceCount": 3, + "InstanceType": "m3g", + } + }, + "JobFlowRole" : "EMR_EC2_DefaultRole", + "Name" : "my cluster", + "ServiceRole" : "EMR_DefaultRole", + } + }, + }, + "Outputs": { + "ClusterId": { + "Description": "Cluster info", + "Value": { + "Fn::GetAtt": ["Cluster1", "Id"] + }, + } + } +}""" + + +@mock_aws +def test_create_simple_cluster__using_cloudformation(): + region = "us-east-1" + cf = boto3.client("cloudformation", region_name=region) + emr = boto3.client("emr", region_name=region) + cf.create_stack(StackName="teststack", TemplateBody=template) + + # Verify resources + res = cf.describe_stack_resources(StackName="teststack")["StackResources"][0] + cluster_id = res["PhysicalResourceId"] + assert res["LogicalResourceId"] == "Cluster1" + assert res["ResourceType"] == "AWS::EMR::Cluster" + assert cluster_id.startswith("j-") + + # Verify outputs + stack = cf.describe_stacks(StackName="teststack")["Stacks"][0] + assert {"OutputKey": "ClusterId", "OutputValue": cluster_id} in stack["Outputs"] + + # Verify EMR Cluster + cl = emr.describe_cluster(ClusterId=cluster_id)["Cluster"] + assert cl["Name"] == "my cluster" + assert cl["Ec2InstanceAttributes"]["IamInstanceProfile"] == "EMR_EC2_DefaultRole" + assert cl["ServiceRole"] == "EMR_DefaultRole" + assert cl["Tags"] == [] + + +template_with_tags = { + "Resources": { + "Cluster1": { + "Type": "AWS::EMR::Cluster", + "Properties": { + "Instances": { + "CoreInstanceGroup": { + "InstanceCount": 3, + "InstanceType": "m3g", + } + }, + "JobFlowRole": "EMR_EC2_DefaultRole", + "Name": "my cluster", + "ServiceRole": "EMR_DefaultRole", + "Tags": [ + {"Key": "k1", "Value": "v1"}, + {"Key": "k2", "Value": "v2"}, + ], + }, + }, + }, +} + + +@mock_aws +def test_create_simple_cluster_with_tags(): + region = "us-east-1" + cf = boto3.client("cloudformation", region_name=region) + emr = boto3.client("emr", region_name=region) + cf.create_stack(StackName="teststack", TemplateBody=json.dumps(template_with_tags)) + + # Verify resources + res = cf.describe_stack_resources(StackName="teststack")["StackResources"][0] + cluster_id = res["PhysicalResourceId"] + + # Verify EMR Cluster + cl = emr.describe_cluster(ClusterId=cluster_id)["Cluster"] + assert cl["Tags"] == [{"Key": "k1", "Value": "v1"}, {"Key": "k2", "Value": "v2"}] + + +template_with_custom_ami = { + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": { + "SubnetId": {"Type": "String"}, + "InstanceType": {"Type": "String"}, + "TerminationProtected": {"Type": "String", "Default": "false"}, + "ElasticMapReducePrincipal": {"Type": "String"}, + "Ec2Principal": {"Type": "String"}, + }, + "Resources": { + "cluster": { + "Type": "AWS::EMR::Cluster", + "Properties": { + "CustomAmiId": EXAMPLE_AMI_ID, + "Instances": { + "MasterInstanceGroup": { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnMaster", + }, + "CoreInstanceGroup": { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnCore", + }, + "TaskInstanceGroups": [ + { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnTask-1", + }, + { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnTask-2", + }, + ], + "TerminationProtected": {"Ref": "TerminationProtected"}, + "Ec2SubnetId": {"Ref": "SubnetId"}, + }, + "Name": "CFNtest", + "JobFlowRole": {"Ref": "emrEc2InstanceProfile"}, + "ServiceRole": {"Ref": "emrRole"}, + "ReleaseLabel": "release_2024", + "VisibleToAllUsers": True, + "Tags": [{"Key": "key1", "Value": "value1"}], + }, + }, + "emrRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2008-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": { + "Service": {"Ref": "ElasticMapReducePrincipal"} + }, + "Action": "sts:AssumeRole", + } + ], + }, + "Path": "/", + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceRole" + ], + }, + }, + "emrEc2Role": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2008-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": {"Service": {"Ref": "Ec2Principal"}}, + "Action": "sts:AssumeRole", + } + ], + }, + "Path": "/", + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceforEC2Role" + ], + }, + }, + "emrEc2InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": {"Path": "/", "Roles": [{"Ref": "emrEc2Role"}]}, + }, + }, +} + + +@mock_aws +def test_create_cluster_with_custom_ami(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + + params = [ + {"ParameterKey": "InstanceType", "ParameterValue": "i4s"}, + {"ParameterKey": "SubnetId", "ParameterValue": subnet.id}, + { + "ParameterKey": "ElasticMapReducePrincipal", + "ParameterValue": "emr_principal", + }, + {"ParameterKey": "Ec2Principal", "ParameterValue": "ec2_principal"}, + ] + + region = "us-east-1" + cf = boto3.client("cloudformation", region_name=region) + emr = boto3.client("emr", region_name=region) + cf.create_stack( + StackName="teststack", + TemplateBody=json.dumps(template_with_custom_ami), + Parameters=params, + ) + + # Verify resources + res = cf.describe_stack_resources(StackName="teststack")["StackResources"] + cluster = [r for r in res if r["ResourceType"] == "AWS::EMR::Cluster"][0] + cluster_id = cluster["PhysicalResourceId"] + + instance_profile = [ + r for r in res if r["ResourceType"] == "AWS::IAM::InstanceProfile" + ][0] + instance_profile_id = instance_profile["PhysicalResourceId"] + + role = [r for r in res if r["ResourceType"] == "AWS::IAM::Role"][0] + role_id = role["PhysicalResourceId"] + + # Verify EMR Cluster + cl = emr.describe_cluster(ClusterId=cluster_id)["Cluster"] + assert cl["Name"] == "CFNtest" + assert cl["Ec2InstanceAttributes"]["Ec2SubnetId"] == subnet.id + assert cl["Ec2InstanceAttributes"]["IamInstanceProfile"] == instance_profile_id + assert cl["ServiceRole"] == role_id + assert cl["ReleaseLabel"] == "release_2024" + assert cl["CustomAmiId"] == EXAMPLE_AMI_ID + + +template_with_root_volume = { + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": { + "InstanceType": {"Type": "String"}, + "ReleaseLabel": {"Type": "String"}, + "SubnetId": {"Type": "String"}, + "TerminationProtected": {"Type": "String", "Default": "false"}, + "EbsRootVolumeSize": {"Type": "String"}, + }, + "Resources": { + "cluster": { + "Type": "AWS::EMR::Cluster", + "Properties": { + "EbsRootVolumeSize": {"Ref": "EbsRootVolumeSize"}, + "Instances": { + "MasterInstanceGroup": { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnMaster", + }, + "CoreInstanceGroup": { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnCore", + }, + "TaskInstanceGroups": [ + { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnTask-1", + }, + { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnTask-2", + }, + ], + "TerminationProtected": {"Ref": "TerminationProtected"}, + "Ec2SubnetId": {"Ref": "SubnetId"}, + }, + "Name": "CFNtest", + "JobFlowRole": {"Ref": "emrEc2InstanceProfile"}, + "ServiceRole": {"Ref": "emrRole"}, + "ReleaseLabel": {"Ref": "ReleaseLabel"}, + "VisibleToAllUsers": True, + "Tags": [{"Key": "key1", "Value": "value1"}], + }, + }, + "emrRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2008-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": {"Service": "elasticmapreduce.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + }, + "Path": "/", + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceRole" + ], + }, + }, + "emrEc2Role": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2008-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": {"Service": "ec2.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + }, + "Path": "/", + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceforEC2Role" + ], + }, + }, + "emrEc2InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": {"Path": "/", "Roles": [{"Ref": "emrEc2Role"}]}, + }, + }, +} + + +@mock_aws +def test_create_cluster_with_root_volume(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + + params = [ + {"ParameterKey": "InstanceType", "ParameterValue": "i4s"}, + {"ParameterKey": "ReleaseLabel", "ParameterValue": "latest"}, + {"ParameterKey": "EbsRootVolumeSize", "ParameterValue": "15"}, + {"ParameterKey": "SubnetId", "ParameterValue": subnet.id}, + { + "ParameterKey": "ElasticMapReducePrincipal", + "ParameterValue": "emr_principal", + }, + {"ParameterKey": "Ec2Principal", "ParameterValue": "ec2_principal"}, + ] + + region = "us-east-1" + cf = boto3.client("cloudformation", region_name=region) + emr = boto3.client("emr", region_name=region) + cf.create_stack( + StackName="teststack", + TemplateBody=json.dumps(template_with_root_volume), + Parameters=params, + ) + + # Verify resources + res = cf.describe_stack_resources(StackName="teststack")["StackResources"] + cluster = [r for r in res if r["ResourceType"] == "AWS::EMR::Cluster"][0] + cluster_id = cluster["PhysicalResourceId"] + + instance_profile = [ + r for r in res if r["ResourceType"] == "AWS::IAM::InstanceProfile" + ][0] + instance_profile_id = instance_profile["PhysicalResourceId"] + + role = [r for r in res if r["ResourceType"] == "AWS::IAM::Role"][0] + role_id = role["PhysicalResourceId"] + + # Verify EMR Cluster + cl = emr.describe_cluster(ClusterId=cluster_id)["Cluster"] + assert cl["Name"] == "CFNtest" + assert cl["Ec2InstanceAttributes"]["Ec2SubnetId"] == subnet.id + assert cl["Ec2InstanceAttributes"]["IamInstanceProfile"] == instance_profile_id + assert cl["ServiceRole"] == role_id + + +template_with_kerberos_attrs = { + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": { + "CrossRealmTrustPrincipalPassword": {"Type": "String"}, + "KdcAdminPassword": {"Type": "String"}, + "InstanceType": {"Type": "String"}, + "ReleaseLabel": {"Type": "String"}, + "SubnetId": {"Type": "String"}, + }, + "Resources": { + "cluster": { + "Type": "AWS::EMR::Cluster", + "Properties": { + "Instances": { + "MasterInstanceGroup": { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnMaster", + }, + "CoreInstanceGroup": { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnCore", + }, + "TaskInstanceGroups": [ + { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnTask-1", + }, + { + "InstanceCount": 1, + "InstanceType": {"Ref": "InstanceType"}, + "Market": "ON_DEMAND", + "Name": "cfnTask-2", + }, + ], + "Ec2SubnetId": {"Ref": "SubnetId"}, + }, + "Name": "CFNtest", + "JobFlowRole": {"Ref": "emrEc2InstanceProfile"}, + "KerberosAttributes": { + "CrossRealmTrustPrincipalPassword": { + "Ref": "CrossRealmTrustPrincipalPassword" + }, + "KdcAdminPassword": {"Ref": "KdcAdminPassword"}, + "Realm": "EC2.INTERNAL", + }, + "ServiceRole": {"Ref": "emrRole"}, + "ReleaseLabel": {"Ref": "ReleaseLabel"}, + "SecurityConfiguration": {"Ref": "securityConfiguration"}, + "VisibleToAllUsers": True, + "Tags": [{"Key": "key1", "Value": "value1"}], + }, + }, + "key": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Version": "2012-10-17", + "Id": "key-default-1", + "Statement": [ + { + "Sid": "Enable IAM User Permissions", + "Effect": "Allow", + "Principal": {"AWS": {"Fn::GetAtt": ["emrEc2Role", "Arn"]}}, + "Action": "kms:*", + "Resource": "*", + }, + { + "Sid": "Enable IAM User Permissions", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + {"Ref": "AWS::AccountId"}, + ":root", + ], + ] + } + }, + "Action": "kms:*", + "Resource": "*", + }, + ], + } + }, + }, + "securityConfiguration": { + "Type": "AWS::EMR::SecurityConfiguration", + "Properties": { + "SecurityConfiguration": { + "AuthenticationConfiguration": { + "KerberosConfiguration": { + "Provider": "ClusterDedicatedKdc", + "ClusterDedicatedKdcConfiguration": { + "TicketLifetimeInHours": 24, + "CrossRealmTrustConfiguration": { + "Realm": "AD.DOMAIN.COM", + "Domain": "ad.domain.com", + "AdminServer": "ad.domain.com", + "KdcServer": "ad.domain.com", + }, + }, + } + } + } + }, + }, + "emrRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2008-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": {"Service": "elasticmapreduce.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + }, + "Path": "/", + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceRole" + ], + }, + }, + "emrEc2Role": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2008-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": {"Service": "ec2.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + }, + "Path": "/", + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceforEC2Role" + ], + }, + }, + "emrEc2InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": {"Path": "/", "Roles": [{"Ref": "emrEc2Role"}]}, + }, + }, + "Outputs": {"keyArn": {"Value": {"Fn::GetAtt": ["key", "Arn"]}}}, +} + + +@mock_aws +def test_create_cluster_with_kerberos_attrs(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + + params = [ + {"ParameterKey": "CrossRealmTrustPrincipalPassword", "ParameterValue": "p2ss"}, + {"ParameterKey": "KdcAdminPassword", "ParameterValue": "adminp2ss"}, + {"ParameterKey": "InstanceType", "ParameterValue": "i4s"}, + {"ParameterKey": "ReleaseLabel", "ParameterValue": "latest"}, + {"ParameterKey": "EbsRootVolumeSize", "ParameterValue": "15"}, + {"ParameterKey": "SubnetId", "ParameterValue": subnet.id}, + { + "ParameterKey": "ElasticMapReducePrincipal", + "ParameterValue": "emr_principal", + }, + {"ParameterKey": "Ec2Principal", "ParameterValue": "ec2_principal"}, + ] + + region = "us-east-1" + cf = boto3.client("cloudformation", region_name=region) + emr = boto3.client("emr", region_name=region) + cf.create_stack( + StackName="teststack", + TemplateBody=json.dumps(template_with_kerberos_attrs), + Parameters=params, + ) + + # Verify resources + res = cf.describe_stack_resources(StackName="teststack")["StackResources"] + cluster = [r for r in res if r["ResourceType"] == "AWS::EMR::Cluster"][0] + cluster_id = cluster["PhysicalResourceId"] + + instance_profile = [ + r for r in res if r["ResourceType"] == "AWS::IAM::InstanceProfile" + ][0] + instance_profile_id = instance_profile["PhysicalResourceId"] + + role = [r for r in res if r["ResourceType"] == "AWS::IAM::Role"][0] + role_id = role["PhysicalResourceId"] + + # Verify EMR Cluster + cl = emr.describe_cluster(ClusterId=cluster_id)["Cluster"] + assert cl["Name"] == "CFNtest" + assert cl["Ec2InstanceAttributes"]["Ec2SubnetId"] == subnet.id + assert cl["Ec2InstanceAttributes"]["IamInstanceProfile"] == instance_profile_id + assert cl["ServiceRole"] == role_id + + kerberos = cl["KerberosAttributes"] + assert kerberos == { + "Realm": "EC2.INTERNAL", + "KdcAdminPassword": "adminp2ss", + "CrossRealmTrustPrincipalPassword": "p2ss", + }