From dcd1247dc88f4116359c33e04df60c16f27a4892 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Sat, 17 Oct 2020 18:46:42 -0600 Subject: [PATCH 1/7] feat(aws-ecs-builder): introduce `Environment.fromEnvironmentAttributes()` --- .../ecs-service-extensions/lib/environment.ts | 64 ++++++++++++++++++- .../lib/extensions/appmesh.ts | 2 +- .../ecs-service-extensions/lib/service.ts | 8 +-- .../test/test.environment.ts | 22 ++++++- 4 files changed, 89 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts index 2a5e215d7571e..31ed1cab34156 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts @@ -28,13 +28,44 @@ export interface EnvironmentProps { readonly capacityType?: EnvironmentCapacityType } +/** + * An environment into which to deploy a service. + */ +export interface IEnvironment { + /** + * The name of this environment. + */ + readonly id: string; + + /** + * The VPC into which environment services should be placed. + */ + readonly vpc: ec2.IVpc; + + /** + * The cluster that is providing capacity for this service. + */ + readonly cluster: ecs.ICluster; + + /** + * The capacity type used by the service's cluster. + */ + readonly capacityType: EnvironmentCapacityType; + + addDefaultCloudMapNamespace(options: ecs.CloudMapNamespaceOptions): void; +} + /** * An environment into which to deploy a service. This environment * can either be instantiated with a preexisting AWS VPC and ECS cluster, * or it can create it's own VPC and cluster. By default it will create * a cluster with Fargate capacity. */ -export class Environment extends cdk.Construct { +export class Environment extends cdk.Construct implements IEnvironment { + public static fromEnvironmentAttributes(scope: cdk.Construct, id: string, attrs: EnvironmentAttributes): IEnvironment { + return new ImportedEnvironment(scope, id, attrs); + } + /** * The name of this environment. */ @@ -81,4 +112,35 @@ export class Environment extends cdk.Construct { this.capacityType = EnvironmentCapacityType.FARGATE; } } + + addDefaultCloudMapNamespace(options: ecs.CloudMapNamespaceOptions) { + this.cluster.addDefaultCloudMapNamespace(options); + } +} + +export interface EnvironmentAttributes { + id: string; + capacityType: EnvironmentCapacityType; + cluster: ecs.ICluster; + vpc: ec2.IVpc; } + +export class ImportedEnvironment extends cdk.Construct implements IEnvironment { + public readonly capacityType: EnvironmentCapacityType; + public readonly cluster: ecs.ICluster; + public readonly id: string; + public readonly vpc: ec2.IVpc; + + constructor(scope: cdk.Construct, id: string, props: EnvironmentAttributes) { + super(scope, id); + + this.capacityType = props.capacityType; + this.cluster = props.cluster; + this.id = props.id; + this.vpc = props.vpc; + } + + addDefaultCloudMapNamespace(_options: ecs.CloudMapNamespaceOptions) { + throw new Error('the cluster environment is not mutable'); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts index dcf3f7ac73e56..9a4973cd89d8f 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts @@ -70,7 +70,7 @@ export class AppMeshExtension extends ServiceExtension { // Make sure that the parent cluster for this service has // a namespace attached. if (!this.parentService.cluster.defaultCloudMapNamespace) { - this.parentService.cluster.addDefaultCloudMapNamespace({ + this.parentService.environment.addDefaultCloudMapNamespace({ // Name the namespace after the environment name. // Service DNS will be like . name: this.parentService.environment.id, diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts index 0b46f782a64d8..29134a8c83260 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as cdk from '@aws-cdk/core'; -import { Environment } from './environment'; +import { IEnvironment } from './environment'; import { EnvironmentCapacityType, ServiceBuild } from './extensions/extension-interfaces'; import { ServiceDescription } from './service-description'; @@ -17,7 +17,7 @@ export interface ServiceProps { /** * The environment to launch the service in */ - readonly environment: Environment + readonly environment: IEnvironment } /** @@ -44,7 +44,7 @@ export class Service extends cdk.Construct { * The cluster that is providing capacity for this service * [disable-awslint:ref-via-interface] */ - public readonly cluster: ecs.Cluster; + public readonly cluster: ecs.ICluster; /** * The capacity type that this service will use @@ -59,7 +59,7 @@ export class Service extends cdk.Construct { /** * The environment this service was launched in */ - public readonly environment: Environment; + public readonly environment: IEnvironment; /** * The generated task definition for this service, is only diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts index cb19e71de1d82..02529e598687a 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts @@ -217,4 +217,24 @@ export = { test.done(); }, -}; \ No newline at end of file + 'should be able to create an environment from attributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), + }); + + // WHEN + Environment.fromEnvironmentAttributes(stack, 'Environment', { + capacityType: EnvironmentCapacityType.EC2, + cluster: cluster, + id: 'SomeCluster', + vpc: vpc, + }); + + test.done(); + }, +}; From 9191460f2dd11784062ca73a93d12f7ff2d60b33 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Sat, 17 Oct 2020 19:04:15 -0600 Subject: [PATCH 2/7] chore: test the attributes are set --- .../ecs-service-extensions/test/test.environment.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts index 02529e598687a..250090bdb4ffa 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts @@ -228,13 +228,19 @@ export = { }); // WHEN - Environment.fromEnvironmentAttributes(stack, 'Environment', { + const environment = Environment.fromEnvironmentAttributes(stack, 'Environment', { capacityType: EnvironmentCapacityType.EC2, cluster: cluster, id: 'SomeCluster', vpc: vpc, }); + // THEN + test.equal(environment.capacityType, EnvironmentCapacityType.EC2); + test.equal(environment.cluster, cluster); + test.equal(environment.vpc, vpc); + test.equal(environment.id, 'SomeCluster'); + test.done(); }, }; From db4e2a5e809a8142fd36bdb596fe630663ecf0e9 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Sat, 17 Oct 2020 19:14:16 -0600 Subject: [PATCH 3/7] chore: improve inline documentation --- .../ecs-service-extensions/lib/environment.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts index 31ed1cab34156..378b35be23e84 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts @@ -52,6 +52,9 @@ export interface IEnvironment { */ readonly capacityType: EnvironmentCapacityType; + /** + * Add a default cloudmap namespace to the environment's cluster. + */ addDefaultCloudMapNamespace(options: ecs.CloudMapNamespaceOptions): void; } @@ -62,6 +65,9 @@ export interface IEnvironment { * a cluster with Fargate capacity. */ export class Environment extends cdk.Construct implements IEnvironment { + /** + * Import an existing environment from its attributes. + */ public static fromEnvironmentAttributes(scope: cdk.Construct, id: string, attrs: EnvironmentAttributes): IEnvironment { return new ImportedEnvironment(scope, id, attrs); } @@ -113,15 +119,33 @@ export class Environment extends cdk.Construct implements IEnvironment { } } + /** + * Add a default cloudmap namespace to the environment's cluster. + */ addDefaultCloudMapNamespace(options: ecs.CloudMapNamespaceOptions) { this.cluster.addDefaultCloudMapNamespace(options); } } export interface EnvironmentAttributes { + /** + * The name of this environment. + */ id: string; + + /** + * The capacity type used by the service's cluster. + */ capacityType: EnvironmentCapacityType; + + /** + * The cluster that is providing capacity for this service. + */ cluster: ecs.ICluster; + + /** + * The VPC into which environment services should be placed. + */ vpc: ec2.IVpc; } @@ -140,7 +164,11 @@ export class ImportedEnvironment extends cdk.Construct implements IEnvironment { this.vpc = props.vpc; } + /** + * Refuses to add a default cloudmap namespace to the cluster as we don't + * own it. + */ addDefaultCloudMapNamespace(_options: ecs.CloudMapNamespaceOptions) { - throw new Error('the cluster environment is not mutable'); + throw new Error('the cluster environment is immutable when imported'); } } \ No newline at end of file From 911d280074bdad8363290bedd14cce354ddb1f37 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Sat, 17 Oct 2020 19:18:01 -0600 Subject: [PATCH 4/7] refactor: remove unnecessary vpc attribute --- .../ecs-service-extensions/lib/environment.ts | 7 +------ .../ecs-service-extensions/test/test.environment.ts | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts index 378b35be23e84..fd06e2d583141 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts @@ -142,11 +142,6 @@ export interface EnvironmentAttributes { * The cluster that is providing capacity for this service. */ cluster: ecs.ICluster; - - /** - * The VPC into which environment services should be placed. - */ - vpc: ec2.IVpc; } export class ImportedEnvironment extends cdk.Construct implements IEnvironment { @@ -161,7 +156,7 @@ export class ImportedEnvironment extends cdk.Construct implements IEnvironment { this.capacityType = props.capacityType; this.cluster = props.cluster; this.id = props.id; - this.vpc = props.vpc; + this.vpc = props.cluster.vpc; } /** diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts index 250090bdb4ffa..e56e90fd745b8 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts @@ -232,7 +232,6 @@ export = { capacityType: EnvironmentCapacityType.EC2, cluster: cluster, id: 'SomeCluster', - vpc: vpc, }); // THEN From 05a023b78683a65c6f22f46323bce8810ed6db83 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Sat, 17 Oct 2020 19:25:14 -0600 Subject: [PATCH 5/7] refactor: remove unnecessary id attribute --- .../ecs-service-extensions/lib/environment.ts | 7 +------ .../ecs-service-extensions/test/test.environment.ts | 3 +-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts index fd06e2d583141..dcff0d28960b4 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts @@ -128,11 +128,6 @@ export class Environment extends cdk.Construct implements IEnvironment { } export interface EnvironmentAttributes { - /** - * The name of this environment. - */ - id: string; - /** * The capacity type used by the service's cluster. */ @@ -153,9 +148,9 @@ export class ImportedEnvironment extends cdk.Construct implements IEnvironment { constructor(scope: cdk.Construct, id: string, props: EnvironmentAttributes) { super(scope, id); + this.id = id; this.capacityType = props.capacityType; this.cluster = props.cluster; - this.id = props.id; this.vpc = props.cluster.vpc; } diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts index e56e90fd745b8..d029f81e34bc6 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts @@ -231,14 +231,13 @@ export = { const environment = Environment.fromEnvironmentAttributes(stack, 'Environment', { capacityType: EnvironmentCapacityType.EC2, cluster: cluster, - id: 'SomeCluster', }); // THEN test.equal(environment.capacityType, EnvironmentCapacityType.EC2); test.equal(environment.cluster, cluster); test.equal(environment.vpc, vpc); - test.equal(environment.id, 'SomeCluster'); + test.equal(environment.id, 'Environment'); test.done(); }, From 2bb3a4d859af9656629c7282a2a885fae3ca38f1 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Sat, 17 Oct 2020 19:28:21 -0600 Subject: [PATCH 6/7] chore: add a section to the readme --- .../ecs-service-extensions/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/README.md b/packages/@aws-cdk-containers/ecs-service-extensions/README.md index 4de08fec00b23..b3c50e828c29b 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/README.md +++ b/packages/@aws-cdk-containers/ecs-service-extensions/README.md @@ -283,3 +283,20 @@ The above code uses the well known service discovery name for each service, and passes it as an environment variable to the container so that the container knows what address to use when communicating to the other service. + +## Importing a pre-existing cluster + +To create an environment with a pre-existing cluster, you must import the cluster first, then use `Environment.fromEnvironmentAttributes()`. When a cluster is imported into an environment, the cluster is treated as immutable. As a result, no extension may modify the cluster to change a setting. + +```ts + +const cluster = ecs.Cluster.fromClusterAttributes(stack, 'Cluster', { + ... +}); + +const environment = Environment.fromEnvironmentAttributes(stack, 'Environment', { + capacityType: EnvironmentCapacityType.EC2, // or `FARGATE` + cluster, +}); + +``` From 3c1a1f68dc24a8bab880382f93fc8900a39a1ef5 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Sat, 17 Oct 2020 21:32:05 -0600 Subject: [PATCH 7/7] chore: add an integration test --- .../integ.imported-environment.expected.json | 372 ++++++++++++++++++ .../test/integ.imported-environment.ts | 99 +++++ 2 files changed, 471 insertions(+) create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json new file mode 100644 index 0000000000000..80156c0ed0d2d --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json @@ -0,0 +1,372 @@ +{ + "Resources": { + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3Bucket60C7B412" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3VersionKey6900DC52" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3VersionKey6900DC52" + } + ] + } + ] + } + ] + ] + } + } + }, + "ServiceloadbalancerD5D60894": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "false" + } + ], + "Scheme": "internet-facing", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ServiceloadbalancerSecurityGroup2DA3E8D6", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPublicSubnet1Subnet0D15D382Ref" + ] + }, + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPublicSubnet2Subnet68645ACARef" + ] + }, + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPublicSubnet3Subnet408F449FRef" + ] + } + ], + "Type": "application" + } + }, + "ServiceloadbalancerSecurityGroup2DA3E8D6": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB importedenvironmentintegServiceloadbalancerFAE8A5FA", + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcDC34D4D3Ref" + ] + } + } + }, + "ServiceloadbalancerSecurityGrouptoimportedenvironmentintegServiceserviceSecurityGroup2BE90F7480B17EB7CA": { + "Type": "AWS::EC2::SecurityGroupEgress", + "Properties": { + "GroupId": { + "Fn::GetAtt": [ + "ServiceloadbalancerSecurityGroup2DA3E8D6", + "GroupId" + ] + }, + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "ServiceserviceSecurityGroup1915660F", + "GroupId" + ] + }, + "FromPort": 80, + "ToPort": 80 + } + }, + "ServiceloadbalancerServicelistenerC862F722": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "ServiceloadbalancerServicelistenerServiceGroup844B51E6" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "ServiceloadbalancerD5D60894" + }, + "Port": 80, + "Protocol": "HTTP" + } + }, + "ServiceloadbalancerServicelistenerServiceGroup844B51E6": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "deregistration_delay.timeout_seconds", + "Value": "10" + } + ], + "TargetType": "ip", + "VpcId": { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcDC34D4D3Ref" + ] + } + } + }, + "ServicetaskdefinitionTaskRole5B4B60A4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "Servicetaskdefinition0CEAD834": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Cpu": 256, + "Environment": [ + { + "Name": "PORT", + "Value": "80" + } + ], + "Essential": true, + "Image": "nathanpeck/name", + "Memory": 512, + "Name": "app", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ] + } + ], + "Cpu": "256", + "Family": "importedenvironmentintegServicetaskdefinition63936B87", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "EC2", + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "ServicetaskdefinitionTaskRole5B4B60A4", + "Arn" + ] + } + } + }, + "ServiceserviceService6A153CB8": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentcluster594A3DCARef" + ] + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 100 + }, + "DesiredCount": 1, + "EnableECSManagedTags": false, + "HealthCheckGracePeriodSeconds": 60, + "LaunchType": "FARGATE", + "LoadBalancers": [ + { + "ContainerName": "app", + "ContainerPort": 80, + "TargetGroupArn": { + "Ref": "ServiceloadbalancerServicelistenerServiceGroup844B51E6" + } + } + ], + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ServiceserviceSecurityGroup1915660F", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPrivateSubnet1Subnet7309E967Ref" + ] + }, + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPrivateSubnet2Subnet55014443Ref" + ] + }, + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPrivateSubnet3Subnet6CF08327Ref" + ] + } + ] + } + }, + "TaskDefinition": { + "Ref": "Servicetaskdefinition0CEAD834" + } + }, + "DependsOn": [ + "ServiceloadbalancerServicelistenerC862F722", + "ServiceloadbalancerServicelistenerServiceGroup844B51E6" + ] + }, + "ServiceserviceSecurityGroup1915660F": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "imported-environment-integ/Service-service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcDC34D4D3Ref" + ] + } + } + }, + "ServiceserviceSecurityGroupfromimportedenvironmentintegServiceloadbalancerSecurityGroup68EE533C8070FCF629": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "FromPort": 80, + "GroupId": { + "Fn::GetAtt": [ + "ServiceserviceSecurityGroup1915660F", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ServiceloadbalancerSecurityGroup2DA3E8D6", + "GroupId" + ] + }, + "ToPort": 80 + } + } + }, + "Outputs": { + "Serviceloadbalancerdnsoutput": { + "Value": { + "Fn::GetAtt": [ + "ServiceloadbalancerD5D60894", + "DNSName" + ] + } + } + }, + "Parameters": { + "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3Bucket60C7B412": { + "Type": "String", + "Description": "S3 bucket for asset \"b537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8\"" + }, + "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3VersionKey6900DC52": { + "Type": "String", + "Description": "S3 key for asset version \"b537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8\"" + }, + "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8ArtifactHash5EEB924C": { + "Type": "String", + "Description": "Artifact hash for asset \"b537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts new file mode 100644 index 0000000000000..899d9e4a4f7c4 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts @@ -0,0 +1,99 @@ +import { Vpc } from '@aws-cdk/aws-ec2'; +import { Cluster, ContainerImage } from '@aws-cdk/aws-ecs'; +import { App, NestedStack, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { + Container, + Environment, + EnvironmentCapacityType, + HttpLoadBalancerExtension, + Service, + ServiceDescription, +} from '../lib'; + +class ResourceStack extends NestedStack { + public readonly clusterName: string; + public readonly vpcId: string; + public readonly publicSubnetIds: string[]; + public readonly privateSubnetIds: string[]; + + constructor(scope: Construct, id: string) { + super(scope, id); + + const environment = new Environment(this, 'Environment'); + + this.clusterName = environment.cluster.clusterName; + this.vpcId = environment.vpc.vpcId; + this.privateSubnetIds = environment.vpc.privateSubnets.map(m => m.subnetId); + this.publicSubnetIds = environment.vpc.publicSubnets.map(m => m.subnetId); + } +} + +class TestStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + // Create a nested stack with the shared resources + const resourceStack = new ResourceStack(this, 'Resources'); + + // Import the vpc from the nested stack + const vpc = Vpc.fromVpcAttributes(this, 'Vpc', { + availabilityZones: resourceStack.availabilityZones, + vpcId: resourceStack.vpcId, + privateSubnetIds: resourceStack.privateSubnetIds, + publicSubnetIds: resourceStack.publicSubnetIds, + }); + + // Import the cluster from the nested stack + const cluster = Cluster.fromClusterAttributes(this, 'Cluster', { + clusterName: resourceStack.clusterName, + securityGroups: [], + vpc: vpc, + }); + + // Create the environment from attributes. + const environment = Environment.fromEnvironmentAttributes(this, 'Environment', { + cluster, + capacityType: EnvironmentCapacityType.FARGATE, + }); + + // Add a workload. + const serviceDescription = new ServiceDescription(); + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, + })); + serviceDescription.add(new HttpLoadBalancerExtension()); + + new Service(this, 'Service', { + environment, + serviceDescription, + }); + } +} + +const app = new App(); +new TestStack(app, 'imported-environment-integ'); + +/** + * Expect this stack to deploy and show a load balancer DNS address. When you + * request the address with curl, you should see the name container's output. + * The load balancer may response 503 Service Temporarily Unavailable for a + * short while, before you can see the container output. + * + * Example: + * ``` + * $ cdk --app 'node integ.imported-environment.js' deploy + * ... + * Outputs: + * shared-cluster-integ.Serviceloadbalancerdnsoutput = share-Servi-6JALU1FDE36L-2093347098.us-east-1.elb.amazonaws.com + * ... + * + * $ curl share-Servi-6JALU1FDE36L-2093347098.us-east-1.elb.amazonaws.com + * Keira (ip-10-0-153-44.ec2.internal) + * ``` + */