Skip to content

Commit 4937cd0

Browse files
authoredDec 13, 2021
feat(aws-ecs): expose environment from containerDefinition (#17889)
Closes #17867 * Assigned props.environment to a public readonly member * Added integration test that confirms the environment can be appended after the task is instantiated Made 2 cosmetic, but no obvious changes. Environment values are specified: name: value name2: value But in the test and the README.md files the sample values were: name: something value: something else This is using the string 'value" as a key - which, as someone reading the code for the first time, was confusing. So I changed the sample values to more clearly display what's a key and what's a value. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 4e7a275 commit 4937cd0

5 files changed

+567
-3
lines changed
 

‎packages/@aws-cdk/aws-ecs/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ declare const parameter: ssm.StringParameter;
405405
declare const taskDefinition: ecs.TaskDefinition;
406406
declare const s3Bucket: s3.Bucket;
407407

408-
taskDefinition.addContainer('container', {
408+
const newContainer = taskDefinition.addContainer('container', {
409409
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
410410
memoryLimitMiB: 1024,
411411
environment: { // clear text, not for sensitive data
@@ -421,6 +421,7 @@ taskDefinition.addContainer('container', {
421421
PARAMETER: ecs.Secret.fromSsmParameter(parameter),
422422
},
423423
});
424+
newContainer.addEnvironment('QUEUE_NAME', 'MyQueue');
424425
```
425426

426427
The task execution role is automatically granted read permissions on the secrets/parameters. Support for environment

‎packages/@aws-cdk/aws-ecs/lib/container-definition.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,8 @@ export class ContainerDefinition extends CoreConstruct {
422422

423423
private readonly secrets?: CfnTaskDefinition.SecretProperty[];
424424

425+
private readonly environment: { [key: string]: string };
426+
425427
/**
426428
* Constructs a new instance of the ContainerDefinition class.
427429
*/
@@ -457,6 +459,12 @@ export class ContainerDefinition extends CoreConstruct {
457459
}
458460
}
459461

462+
if (props.environment) {
463+
this.environment = { ...props.environment };
464+
} else {
465+
this.environment = {};
466+
}
467+
460468
if (props.environmentFiles) {
461469
this.environmentFiles = [];
462470

@@ -547,6 +555,13 @@ export class ContainerDefinition extends CoreConstruct {
547555
}));
548556
}
549557

558+
/**
559+
* This method adds an environment variable to the container.
560+
*/
561+
public addEnvironment(name: string, value: string) {
562+
this.environment[name] = value;
563+
}
564+
550565
/**
551566
* This method adds one or more resources to the container.
552567
*/
@@ -669,7 +684,7 @@ export class ContainerDefinition extends CoreConstruct {
669684
volumesFrom: cdk.Lazy.any({ produce: () => this.volumesFrom.map(renderVolumeFrom) }, { omitEmptyArray: true }),
670685
workingDirectory: this.props.workingDirectory,
671686
logConfiguration: this.logDriverConfig,
672-
environment: this.props.environment && renderKV(this.props.environment, 'name', 'value'),
687+
environment: this.environment && Object.keys(this.environment).length ? renderKV(this.environment, 'name', 'value') : undefined,
673688
environmentFiles: this.environmentFiles && renderEnvironmentFiles(this.environmentFiles),
674689
secrets: this.secrets,
675690
extraHosts: this.props.extraHosts && renderKV(this.props.extraHosts, 'hostname', 'ipAddress'),

‎packages/@aws-cdk/aws-ecs/test/container-definition.test.ts

+33-1
Original file line numberDiff line numberDiff line change
@@ -690,13 +690,14 @@ describe('container definition', () => {
690690
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef');
691691

692692
// WHEN
693-
taskDefinition.addContainer('cont', {
693+
const container = taskDefinition.addContainer('cont', {
694694
image: ecs.ContainerImage.fromRegistry('test'),
695695
memoryLimitMiB: 1024,
696696
environment: {
697697
TEST_ENVIRONMENT_VARIABLE: 'test environment variable value',
698698
},
699699
});
700+
container.addEnvironment('SECOND_ENVIRONEMENT_VARIABLE', 'second test value');
700701

701702
// THEN
702703
expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', {
@@ -705,6 +706,37 @@ describe('container definition', () => {
705706
Environment: [{
706707
Name: 'TEST_ENVIRONMENT_VARIABLE',
707708
Value: 'test environment variable value',
709+
},
710+
{
711+
Name: 'SECOND_ENVIRONEMENT_VARIABLE',
712+
Value: 'second test value',
713+
}],
714+
},
715+
],
716+
});
717+
718+
719+
});
720+
721+
test('can add environment variables to container definition with no environment', () => {
722+
// GIVEN
723+
const stack = new cdk.Stack();
724+
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef');
725+
726+
// WHEN
727+
const container = taskDefinition.addContainer('cont', {
728+
image: ecs.ContainerImage.fromRegistry('test'),
729+
memoryLimitMiB: 1024,
730+
});
731+
container.addEnvironment('SECOND_ENVIRONEMENT_VARIABLE', 'second test value');
732+
733+
// THEN
734+
expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', {
735+
ContainerDefinitions: [
736+
{
737+
Environment: [{
738+
Name: 'SECOND_ENVIRONEMENT_VARIABLE',
739+
Value: 'second test value',
708740
}],
709741
},
710742
],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,479 @@
1+
{
2+
"Resources": {
3+
"Vpc8378EB38": {
4+
"Type": "AWS::EC2::VPC",
5+
"Properties": {
6+
"CidrBlock": "10.0.0.0/16",
7+
"EnableDnsHostnames": true,
8+
"EnableDnsSupport": true,
9+
"InstanceTenancy": "default",
10+
"Tags": [
11+
{
12+
"Key": "Name",
13+
"Value": "aws-ecs-integ/Vpc"
14+
}
15+
]
16+
}
17+
},
18+
"VpcPublicSubnet1Subnet5C2D37C4": {
19+
"Type": "AWS::EC2::Subnet",
20+
"Properties": {
21+
"CidrBlock": "10.0.0.0/18",
22+
"VpcId": {
23+
"Ref": "Vpc8378EB38"
24+
},
25+
"AvailabilityZone": "test-region-1a",
26+
"MapPublicIpOnLaunch": true,
27+
"Tags": [
28+
{
29+
"Key": "aws-cdk:subnet-name",
30+
"Value": "Public"
31+
},
32+
{
33+
"Key": "aws-cdk:subnet-type",
34+
"Value": "Public"
35+
},
36+
{
37+
"Key": "Name",
38+
"Value": "aws-ecs-integ/Vpc/PublicSubnet1"
39+
}
40+
]
41+
}
42+
},
43+
"VpcPublicSubnet1RouteTable6C95E38E": {
44+
"Type": "AWS::EC2::RouteTable",
45+
"Properties": {
46+
"VpcId": {
47+
"Ref": "Vpc8378EB38"
48+
},
49+
"Tags": [
50+
{
51+
"Key": "Name",
52+
"Value": "aws-ecs-integ/Vpc/PublicSubnet1"
53+
}
54+
]
55+
}
56+
},
57+
"VpcPublicSubnet1RouteTableAssociation97140677": {
58+
"Type": "AWS::EC2::SubnetRouteTableAssociation",
59+
"Properties": {
60+
"RouteTableId": {
61+
"Ref": "VpcPublicSubnet1RouteTable6C95E38E"
62+
},
63+
"SubnetId": {
64+
"Ref": "VpcPublicSubnet1Subnet5C2D37C4"
65+
}
66+
}
67+
},
68+
"VpcPublicSubnet1DefaultRoute3DA9E72A": {
69+
"Type": "AWS::EC2::Route",
70+
"Properties": {
71+
"RouteTableId": {
72+
"Ref": "VpcPublicSubnet1RouteTable6C95E38E"
73+
},
74+
"DestinationCidrBlock": "0.0.0.0/0",
75+
"GatewayId": {
76+
"Ref": "VpcIGWD7BA715C"
77+
}
78+
},
79+
"DependsOn": [
80+
"VpcVPCGWBF912B6E"
81+
]
82+
},
83+
"VpcPublicSubnet1EIPD7E02669": {
84+
"Type": "AWS::EC2::EIP",
85+
"Properties": {
86+
"Domain": "vpc",
87+
"Tags": [
88+
{
89+
"Key": "Name",
90+
"Value": "aws-ecs-integ/Vpc/PublicSubnet1"
91+
}
92+
]
93+
}
94+
},
95+
"VpcPublicSubnet1NATGateway4D7517AA": {
96+
"Type": "AWS::EC2::NatGateway",
97+
"Properties": {
98+
"SubnetId": {
99+
"Ref": "VpcPublicSubnet1Subnet5C2D37C4"
100+
},
101+
"AllocationId": {
102+
"Fn::GetAtt": [
103+
"VpcPublicSubnet1EIPD7E02669",
104+
"AllocationId"
105+
]
106+
},
107+
"Tags": [
108+
{
109+
"Key": "Name",
110+
"Value": "aws-ecs-integ/Vpc/PublicSubnet1"
111+
}
112+
]
113+
}
114+
},
115+
"VpcPublicSubnet2Subnet691E08A3": {
116+
"Type": "AWS::EC2::Subnet",
117+
"Properties": {
118+
"CidrBlock": "10.0.64.0/18",
119+
"VpcId": {
120+
"Ref": "Vpc8378EB38"
121+
},
122+
"AvailabilityZone": "test-region-1b",
123+
"MapPublicIpOnLaunch": true,
124+
"Tags": [
125+
{
126+
"Key": "aws-cdk:subnet-name",
127+
"Value": "Public"
128+
},
129+
{
130+
"Key": "aws-cdk:subnet-type",
131+
"Value": "Public"
132+
},
133+
{
134+
"Key": "Name",
135+
"Value": "aws-ecs-integ/Vpc/PublicSubnet2"
136+
}
137+
]
138+
}
139+
},
140+
"VpcPublicSubnet2RouteTable94F7E489": {
141+
"Type": "AWS::EC2::RouteTable",
142+
"Properties": {
143+
"VpcId": {
144+
"Ref": "Vpc8378EB38"
145+
},
146+
"Tags": [
147+
{
148+
"Key": "Name",
149+
"Value": "aws-ecs-integ/Vpc/PublicSubnet2"
150+
}
151+
]
152+
}
153+
},
154+
"VpcPublicSubnet2RouteTableAssociationDD5762D8": {
155+
"Type": "AWS::EC2::SubnetRouteTableAssociation",
156+
"Properties": {
157+
"RouteTableId": {
158+
"Ref": "VpcPublicSubnet2RouteTable94F7E489"
159+
},
160+
"SubnetId": {
161+
"Ref": "VpcPublicSubnet2Subnet691E08A3"
162+
}
163+
}
164+
},
165+
"VpcPublicSubnet2DefaultRoute97F91067": {
166+
"Type": "AWS::EC2::Route",
167+
"Properties": {
168+
"RouteTableId": {
169+
"Ref": "VpcPublicSubnet2RouteTable94F7E489"
170+
},
171+
"DestinationCidrBlock": "0.0.0.0/0",
172+
"GatewayId": {
173+
"Ref": "VpcIGWD7BA715C"
174+
}
175+
},
176+
"DependsOn": [
177+
"VpcVPCGWBF912B6E"
178+
]
179+
},
180+
"VpcPublicSubnet2EIP3C605A87": {
181+
"Type": "AWS::EC2::EIP",
182+
"Properties": {
183+
"Domain": "vpc",
184+
"Tags": [
185+
{
186+
"Key": "Name",
187+
"Value": "aws-ecs-integ/Vpc/PublicSubnet2"
188+
}
189+
]
190+
}
191+
},
192+
"VpcPublicSubnet2NATGateway9182C01D": {
193+
"Type": "AWS::EC2::NatGateway",
194+
"Properties": {
195+
"SubnetId": {
196+
"Ref": "VpcPublicSubnet2Subnet691E08A3"
197+
},
198+
"AllocationId": {
199+
"Fn::GetAtt": [
200+
"VpcPublicSubnet2EIP3C605A87",
201+
"AllocationId"
202+
]
203+
},
204+
"Tags": [
205+
{
206+
"Key": "Name",
207+
"Value": "aws-ecs-integ/Vpc/PublicSubnet2"
208+
}
209+
]
210+
}
211+
},
212+
"VpcPrivateSubnet1Subnet536B997A": {
213+
"Type": "AWS::EC2::Subnet",
214+
"Properties": {
215+
"CidrBlock": "10.0.128.0/18",
216+
"VpcId": {
217+
"Ref": "Vpc8378EB38"
218+
},
219+
"AvailabilityZone": "test-region-1a",
220+
"MapPublicIpOnLaunch": false,
221+
"Tags": [
222+
{
223+
"Key": "aws-cdk:subnet-name",
224+
"Value": "Private"
225+
},
226+
{
227+
"Key": "aws-cdk:subnet-type",
228+
"Value": "Private"
229+
},
230+
{
231+
"Key": "Name",
232+
"Value": "aws-ecs-integ/Vpc/PrivateSubnet1"
233+
}
234+
]
235+
}
236+
},
237+
"VpcPrivateSubnet1RouteTableB2C5B500": {
238+
"Type": "AWS::EC2::RouteTable",
239+
"Properties": {
240+
"VpcId": {
241+
"Ref": "Vpc8378EB38"
242+
},
243+
"Tags": [
244+
{
245+
"Key": "Name",
246+
"Value": "aws-ecs-integ/Vpc/PrivateSubnet1"
247+
}
248+
]
249+
}
250+
},
251+
"VpcPrivateSubnet1RouteTableAssociation70C59FA6": {
252+
"Type": "AWS::EC2::SubnetRouteTableAssociation",
253+
"Properties": {
254+
"RouteTableId": {
255+
"Ref": "VpcPrivateSubnet1RouteTableB2C5B500"
256+
},
257+
"SubnetId": {
258+
"Ref": "VpcPrivateSubnet1Subnet536B997A"
259+
}
260+
}
261+
},
262+
"VpcPrivateSubnet1DefaultRouteBE02A9ED": {
263+
"Type": "AWS::EC2::Route",
264+
"Properties": {
265+
"RouteTableId": {
266+
"Ref": "VpcPrivateSubnet1RouteTableB2C5B500"
267+
},
268+
"DestinationCidrBlock": "0.0.0.0/0",
269+
"NatGatewayId": {
270+
"Ref": "VpcPublicSubnet1NATGateway4D7517AA"
271+
}
272+
}
273+
},
274+
"VpcPrivateSubnet2Subnet3788AAA1": {
275+
"Type": "AWS::EC2::Subnet",
276+
"Properties": {
277+
"CidrBlock": "10.0.192.0/18",
278+
"VpcId": {
279+
"Ref": "Vpc8378EB38"
280+
},
281+
"AvailabilityZone": "test-region-1b",
282+
"MapPublicIpOnLaunch": false,
283+
"Tags": [
284+
{
285+
"Key": "aws-cdk:subnet-name",
286+
"Value": "Private"
287+
},
288+
{
289+
"Key": "aws-cdk:subnet-type",
290+
"Value": "Private"
291+
},
292+
{
293+
"Key": "Name",
294+
"Value": "aws-ecs-integ/Vpc/PrivateSubnet2"
295+
}
296+
]
297+
}
298+
},
299+
"VpcPrivateSubnet2RouteTableA678073B": {
300+
"Type": "AWS::EC2::RouteTable",
301+
"Properties": {
302+
"VpcId": {
303+
"Ref": "Vpc8378EB38"
304+
},
305+
"Tags": [
306+
{
307+
"Key": "Name",
308+
"Value": "aws-ecs-integ/Vpc/PrivateSubnet2"
309+
}
310+
]
311+
}
312+
},
313+
"VpcPrivateSubnet2RouteTableAssociationA89CAD56": {
314+
"Type": "AWS::EC2::SubnetRouteTableAssociation",
315+
"Properties": {
316+
"RouteTableId": {
317+
"Ref": "VpcPrivateSubnet2RouteTableA678073B"
318+
},
319+
"SubnetId": {
320+
"Ref": "VpcPrivateSubnet2Subnet3788AAA1"
321+
}
322+
}
323+
},
324+
"VpcPrivateSubnet2DefaultRoute060D2087": {
325+
"Type": "AWS::EC2::Route",
326+
"Properties": {
327+
"RouteTableId": {
328+
"Ref": "VpcPrivateSubnet2RouteTableA678073B"
329+
},
330+
"DestinationCidrBlock": "0.0.0.0/0",
331+
"NatGatewayId": {
332+
"Ref": "VpcPublicSubnet2NATGateway9182C01D"
333+
}
334+
}
335+
},
336+
"VpcIGWD7BA715C": {
337+
"Type": "AWS::EC2::InternetGateway",
338+
"Properties": {
339+
"Tags": [
340+
{
341+
"Key": "Name",
342+
"Value": "aws-ecs-integ/Vpc"
343+
}
344+
]
345+
}
346+
},
347+
"VpcVPCGWBF912B6E": {
348+
"Type": "AWS::EC2::VPCGatewayAttachment",
349+
"Properties": {
350+
"VpcId": {
351+
"Ref": "Vpc8378EB38"
352+
},
353+
"InternetGatewayId": {
354+
"Ref": "VpcIGWD7BA715C"
355+
}
356+
}
357+
},
358+
"FargateCluster7CCD5F93": {
359+
"Type": "AWS::ECS::Cluster"
360+
},
361+
"TaskDefTaskRole1EDB4A67": {
362+
"Type": "AWS::IAM::Role",
363+
"Properties": {
364+
"AssumeRolePolicyDocument": {
365+
"Statement": [
366+
{
367+
"Action": "sts:AssumeRole",
368+
"Effect": "Allow",
369+
"Principal": {
370+
"Service": "ecs-tasks.amazonaws.com"
371+
}
372+
}
373+
],
374+
"Version": "2012-10-17"
375+
}
376+
}
377+
},
378+
"TaskDef54694570": {
379+
"Type": "AWS::ECS::TaskDefinition",
380+
"Properties": {
381+
"ContainerDefinitions": [
382+
{
383+
"Environment": [
384+
{
385+
"Name": "nameOne",
386+
"Value": "valueOne"
387+
}
388+
],
389+
"Essential": true,
390+
"Image": "nginx",
391+
"Name": "nginx",
392+
"PortMappings": [
393+
{
394+
"ContainerPort": 80,
395+
"Protocol": "tcp"
396+
}
397+
]
398+
}
399+
],
400+
"Cpu": "512",
401+
"Family": "awsecsintegTaskDef6FDFB69A",
402+
"Memory": "1024",
403+
"NetworkMode": "awsvpc",
404+
"RequiresCompatibilities": [
405+
"FARGATE"
406+
],
407+
"TaskRoleArn": {
408+
"Fn::GetAtt": [
409+
"TaskDefTaskRole1EDB4A67",
410+
"Arn"
411+
]
412+
}
413+
}
414+
},
415+
"websvcsgA808F313": {
416+
"Type": "AWS::EC2::SecurityGroup",
417+
"Properties": {
418+
"GroupDescription": "aws-ecs-integ/websvc-sg",
419+
"SecurityGroupEgress": [
420+
{
421+
"CidrIp": "0.0.0.0/0",
422+
"Description": "Allow all outbound traffic by default",
423+
"IpProtocol": "-1"
424+
}
425+
],
426+
"SecurityGroupIngress": [
427+
{
428+
"CidrIp": "0.0.0.0/0",
429+
"Description": "from 0.0.0.0/0:80",
430+
"FromPort": 80,
431+
"IpProtocol": "tcp",
432+
"ToPort": 80
433+
}
434+
],
435+
"VpcId": {
436+
"Ref": "Vpc8378EB38"
437+
}
438+
}
439+
},
440+
"ServiceD69D759B": {
441+
"Type": "AWS::ECS::Service",
442+
"Properties": {
443+
"Cluster": {
444+
"Ref": "FargateCluster7CCD5F93"
445+
},
446+
"DeploymentConfiguration": {
447+
"MaximumPercent": 200,
448+
"MinimumHealthyPercent": 50
449+
},
450+
"EnableECSManagedTags": false,
451+
"LaunchType": "FARGATE",
452+
"NetworkConfiguration": {
453+
"AwsvpcConfiguration": {
454+
"AssignPublicIp": "ENABLED",
455+
"SecurityGroups": [
456+
{
457+
"Fn::GetAtt": [
458+
"websvcsgA808F313",
459+
"GroupId"
460+
]
461+
}
462+
],
463+
"Subnets": [
464+
{
465+
"Ref": "VpcPublicSubnet1Subnet5C2D37C4"
466+
},
467+
{
468+
"Ref": "VpcPublicSubnet2Subnet691E08A3"
469+
}
470+
]
471+
}
472+
},
473+
"TaskDefinition": {
474+
"Ref": "TaskDef54694570"
475+
}
476+
}
477+
}
478+
}
479+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import * as ec2 from '@aws-cdk/aws-ec2';
2+
import * as cdk from '@aws-cdk/core';
3+
import * as ecs from '../../lib';
4+
5+
const app = new cdk.App();
6+
const stack = new cdk.Stack(app, 'aws-ecs-integ');
7+
const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 });
8+
const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc });
9+
10+
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef', {
11+
memoryLimitMiB: 1024,
12+
cpu: 512,
13+
});
14+
15+
// new container with firelens log driver, firelens log router will be created automatically.
16+
const container = taskDefinition.addContainer('nginx', {
17+
image: ecs.ContainerImage.fromRegistry('nginx'),
18+
});
19+
20+
container.addPortMappings({
21+
containerPort: 80,
22+
protocol: ecs.Protocol.TCP,
23+
});
24+
25+
// Create a security group that allows tcp @ port 80
26+
const securityGroup = new ec2.SecurityGroup(stack, 'websvc-sg', { vpc });
27+
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80));
28+
new ecs.FargateService(stack, 'Service', {
29+
cluster,
30+
taskDefinition,
31+
securityGroup,
32+
assignPublicIp: true,
33+
});
34+
35+
container.addEnvironment('nameOne', 'valueOne');
36+
37+
app.synth();

0 commit comments

Comments
 (0)
Please sign in to comment.