EFS: CF support for FileSystems/AccessPoints (#7433)
This commit is contained in:
		
							parent
							
								
									4c65c32d40
								
							
						
					
					
						commit
						74ea84edb4
					
				| @ -263,6 +263,22 @@ class VPC(TaggedEC2Resource, CloudFormationModel): | ||||
|         # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html | ||||
|         return "AWS::EC2::VPC" | ||||
| 
 | ||||
|     def get_cfn_attribute(self, attribute_name: str) -> Any: | ||||
|         from moto.cloudformation.exceptions import UnformattedGetAttTemplateException | ||||
| 
 | ||||
|         if attribute_name == "CidrBlock": | ||||
|             return self.cidr_block | ||||
|         elif attribute_name == "CidrBlockAssociations": | ||||
|             return self.cidr_block_association_set | ||||
|         elif attribute_name == "DefaultSecurityGroup": | ||||
|             sec_group = self.ec2_backend.get_security_group_from_name( | ||||
|                 "default", vpc_id=self.id | ||||
|             ) | ||||
|             return sec_group.id | ||||
|         elif attribute_name == "VpcId": | ||||
|             return self.id | ||||
|         raise UnformattedGetAttTemplateException() | ||||
| 
 | ||||
|     @classmethod | ||||
|     def create_from_cloudformation_json(  # type: ignore[misc] | ||||
|         cls, | ||||
|  | ||||
| @ -10,7 +10,7 @@ from copy import deepcopy | ||||
| from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union | ||||
| 
 | ||||
| from moto.core.base_backend import BackendDict, BaseBackend | ||||
| from moto.core.common_models import BaseModel, CloudFormationModel | ||||
| from moto.core.common_models import CloudFormationModel | ||||
| from moto.core.utils import camelcase_to_underscores, underscores_to_camelcase | ||||
| from moto.ec2 import ec2_backends | ||||
| from moto.ec2.exceptions import InvalidSubnetIdError | ||||
| @ -43,7 +43,7 @@ def _lookup_az_id(account_id: str, az_name: str) -> Optional[str]: | ||||
|     return None | ||||
| 
 | ||||
| 
 | ||||
| class AccessPoint(BaseModel): | ||||
| class AccessPoint(CloudFormationModel): | ||||
|     def __init__( | ||||
|         self, | ||||
|         account_id: str, | ||||
| @ -84,6 +84,38 @@ class AccessPoint(BaseModel): | ||||
|             "LifeCycleState": "available", | ||||
|         } | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def cloudformation_type() -> str: | ||||
|         return "AWS::EFS::AccessPoint" | ||||
| 
 | ||||
|     @classmethod | ||||
|     def create_from_cloudformation_json(  # type: ignore[misc] | ||||
|         cls, | ||||
|         resource_name: str, | ||||
|         cloudformation_json: Any, | ||||
|         account_id: str, | ||||
|         region_name: str, | ||||
|         **kwargs: Any, | ||||
|     ) -> "AccessPoint": | ||||
|         props = cloudformation_json["Properties"] | ||||
|         file_system_id = props["FileSystemId"] | ||||
|         posix_user = props.get("PosixUser", {}) | ||||
|         root_directory = props.get("RootDirectory", {}) | ||||
|         tags = props.get("AccessPointTags", []) | ||||
| 
 | ||||
|         backend: EFSBackend = efs_backends[account_id][region_name] | ||||
|         return backend.create_access_point( | ||||
|             resource_name, | ||||
|             file_system_id=file_system_id, | ||||
|             posix_user=posix_user, | ||||
|             root_directory=root_directory, | ||||
|             tags=tags, | ||||
|         ) | ||||
| 
 | ||||
|     def delete(self, account_id: str, region_name: str) -> None: | ||||
|         backend: EFSBackend = efs_backends[account_id][region_name] | ||||
|         backend.delete_access_point(self.access_point_id) | ||||
| 
 | ||||
| 
 | ||||
| class FileSystem(CloudFormationModel): | ||||
|     """A model for an EFS File System Volume.""" | ||||
| @ -223,40 +255,36 @@ class FileSystem(CloudFormationModel): | ||||
|         region_name: str, | ||||
|         **kwargs: Any, | ||||
|     ) -> "FileSystem": | ||||
|         # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html | ||||
|         props = deepcopy(cloudformation_json["Properties"]) | ||||
|         props = {camelcase_to_underscores(k): v for k, v in props.items()} | ||||
|         if "file_system_tags" in props: | ||||
|             props["tags"] = props.pop("file_system_tags") | ||||
|         if "backup_policy" in props: | ||||
|             if "status" not in props["backup_policy"]: | ||||
|                 raise ValueError("BackupPolicy must be of type BackupPolicy.") | ||||
|             status = props.pop("backup_policy")["status"] | ||||
|             if status not in ["ENABLED", "DISABLED"]: | ||||
|                 raise ValueError(f'Invalid status: "{status}".') | ||||
|             props["backup"] = status == "ENABLED" | ||||
|         if "bypass_policy_lockout_safety_check" in props: | ||||
|             raise ValueError( | ||||
|                 "BypassPolicyLockoutSafetyCheck not currently " | ||||
|                 "supported by AWS Cloudformation." | ||||
|         props = cloudformation_json["Properties"] | ||||
|         performance_mode = props.get("PerformanceMode", "generalPurpose") | ||||
|         encrypted = props.get("Encrypted", False) | ||||
|         kms_key_id = props.get("KmsKeyId") | ||||
|         throughput_mode = props.get("ThroughputMode", "bursting") | ||||
|         provisioned_throughput_in_mibps = props.get("ProvisionedThroughputInMibps", 0) | ||||
|         availability_zone_name = props.get("AvailabilityZoneName") | ||||
|         backup = props.get("BackupPolicy", {}).get("Status") == "ENABLED" | ||||
|         tags = props.get("FileSystemTags", []) | ||||
| 
 | ||||
|         lifecycle_policies = props.get("LifecyclePolicies", []) | ||||
| 
 | ||||
|         backend: EFSBackend = efs_backends[account_id][region_name] | ||||
|         fs = backend.create_file_system( | ||||
|             resource_name, | ||||
|             performance_mode=performance_mode, | ||||
|             encrypted=encrypted, | ||||
|             kms_key_id=kms_key_id, | ||||
|             throughput_mode=throughput_mode, | ||||
|             provisioned_throughput_in_mibps=provisioned_throughput_in_mibps, | ||||
|             availability_zone_name=availability_zone_name, | ||||
|             backup=backup, | ||||
|             tags=tags, | ||||
|         ) | ||||
| 
 | ||||
|         if lifecycle_policies: | ||||
|             backend.put_lifecycle_configuration( | ||||
|                 file_system_id=fs.file_system_id, policies=lifecycle_policies | ||||
|             ) | ||||
| 
 | ||||
|         return efs_backends[account_id][region_name].create_file_system( | ||||
|             resource_name, **props | ||||
|         ) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def update_from_cloudformation_json(  # type: ignore[misc] | ||||
|         cls, | ||||
|         original_resource: Any, | ||||
|         new_resource_name: str, | ||||
|         cloudformation_json: Any, | ||||
|         account_id: str, | ||||
|         region_name: str, | ||||
|     ) -> None: | ||||
|         raise NotImplementedError( | ||||
|             "Update of EFS File System via cloudformation is not yet implemented." | ||||
|         ) | ||||
|         return fs | ||||
| 
 | ||||
|     @classmethod | ||||
|     def delete_from_cloudformation_json(  # type: ignore[misc] | ||||
| @ -357,7 +385,6 @@ class MountTarget(CloudFormationModel): | ||||
|         region_name: str, | ||||
|         **kwargs: Any, | ||||
|     ) -> "MountTarget": | ||||
|         # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-mounttarget.html | ||||
|         props = deepcopy(cloudformation_json["Properties"]) | ||||
|         props = {camelcase_to_underscores(k): v for k, v in props.items()} | ||||
|         return efs_backends[account_id][region_name].create_mount_target(**props) | ||||
|  | ||||
							
								
								
									
										189
									
								
								tests/test_efs/test_efs_cloudformation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								tests/test_efs/test_efs_cloudformation.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,189 @@ | ||||
| import json | ||||
| 
 | ||||
| import boto3 | ||||
| 
 | ||||
| from moto import mock_aws | ||||
| 
 | ||||
| template_fs_simple = { | ||||
|     "AWSTemplateFormatVersion": "2010-09-09", | ||||
|     "Resources": { | ||||
|         "FileSystemResource": {"Type": "AWS::EFS::FileSystem", "Properties": {}}, | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| template_complete = { | ||||
|     "AWSTemplateFormatVersion": "2010-09-09", | ||||
|     "Resources": { | ||||
|         "MountTargetVPC": { | ||||
|             "Type": "AWS::EC2::VPC", | ||||
|             "Properties": {"CidrBlock": "172.31.0.0/16"}, | ||||
|         }, | ||||
|         "MountTargetSubnetOne": { | ||||
|             "Type": "AWS::EC2::Subnet", | ||||
|             "Properties": { | ||||
|                 "CidrBlock": "172.31.1.0/24", | ||||
|                 "VpcId": {"Ref": "MountTargetVPC"}, | ||||
|                 "AvailabilityZone": "us-east-1a", | ||||
|             }, | ||||
|         }, | ||||
|         "MountTargetSubnetTwo": { | ||||
|             "Type": "AWS::EC2::Subnet", | ||||
|             "Properties": { | ||||
|                 "CidrBlock": "172.31.2.0/24", | ||||
|                 "VpcId": {"Ref": "MountTargetVPC"}, | ||||
|                 "AvailabilityZone": "us-east-1b", | ||||
|             }, | ||||
|         }, | ||||
|         "MountTargetSubnetThree": { | ||||
|             "Type": "AWS::EC2::Subnet", | ||||
|             "Properties": { | ||||
|                 "CidrBlock": "172.31.3.0/24", | ||||
|                 "VpcId": {"Ref": "MountTargetVPC"}, | ||||
|                 "AvailabilityZone": "us-east-1c", | ||||
|             }, | ||||
|         }, | ||||
|         "FileSystemResource": { | ||||
|             "Type": "AWS::EFS::FileSystem", | ||||
|             "Properties": { | ||||
|                 "PerformanceMode": "maxIO", | ||||
|                 "LifecyclePolicies": [ | ||||
|                     {"TransitionToIA": "AFTER_30_DAYS"}, | ||||
|                     {"TransitionToPrimaryStorageClass": "AFTER_1_ACCESS"}, | ||||
|                 ], | ||||
|                 "Encrypted": True, | ||||
|                 "FileSystemTags": [{"Key": "Name", "Value": "TestFileSystem"}], | ||||
|                 "FileSystemPolicy": { | ||||
|                     "Version": "2012-10-17", | ||||
|                     "Statement": [ | ||||
|                         { | ||||
|                             "Effect": "Allow", | ||||
|                             "Action": ["elasticfilesystem:ClientMount"], | ||||
|                             "Principal": { | ||||
|                                 "AWS": "arn:aws:iam::111122223333:role/EfsReadOnly" | ||||
|                             }, | ||||
|                         } | ||||
|                     ], | ||||
|                 }, | ||||
|                 "BackupPolicy": {"Status": "ENABLED"}, | ||||
|                 "KmsKeyId": {"Fn::GetAtt": ["key", "Arn"]}, | ||||
|             }, | ||||
|         }, | ||||
|         "key": { | ||||
|             "Type": "AWS::KMS::Key", | ||||
|             "Properties": { | ||||
|                 "KeyPolicy": { | ||||
|                     "Version": "2012-10-17", | ||||
|                     "Id": "key-default-1", | ||||
|                     "Statement": [ | ||||
|                         { | ||||
|                             "Sid": "Allow administration of the key", | ||||
|                             "Effect": "Allow", | ||||
|                             "Principal": { | ||||
|                                 "AWS": { | ||||
|                                     "Fn::Join": [ | ||||
|                                         "", | ||||
|                                         [ | ||||
|                                             "arn:aws:iam::", | ||||
|                                             {"Ref": "AWS::AccountId"}, | ||||
|                                             ":root", | ||||
|                                         ], | ||||
|                                     ] | ||||
|                                 } | ||||
|                             }, | ||||
|                             "Action": ["kms:*"], | ||||
|                             "Resource": "*", | ||||
|                         } | ||||
|                     ], | ||||
|                 } | ||||
|             }, | ||||
|         }, | ||||
|         "MountTargetResource1": { | ||||
|             "Type": "AWS::EFS::MountTarget", | ||||
|             "Properties": { | ||||
|                 "FileSystemId": {"Ref": "FileSystemResource"}, | ||||
|                 "SubnetId": {"Ref": "MountTargetSubnetOne"}, | ||||
|                 "SecurityGroups": [ | ||||
|                     {"Fn::GetAtt": ["MountTargetVPC", "DefaultSecurityGroup"]} | ||||
|                 ], | ||||
|             }, | ||||
|         }, | ||||
|         "MountTargetResource2": { | ||||
|             "Type": "AWS::EFS::MountTarget", | ||||
|             "Properties": { | ||||
|                 "FileSystemId": {"Ref": "FileSystemResource"}, | ||||
|                 "SubnetId": {"Ref": "MountTargetSubnetTwo"}, | ||||
|                 "SecurityGroups": [ | ||||
|                     {"Fn::GetAtt": ["MountTargetVPC", "DefaultSecurityGroup"]} | ||||
|                 ], | ||||
|             }, | ||||
|         }, | ||||
|         "MountTargetResource3": { | ||||
|             "Type": "AWS::EFS::MountTarget", | ||||
|             "Properties": { | ||||
|                 "FileSystemId": {"Ref": "FileSystemResource"}, | ||||
|                 "SubnetId": {"Ref": "MountTargetSubnetThree"}, | ||||
|                 "SecurityGroups": [ | ||||
|                     {"Fn::GetAtt": ["MountTargetVPC", "DefaultSecurityGroup"]} | ||||
|                 ], | ||||
|             }, | ||||
|         }, | ||||
|         "AccessPointResource": { | ||||
|             "Type": "AWS::EFS::AccessPoint", | ||||
|             "Properties": { | ||||
|                 "FileSystemId": {"Ref": "FileSystemResource"}, | ||||
|                 "PosixUser": { | ||||
|                     "Uid": "13234", | ||||
|                     "Gid": "1322", | ||||
|                     "SecondaryGids": ["1344", "1452"], | ||||
|                 }, | ||||
|                 "RootDirectory": { | ||||
|                     "CreationInfo": { | ||||
|                         "OwnerGid": "708798", | ||||
|                         "OwnerUid": "7987987", | ||||
|                         "Permissions": "0755", | ||||
|                     }, | ||||
|                     "Path": "/testcfn/abc", | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @mock_aws | ||||
| def test_simple_template(): | ||||
|     region = "us-east-1" | ||||
|     cf = boto3.client("cloudformation", region_name=region) | ||||
|     cf.create_stack(StackName="teststack", TemplateBody=json.dumps(template_fs_simple)) | ||||
| 
 | ||||
|     efs = boto3.client("efs", region) | ||||
|     fs = efs.describe_file_systems()["FileSystems"][0] | ||||
|     assert fs["PerformanceMode"] == "generalPurpose" | ||||
|     assert fs["Encrypted"] is False | ||||
|     assert fs["ThroughputMode"] == "bursting" | ||||
| 
 | ||||
| 
 | ||||
| @mock_aws | ||||
| def test_full_template(): | ||||
|     region = "us-east-1" | ||||
|     cf = boto3.client("cloudformation", region_name=region) | ||||
|     cf.create_stack(StackName="teststack", TemplateBody=json.dumps(template_complete)) | ||||
| 
 | ||||
|     efs = boto3.client("efs", region) | ||||
|     fs = efs.describe_file_systems()["FileSystems"][0] | ||||
|     fs_id = fs["FileSystemId"] | ||||
|     assert fs["Name"] == "TestFileSystem" | ||||
|     assert fs["KmsKeyId"] | ||||
| 
 | ||||
|     lc = efs.describe_lifecycle_configuration(FileSystemId=fs_id)["LifecyclePolicies"] | ||||
|     assert {"TransitionToIA": "AFTER_30_DAYS"} in lc | ||||
|     assert {"TransitionToPrimaryStorageClass": "AFTER_1_ACCESS"} in lc | ||||
| 
 | ||||
|     aps = efs.describe_access_points()["AccessPoints"][0] | ||||
|     assert aps["FileSystemId"] == fs_id | ||||
| 
 | ||||
|     cf.delete_stack(StackName="teststack") | ||||
| 
 | ||||
|     assert efs.describe_file_systems()["FileSystems"] == [] | ||||
|     assert efs.describe_access_points()["AccessPoints"] == [] | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user