diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/EBSTaskAttachDefaultTestDeployAssertF52EF4F9.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/EBSTaskAttachDefaultTestDeployAssertF52EF4F9.assets.json new file mode 100644 index 0000000000000..7478188235117 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/EBSTaskAttachDefaultTestDeployAssertF52EF4F9.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "EBSTaskAttachDefaultTestDeployAssertF52EF4F9.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/EBSTaskAttachDefaultTestDeployAssertF52EF4F9.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/EBSTaskAttachDefaultTestDeployAssertF52EF4F9.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/EBSTaskAttachDefaultTestDeployAssertF52EF4F9.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/cdk.out new file mode 100644 index 0000000000000..1f0068d32659a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ-aws-ecs-ebs-task-attach.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ-aws-ecs-ebs-task-attach.assets.json new file mode 100644 index 0000000000000..95bfe458f82d1 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ-aws-ecs-ebs-task-attach.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "114105e2d29626a5303e48c5ab4b3be1059592f803b1d317c99665664cd954a9": { + "source": { + "path": "integ-aws-ecs-ebs-task-attach.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "114105e2d29626a5303e48c5ab4b3be1059592f803b1d317c99665664cd954a9.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ-aws-ecs-ebs-task-attach.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ-aws-ecs-ebs-task-attach.template.json new file mode 100644 index 0000000000000..bac09e2c34fec --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ-aws-ecs-ebs-task-attach.template.json @@ -0,0 +1,451 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "integ-aws-ecs-ebs-task-attach/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/17", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1" + } + ] + }, + "DependsOn": [ + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1RouteTableAssociation97140677" + ] + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/17", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + }, + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-aws-ecs-ebs-task-attach/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "FargateCluster7CCD5F93": { + "Type": "AWS::ECS::Cluster" + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "MountPoints": [ + { + "ContainerPath": "/var/lib", + "ReadOnly": false, + "SourceVolume": "ebs1" + } + ], + "Name": "web", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ] + } + ], + "Cpu": "256", + "Family": "integawsecsebstaskattachTaskDefB8F13A4F", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + "Volumes": [ + { + "ConfiguredAtLaunch": true, + "Name": "ebs1" + } + ] + } + }, + "EBSVolumeEBSRoleC27DD941": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonECSInfrastructureRolePolicyForVolumes" + ] + ] + } + ] + } + }, + "FargateServiceAC2B3B85": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "FargateCluster7CCD5F93" + }, + "DeploymentConfiguration": { + "Alarms": { + "AlarmNames": [], + "Enable": false, + "Rollback": false + }, + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "DesiredCount": 1, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + ] + } + }, + "TaskDefinition": { + "Ref": "TaskDef54694570" + }, + "VolumeConfigurations": [ + { + "ManagedEBSVolume": { + "Encrypted": true, + "FilesystemType": "ext4", + "Iops": 4000, + "RoleArn": { + "Fn::GetAtt": [ + "EBSVolumeEBSRoleC27DD941", + "Arn" + ] + }, + "SizeInGiB": 15, + "TagSpecifications": [ + { + "PropagateTags": "SERVICE", + "ResourceType": "volume", + "Tags": [ + { + "Key": "purpose", + "Value": "production" + } + ] + }, + { + "PropagateTags": "TASK_DEFINITION", + "ResourceType": "volume", + "Tags": [ + { + "Key": "purpose", + "Value": "development" + } + ] + } + ], + "Throughput": 500, + "VolumeType": "gp3" + }, + "Name": "ebs1" + } + ] + }, + "DependsOn": [ + "TaskDefTaskRole1EDB4A67" + ] + }, + "FargateServiceSecurityGroup0A0E79CB": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "integ-aws-ecs-ebs-task-attach/FargateService/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "DependsOn": [ + "TaskDefTaskRole1EDB4A67" + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ.json new file mode 100644 index 0000000000000..7e4cd54d7f54c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "36.0.0", + "testCases": { + "EBSTaskAttach/DefaultTest": { + "stacks": [ + "integ-aws-ecs-ebs-task-attach" + ], + "assertionStack": "EBSTaskAttach/DefaultTest/DeployAssert", + "assertionStackName": "EBSTaskAttachDefaultTestDeployAssertF52EF4F9" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/manifest.json new file mode 100644 index 0000000000000..5c7609dc81843 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/manifest.json @@ -0,0 +1,221 @@ +{ + "version": "36.0.0", + "artifacts": { + "integ-aws-ecs-ebs-task-attach.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-aws-ecs-ebs-task-attach.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-aws-ecs-ebs-task-attach": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integ-aws-ecs-ebs-task-attach.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/114105e2d29626a5303e48c5ab4b3be1059592f803b1d317c99665664cd954a9.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-aws-ecs-ebs-task-attach.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integ-aws-ecs-ebs-task-attach.assets" + ], + "metadata": { + "/integ-aws-ecs-ebs-task-attach/Vpc/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Vpc8378EB38" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1Subnet5C2D37C4" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1RouteTable6C95E38E" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1RouteTableAssociation97140677" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1DefaultRoute3DA9E72A" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1EIPD7E02669" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1NATGateway4D7517AA" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1Subnet536B997A" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1RouteTableB2C5B500" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1RouteTableAssociation70C59FA6" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1DefaultRouteBE02A9ED" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcIGWD7BA715C" + } + ], + "/integ-aws-ecs-ebs-task-attach/Vpc/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcVPCGWBF912B6E" + } + ], + "/integ-aws-ecs-ebs-task-attach/FargateCluster/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FargateCluster7CCD5F93" + } + ], + "/integ-aws-ecs-ebs-task-attach/TaskDef/TaskRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TaskDefTaskRole1EDB4A67" + } + ], + "/integ-aws-ecs-ebs-task-attach/TaskDef/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TaskDef54694570" + } + ], + "/integ-aws-ecs-ebs-task-attach/EBSVolume/EBSRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "EBSVolumeEBSRoleC27DD941" + } + ], + "/integ-aws-ecs-ebs-task-attach/FargateService/Service": [ + { + "type": "aws:cdk:logicalId", + "data": "FargateServiceAC2B3B85" + } + ], + "/integ-aws-ecs-ebs-task-attach/FargateService/SecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FargateServiceSecurityGroup0A0E79CB" + } + ], + "/integ-aws-ecs-ebs-task-attach/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-aws-ecs-ebs-task-attach/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-aws-ecs-ebs-task-attach" + }, + "EBSTaskAttachDefaultTestDeployAssertF52EF4F9.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "EBSTaskAttachDefaultTestDeployAssertF52EF4F9.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "EBSTaskAttachDefaultTestDeployAssertF52EF4F9": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "EBSTaskAttachDefaultTestDeployAssertF52EF4F9.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "EBSTaskAttachDefaultTestDeployAssertF52EF4F9.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "EBSTaskAttachDefaultTestDeployAssertF52EF4F9.assets" + ], + "metadata": { + "/EBSTaskAttach/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/EBSTaskAttach/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "EBSTaskAttach/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/tree.json new file mode 100644 index 0000000000000..896bcffef46a0 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/tree.json @@ -0,0 +1,794 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "integ-aws-ecs-ebs-task-attach": { + "id": "integ-aws-ecs-ebs-task-attach", + "path": "integ-aws-ecs-ebs-task-attach", + "children": { + "Vpc": { + "id": "Vpc", + "path": "integ-aws-ecs-ebs-task-attach/Vpc", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPC", + "aws:cdk:cloudformation:props": { + "cidrBlock": "10.0.0.0/16", + "enableDnsHostnames": true, + "enableDnsSupport": true, + "instanceTenancy": "default", + "tags": [ + { + "key": "Name", + "value": "integ-aws-ecs-ebs-task-attach/Vpc" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnVPC", + "version": "0.0.0" + } + }, + "PublicSubnet1": { + "id": "PublicSubnet1", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.0.0/17", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/Acl", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "subnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "allocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "subnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "tags": [ + { + "key": "Name", + "value": "integ-aws-ecs-ebs-task-attach/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet1": { + "id": "PrivateSubnet1", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.128.0/17", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1/Acl", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "subnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/PrivateSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + }, + "routeTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "IGW": { + "id": "IGW", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/IGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::InternetGateway", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "integ-aws-ecs-ebs-task-attach/Vpc" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnInternetGateway", + "version": "0.0.0" + } + }, + "VPCGW": { + "id": "VPCGW", + "path": "integ-aws-ecs-ebs-task-attach/Vpc/VPCGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", + "aws:cdk:cloudformation:props": { + "internetGatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnVPCGatewayAttachment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.Vpc", + "version": "0.0.0" + } + }, + "FargateCluster": { + "id": "FargateCluster", + "path": "integ-aws-ecs-ebs-task-attach/FargateCluster", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-aws-ecs-ebs-task-attach/FargateCluster/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::Cluster", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.CfnCluster", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.Cluster", + "version": "0.0.0" + } + }, + "TaskDef": { + "id": "TaskDef", + "path": "integ-aws-ecs-ebs-task-attach/TaskDef", + "children": { + "TaskRole": { + "id": "TaskRole", + "path": "integ-aws-ecs-ebs-task-attach/TaskDef/TaskRole", + "children": { + "ImportTaskRole": { + "id": "ImportTaskRole", + "path": "integ-aws-ecs-ebs-task-attach/TaskDef/TaskRole/ImportTaskRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-aws-ecs-ebs-task-attach/TaskDef/TaskRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-aws-ecs-ebs-task-attach/TaskDef/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::TaskDefinition", + "aws:cdk:cloudformation:props": { + "containerDefinitions": [ + { + "essential": true, + "image": "amazon/amazon-ecs-sample", + "mountPoints": [ + { + "containerPath": "/var/lib", + "readOnly": false, + "sourceVolume": "ebs1" + } + ], + "name": "web", + "portMappings": [ + { + "containerPort": 80, + "protocol": "tcp" + } + ] + } + ], + "cpu": "256", + "family": "integawsecsebstaskattachTaskDefB8F13A4F", + "memory": "512", + "networkMode": "awsvpc", + "requiresCompatibilities": [ + "FARGATE" + ], + "taskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + "volumes": [ + { + "name": "ebs1", + "configuredAtLaunch": true + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.CfnTaskDefinition", + "version": "0.0.0" + } + }, + "web": { + "id": "web", + "path": "integ-aws-ecs-ebs-task-attach/TaskDef/web", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.ContainerDefinition", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.FargateTaskDefinition", + "version": "0.0.0" + } + }, + "EBSVolume": { + "id": "EBSVolume", + "path": "integ-aws-ecs-ebs-task-attach/EBSVolume", + "children": { + "EBSRole": { + "id": "EBSRole", + "path": "integ-aws-ecs-ebs-task-attach/EBSVolume/EBSRole", + "children": { + "ImportEBSRole": { + "id": "ImportEBSRole", + "path": "integ-aws-ecs-ebs-task-attach/EBSVolume/EBSRole/ImportEBSRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-aws-ecs-ebs-task-attach/EBSVolume/EBSRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonECSInfrastructureRolePolicyForVolumes" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.ServiceManagedVolume", + "version": "0.0.0" + } + }, + "FargateService": { + "id": "FargateService", + "path": "integ-aws-ecs-ebs-task-attach/FargateService", + "children": { + "Service": { + "id": "Service", + "path": "integ-aws-ecs-ebs-task-attach/FargateService/Service", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::Service", + "aws:cdk:cloudformation:props": { + "cluster": { + "Ref": "FargateCluster7CCD5F93" + }, + "deploymentConfiguration": { + "maximumPercent": 200, + "minimumHealthyPercent": 50, + "alarms": { + "alarmNames": [], + "enable": false, + "rollback": false + } + }, + "desiredCount": 1, + "enableEcsManagedTags": false, + "launchType": "FARGATE", + "networkConfiguration": { + "awsvpcConfiguration": { + "assignPublicIp": "DISABLED", + "subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + ], + "securityGroups": [ + { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + } + ] + } + }, + "taskDefinition": { + "Ref": "TaskDef54694570" + }, + "volumeConfigurations": [ + { + "name": "ebs1", + "managedEbsVolume": { + "roleArn": { + "Fn::GetAtt": [ + "EBSVolumeEBSRoleC27DD941", + "Arn" + ] + }, + "encrypted": true, + "filesystemType": "ext4", + "iops": 4000, + "throughput": 500, + "volumeType": "gp3", + "sizeInGiB": 15, + "tagSpecifications": [ + { + "resourceType": "volume", + "propagateTags": "SERVICE", + "tags": [ + { + "key": "purpose", + "value": "production" + } + ] + }, + { + "resourceType": "volume", + "propagateTags": "TASK_DEFINITION", + "tags": [ + { + "key": "purpose", + "value": "development" + } + ] + } + ] + } + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.CfnService", + "version": "0.0.0" + } + }, + "SecurityGroup": { + "id": "SecurityGroup", + "path": "integ-aws-ecs-ebs-task-attach/FargateService/SecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-aws-ecs-ebs-task-attach/FargateService/SecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "integ-aws-ecs-ebs-task-attach/FargateService/SecurityGroup", + "securityGroupEgress": [ + { + "cidrIp": "0.0.0.0/0", + "description": "Allow all outbound traffic by default", + "ipProtocol": "-1" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.CfnSecurityGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ec2.SecurityGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.FargateService", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-aws-ecs-ebs-task-attach/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-aws-ecs-ebs-task-attach/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "EBSTaskAttach": { + "id": "EBSTaskAttach", + "path": "EBSTaskAttach", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "EBSTaskAttach/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "EBSTaskAttach/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "EBSTaskAttach/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "EBSTaskAttach/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "EBSTaskAttach/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.ts new file mode 100644 index 0000000000000..d178806e48136 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.ts @@ -0,0 +1,77 @@ +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as cdk from 'aws-cdk-lib'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import { Construct } from 'constructs'; + +class TestStack extends cdk.Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const vpc = new ec2.Vpc(this, 'Vpc', { + maxAzs: 1, + restrictDefaultSecurityGroup: false, + }); + + const cluster = new ecs.Cluster(this, 'FargateCluster', { + vpc, + }); + + const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef'); + + const container = taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + portMappings: [{ + containerPort: 80, + protocol: ecs.Protocol.TCP, + }], + }); + + const volume = new ecs.ServiceManagedVolume(this, 'EBSVolume', { + name: 'ebs1', + managedEBSVolume: { + encrypted: true, + volumeType: ec2.EbsDeviceVolumeType.GP3, + size: cdk.Size.gibibytes(15), + iops: 4000, + throughput: 500, + fileSystemType: ecs.FileSystemType.EXT4, + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, + }, + { + tags: { + purpose: 'development', + }, + propagateTags: ecs.EbsPropagatedTagSource.TASK_DEFINITION, + }], + }, + }); + + volume.mountIn(container, { + containerPath: '/var/lib', + readOnly: false, + }); + + taskDefinition.addVolume(volume); + + const service = new ecs.FargateService(this, 'FargateService', { + cluster, + taskDefinition, + desiredCount: 1, + }); + + service.addVolume(volume); + } +} + +const app = new cdk.App(); +const stack = new TestStack(app, 'integ-aws-ecs-ebs-task-attach'); + +new integ.IntegTest(app, 'EBSTaskAttach', { + testCases: [stack], +}); +app.synth(); diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index b120df0bf0049..3b0db4577e94c 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -1631,6 +1631,89 @@ const customService = new ecs.FargateService(this, 'CustomizedService', { }); ``` +## ServiceManagedVolume + +Amazon ECS now supports the attachment of Amazon Elastic Block Store (EBS) volumes to ECS tasks, +allowing you to utilize persistent, high-performance block storage with your ECS services. +This feature supports various use cases, such as using EBS volumes as extended ephemeral storage or +loading data from EBS snapshots. +You can also specify `encrypted: true` so that ECS will manage the KMS key. If you want to use your own KMS key, you may do so by providing both `encrypted: true` and `kmsKeyId`. + +You can only attach a single volume for each task in the ECS Service. + +To add an empty EBS Volume to an ECS Service, call service.addVolume(). + +```ts +declare const cluster: ecs.Cluster; +const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef'); + +const container = taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + portMappings: [{ + containerPort: 80, + protocol: ecs.Protocol.TCP, + }], +}); + +const volume = new ecs.ServiceManagedVolume(this, 'EBSVolume', { + name: 'ebs1', + managedEBSVolume: { + size: Size.gibibytes(15), + volumeType: ec2.EbsDeviceVolumeType.GP3, + fileSystemType: ecs.FileSystemType.XFS, + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, + }], + }, +}); + +volume.mountIn(container, { + containerPath: '/var/lib', + readOnly: false, +}); + +taskDefinition.addVolume(volume); + +const service = new ecs.FargateService(this, 'FargateService', { + cluster, + taskDefinition, +}); + +service.addVolume(volume); +``` + +To create an EBS volume from an existing snapshot by specifying the `snapShotId` while adding a volume to the service. + +```ts +declare const container: ecs.ContainerDefinition; +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; + +const volumeFromSnapshot = new ecs.ServiceManagedVolume(this, 'EBSVolume', { + name: 'nginx-vol', + managedEBSVolume: { + snapShotId: 'snap-066877671789bd71b', + volumeType: ec2.EbsDeviceVolumeType.GP3, + fileSystemType: ecs.FileSystemType.XFS, + }, +}); + +volumeFromSnapshot.mountIn(container, { + containerPath: '/var/lib', + readOnly: false, +}); +taskDefinition.addVolume(volumeFromSnapshot); +const service = new ecs.FargateService(this, 'FargateService', { + cluster, + taskDefinition, +}); + +service.addVolume(volumeFromSnapshot); +``` + ## Enable pseudo-terminal (TTY) allocation You can allocate a pseudo-terminal (TTY) for a container passing `pseudoTerminal` option while adding the container diff --git a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts index 9726ef21526ce..00c0941db1022 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts @@ -1,5 +1,6 @@ import { Construct } from 'constructs'; import { ScalableTaskCount } from './scalable-task-count'; +import { ServiceManagedVolume } from './service-managed-volume'; import * as appscaling from '../../../aws-applicationautoscaling'; import * as cloudwatch from '../../../aws-cloudwatch'; import * as ec2 from '../../../aws-ec2'; @@ -357,6 +358,14 @@ export interface BaseServiceOptions { * @default - Uses the revision of the passed task definition deployed by CloudFormation */ readonly taskDefinitionRevision?: TaskDefinitionRevision; + + /** + * Configuration details for a volume used by the service. This allows you to specify + * details about the EBS volume that can be attched to ECS tasks. + * + * @default - undefined + */ + readonly volumeConfigurations?: ServiceManagedVolume[]; } /** @@ -576,6 +585,11 @@ export abstract class BaseService extends Resource private readonly resource: CfnService; private scalableTaskCount?: ScalableTaskCount; + /** + * All volumes + */ + private readonly volumes: ServiceManagedVolume[] = []; + /** * Constructs a new instance of the BaseService class. */ @@ -626,6 +640,7 @@ export abstract class BaseService extends Resource networkConfiguration: Lazy.any({ produce: () => this.networkConfiguration }, { omitEmptyArray: true }), serviceRegistries: Lazy.any({ produce: () => this.serviceRegistries }, { omitEmptyArray: true }), serviceConnectConfiguration: Lazy.any({ produce: () => this._serviceConnectConfig }, { omitEmptyArray: true }), + volumeConfigurations: Lazy.any({ produce: () => this.renderVolumes() }, { omitEmptyArray: true }), ...additionalProps, }); @@ -686,6 +701,10 @@ export abstract class BaseService extends Resource this.enableServiceConnect(props.serviceConnectConfiguration); } + if (props.volumeConfigurations) { + props.volumeConfigurations.forEach(v => this.addVolume(v)); + } + if (props.enableExecuteCommand) { this.enableExecuteCommand(); @@ -721,6 +740,48 @@ export abstract class BaseService extends Resource this.node.defaultChild = this.resource; } + /** + * Adds a volume to the Service. + */ + public addVolume(volume: ServiceManagedVolume) { + this.volumes.push(volume); + } + + private renderVolumes(): CfnService.ServiceVolumeConfigurationProperty[] { + if (this.volumes.length > 1) { + throw new Error(`Only one EBS volume can be specified for 'volumeConfigurations', got: ${this.volumes.length}`); + } + return this.volumes.map(renderVolume); + function renderVolume(spec: ServiceManagedVolume): CfnService.ServiceVolumeConfigurationProperty { + const tagSpecifications = spec.config?.tagSpecifications?.map(ebsTagSpec => { + return { + resourceType: 'volume', + propagateTags: ebsTagSpec.propagateTags, + tags: ebsTagSpec.tags ? Object.entries(ebsTagSpec.tags).map(([key, value]) => ({ + key: key, + value: value, + })) : undefined, + } as CfnService.EBSTagSpecificationProperty; + }); + + return { + name: spec.name, + managedEbsVolume: spec.config && { + roleArn: spec.role.roleArn, + encrypted: spec.config.encrypted, + filesystemType: spec.config.fileSystemType, + iops: spec.config.iops, + kmsKeyId: spec.config.kmsKeyId?.keyId, + throughput: spec.config.throughput, + volumeType: spec.config.volumeType, + snapshotId: spec.config.snapShotId, + sizeInGiB: spec.config.size?.toGibibytes(), + tagSpecifications: tagSpecifications, + }, + }; + } + } + /** * Enable Deployment Alarms which take advantage of arbitrary alarms and configure them after service initialization. * If you have already enabled deployment alarms, this function can be used to tell ECS about additional alarms that diff --git a/packages/aws-cdk-lib/aws-ecs/lib/base/service-managed-volume.ts b/packages/aws-cdk-lib/aws-ecs/lib/base/service-managed-volume.ts new file mode 100644 index 0000000000000..6cea5e124224a --- /dev/null +++ b/packages/aws-cdk-lib/aws-ecs/lib/base/service-managed-volume.ts @@ -0,0 +1,310 @@ +import { Construct } from 'constructs'; +import * as ec2 from '../../../aws-ec2'; +import * as iam from '../../../aws-iam'; +import * as kms from '../../../aws-kms'; +import { Size, Token } from '../../../core'; +import { BaseMountPoint, ContainerDefinition } from '../container-definition'; + +/** +* Represents the Volume configuration for an ECS service. +*/ +export interface ServiceManagedVolumeProps { + /** + * The name of the volume. This corresponds to the name provided in the ECS TaskDefinition. + */ + readonly name: string; + + /** + * Configuration for an Amazon Elastic Block Store (EBS) volume managed by ECS. + * + * @default - undefined + */ + readonly managedEBSVolume?: ServiceManagedEBSVolumeConfiguration +} + +/** +* Represents the configuration for an ECS Service managed EBS volume. +*/ +export interface ServiceManagedEBSVolumeConfiguration { + /** + * An IAM role that allows ECS to make calls to EBS APIs on your behalf. + * This role is required to create and manage the Amazon EBS volume. + * + * @default - automatically generated role. + */ + readonly role?: iam.IRole; + + /** + * Indicates whether the volume should be encrypted. + * + * @default - Default Amazon EBS encryption. + */ + readonly encrypted?: boolean; + + /** + * AWS Key Management Service key to use for Amazon EBS encryption. + * + * @default - When `encryption` is turned on and no `kmsKey` is specified, + * the default AWS managed key for Amazon EBS volumes is used. + */ + readonly kmsKeyId?: kms.IKey; + + /** + * The volume type. + * + * @default - ec2.EbsDeviceVolumeType.GP2 + */ + readonly volumeType?: ec2.EbsDeviceVolumeType; + + /** + * The size of the volume in GiB. + * + * You must specify either `size` or `snapshotId`. + * You can optionally specify a volume size greater than or equal to the snapshot size. + * + * The following are the supported volume size values for each volume type. + * - gp2 and gp3: 1-16,384 + * - io1 and io2: 4-16,384 + * - st1 and sc1: 125-16,384 + * - standard: 1-1,024 + * + * @default - The snapshot size is used for the volume size if you specify `snapshotId`, + * otherwise this parameter is required. + */ + readonly size?: Size; + + /** + * The snapshot that Amazon ECS uses to create the volume. + * + * You must specify either `size` or `snapshotId`. + * + * @default - No snapshot. + */ + readonly snapShotId?: string; + + /** + * The number of I/O operations per second (IOPS). + * + * For gp3, io1, and io2 volumes, this represents the number of IOPS that are provisioned + * for the volume. For gp2 volumes, this represents the baseline performance of the volume + * and the rate at which the volume accumulates I/O credits for bursting. + * + * The following are the supported values for each volume type. + * - gp3: 3,000 - 16,000 IOPS + * - io1: 100 - 64,000 IOPS + * - io2: 100 - 256,000 IOPS + * + * This parameter is required for io1 and io2 volume types. The default for gp3 volumes is + * 3,000 IOPS. This parameter is not supported for st1, sc1, or standard volume types. + * + * @default - undefined + */ + readonly iops?: number; + + /** + * The throughput to provision for a volume, in MiB/s, with a maximum of 1,000 MiB/s. + * + * This parameter is only supported for the gp3 volume type. + * + * @default - No throughput. + */ + readonly throughput?: number; + + /** + * The Linux filesystem type for the volume. + * + * For volumes created from a snapshot, you must specify the same filesystem type that + * the volume was using when the snapshot was created. + * The available filesystem types are ext3, ext4, and xfs. + * + * @default - FileSystemType.XFS + */ + readonly fileSystemType?: FileSystemType; + + /** + * Specifies the tags to apply to the volume and whether to propagate those tags to the volume. + * + * @default - No tags are specified. + */ + readonly tagSpecifications?: EBSTagSpecification[]; +} + +/** + * Tag Specification for EBS volume. + */ +export interface EBSTagSpecification { + /** + * The tags to apply to the volume. + * + * @default - No tags + */ + readonly tags?: {[key: string]: string}; + + /** + * Specifies whether to propagate the tags from the task definition or the service to the task. + * Valid values are: PropagatedTagSource.SERVICE, PropagatedTagSource.TASK_DEFINITION + * + * @default - undefined + */ + readonly propagateTags?: EbsPropagatedTagSource; +} + +/** + * FileSystemType for Service Managed EBS Volume Configuration. + */ +export enum FileSystemType { + /** + * ext3 type + */ + EXT3 = 'ext3', + /** + * ext4 type + */ + EXT4 = 'ext4', + /** + * xfs type + */ + XFS = 'xfs', +} + +/** + * Propagate tags for EBS Volume Configuration from either service or task definition. + */ +export enum EbsPropagatedTagSource { + /** + * SERVICE + */ + SERVICE = 'SERVICE', + /** + * TASK_DEFINITION + */ + TASK_DEFINITION = 'TASK_DEFINITION', +} + +/** + * Defines the mount point details for attaching a volume to a container. + */ +export interface ContainerMountPoint extends BaseMountPoint { +} + +/** + * Represents a service-managed volume and always configured at launch. + */ +export class ServiceManagedVolume extends Construct { + /** + * Name of the volume, referenced by taskdefintion and mount point. + */ + public readonly name: string; + + /** + * Volume configuration + */ + public readonly config?: ServiceManagedEBSVolumeConfiguration; + + /** + * configuredAtLaunch indicates volume at launch time, referenced by taskdefinition volume. + */ + public readonly configuredAtLaunch: boolean = true; + + /** + * An IAM role that allows ECS to make calls to EBS APIs. + * If not provided, a new role with appropriate permissions will be created by default. + */ + public readonly role: iam.IRole; + + constructor(scope: Construct, id: string, props: ServiceManagedVolumeProps) { + super(scope, id); + this.validateEbsVolumeConfiguration(props.managedEBSVolume); + this.name = props.name; + this.role = props.managedEBSVolume?.role ?? new iam.Role(this, 'EBSRole', { + assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSInfrastructureRolePolicyForVolumes'), + ], + }); + this.config = { + ...props.managedEBSVolume, + role: this.role, + }; + } + + /** + * Mounts the service managed volume to a specified container at a defined mount point. + * @param container The container to mount the volume on. + * @param mountPoint The mounting point details within the container. + */ + public mountIn(container: ContainerDefinition, mountPoint: ContainerMountPoint) { + container.addMountPoints({ + sourceVolume: this.name, + ...mountPoint, + }); + } + + private validateEbsVolumeConfiguration(volumeConfig?: ServiceManagedEBSVolumeConfiguration) { + if (!volumeConfig) return; + + const { volumeType = ec2.EbsDeviceVolumeType.GP2, iops, size, throughput, snapShotId } = volumeConfig; + + // Validate if both size and snapShotId are not specified. + if (size === undefined && snapShotId === undefined) { + throw new Error('\'size\' or \'snapShotId\' must be specified'); + } + + if (snapShotId && !Token.isUnresolved(snapShotId) && !/^snap-[0-9a-fA-F]+$/.test(snapShotId)) { + throw new Error(`'snapshotId' does match expected pattern. Expected 'snap-' (ex: 'snap-05abe246af') or Token, got: ${snapShotId}`); + } + + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-servicemanagedebsvolumeconfiguration.html#cfn-ecs-service-servicemanagedebsvolumeconfiguration-sizeingib + const sizeInGiBRanges = { + [ec2.EbsDeviceVolumeType.GP2]: { minSize: 1, maxSize: 16384 }, + [ec2.EbsDeviceVolumeType.GP3]: { minSize: 1, maxSize: 16384 }, + [ec2.EbsDeviceVolumeType.IO1]: { minSize: 4, maxSize: 16384 }, + [ec2.EbsDeviceVolumeType.IO2]: { minSize: 4, maxSize: 16384 }, + [ec2.EbsDeviceVolumeType.SC1]: { minSize: 125, maxSize: 16384 }, + [ec2.EbsDeviceVolumeType.ST1]: { minSize: 125, maxSize: 16384 }, + [ec2.EbsDeviceVolumeType.STANDARD]: { minSize: 1, maxSize: 1024 }, + }; + + // Validate volume size ranges. + if (size !== undefined) { + const { minSize, maxSize } = sizeInGiBRanges[volumeType]; + if (size.toGibibytes() < minSize || size.toGibibytes() > maxSize) { + throw new Error(`'${volumeType}' volumes must have a size between ${minSize} and ${maxSize} GiB, got ${size.toGibibytes()} GiB`); + } + } + + // Validate throughput. + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-servicemanagedebsvolumeconfiguration.html#cfn-ecs-service-servicemanagedebsvolumeconfiguration-throughput + if (throughput !== undefined) { + if (volumeType !== ec2.EbsDeviceVolumeType.GP3) { + throw new Error(`'throughput' can only be configured with gp3 volume type, got ${volumeType}`); + } else if (!Token.isUnresolved(throughput) && throughput > 1000) { + throw new Error(`'throughput' must be less than or equal to 1000 MiB/s, got ${throughput} MiB/s`); + } + } + + // Check if IOPS is not supported for the volume type. + // https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateVolume.html + if ([ec2.EbsDeviceVolumeType.SC1, ec2.EbsDeviceVolumeType.ST1, ec2.EbsDeviceVolumeType.STANDARD, + ec2.EbsDeviceVolumeType.GP2].includes(volumeType) && iops !== undefined) { + throw new Error(`'iops' cannot be specified with sc1, st1, gp2 and standard volume types, got ${volumeType}`); + } + + // Check if IOPS is required but not provided. + if ([ec2.EbsDeviceVolumeType.IO1, ec2.EbsDeviceVolumeType.IO2].includes(volumeType) && iops === undefined) { + throw new Error(`'iops' must be specified with io1 or io2 volume types, got ${volumeType}`); + } + + // Validate IOPS range if specified. + const iopsRanges: { [key: string]: { min: number, max: number } } = {}; + iopsRanges[ec2.EbsDeviceVolumeType.GP3]= { min: 3000, max: 16000 }; + iopsRanges[ec2.EbsDeviceVolumeType.IO1]= { min: 100, max: 64000 }; + iopsRanges[ec2.EbsDeviceVolumeType.IO2]= { min: 100, max: 256000 }; + if (iops !== undefined && !Token.isUnresolved(iops)) { + const { min, max } = iopsRanges[volumeType]; + if ((iops < min || iops > max)) { + throw new Error(`'${volumeType}' volumes must have 'iops' between ${min} and ${max}, got ${iops}`); + } + } + } +} diff --git a/packages/aws-cdk-lib/aws-ecs/lib/base/task-definition.ts b/packages/aws-cdk-lib/aws-ecs/lib/base/task-definition.ts index 34793208d28d5..cb2823ffb24ba 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/base/task-definition.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/base/task-definition.ts @@ -525,6 +525,7 @@ export class TaskDefinition extends TaskDefinitionBase { return { host: spec.host, name: spec.name, + configuredAtLaunch: spec.configuredAtLaunch, dockerVolumeConfiguration: spec.dockerVolumeConfiguration && { autoprovision: spec.dockerVolumeConfiguration.autoprovision, driver: spec.dockerVolumeConfiguration.driver, @@ -653,9 +654,21 @@ export class TaskDefinition extends TaskDefinitionBase { * Adds a volume to the task definition. */ public addVolume(volume: Volume) { + this.validateVolume(volume); this.volumes.push(volume); } + private validateVolume(volume: Volume):void { + if (volume.configuredAtLaunch !== true) { + return; + } + + // Other volume configurations must not be specified. + if (volume.host || volume.dockerVolumeConfiguration || volume.efsVolumeConfiguration) { + throw new Error(`Volume Configurations must not be specified for '${volume.name}' when 'configuredAtLaunch' is set to true`); + } + } + /** * Adds the specified placement constraint to the task definition. */ @@ -764,7 +777,25 @@ export class TaskDefinition extends TaskDefinitionBase { } } }); + // Validate if multiple volumes configured with configuredAtLaunch. + const runtimeVolumes = this.volumes.filter(vol => vol.configuredAtLaunch); + if (runtimeVolumes.length > 1) { + const volumeNames = runtimeVolumes.map(vol => vol.name).join(','); + ret.push(`More than one volume is configured at launch: [${volumeNames}]`); + } + + // Validate that volume with configuredAtLaunch set to true is mounted by at least one container. + for (const volume of this.volumes) { + if (volume.configuredAtLaunch) { + const isVolumeMounted = this.containers.some(container => { + return container.mountPoints.some(mp => mp.sourceVolume === volume.name); + }); + if (!isVolumeMounted) { + ret.push(`Volume '${volume.name}' should be mounted by at least one container when 'configuredAtLaunch' is true`); + } + } + } return ret; } @@ -978,6 +1009,13 @@ export interface Volume { */ readonly name: string; + /** + * Indicates if the volume should be configured at launch. + * + * @default false + */ + readonly configuredAtLaunch ?: boolean; + /** * This property is specified when you are using Docker volumes. * diff --git a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts index 0478a059df68d..1d4e3d995dc76 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts @@ -1364,9 +1364,9 @@ export interface ScratchSpace { } /** - * The details of data volume mount points for a container. + * The base details of where a volume will be mounted within a container */ -export interface MountPoint { +export interface BaseMountPoint { /** * The path on the container to mount the host volume at. */ @@ -1378,6 +1378,12 @@ export interface MountPoint { * If this value is false, then the container can write to the volume. */ readonly readOnly: boolean, +} + +/** + * The details of data volume mount points for a container. + */ +export interface MountPoint extends BaseMountPoint { /** * The name of the volume to mount. * diff --git a/packages/aws-cdk-lib/aws-ecs/lib/index.ts b/packages/aws-cdk-lib/aws-ecs/lib/index.ts index 498e8a0db5081..e3e34f236dd4d 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/index.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/index.ts @@ -1,6 +1,7 @@ export * from './base/base-service'; export * from './base/scalable-task-count'; export * from './base/task-definition'; +export * from './base/service-managed-volume'; export * from './container-definition'; export * from './container-image'; diff --git a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts index aec9f09bff889..791c913f261b4 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts @@ -4,6 +4,7 @@ import * as appscaling from '../../../aws-applicationautoscaling'; import * as cloudwatch from '../../../aws-cloudwatch'; import * as ec2 from '../../../aws-ec2'; import * as elbv2 from '../../../aws-elasticloadbalancingv2'; +import * as iam from '../../../aws-iam'; import * as kms from '../../../aws-kms'; import * as logs from '../../../aws-logs'; import * as s3 from '../../../aws-s3'; @@ -15,6 +16,7 @@ import * as cxapi from '../../../cx-api'; import { ECS_ARN_FORMAT_INCLUDES_CLUSTER_NAME } from '../../../cx-api'; import * as ecs from '../../lib'; import { DeploymentControllerType, LaunchType, PropagatedTagSource, ServiceConnectProps } from '../../lib/base/base-service'; +import { ServiceManagedVolume } from '../../lib/base/service-managed-volume'; import { addDefaultCapacityProvider } from '../util'; describe('fargate service', () => { @@ -1453,6 +1455,553 @@ describe('fargate service', () => { }); }); + describe('When setting up a service volume configurations', ()=>{ + let service: ecs.FargateService; + let stack: cdk.Stack; + let cluster: ecs.Cluster; + let taskDefinition: ecs.TaskDefinition; + let container: ecs.ContainerDefinition; + let role: iam.IRole; + let app: cdk.App; + + beforeEach(() => { + // GIVEN + app = new cdk.App(); + stack = new cdk.Stack(app); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), + }); + container = taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + }); + test('success when adding a service volume', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + role: role, + size: cdk.Size.gibibytes(20), + fileSystemType: ecs.FileSystemType.XFS, + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, + }], + }, + })); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + VolumeConfigurations: [ + { + ManagedEBSVolume: { + RoleArn: { 'Fn::GetAtt': ['Role1ABCC5F0', 'Arn'] }, + SizeInGiB: 20, + FilesystemType: 'xfs', + TagSpecifications: [ + { + PropagateTags: 'SERVICE', + ResourceType: 'volume', + Tags: [ + { + Key: 'purpose', + Value: 'production', + }, + ], + }, + ], + }, + Name: 'nginx-vol', + }, + ], + }); + }); + + test('success when mounting via ServiceManagedVolume', () => { + // WHEN + const volume = new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + role: role, + size: cdk.Size.gibibytes(20), + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, + }], + }, + }); + taskDefinition.addVolume(volume); + service.addVolume(volume); + volume.mountIn(container, { + containerPath: '/var/lib', + readOnly: false, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + VolumeConfigurations: [ + { + ManagedEBSVolume: { + RoleArn: { 'Fn::GetAtt': ['Role1ABCC5F0', 'Arn'] }, + SizeInGiB: 20, + TagSpecifications: [ + { + PropagateTags: 'SERVICE', + ResourceType: 'volume', + Tags: [ + { + Key: 'purpose', + Value: 'production', + }, + ], + }, + ], + }, + Name: 'nginx-vol', + }, + ], + }); + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + MountPoints: [ + { + ContainerPath: '/var/lib', + ReadOnly: false, + SourceVolume: 'nginx-vol', + }, + ], + }, + ], + Volumes: [ + { + Name: 'nginx-vol', + ConfiguredAtLaunch: true, + }, + ], + }); + }); + + test('throw an error when multiple volume configurations are added to ECS service', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + const vol1 = new ServiceManagedVolume(stack, 'EBSVolume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + size: cdk.Size.gibibytes(15), + }, + }); + const vol2 = new ServiceManagedVolume(stack, 'ebs1', { + name: 'ebs1', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + size: cdk.Size.gibibytes(15), + }, + }); + service.addVolume(vol1); + service.addVolume(vol2); + expect(() => { + app.synth(); + }).toThrow(/Only one EBS volume can be specified for 'volumeConfigurations', got: 2/); + }); + + test('create a default ebsrole when not provided', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + size: cdk.Size.gibibytes(20), + fileSystemType: ecs.FileSystemType.XFS, + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, + }], + }, + })); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + VolumeConfigurations: [ + { + ManagedEBSVolume: { + RoleArn: { 'Fn::GetAtt': ['EBSVolumeEBSRoleD38B9F31', 'Arn'] }, + SizeInGiB: 20, + FilesystemType: 'xfs', + TagSpecifications: [ + { + PropagateTags: 'SERVICE', + ResourceType: 'volume', + Tags: [ + { + Key: 'purpose', + Value: 'production', + }, + ], + }, + ], + }, + Name: 'nginx-vol', + }, + ], + }); + }); + + test('throw an error when both size and snapshotId are not provided', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBSVolume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + }, + })); + }).toThrow("'size' or 'snapShotId' must be specified"); + }); + + test('throw an error snapshot does not match pattern', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + snapShotId: 'snap-0d48decab5c493eee_', + }, + })); + }).toThrow("'snapshotId' does match expected pattern. Expected 'snap-' (ex: 'snap-05abe246af') or Token, got: snap-0d48decab5c493eee_"); + }); + + test('success when snapshotId matches the pattern', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + const vol = new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + snapShotId: 'snap-0d48decab5c493eee', + }, + }); + service.addVolume(vol); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + VolumeConfigurations: [ + { + ManagedEBSVolume: { + RoleArn: { 'Fn::GetAtt': ['EBSVolumeEBSRoleD38B9F31', 'Arn'] }, + SnapshotId: 'snap-0d48decab5c493eee', + FilesystemType: 'xfs', + }, + Name: 'nginx-vol', + }, + ], + }); + }); + + test('throw an error when size is greater than 16384 for gp2', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + size: cdk.Size.gibibytes(16390), + }, + })); + }).toThrow(/'gp2' volumes must have a size between 1 and 16384 GiB, got 16390 GiB/); + }); + + test('throw an error when size is less than 4 for volume type io1', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + volumeType: ec2.EbsDeviceVolumeType.IO1, + size: cdk.Size.gibibytes(0), + }, + })); + }).toThrow(/'io1' volumes must have a size between 4 and 16384 GiB, got 0 GiB/); + }); + test('throw an error when size is greater than 1024 for volume type standard', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + volumeType: ec2.EbsDeviceVolumeType.STANDARD, + size: cdk.Size.gibibytes(1500), + }, + })); + }).toThrow(/'standard' volumes must have a size between 1 and 1024 GiB, got 1500 GiB/); + }); + + test('throw an error if throughput is configured for volumetype gp2', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + size: cdk.Size.gibibytes(10), + throughput: 0, + }, + })); + }).toThrow(/'throughput' can only be configured with gp3 volume type, got gp2/); + }); + + test('throw an error if throughput is greater tahn 1000 for volume type gp3', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + size: cdk.Size.gibibytes(10), + throughput: 10001, + }, + })); + }).toThrow("'throughput' must be less than or equal to 1000 MiB/s, got 10001 MiB/s"); + }); + + test('throw an error if throughput is greater tahn 1000 for volume type gp3', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + size: cdk.Size.gibibytes(10), + throughput: 10001, + }, + })); + }).toThrow("'throughput' must be less than or equal to 1000 MiB/s, got 10001 MiB/s"); + }); + + test('throw an error if iops is not supported for volume type sc1', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBSVolume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + volumeType: ec2.EbsDeviceVolumeType.SC1, + size: cdk.Size.gibibytes(125), + iops: 0, + }, + })); + }).toThrow(/'iops' cannot be specified with sc1, st1, gp2 and standard volume types, got sc1/); + }); + + test('throw an error if iops is not supported for volume type sc1', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBSVolume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + size: cdk.Size.gibibytes(125), + iops: 0, + }, + })); + }).toThrow(/'iops' cannot be specified with sc1, st1, gp2 and standard volume types, got gp2/); + }); + + test('throw an error if if iops is required but not provided for volume type io2', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBSVolume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + volumeType: ec2.EbsDeviceVolumeType.IO2, + size: cdk.Size.gibibytes(125), + }, + })); + }).toThrow(/'iops' must be specified with io1 or io2 volume types, got io2/); + }); + + test('throw an error if if iops is less than 100 for volume type io2', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBSVolume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + volumeType: ec2.EbsDeviceVolumeType.IO2, + size: cdk.Size.gibibytes(125), + iops: 0, + }, + })); + }).toThrow("io2' volumes must have 'iops' between 100 and 256000, got 0"); + }); + + test('throw an error if if iops is greater than 256000 for volume type io2', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + expect(() => { + service.addVolume(new ServiceManagedVolume(stack, 'EBSVolume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + volumeType: ec2.EbsDeviceVolumeType.IO2, + size: cdk.Size.gibibytes(125), + iops: 256001, + }, + })); + }).toThrow("io2' volumes must have 'iops' between 100 and 256000, got 256001"); + }); + + test('success adding gp3 volume with throughput 0', ()=> { + // WHEN + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + + service.addVolume(new ServiceManagedVolume(stack, 'EBSVolume', { + name: 'nginx-vol', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + size: cdk.Size.gibibytes(15), + throughput: 0, + }, + })); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + VolumeConfigurations: [ + { + ManagedEBSVolume: { + RoleArn: { 'Fn::GetAtt': ['EBSVolumeEBSRoleC27DD941', 'Arn'] }, + SizeInGiB: 15, + FilesystemType: 'xfs', + VolumeType: 'gp3', + Throughput: 0, + }, + Name: 'nginx-vol', + }, + ], + }); + }); + }); + describe('When setting up a health check', () => { test('grace period is respected', () => { // GIVEN diff --git a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-task-definition.test.ts b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-task-definition.test.ts index eb1c280ec69c8..df7beb1b3fec8 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-task-definition.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-task-definition.test.ts @@ -162,6 +162,45 @@ describe('fargate task definition', () => { // THEN }); }); + describe('When configuredAtLaunch in the Volume', ()=> { + test('do not throw when configuredAtLaunch is false', () => { + // GIVEN + const stack = new cdk.Stack(); + + // THEN + expect(() => { + const taskDefinition =new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + taskDefinition.addVolume({ + name: 'nginx-vol', + efsVolumeConfiguration: { + fileSystemId: 'fs-1234', + }, + }); + taskDefinition.addVolume({ + name: 'nginx-vol1', + efsVolumeConfiguration: { + fileSystemId: 'fs-456', + }, + }); + }); + }); + test('throws when other volume configuration set with configuredAtLaunch', () => { + // GIVEN + const stack = new cdk.Stack(); + + // THEN + expect(() => { + const taskDefinition =new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + taskDefinition.addVolume({ + name: 'nginx-vol', + configuredAtLaunch: true, + efsVolumeConfiguration: { + fileSystemId: 'fs-1234', + }, + }); + }).toThrow(/Volume Configurations must not be specified for 'nginx-vol' when 'configuredAtLaunch' is set to true/); + }); + }); describe('When importing from an existing Fargate TaskDefinition', () => { test('can succeed using TaskDefinition Arn', () => { diff --git a/packages/aws-cdk-lib/aws-ecs/test/task-definition.test.ts b/packages/aws-cdk-lib/aws-ecs/test/task-definition.test.ts index 2b4bad0b5d5a1..f098b3e89afcb 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/task-definition.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/task-definition.test.ts @@ -1,8 +1,10 @@ import { Template } from '../../assertions'; +import { EbsDeviceVolumeType } from '../../aws-ec2'; import * as ecr from '../../aws-ecr'; import * as iam from '../../aws-iam'; import * as cdk from '../../core'; import * as ecs from '../lib'; +import { ServiceManagedVolume } from '../lib/base/service-managed-volume'; describe('task definition', () => { describe('When creating a new TaskDefinition', () => { @@ -230,6 +232,146 @@ describe('task definition', () => { }).toThrow("Port mapping name 'api' cannot appear in both 'Container2' and 'Container'"); }); + test('throws when multiple runtime volumes are set', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition =new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + const container = taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + container.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol', + }); + const container1 = taskDefinition.addContainer('front', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + container1.addMountPoints({ + containerPath: '/var/lib', + readOnly: false, + sourceVolume: 'nginx-vol1', + }); + taskDefinition.addVolume({ + name: 'nginx-vol', + configuredAtLaunch: true, + }); + taskDefinition.addVolume({ + name: 'nginx-vol1', + configuredAtLaunch: true, + }); + + // THEN + expect(() => { + Template.fromStack(stack); + }).toThrow('More than one volume is configured at launch: [nginx-vol,nginx-vol1]'); + }); + + test('throws when none of the container mounts the volume', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition =new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + taskDefinition.addVolume({ + name: 'nginx-vol', + configuredAtLaunch: true, + }); + + // THEN + expect(() => { + Template.fromStack(stack); + }).toThrow(/Volume 'nginx-vol' should be mounted by at least one container when 'configuredAtLaunch' is true/); + }); + + test('throws when none of the container mount the volume using ServiceManagedVolume', () => { + // GIVEN + const stack = new cdk.Stack(); + const ebsRole = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), + }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + taskDefinition.addContainer('db', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + const serviceManagedVolume = new ServiceManagedVolume(stack, 'EBS Volume', { + name: 'nginx-vol', + managedEBSVolume: { + role: ebsRole, + size: cdk.Size.gibibytes(3), + volumeType: EbsDeviceVolumeType.GP3, + fileSystemType: ecs.FileSystemType.XFS, + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, + }], + }, + }); + taskDefinition.addVolume(serviceManagedVolume); + + // THEN + expect(() => { + Template.fromStack(stack); + }).toThrow(/Volume 'nginx-vol' should be mounted by at least one container when 'configuredAtLaunch' is true/); + }); + + test('throws when multiple runtime volumes are set using ServiceManagedVolume', () => { + // GIVEN + const stack = new cdk.Stack(); + const ebsRole = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), + }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + const containerDef = taskDefinition.addContainer('db', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + const volume1 = new ServiceManagedVolume(stack, 'EBS Volume1', { + name: 'nginx-vol', + managedEBSVolume: { + role: ebsRole, + size: cdk.Size.gibibytes(3), + volumeType: EbsDeviceVolumeType.GP3, + fileSystemType: ecs.FileSystemType.XFS, + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, + }], + }, + }); + volume1.mountIn(containerDef, { + readOnly: false, + containerPath: 'var/lib', + }); + taskDefinition.addVolume(volume1); + const volume2 = new ServiceManagedVolume(stack, 'EBS Volume2', { + name: 'nginx-vol1', + managedEBSVolume: { + role: ebsRole, + size: cdk.Size.gibibytes(3), + volumeType: EbsDeviceVolumeType.GP3, + fileSystemType: ecs.FileSystemType.XFS, + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, + }], + }, + }); + volume2.mountIn(containerDef, { + readOnly: false, + containerPath: 'var/lib', + }); + taskDefinition.addVolume(volume2); + + // THEN + expect(() => { + Template.fromStack(stack); + }).toThrow('More than one volume is configured at launch: [nginx-vol,nginx-vol1]'); + }); + test('You can specify a container ulimits using the dedicated property in ContainerDefinitionOptions', () => { // GIVEN const stack = new cdk.Stack();