From 870fed5a78f55248022a76b3cf660800b4e69bcc Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Fri, 12 Jan 2024 06:28:48 -0800 Subject: [PATCH 1/9] ebs task attach L2 --- ...efaultTestDeployAssertF52EF4F9.assets.json | 19 + ...aultTestDeployAssertF52EF4F9.template.json | 36 + .../integ.ebs-taskattach.js.snapshot/cdk.out | 1 + .../integ-aws-ecs-ebs-task-attach.assets.json | 19 + ...nteg-aws-ecs-ebs-task-attach.template.json | 436 ++++++++++ .../integ.json | 12 + .../manifest.json | 221 +++++ .../tree.json | 769 ++++++++++++++++++ .../test/fargate/integ.ebs-taskattach.ts | 73 ++ packages/aws-cdk-lib/aws-ecs/README.md | 91 +++ .../aws-ecs/lib/base/base-service.ts | 59 ++ .../lib/base/service-managed-volume.ts | 190 +++++ .../aws-ecs/lib/base/task-definition.ts | 38 + .../aws-ecs/lib/container-definition.ts | 10 +- packages/aws-cdk-lib/aws-ecs/lib/index.ts | 1 + .../test/fargate/fargate-service.test.ts | 143 ++++ .../fargate/fargate-task-definition.test.ts | 39 + .../aws-ecs/test/task-definition.test.ts | 142 ++++ 18 files changed, 2297 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/EBSTaskAttachDefaultTestDeployAssertF52EF4F9.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/EBSTaskAttachDefaultTestDeployAssertF52EF4F9.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ-aws-ecs-ebs-task-attach.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ-aws-ecs-ebs-task-attach.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/tree.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.ts create mode 100644 packages/aws-cdk-lib/aws-ecs/lib/base/service-managed-volume.ts 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..ee28782a6bdc0 --- /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": { + "d5b503c183b8942f8aa867359c9075a4422b4890aa0a7bb19c100e5439bf07d1": { + "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": "d5b503c183b8942f8aa867359c9075a4422b4890aa0a7bb19c100e5439bf07d1.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..093e52155dfba --- /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,436 @@ +{ + "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" + } + } + }, + "EBSRole9CBFC881": { + "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/AmazonEC2FullAccess" + ] + ] + } + ] + } + }, + "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" + } + ] + } + }, + "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": { + "RoleArn": { + "Fn::GetAtt": [ + "EBSRole9CBFC881", + "Arn" + ] + }, + "SizeInGiB": 20, + "TagSpecifications": [ + { + "PropagateTags": "SERVICE", + "ResourceType": "volume", + "Tags": [ + { + "Key": "purpose", + "Value": "production" + } + ] + } + ] + }, + "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..4674a88d322d1 --- /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}/d5b503c183b8942f8aa867359c9075a4422b4890aa0a7bb19c100e5439bf07d1.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/EBSRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "EBSRole9CBFC881" + } + ], + "/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/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..496ffb5feaa1b --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.js.snapshot/tree.json @@ -0,0 +1,769 @@ +{ + "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" + } + }, + "EBSRole": { + "id": "EBSRole", + "path": "integ-aws-ecs-ebs-task-attach/EBSRole", + "children": { + "ImportEBSRole": { + "id": "ImportEBSRole", + "path": "integ-aws-ecs-ebs-task-attach/EBSRole/ImportEBSRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-aws-ecs-ebs-task-attach/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/AmazonEC2FullAccess" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "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" + } + }, + "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": [ + "EBSRole9CBFC881", + "Arn" + ] + }, + "sizeInGiB": 20, + "tagSpecifications": [ + { + "resourceType": "volume", + "propagateTags": "SERVICE", + "tags": [ + { + "key": "purpose", + "value": "production" + } + ] + } + ] + } + } + ] + } + }, + "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..b033e0285c8c5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/fargate/integ.ebs-taskattach.ts @@ -0,0 +1,73 @@ +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 iam from 'aws-cdk-lib/aws-iam'; +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 ebsRole = new iam.Role(this, 'EBSRole', { + assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2FullAccess')], + }); + + 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({ + name: 'ebs1', + managedEBSVolume: { + role: ebsRole, + sizeInGiB: 20, + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.PropagatedTagSource.SERVICE, + }], + }, + }); + + 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(); \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index b120df0bf0049..c782cc739585c 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -1631,6 +1631,97 @@ 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 ebsRole = new iam.Role(this, 'EBSRole', { + assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2FullAccess')], +}); + +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({ + name: 'ebs1', + managedEBSVolume: { + role: ebsRole, + sizeInGiB: 10, + volumeType: ec2.EbsDeviceVolumeType.GP3, + fileSystemType: 'xfs', + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.PropagatedTagSource.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; +declare const ebsRole: iam.IRole; + +const volumeFromSnapshot = new ecs.ServiceManagedVolume({ + name: 'nginx-vol', + managedEBSVolume: { + role: ebsRole, + snapShotId: 'snap-066877671789bd71b', + volumeType: ec2.EbsDeviceVolumeType.GP3, + 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..2e0f47d97784c 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,46 @@ 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[] { + 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.config.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.sizeInGiB, + 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..f8826b5a23c68 --- /dev/null +++ b/packages/aws-cdk-lib/aws-ecs/lib/base/service-managed-volume.ts @@ -0,0 +1,190 @@ +import { PropagatedTagSource } from './base-service'; +import * as ec2 from '../../../aws-ec2'; +import * as iam from '../../../aws-iam'; +import * as kms from '../../../aws-kms'; +import { BaseMountPoint, ContainerDefinition } from '../container-definition'; + +/** +* Represents the Volume configuration for an ECS service. +*/ +export interface ServiceVolumeProps { + /** + * 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. + */ + 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 `sizeInGiB` 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 sizeInGiB?: number; + + /** + * The snapshot that Amazon ECS uses to create the volume. + * + * You must specify either `sizeInGiB` 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 - xfs. + */ + readonly fileSystemType?: string; + + /** + * 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 none + */ + 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?: PropagatedTagSource.SERVICE | PropagatedTagSource.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 { + /** + * 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; + + constructor(props: ServiceVolumeProps) { + this.name = props.name; + this.config = props.managedEBSVolume; + } + + /** + * 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, + }); + } +} \ No newline at end of file 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..3498ef8b30a32 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,147 @@ 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; + + beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); + 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({ + name: 'nginx-vol', + managedEBSVolume: { + role: role, + sizeInGiB: 20, + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.PropagatedTagSource.SERVICE, + }], + }, + })); + + // 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', + }, + ], + }); + }); + + test('success when mounting via ServiceManagedVolume', () => { + // WHEN + const volume = new ServiceManagedVolume({ + name: 'nginx-vol', + managedEBSVolume: { + role: role, + sizeInGiB: 20, + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.PropagatedTagSource.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, + }, + ], + }); + }); + }); + 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..339811589c652 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({ + name: 'nginx-vol', + managedEBSVolume: { + role: ebsRole, + sizeInGiB: 3, + volumeType: EbsDeviceVolumeType.GP3, + fileSystemType: 'xfs', + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.PropagatedTagSource.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({ + name: 'nginx-vol', + managedEBSVolume: { + role: ebsRole, + sizeInGiB: 3, + volumeType: EbsDeviceVolumeType.GP3, + fileSystemType: 'xfs', + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.PropagatedTagSource.SERVICE, + }], + }, + }); + volume1.mountIn(containerDef, { + readOnly: false, + containerPath: 'var/lib', + }); + taskDefinition.addVolume(volume1); + const volume2 = new ServiceManagedVolume({ + name: 'nginx-vol1', + managedEBSVolume: { + role: ebsRole, + sizeInGiB: 3, + volumeType: EbsDeviceVolumeType.GP3, + fileSystemType: 'xfs', + tagSpecifications: [{ + tags: { + purpose: 'production', + }, + propagateTags: ecs.PropagatedTagSource.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(); From 46e22842deb50f3166f4e5d685e711e8930511dc Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Fri, 12 Jan 2024 08:20:42 -0800 Subject: [PATCH 2/9] update integ test --- .../integ-aws-ecs-ebs-task-attach.assets.json | 4 ++-- ...nteg-aws-ecs-ebs-task-attach.template.json | 19 +++++++++++++++++-- .../manifest.json | 2 +- .../tree.json | 17 ++++++++++++++++- .../test/fargate/integ.ebs-taskattach.ts | 15 +++++++++++++-- 5 files changed, 49 insertions(+), 8 deletions(-) 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 index ee28782a6bdc0..8791ecefcba90 100644 --- 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 @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "d5b503c183b8942f8aa867359c9075a4422b4890aa0a7bb19c100e5439bf07d1": { + "fdd06654e90c1ee28a4e562b297f23152c4973e0a4d0699c38d020df6a14ba22": { "source": { "path": "integ-aws-ecs-ebs-task-attach.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "d5b503c183b8942f8aa867359c9075a4422b4890aa0a7bb19c100e5439bf07d1.json", + "objectKey": "fdd06654e90c1ee28a4e562b297f23152c4973e0a4d0699c38d020df6a14ba22.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } 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 index 093e52155dfba..887cafa2d2062 100644 --- 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 @@ -351,13 +351,16 @@ "VolumeConfigurations": [ { "ManagedEBSVolume": { + "Encrypted": true, + "FilesystemType": "ext4", + "Iops": 4000, "RoleArn": { "Fn::GetAtt": [ "EBSRole9CBFC881", "Arn" ] }, - "SizeInGiB": 20, + "SizeInGiB": 15, "TagSpecifications": [ { "PropagateTags": "SERVICE", @@ -368,8 +371,20 @@ "Value": "production" } ] + }, + { + "PropagateTags": "TASK_DEFINITION", + "ResourceType": "volume", + "Tags": [ + { + "Key": "purpose", + "Value": "development" + } + ] } - ] + ], + "Throughput": 500, + "VolumeType": "gp3" }, "Name": "ebs1" } 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 index 4674a88d322d1..8cd435a5696f5 100644 --- 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 @@ -18,7 +18,7 @@ "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}/d5b503c183b8942f8aa867359c9075a4422b4890aa0a7bb19c100e5439bf07d1.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/fdd06654e90c1ee28a4e562b297f23152c4973e0a4d0699c38d020df6a14ba22.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ 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 index 496ffb5feaa1b..14e3b9fcd4b4d 100644 --- 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 @@ -613,7 +613,12 @@ "Arn" ] }, - "sizeInGiB": 20, + "encrypted": true, + "filesystemType": "ext4", + "iops": 4000, + "throughput": 500, + "volumeType": "gp3", + "sizeInGiB": 15, "tagSpecifications": [ { "resourceType": "volume", @@ -624,6 +629,16 @@ "value": "production" } ] + }, + { + "resourceType": "volume", + "propagateTags": "TASK_DEFINITION", + "tags": [ + { + "key": "purpose", + "value": "development" + } + ] } ] } 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 index b033e0285c8c5..d6b479b89e434 100644 --- 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 @@ -37,12 +37,23 @@ class TestStack extends cdk.Stack { name: 'ebs1', managedEBSVolume: { role: ebsRole, - sizeInGiB: 20, + encrypted: true, + volumeType: ec2.EbsDeviceVolumeType.GP3, + sizeInGiB: 15, + iops: 4000, + throughput: 500, + fileSystemType: 'ext4', tagSpecifications: [{ tags: { purpose: 'production', }, propagateTags: ecs.PropagatedTagSource.SERVICE, + }, + { + tags: { + purpose: 'development', + }, + propagateTags: ecs.PropagatedTagSource.TASK_DEFINITION, }], }, }); @@ -70,4 +81,4 @@ const stack = new TestStack(app, 'integ-aws-ecs-ebs-task-attach'); new integ.IntegTest(app, 'EBSTaskAttach', { testCases: [stack], }); -app.synth(); \ No newline at end of file +app.synth(); From 9c343d2822c5ce72193642116fe7897f8d9022b8 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Fri, 12 Jan 2024 09:34:07 -0800 Subject: [PATCH 3/9] use enum for FileSystemType Co-authored-by: go-to-k <24818752+go-to-k@users.noreply.github.com> --- .../test/fargate/integ.ebs-taskattach.ts | 2 +- packages/aws-cdk-lib/aws-ecs/README.md | 4 ++-- .../lib/base/service-managed-volume.ts | 22 +++++++++++++++++-- .../test/fargate/fargate-service.test.ts | 2 ++ .../aws-ecs/test/task-definition.test.ts | 6 ++--- 5 files changed, 28 insertions(+), 8 deletions(-) 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 index d6b479b89e434..4aa239b5036d7 100644 --- 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 @@ -42,7 +42,7 @@ class TestStack extends cdk.Stack { sizeInGiB: 15, iops: 4000, throughput: 500, - fileSystemType: 'ext4', + fileSystemType: ecs.FileSystemType.EXT4, tagSpecifications: [{ tags: { purpose: 'production', diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index c782cc739585c..7d58fde98152a 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -1666,7 +1666,7 @@ const volume = new ecs.ServiceManagedVolume({ role: ebsRole, sizeInGiB: 10, volumeType: ec2.EbsDeviceVolumeType.GP3, - fileSystemType: 'xfs', + fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ tags: { purpose: 'production', @@ -1705,7 +1705,7 @@ const volumeFromSnapshot = new ecs.ServiceManagedVolume({ role: ebsRole, snapShotId: 'snap-066877671789bd71b', volumeType: ec2.EbsDeviceVolumeType.GP3, - fileSystemType: 'xfs', + fileSystemType: ecs.FileSystemType.XFS, }, }); 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 index f8826b5a23c68..984b945c9d1a7 100644 --- 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 @@ -114,9 +114,9 @@ export interface ServiceManagedEBSVolumeConfiguration { * the volume was using when the snapshot was created. * The available filesystem types are ext3, ext4, and xfs. * - * @default - xfs. + * @default - FileSystemType.XFS */ - readonly fileSystemType?: string; + readonly fileSystemType?: FileSystemType; /** * Specifies the tags to apply to the volume and whether to propagate those tags to the volume. @@ -146,6 +146,24 @@ export interface EBSTagSpecification { readonly propagateTags?: PropagatedTagSource.SERVICE | PropagatedTagSource.TASK_DEFINITION; } +/** + * FileSystemType for Service Managed EBS Volume Configuration. + */ +export enum FileSystemType { + /** + * ext3 type + */ + EXT3 = 'ext3', + /** + * ext4 type + */ + EXT4 = 'ext4', + /** + * xfs type + */ + XFS = 'xfs', +} + /** * Defines the mount point details for attaching a volume to a container. */ 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 3498ef8b30a32..097942226d358 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 @@ -1493,6 +1493,7 @@ describe('fargate service', () => { managedEBSVolume: { role: role, sizeInGiB: 20, + fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ tags: { purpose: 'production', @@ -1509,6 +1510,7 @@ describe('fargate service', () => { ManagedEBSVolume: { RoleArn: { 'Fn::GetAtt': ['Role1ABCC5F0', 'Arn'] }, SizeInGiB: 20, + FilesystemType: 'xfs', TagSpecifications: [ { PropagateTags: 'SERVICE', 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 339811589c652..d0084b0640371 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 @@ -298,7 +298,7 @@ describe('task definition', () => { role: ebsRole, sizeInGiB: 3, volumeType: EbsDeviceVolumeType.GP3, - fileSystemType: 'xfs', + fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ tags: { purpose: 'production', @@ -331,7 +331,7 @@ describe('task definition', () => { role: ebsRole, sizeInGiB: 3, volumeType: EbsDeviceVolumeType.GP3, - fileSystemType: 'xfs', + fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ tags: { purpose: 'production', @@ -351,7 +351,7 @@ describe('task definition', () => { role: ebsRole, sizeInGiB: 3, volumeType: EbsDeviceVolumeType.GP3, - fileSystemType: 'xfs', + fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ tags: { purpose: 'production', From 19d47fe1c951ef46979382db81333c55cbccb31a Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Fri, 12 Jan 2024 20:43:00 -0800 Subject: [PATCH 4/9] use enum for propagateTags add validations Co-authored-by: go-to-k <24818752+go-to-k@users.noreply.github.com> --- .../integ-aws-ecs-ebs-task-attach.assets.json | 4 +- ...nteg-aws-ecs-ebs-task-attach.template.json | 64 +-- .../manifest.json | 14 +- .../tree.json | 126 +++--- .../test/fargate/integ.ebs-taskattach.ts | 13 +- packages/aws-cdk-lib/aws-ecs/README.md | 14 +- .../aws-ecs/lib/base/base-service.ts | 6 +- .../lib/base/service-managed-volume.ts | 120 ++++- .../test/fargate/fargate-service.test.ts | 410 +++++++++++++++++- .../aws-ecs/test/task-definition.test.ts | 12 +- 10 files changed, 643 insertions(+), 140 deletions(-) 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 index 8791ecefcba90..95bfe458f82d1 100644 --- 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 @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "fdd06654e90c1ee28a4e562b297f23152c4973e0a4d0699c38d020df6a14ba22": { + "114105e2d29626a5303e48c5ab4b3be1059592f803b1d317c99665664cd954a9": { "source": { "path": "integ-aws-ecs-ebs-task-attach.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "fdd06654e90c1ee28a4e562b297f23152c4973e0a4d0699c38d020df6a14ba22.json", + "objectKey": "114105e2d29626a5303e48c5ab4b3be1059592f803b1d317c99665664cd954a9.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } 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 index 887cafa2d2062..bac09e2c34fec 100644 --- 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 @@ -214,37 +214,6 @@ } } }, - "EBSRole9CBFC881": { - "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/AmazonEC2FullAccess" - ] - ] - } - ] - } - }, "FargateCluster7CCD5F93": { "Type": "AWS::ECS::Cluster" }, @@ -309,6 +278,37 @@ ] } }, + "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": { @@ -356,7 +356,7 @@ "Iops": 4000, "RoleArn": { "Fn::GetAtt": [ - "EBSRole9CBFC881", + "EBSVolumeEBSRoleC27DD941", "Arn" ] }, 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 index 8cd435a5696f5..5c7609dc81843 100644 --- 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 @@ -18,7 +18,7 @@ "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}/fdd06654e90c1ee28a4e562b297f23152c4973e0a4d0699c38d020df6a14ba22.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/114105e2d29626a5303e48c5ab4b3be1059592f803b1d317c99665664cd954a9.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -112,12 +112,6 @@ "data": "VpcVPCGWBF912B6E" } ], - "/integ-aws-ecs-ebs-task-attach/EBSRole/Resource": [ - { - "type": "aws:cdk:logicalId", - "data": "EBSRole9CBFC881" - } - ], "/integ-aws-ecs-ebs-task-attach/FargateCluster/Resource": [ { "type": "aws:cdk:logicalId", @@ -136,6 +130,12 @@ "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", 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 index 14e3b9fcd4b4d..896bcffef46a0 100644 --- 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 @@ -365,63 +365,6 @@ "version": "0.0.0" } }, - "EBSRole": { - "id": "EBSRole", - "path": "integ-aws-ecs-ebs-task-attach/EBSRole", - "children": { - "ImportEBSRole": { - "id": "ImportEBSRole", - "path": "integ-aws-ecs-ebs-task-attach/EBSRole/ImportEBSRole", - "constructInfo": { - "fqn": "aws-cdk-lib.Resource", - "version": "0.0.0" - } - }, - "Resource": { - "id": "Resource", - "path": "integ-aws-ecs-ebs-task-attach/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/AmazonEC2FullAccess" - ] - ] - } - ] - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_iam.CfnRole", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_iam.Role", - "version": "0.0.0" - } - }, "FargateCluster": { "id": "FargateCluster", "path": "integ-aws-ecs-ebs-task-attach/FargateCluster", @@ -557,6 +500,73 @@ "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", @@ -609,7 +619,7 @@ "managedEbsVolume": { "roleArn": { "Fn::GetAtt": [ - "EBSRole9CBFC881", + "EBSVolumeEBSRoleC27DD941", "Arn" ] }, 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 index 4aa239b5036d7..dae2941e224a1 100644 --- 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 @@ -1,7 +1,6 @@ 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 iam from 'aws-cdk-lib/aws-iam'; import * as integ from '@aws-cdk/integ-tests-alpha'; import { Construct } from 'constructs'; @@ -14,11 +13,6 @@ class TestStack extends cdk.Stack { restrictDefaultSecurityGroup: false, }); - const ebsRole = new iam.Role(this, 'EBSRole', { - assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), - managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2FullAccess')], - }); - const cluster = new ecs.Cluster(this, 'FargateCluster', { vpc, }); @@ -33,10 +27,9 @@ class TestStack extends cdk.Stack { }], }); - const volume = new ecs.ServiceManagedVolume({ + const volume = new ecs.ServiceManagedVolume(this, 'EBSVolume', { name: 'ebs1', managedEBSVolume: { - role: ebsRole, encrypted: true, volumeType: ec2.EbsDeviceVolumeType.GP3, sizeInGiB: 15, @@ -47,13 +40,13 @@ class TestStack extends cdk.Stack { tags: { purpose: 'production', }, - propagateTags: ecs.PropagatedTagSource.SERVICE, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, }, { tags: { purpose: 'development', }, - propagateTags: ecs.PropagatedTagSource.TASK_DEFINITION, + propagateTags: ecs.EbsPropagatedTagSource.TASK_DEFINITION, }], }, }); diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index 7d58fde98152a..8f145e6e7f445 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -1647,11 +1647,6 @@ To add an empty EBS Volume to an ECS Service, call service.addVolume(). declare const cluster: ecs.Cluster; const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef'); -const ebsRole = new iam.Role(this, 'EBSRole', { - assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), - managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2FullAccess')], -}); - const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), portMappings: [{ @@ -1660,10 +1655,9 @@ const container = taskDefinition.addContainer('web', { }], }); -const volume = new ecs.ServiceManagedVolume({ +const volume = new ecs.ServiceManagedVolume(this, 'EBSVolume', { name: 'ebs1', managedEBSVolume: { - role: ebsRole, sizeInGiB: 10, volumeType: ec2.EbsDeviceVolumeType.GP3, fileSystemType: ecs.FileSystemType.XFS, @@ -1671,7 +1665,7 @@ const volume = new ecs.ServiceManagedVolume({ tags: { purpose: 'production', }, - propagateTags: ecs.PropagatedTagSource.SERVICE, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, }], }, }); @@ -1697,12 +1691,10 @@ To create an EBS volume from an existing snapshot by specifying the `snapShotId` declare const container: ecs.ContainerDefinition; declare const cluster: ecs.Cluster; declare const taskDefinition: ecs.TaskDefinition; -declare const ebsRole: iam.IRole; -const volumeFromSnapshot = new ecs.ServiceManagedVolume({ +const volumeFromSnapshot = new ecs.ServiceManagedVolume(this, 'EBSVolume', { name: 'nginx-vol', managedEBSVolume: { - role: ebsRole, snapShotId: 'snap-066877671789bd71b', volumeType: ec2.EbsDeviceVolumeType.GP3, fileSystemType: ecs.FileSystemType.XFS, 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 2e0f47d97784c..e0fc64cf5ecfa 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 @@ -744,6 +744,10 @@ export abstract class BaseService extends Resource * Adds a volume to the Service. */ public addVolume(volume: ServiceManagedVolume) { + // only one ServiceVolumeConfiguration can be specified. + if (this.volumes.length >= 1) { + throw new Error('Invalid VolumeConfiguration. Only one volume can be configured at launch.'); + } this.volumes.push(volume); } @@ -765,7 +769,7 @@ export abstract class BaseService extends Resource return { name: spec.name, managedEbsVolume: spec.config && { - roleArn: spec.config.role.roleArn, + roleArn: spec.role.roleArn, encrypted: spec.config.encrypted, filesystemType: spec.config.fileSystemType, iops: spec.config.iops, 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 index 984b945c9d1a7..c79394d97b9ac 100644 --- 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 @@ -1,13 +1,14 @@ -import { PropagatedTagSource } from './base-service'; +import { Construct } from 'constructs'; import * as ec2 from '../../../aws-ec2'; import * as iam from '../../../aws-iam'; import * as kms from '../../../aws-kms'; +import { Token } from '../../../core'; import { BaseMountPoint, ContainerDefinition } from '../container-definition'; /** * Represents the Volume configuration for an ECS service. */ -export interface ServiceVolumeProps { +export interface ServiceManagedVolumeProps { /** * The name of the volume. This corresponds to the name provided in the ECS TaskDefinition. */ @@ -28,8 +29,10 @@ 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; + readonly role?: iam.IRole; /** * Indicates whether the volume should be encrypted. @@ -133,7 +136,7 @@ export interface EBSTagSpecification { /** * The tags to apply to the volume. * - * @default none + * @default - No tags */ readonly tags?: {[key: string]: string}; @@ -143,7 +146,7 @@ export interface EBSTagSpecification { * * @default - undefined */ - readonly propagateTags?: PropagatedTagSource.SERVICE | PropagatedTagSource.TASK_DEFINITION; + readonly propagateTags?: EbsPropagatedTagSource; } /** @@ -164,6 +167,20 @@ export enum FileSystemType { 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. */ @@ -173,7 +190,7 @@ export interface ContainerMountPoint extends BaseMountPoint { /** * Represents a service-managed volume and always configured at launch. */ -export class ServiceManagedVolume { +export class ServiceManagedVolume extends Construct { /** * Name of the volume, referenced by taskdefintion and mount point. */ @@ -189,9 +206,26 @@ export class ServiceManagedVolume { */ public readonly configuredAtLaunch: boolean = true; - constructor(props: ServiceVolumeProps) { + /** + * 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.validateVolumeConfiguration(props.managedEBSVolume); this.name = props.name; - this.config = props.managedEBSVolume; + 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, + }; } /** @@ -205,4 +239,72 @@ export class ServiceManagedVolume { ...mountPoint, }); } -} \ No newline at end of file + + private validateVolumeConfiguration(volumeConfig?: ServiceManagedEBSVolumeConfiguration) { + if (!volumeConfig) return; + + const { volumeType = ec2.EbsDeviceVolumeType.GP2, iops, sizeInGiB, throughput, snapShotId } = volumeConfig; + + // Validate if both sizeInGiB and snapShotId are not specified. + if (sizeInGiB === undefined && snapShotId === undefined) { + throw new Error('sizeInGiB 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'); + } + + // 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 sizeInGiB ranges. + if (sizeInGiB !== undefined) { + const { minSize, maxSize } = sizeInGiBRanges[volumeType]; + if (sizeInGiB < minSize || sizeInGiB > maxSize) { + throw new Error(`'${volumeType}' volumes must have a size between ${minSize} and ${maxSize} GiB, got ${sizeInGiB} 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 (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/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-servicemanagedebsvolumeconfiguration.html#cfn-ecs-service-servicemanagedebsvolumeconfiguration-iops + 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 '${volumeType}' volume type`); + } + + // 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 '${volumeType}' volume type`); + } + + // 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) { + 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/test/fargate/fargate-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts index 097942226d358..d6ef732364a6d 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 @@ -1488,7 +1488,7 @@ describe('fargate service', () => { sourceVolume: 'nginx-vol', }); - service.addVolume(new ServiceManagedVolume({ + service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { name: 'nginx-vol', managedEBSVolume: { role: role, @@ -1498,7 +1498,7 @@ describe('fargate service', () => { tags: { purpose: 'production', }, - propagateTags: ecs.PropagatedTagSource.SERVICE, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, }], }, })); @@ -1532,7 +1532,7 @@ describe('fargate service', () => { test('success when mounting via ServiceManagedVolume', () => { // WHEN - const volume = new ServiceManagedVolume({ + const volume = new ServiceManagedVolume(stack, 'EBS Volume', { name: 'nginx-vol', managedEBSVolume: { role: role, @@ -1541,7 +1541,7 @@ describe('fargate service', () => { tags: { purpose: 'production', }, - propagateTags: ecs.PropagatedTagSource.SERVICE, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, }], }, }); @@ -1596,6 +1596,408 @@ describe('fargate service', () => { ], }); }); + + 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, + sizeInGiB: 15, + }, + }); + const vol2 = new ServiceManagedVolume(stack, 'ebs1', { + name: 'ebs1', + managedEBSVolume: { + fileSystemType: ecs.FileSystemType.XFS, + sizeInGiB: 15, + }, + }); + service.addVolume(vol1); + // Attempt to add a second volume and expect an error + expect(() => { + service.addVolume(vol2); + }).toThrow('Invalid VolumeConfiguration. Only one volume can be configured at launch.'); + }); + + 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: { + sizeInGiB: 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 sizeInGIB 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(/sizeInGiB 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'); + }); + + 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 sizeInGiB 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, + sizeInGiB: 16390, + }, + })); + }).toThrow(/'gp2' volumes must have a size between 1 and 16384 GiB, got 16390 GiB/); + }); + + test('throw an error when sizeInGiB 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, + sizeInGiB: 0, + }, + })); + }).toThrow(/'io1' volumes must have a size between 4 and 16384 GiB, got 0 GiB/); + }); + test('throw an error when sizeInGiB 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, + sizeInGiB: 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, + sizeInGiB: 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, + sizeInGiB: 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, + sizeInGiB: 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, + sizeInGiB: 125, + iops: 0, + }, + })); + }).toThrow("'iops' cannot be specified with 'sc1' volume type"); + }); + + 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, + sizeInGiB: 125, + iops: 0, + }, + })); + }).toThrow("'iops' cannot be specified with 'gp2' volume type"); + }); + + 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, + sizeInGiB: 125, + }, + })); + }).toThrow("'iops' must be specified with 'io2' volume type"); + }); + + 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, + sizeInGiB: 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, + sizeInGiB: 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, + sizeInGiB: 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', () => { 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 d0084b0640371..5bd3bfc63200e 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 @@ -292,7 +292,7 @@ describe('task definition', () => { taskDefinition.addContainer('db', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), }); - const serviceManagedVolume = new ServiceManagedVolume({ + const serviceManagedVolume = new ServiceManagedVolume(stack, 'EBS Volume', { name: 'nginx-vol', managedEBSVolume: { role: ebsRole, @@ -303,7 +303,7 @@ describe('task definition', () => { tags: { purpose: 'production', }, - propagateTags: ecs.PropagatedTagSource.SERVICE, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, }], }, }); @@ -325,7 +325,7 @@ describe('task definition', () => { const containerDef = taskDefinition.addContainer('db', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), }); - const volume1 = new ServiceManagedVolume({ + const volume1 = new ServiceManagedVolume(stack, 'EBS Volume1', { name: 'nginx-vol', managedEBSVolume: { role: ebsRole, @@ -336,7 +336,7 @@ describe('task definition', () => { tags: { purpose: 'production', }, - propagateTags: ecs.PropagatedTagSource.SERVICE, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, }], }, }); @@ -345,7 +345,7 @@ describe('task definition', () => { containerPath: 'var/lib', }); taskDefinition.addVolume(volume1); - const volume2 = new ServiceManagedVolume({ + const volume2 = new ServiceManagedVolume(stack, 'EBS Volume2', { name: 'nginx-vol1', managedEBSVolume: { role: ebsRole, @@ -356,7 +356,7 @@ describe('task definition', () => { tags: { purpose: 'production', }, - propagateTags: ecs.PropagatedTagSource.SERVICE, + propagateTags: ecs.EbsPropagatedTagSource.SERVICE, }], }, }); From 6904e2a3ea17402afab16f7316dfa4d08a8157b7 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Sun, 14 Jan 2024 12:41:02 -0800 Subject: [PATCH 5/9] address few nits Co-authored-by: go-to-k <24818752+go-to-k@users.noreply.github.com> --- .../aws-ecs/lib/base/base-service.ts | 8 +++----- .../lib/base/service-managed-volume.ts | 14 ++++++------- .../test/fargate/fargate-service.test.ts | 20 ++++++++++--------- 3 files changed, 21 insertions(+), 21 deletions(-) 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 e0fc64cf5ecfa..310d7c614bbac 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 @@ -744,16 +744,14 @@ export abstract class BaseService extends Resource * Adds a volume to the Service. */ public addVolume(volume: ServiceManagedVolume) { - // only one ServiceVolumeConfiguration can be specified. - if (this.volumes.length >= 1) { - throw new Error('Invalid VolumeConfiguration. Only one volume can be configured at launch.'); - } this.volumes.push(volume); } private renderVolumes(): CfnService.ServiceVolumeConfigurationProperty[] { + if (this.volumes.length > 1) { + throw new Error(`Invalid VolumeConfiguration. Only one volume can be configured at launch, got: ${this.volumes.length}`); + } return this.volumes.map(renderVolume); - function renderVolume(spec: ServiceManagedVolume): CfnService.ServiceVolumeConfigurationProperty { const tagSpecifications = spec.config?.tagSpecifications?.map(ebsTagSpec => { return { 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 index c79394d97b9ac..dd65e5d6d7bcb 100644 --- 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 @@ -247,11 +247,11 @@ export class ServiceManagedVolume extends Construct { // Validate if both sizeInGiB and snapShotId are not specified. if (sizeInGiB === undefined && snapShotId === undefined) { - throw new Error('sizeInGiB or snapShotId must be specified'); + throw new Error('\'sizeInGiB\' 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'); + 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 @@ -278,21 +278,21 @@ export class ServiceManagedVolume extends Construct { if (throughput !== undefined) { if (volumeType !== ec2.EbsDeviceVolumeType.GP3) { throw new Error(`'throughput' can only be configured with gp3 volume type, got ${volumeType}`); - } else if (throughput > 1000) { + } 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/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-servicemanagedebsvolumeconfiguration.html#cfn-ecs-service-servicemanagedebsvolumeconfiguration-iops + // 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 '${volumeType}' volume type`); + 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 '${volumeType}' volume type`); + throw new Error(`iops must be specified with io1 or io2 volume types, got ${volumeType}`); } // Validate IOPS range if specified. @@ -300,7 +300,7 @@ export class ServiceManagedVolume extends Construct { 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) { + 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/test/fargate/fargate-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts index d6ef732364a6d..625a1acc3820d 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 @@ -1462,10 +1462,12 @@ describe('fargate service', () => { let taskDefinition: ecs.TaskDefinition; let container: ecs.ContainerDefinition; let role: iam.IRole; + let app: cdk.App; beforeEach(() => { // GIVEN - stack = new cdk.Stack(); + 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'); @@ -1619,10 +1621,10 @@ describe('fargate service', () => { }, }); service.addVolume(vol1); - // Attempt to add a second volume and expect an error + service.addVolume(vol2); expect(() => { - service.addVolume(vol2); - }).toThrow('Invalid VolumeConfiguration. Only one volume can be configured at launch.'); + app.synth(); + }).toThrow(/Invalid VolumeConfiguration. Only one volume can be configured at launch, got: 2/); }); test('create a default ebsrole when not provided', ()=> { @@ -1689,7 +1691,7 @@ describe('fargate service', () => { fileSystemType: ecs.FileSystemType.XFS, }, })); - }).toThrow(/sizeInGiB or snapShotId must be specified/); + }).toThrow("'sizeInGiB' or 'snapShotId' must be specified"); }); test('throw an error snapshot does not match pattern', ()=> { @@ -1708,7 +1710,7 @@ describe('fargate service', () => { snapShotId: 'snap-0d48decab5c493eee_', }, })); - }).toThrow('`snapshotId` does match expected pattern. Expected `snap-` (ex: `snap-05abe246af`) or Token'); + }).toThrow("'snapshotId' does match expected pattern. Expected 'snap-' (ex: 'snap-05abe246af') or Token, got: snap-0d48decab5c493eee_"); }); test('success when snapshotId matches the pattern', ()=> { @@ -1880,7 +1882,7 @@ describe('fargate service', () => { iops: 0, }, })); - }).toThrow("'iops' cannot be specified with 'sc1' volume type"); + }).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', ()=> { @@ -1900,7 +1902,7 @@ describe('fargate service', () => { iops: 0, }, })); - }).toThrow("'iops' cannot be specified with 'gp2' volume type"); + }).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', ()=> { @@ -1920,7 +1922,7 @@ describe('fargate service', () => { sizeInGiB: 125, }, })); - }).toThrow("'iops' must be specified with 'io2' volume type"); + }).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', ()=> { From 03c888f61acd32941a85323586451afcb084539f Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Sun, 14 Jan 2024 13:28:25 -0800 Subject: [PATCH 6/9] change sizeInGiB to Size instead of number Co-authored-by: go-to-k <24818752+go-to-k@users.noreply.github.com> --- .../test/fargate/integ.ebs-taskattach.ts | 2 +- packages/aws-cdk-lib/aws-ecs/README.md | 2 +- .../aws-ecs/lib/base/base-service.ts | 2 +- .../lib/base/service-managed-volume.ts | 8 ++--- .../test/fargate/fargate-service.test.ts | 34 +++++++++---------- .../aws-ecs/test/task-definition.test.ts | 6 ++-- 6 files changed, 27 insertions(+), 27 deletions(-) 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 index dae2941e224a1..221a5a7616923 100644 --- 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 @@ -32,7 +32,7 @@ class TestStack extends cdk.Stack { managedEBSVolume: { encrypted: true, volumeType: ec2.EbsDeviceVolumeType.GP3, - sizeInGiB: 15, + sizeInGiB: cdk.Size.gibibytes(15), iops: 4000, throughput: 500, fileSystemType: ecs.FileSystemType.EXT4, diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index 8f145e6e7f445..bdd24067cf985 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -1658,7 +1658,7 @@ const container = taskDefinition.addContainer('web', { const volume = new ecs.ServiceManagedVolume(this, 'EBSVolume', { name: 'ebs1', managedEBSVolume: { - sizeInGiB: 10, + sizeInGiB: Size.gibibytes(15), volumeType: ec2.EbsDeviceVolumeType.GP3, fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ 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 310d7c614bbac..6e91deb199c13 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 @@ -775,7 +775,7 @@ export abstract class BaseService extends Resource throughput: spec.config.throughput, volumeType: spec.config.volumeType, snapshotId: spec.config.snapShotId, - sizeInGiB: spec.config.sizeInGiB, + sizeInGiB: spec.config.sizeInGiB?.toGibibytes(), tagSpecifications: tagSpecifications, }, }; 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 index dd65e5d6d7bcb..78cf0adca7257 100644 --- 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 @@ -2,7 +2,7 @@ import { Construct } from 'constructs'; import * as ec2 from '../../../aws-ec2'; import * as iam from '../../../aws-iam'; import * as kms from '../../../aws-kms'; -import { Token } from '../../../core'; +import { Size, Token } from '../../../core'; import { BaseMountPoint, ContainerDefinition } from '../container-definition'; /** @@ -71,7 +71,7 @@ export interface ServiceManagedEBSVolumeConfiguration { * @default - The snapshot size is used for the volume size if you specify `snapshotId`, * otherwise this parameter is required. */ - readonly sizeInGiB?: number; + readonly sizeInGiB?: Size; /** * The snapshot that Amazon ECS uses to create the volume. @@ -268,8 +268,8 @@ export class ServiceManagedVolume extends Construct { // Validate volume sizeInGiB ranges. if (sizeInGiB !== undefined) { const { minSize, maxSize } = sizeInGiBRanges[volumeType]; - if (sizeInGiB < minSize || sizeInGiB > maxSize) { - throw new Error(`'${volumeType}' volumes must have a size between ${minSize} and ${maxSize} GiB, got ${sizeInGiB} GiB`); + if (sizeInGiB.toGibibytes() < minSize || sizeInGiB.toGibibytes() > maxSize) { + throw new Error(`'${volumeType}' volumes must have a size between ${minSize} and ${maxSize} GiB, got ${sizeInGiB.toGibibytes()} GiB`); } } 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 625a1acc3820d..2a1e0bab8f540 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 @@ -1494,7 +1494,7 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { role: role, - sizeInGiB: 20, + sizeInGiB: cdk.Size.gibibytes(20), fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ tags: { @@ -1538,7 +1538,7 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { role: role, - sizeInGiB: 20, + sizeInGiB: cdk.Size.gibibytes(20), tagSpecifications: [{ tags: { purpose: 'production', @@ -1610,14 +1610,14 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, - sizeInGiB: 15, + sizeInGiB: cdk.Size.gibibytes(15), }, }); const vol2 = new ServiceManagedVolume(stack, 'ebs1', { name: 'ebs1', managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, - sizeInGiB: 15, + sizeInGiB: cdk.Size.gibibytes(15), }, }); service.addVolume(vol1); @@ -1638,7 +1638,7 @@ describe('fargate service', () => { service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { name: 'nginx-vol', managedEBSVolume: { - sizeInGiB: 20, + sizeInGiB: cdk.Size.gibibytes(20), fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ tags: { @@ -1757,7 +1757,7 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, - sizeInGiB: 16390, + sizeInGiB: cdk.Size.gibibytes(16390), }, })); }).toThrow(/'gp2' volumes must have a size between 1 and 16384 GiB, got 16390 GiB/); @@ -1777,7 +1777,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.IO1, - sizeInGiB: 0, + sizeInGiB: cdk.Size.gibibytes(0), }, })); }).toThrow(/'io1' volumes must have a size between 4 and 16384 GiB, got 0 GiB/); @@ -1796,7 +1796,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.STANDARD, - sizeInGiB: 1500, + sizeInGiB: cdk.Size.gibibytes(1500), }, })); }).toThrow(/'standard' volumes must have a size between 1 and 1024 GiB, got 1500 GiB/); @@ -1815,7 +1815,7 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, - sizeInGiB: 10, + sizeInGiB: cdk.Size.gibibytes(10), throughput: 0, }, })); @@ -1836,7 +1836,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.GP3, - sizeInGiB: 10, + sizeInGiB: cdk.Size.gibibytes(10), throughput: 10001, }, })); @@ -1857,7 +1857,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.GP3, - sizeInGiB: 10, + sizeInGiB: cdk.Size.gibibytes(10), throughput: 10001, }, })); @@ -1878,7 +1878,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.SC1, - sizeInGiB: 125, + sizeInGiB: cdk.Size.gibibytes(125), iops: 0, }, })); @@ -1898,7 +1898,7 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, - sizeInGiB: 125, + sizeInGiB: cdk.Size.gibibytes(125), iops: 0, }, })); @@ -1919,7 +1919,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.IO2, - sizeInGiB: 125, + sizeInGiB: cdk.Size.gibibytes(125), }, })); }).toThrow(/iops must be specified with io1 or io2 volume types, got io2/); @@ -1939,7 +1939,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.IO2, - sizeInGiB: 125, + sizeInGiB: cdk.Size.gibibytes(125), iops: 0, }, })); @@ -1960,7 +1960,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.IO2, - sizeInGiB: 125, + sizeInGiB: cdk.Size.gibibytes(125), iops: 256001, }, })); @@ -1980,7 +1980,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.GP3, - sizeInGiB: 15, + sizeInGiB: cdk.Size.gibibytes(15), throughput: 0, }, })); 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 5bd3bfc63200e..42e2d3c82c7b7 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 @@ -296,7 +296,7 @@ describe('task definition', () => { name: 'nginx-vol', managedEBSVolume: { role: ebsRole, - sizeInGiB: 3, + sizeInGiB: cdk.Size.gibibytes(3), volumeType: EbsDeviceVolumeType.GP3, fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ @@ -329,7 +329,7 @@ describe('task definition', () => { name: 'nginx-vol', managedEBSVolume: { role: ebsRole, - sizeInGiB: 3, + sizeInGiB: cdk.Size.gibibytes(3), volumeType: EbsDeviceVolumeType.GP3, fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ @@ -349,7 +349,7 @@ describe('task definition', () => { name: 'nginx-vol1', managedEBSVolume: { role: ebsRole, - sizeInGiB: 3, + sizeInGiB: cdk.Size.gibibytes(3), volumeType: EbsDeviceVolumeType.GP3, fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ From 13a5dd8a515665b7ad1f998292bc2b0ea0c954e5 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Sun, 14 Jan 2024 14:14:13 -0800 Subject: [PATCH 7/9] change method name --- .../aws-cdk-lib/aws-ecs/lib/base/service-managed-volume.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 78cf0adca7257..7a5d4b011ee6e 100644 --- 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 @@ -214,7 +214,7 @@ export class ServiceManagedVolume extends Construct { constructor(scope: Construct, id: string, props: ServiceManagedVolumeProps) { super(scope, id); - this.validateVolumeConfiguration(props.managedEBSVolume); + 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'), @@ -240,7 +240,7 @@ export class ServiceManagedVolume extends Construct { }); } - private validateVolumeConfiguration(volumeConfig?: ServiceManagedEBSVolumeConfiguration) { + private validateEbsVolumeConfiguration(volumeConfig?: ServiceManagedEBSVolumeConfiguration) { if (!volumeConfig) return; const { volumeType = ec2.EbsDeviceVolumeType.GP2, iops, sizeInGiB, throughput, snapShotId } = volumeConfig; From 5c7eb2056d744d580ef19105c9c813c20f43b397 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Sun, 14 Jan 2024 21:15:26 -0800 Subject: [PATCH 8/9] change error messages --- packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts | 2 +- .../aws-ecs/lib/base/service-managed-volume.ts | 4 ++-- .../aws-ecs/test/fargate/fargate-service.test.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) 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 6e91deb199c13..05050ac8d1ff0 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 @@ -749,7 +749,7 @@ export abstract class BaseService extends Resource private renderVolumes(): CfnService.ServiceVolumeConfigurationProperty[] { if (this.volumes.length > 1) { - throw new Error(`Invalid VolumeConfiguration. Only one volume can be configured at launch, got: ${this.volumes.length}`); + 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 { 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 index 7a5d4b011ee6e..8c12cbdd5d019 100644 --- 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 @@ -287,12 +287,12 @@ export class ServiceManagedVolume extends Construct { // 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}`); + 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}`); + throw new Error(`'iops' must be specified with io1 or io2 volume types, got ${volumeType}`); } // Validate IOPS range if specified. 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 2a1e0bab8f540..2643ca9e9c075 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 @@ -1624,7 +1624,7 @@ describe('fargate service', () => { service.addVolume(vol2); expect(() => { app.synth(); - }).toThrow(/Invalid VolumeConfiguration. Only one volume can be configured at launch, got: 2/); + }).toThrow(/Only one EBS volume can be specified for 'volumeConfigurations', got: 2/); }); test('create a default ebsrole when not provided', ()=> { @@ -1882,7 +1882,7 @@ describe('fargate service', () => { iops: 0, }, })); - }).toThrow(/iops cannot be specified with sc1, st1, gp2 and standard volume types, got sc1/); + }).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', ()=> { @@ -1902,7 +1902,7 @@ describe('fargate service', () => { iops: 0, }, })); - }).toThrow(/iops cannot be specified with sc1, st1, gp2 and standard volume types, got gp2/); + }).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', ()=> { @@ -1922,7 +1922,7 @@ describe('fargate service', () => { sizeInGiB: cdk.Size.gibibytes(125), }, })); - }).toThrow(/iops must be specified with io1 or io2 volume types, got io2/); + }).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', ()=> { From d5c406970cc5fbdcaa6cb7565d4202a124a492ab Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Sun, 14 Jan 2024 21:40:41 -0800 Subject: [PATCH 9/9] change sizeInGiB to size --- .../test/fargate/integ.ebs-taskattach.ts | 2 +- packages/aws-cdk-lib/aws-ecs/README.md | 2 +- .../aws-ecs/lib/base/base-service.ts | 2 +- .../lib/base/service-managed-volume.ts | 22 +++++----- .../test/fargate/fargate-service.test.ts | 44 +++++++++---------- .../aws-ecs/test/task-definition.test.ts | 6 +-- 6 files changed, 39 insertions(+), 39 deletions(-) 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 index 221a5a7616923..d178806e48136 100644 --- 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 @@ -32,7 +32,7 @@ class TestStack extends cdk.Stack { managedEBSVolume: { encrypted: true, volumeType: ec2.EbsDeviceVolumeType.GP3, - sizeInGiB: cdk.Size.gibibytes(15), + size: cdk.Size.gibibytes(15), iops: 4000, throughput: 500, fileSystemType: ecs.FileSystemType.EXT4, diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index bdd24067cf985..3b0db4577e94c 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -1658,7 +1658,7 @@ const container = taskDefinition.addContainer('web', { const volume = new ecs.ServiceManagedVolume(this, 'EBSVolume', { name: 'ebs1', managedEBSVolume: { - sizeInGiB: Size.gibibytes(15), + size: Size.gibibytes(15), volumeType: ec2.EbsDeviceVolumeType.GP3, fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ 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 05050ac8d1ff0..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 @@ -775,7 +775,7 @@ export abstract class BaseService extends Resource throughput: spec.config.throughput, volumeType: spec.config.volumeType, snapshotId: spec.config.snapShotId, - sizeInGiB: spec.config.sizeInGiB?.toGibibytes(), + sizeInGiB: spec.config.size?.toGibibytes(), tagSpecifications: tagSpecifications, }, }; 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 index 8c12cbdd5d019..6cea5e124224a 100644 --- 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 @@ -59,7 +59,7 @@ export interface ServiceManagedEBSVolumeConfiguration { /** * The size of the volume in GiB. * - * You must specify either `sizeInGiB` or `snapshotId`. + * 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. @@ -71,12 +71,12 @@ export interface ServiceManagedEBSVolumeConfiguration { * @default - The snapshot size is used for the volume size if you specify `snapshotId`, * otherwise this parameter is required. */ - readonly sizeInGiB?: Size; + readonly size?: Size; /** * The snapshot that Amazon ECS uses to create the volume. * - * You must specify either `sizeInGiB` or `snapshotId`. + * You must specify either `size` or `snapshotId`. * * @default - No snapshot. */ @@ -243,11 +243,11 @@ export class ServiceManagedVolume extends Construct { private validateEbsVolumeConfiguration(volumeConfig?: ServiceManagedEBSVolumeConfiguration) { if (!volumeConfig) return; - const { volumeType = ec2.EbsDeviceVolumeType.GP2, iops, sizeInGiB, throughput, snapShotId } = volumeConfig; + const { volumeType = ec2.EbsDeviceVolumeType.GP2, iops, size, throughput, snapShotId } = volumeConfig; - // Validate if both sizeInGiB and snapShotId are not specified. - if (sizeInGiB === undefined && snapShotId === undefined) { - throw new Error('\'sizeInGiB\' or \'snapShotId\' must be specified'); + // 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)) { @@ -265,11 +265,11 @@ export class ServiceManagedVolume extends Construct { [ec2.EbsDeviceVolumeType.STANDARD]: { minSize: 1, maxSize: 1024 }, }; - // Validate volume sizeInGiB ranges. - if (sizeInGiB !== undefined) { + // Validate volume size ranges. + if (size !== undefined) { const { minSize, maxSize } = sizeInGiBRanges[volumeType]; - if (sizeInGiB.toGibibytes() < minSize || sizeInGiB.toGibibytes() > maxSize) { - throw new Error(`'${volumeType}' volumes must have a size between ${minSize} and ${maxSize} GiB, got ${sizeInGiB.toGibibytes()} GiB`); + 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`); } } 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 2643ca9e9c075..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 @@ -1494,7 +1494,7 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { role: role, - sizeInGiB: cdk.Size.gibibytes(20), + size: cdk.Size.gibibytes(20), fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ tags: { @@ -1538,7 +1538,7 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { role: role, - sizeInGiB: cdk.Size.gibibytes(20), + size: cdk.Size.gibibytes(20), tagSpecifications: [{ tags: { purpose: 'production', @@ -1610,14 +1610,14 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, - sizeInGiB: cdk.Size.gibibytes(15), + size: cdk.Size.gibibytes(15), }, }); const vol2 = new ServiceManagedVolume(stack, 'ebs1', { name: 'ebs1', managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, - sizeInGiB: cdk.Size.gibibytes(15), + size: cdk.Size.gibibytes(15), }, }); service.addVolume(vol1); @@ -1638,7 +1638,7 @@ describe('fargate service', () => { service.addVolume(new ServiceManagedVolume(stack, 'EBS Volume', { name: 'nginx-vol', managedEBSVolume: { - sizeInGiB: cdk.Size.gibibytes(20), + size: cdk.Size.gibibytes(20), fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ tags: { @@ -1676,7 +1676,7 @@ describe('fargate service', () => { }); }); - test('throw an error when both sizeInGIB and snapshotId are not provided', ()=> { + test('throw an error when both size and snapshotId are not provided', ()=> { // WHEN container.addMountPoints({ containerPath: '/var/lib', @@ -1691,7 +1691,7 @@ describe('fargate service', () => { fileSystemType: ecs.FileSystemType.XFS, }, })); - }).toThrow("'sizeInGiB' or 'snapShotId' must be specified"); + }).toThrow("'size' or 'snapShotId' must be specified"); }); test('throw an error snapshot does not match pattern', ()=> { @@ -1744,7 +1744,7 @@ describe('fargate service', () => { }); }); - test('throw an error when sizeInGiB is greater than 16384 for gp2', ()=> { + test('throw an error when size is greater than 16384 for gp2', ()=> { // WHEN container.addMountPoints({ containerPath: '/var/lib', @@ -1757,13 +1757,13 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, - sizeInGiB: cdk.Size.gibibytes(16390), + 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 sizeInGiB is less than 4 for volume type io1', ()=> { + test('throw an error when size is less than 4 for volume type io1', ()=> { // WHEN container.addMountPoints({ containerPath: '/var/lib', @@ -1777,12 +1777,12 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.IO1, - sizeInGiB: cdk.Size.gibibytes(0), + 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 sizeInGiB is greater than 1024 for volume type standard', ()=> { + test('throw an error when size is greater than 1024 for volume type standard', ()=> { // WHEN container.addMountPoints({ containerPath: '/var/lib', @@ -1796,7 +1796,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.STANDARD, - sizeInGiB: cdk.Size.gibibytes(1500), + size: cdk.Size.gibibytes(1500), }, })); }).toThrow(/'standard' volumes must have a size between 1 and 1024 GiB, got 1500 GiB/); @@ -1815,7 +1815,7 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, - sizeInGiB: cdk.Size.gibibytes(10), + size: cdk.Size.gibibytes(10), throughput: 0, }, })); @@ -1836,7 +1836,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.GP3, - sizeInGiB: cdk.Size.gibibytes(10), + size: cdk.Size.gibibytes(10), throughput: 10001, }, })); @@ -1857,7 +1857,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.GP3, - sizeInGiB: cdk.Size.gibibytes(10), + size: cdk.Size.gibibytes(10), throughput: 10001, }, })); @@ -1878,7 +1878,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.SC1, - sizeInGiB: cdk.Size.gibibytes(125), + size: cdk.Size.gibibytes(125), iops: 0, }, })); @@ -1898,7 +1898,7 @@ describe('fargate service', () => { name: 'nginx-vol', managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, - sizeInGiB: cdk.Size.gibibytes(125), + size: cdk.Size.gibibytes(125), iops: 0, }, })); @@ -1919,7 +1919,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.IO2, - sizeInGiB: cdk.Size.gibibytes(125), + size: cdk.Size.gibibytes(125), }, })); }).toThrow(/'iops' must be specified with io1 or io2 volume types, got io2/); @@ -1939,7 +1939,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.IO2, - sizeInGiB: cdk.Size.gibibytes(125), + size: cdk.Size.gibibytes(125), iops: 0, }, })); @@ -1960,7 +1960,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.IO2, - sizeInGiB: cdk.Size.gibibytes(125), + size: cdk.Size.gibibytes(125), iops: 256001, }, })); @@ -1980,7 +1980,7 @@ describe('fargate service', () => { managedEBSVolume: { fileSystemType: ecs.FileSystemType.XFS, volumeType: ec2.EbsDeviceVolumeType.GP3, - sizeInGiB: cdk.Size.gibibytes(15), + size: cdk.Size.gibibytes(15), throughput: 0, }, })); 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 42e2d3c82c7b7..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 @@ -296,7 +296,7 @@ describe('task definition', () => { name: 'nginx-vol', managedEBSVolume: { role: ebsRole, - sizeInGiB: cdk.Size.gibibytes(3), + size: cdk.Size.gibibytes(3), volumeType: EbsDeviceVolumeType.GP3, fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ @@ -329,7 +329,7 @@ describe('task definition', () => { name: 'nginx-vol', managedEBSVolume: { role: ebsRole, - sizeInGiB: cdk.Size.gibibytes(3), + size: cdk.Size.gibibytes(3), volumeType: EbsDeviceVolumeType.GP3, fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{ @@ -349,7 +349,7 @@ describe('task definition', () => { name: 'nginx-vol1', managedEBSVolume: { role: ebsRole, - sizeInGiB: cdk.Size.gibibytes(3), + size: cdk.Size.gibibytes(3), volumeType: EbsDeviceVolumeType.GP3, fileSystemType: ecs.FileSystemType.XFS, tagSpecifications: [{