Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ecs): support secret environment variables #2994

Merged
merged 30 commits into from
Jul 29, 2019
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
146f13e
feat(ecs): support secret environment variables
jogold Jun 21, 2019
5384d55
Merge branch 'master' into ecs-secrets
jogold Jun 21, 2019
bf91871
Merge branch 'master' into ecs-secrets
jogold Jun 21, 2019
a0bb714
add IAM permissions
jogold Jun 24, 2019
2a5155b
Merge branch 'master' into ecs-secrets
jogold Jun 24, 2019
d697273
disable-awslint:ref-via-interface
jogold Jun 24, 2019
d1c31bf
Merge branch 'master' into ecs-secrets
jogold Jun 26, 2019
3c75145
Merge branch 'master' into ecs-secrets
Jul 1, 2019
a242dca
Merge branch 'master' into ecs-secrets
jogold Jul 3, 2019
67b0abe
Merge branch 'ecs-secrets' of github.com:jogold/aws-cdk into ecs-secrets
jogold Jul 3, 2019
f8d0efc
scheduled task
jogold Jul 3, 2019
f3cb37a
typo
jogold Jul 4, 2019
39909d7
typo
jogold Jul 4, 2019
43f8244
typo
jogold Jul 4, 2019
3a6a374
Merge branch 'master' into ecs-secrets
jogold Jul 22, 2019
f300b28
secrets and non breaking
jogold Jul 22, 2019
bcc350a
JSDoc
jogold Jul 22, 2019
17e0964
non breaking renderContainerDefinition
jogold Jul 22, 2019
d629929
renderKV
jogold Jul 22, 2019
80d4495
base props
jogold Jul 22, 2019
199de19
polymorphism
jogold Jul 22, 2019
037f9e4
base props
jogold Jul 22, 2019
6cca252
JSDoc for queue env vars
jogold Jul 23, 2019
aedbbb8
make base props internal
jogold Jul 23, 2019
410cfd5
remove note about scheduled fargate tasks
jogold Jul 23, 2019
5f5cf12
update README
jogold Jul 23, 2019
6298b4d
Merge branch 'master' into ecs-secrets
Jul 23, 2019
865d7f2
revert base props
jogold Jul 23, 2019
a2032a6
Merge branch 'ecs-secrets' of github.com:jogold/aws-cdk into ecs-secrets
jogold Jul 23, 2019
f27f8c5
Merge branch 'master' into ecs-secrets
jogold Jul 25, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
import { AddressRecordTarget, ARecord, IHostedZone } from '@aws-cdk/aws-route53';
import route53targets = require('@aws-cdk/aws-route53-targets');
import cdk = require('@aws-cdk/core');
import { BaseProps } from './props';

export enum LoadBalancerType {
APPLICATION,
Expand All @@ -13,17 +14,7 @@ export enum LoadBalancerType {
/**
* Base properties for load-balanced Fargate and ECS services
*/
export interface LoadBalancedServiceBaseProps {
/**
* The cluster where your service will be deployed
*/
readonly cluster: ecs.ICluster;

/**
* The image to start.
*/
readonly image: ecs.ContainerImage;

export interface LoadBalancedServiceBaseProps extends BaseProps {
/**
* The container port of the application load balancer attached to your Fargate service. Corresponds to container port mapping.
*
Expand Down
23 changes: 23 additions & 0 deletions packages/@aws-cdk/aws-ecs-patterns/lib/base/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import ecs = require('@aws-cdk/aws-ecs');

/**
* Base properties for service and task.
*/
export interface BaseProps {
/**
* The cluster where your service will be deployed
*/
readonly cluster: ecs.ICluster;

/**
* The image to start.
*/
readonly image: ecs.ContainerImage;

/**
* Secret environment variables to pass to the container
*
* @default - No secret environment variables.
*/
readonly secrets?: { [key: string]: ecs.Secret };
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,12 @@ import autoscaling = require('@aws-cdk/aws-applicationautoscaling');
import ecs = require('@aws-cdk/aws-ecs');
import sqs = require('@aws-cdk/aws-sqs');
import cdk = require('@aws-cdk/core');
import { BaseProps } from './props';

/**
* Properties to define a queue processing service
*/
export interface QueueProcessingServiceBaseProps {
/**
* Cluster where service will be deployed
*/
readonly cluster: ecs.ICluster;

/**
* The image to start.
*/
readonly image: ecs.ContainerImage;

export interface QueueProcessingServiceBaseProps extends BaseProps {
/**
* The CMD value to pass to the container. A string with commands delimited by commas.
*
Expand Down Expand Up @@ -89,18 +80,27 @@ export abstract class QueueProcessingServiceBase extends cdk.Construct {
* Environment variables that will include the queue name
*/
public readonly environment: { [key: string]: string };

/**
* Secret environment variables
*/
public readonly secrets?: { [key: string]: ecs.Secret };

/**
* The minimum number of tasks to run
*/
public readonly desiredCount: number;

/**
* The maximum number of instances for autoscaling to scale up to
*/
public readonly maxCapacity: number;

/**
* The scaling interval for autoscaling based off an SQS Queue size
*/
public readonly scalingSteps: autoscaling.ScalingInterval[];

/**
* The AwsLogDriver to use for logging if logging is enabled.
*/
Expand All @@ -122,6 +122,7 @@ export abstract class QueueProcessingServiceBase extends cdk.Construct {

// Add the queue name to environment variables
this.environment = { ...(props.environment || {}), QUEUE_NAME: this.sqsQueue.queueName };
this.secrets = props.secrets;

// Determine the desired task count (minimum) and maximum scaling capacity
this.desiredCount = props.desiredTaskCount || 1;
Expand Down
13 changes: 2 additions & 11 deletions packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,9 @@ import { ICluster } from '@aws-cdk/aws-ecs';
import events = require('@aws-cdk/aws-events');
import eventsTargets = require('@aws-cdk/aws-events-targets');
import cdk = require('@aws-cdk/core');
import { BaseProps } from './props';

export interface ScheduledTaskBaseProps {
/**
* The cluster where your service will be deployed.
*/
readonly cluster: ecs.ICluster;

/**
* The image to start.
*/
readonly image: ecs.ContainerImage;

export interface ScheduledTaskBaseProps extends BaseProps {
/**
* The schedule or rate (frequency) that determines when CloudWatch Events
* runs the rule. For more information, see Schedule Expression Syntax for
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class LoadBalancedEc2Service extends LoadBalancedServiceBase {
memoryLimitMiB: props.memoryLimitMiB,
memoryReservationMiB: props.memoryReservationMiB,
environment: props.environment,
secrets: props.secrets,
logging: this.logDriver,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class QueueProcessingEc2Service extends QueueProcessingServiceBase {
cpu: props.cpu,
command: props.command,
environment: this.environment,
secrets: this.secrets,
logging: this.logDriver
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class ScheduledEc2Task extends ScheduledTaskBase {
cpu: props.cpu,
command: props.command,
environment: props.environment,
secrets: props.secrets,
logging: this.createAWSLogDriver(this.node.id)
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export class LoadBalancedFargateService extends LoadBalancedServiceBase {
const container = taskDefinition.addContainer(containerName, {
image: props.image,
logging: this.logDriver,
environment: props.environment
environment: props.environment,
secrets: props.secrets,
});

container.addPortMappings({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class QueueProcessingFargateService extends QueueProcessingServiceBase {
image: props.image,
command: props.command,
environment: this.environment,
secrets: this.secrets,
logging: this.logDriver
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class ScheduledFargateTask extends ScheduledTaskBase {
image: props.image,
command: props.command,
environment: props.environment,
secrets: props.secrets,
logging: this.createAWSLogDriver(this.node.id)
});

Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-ecs-patterns/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './base/props';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this exported? The base interface e doesn’t need to be public

Copy link
Contributor Author

@jogold jogold Jul 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you do this with jsii?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, need to add @internal for this to work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if you just not export this file here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error TS9999: JSII: Unable to resolve referenced type '@aws-cdk/aws-ecs-patterns.BaseProps'. Type may be @internal or unexported

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahhh... apologies for the hassle... Let's revert this base struct... On second thought, it does not really make sense for these to be shared between the patterns...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry!


export * from './ecs/queue-processing-ecs-service';
export * from './fargate/queue-processing-fargate-service';
export * from './base/queue-processing-service-base';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -665,11 +665,7 @@
"Cpu": 1,
"Environment": [
{
"Name": "name",
"Value": "TRIGGER"
},
{
"Name": "value",
"Name": "TRIGGER",
"Value": "CloudWatch Events"
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class EventStack extends cdk.Stack {
desiredTaskCount: 2,
memoryLimitMiB: 512,
cpu: 1,
environment: { name: 'TRIGGER', value: 'CloudWatch Events' },
environment: { TRIGGER: 'CloudWatch Events' },
schedule: events.Schedule.rate(cdk.Duration.minutes(1)),
});
/// !hide
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export = {
desiredTaskCount: 2,
memoryLimitMiB: 512,
cpu: 2,
environment: { name: 'TRIGGER', value: 'CloudWatch Events' },
environment: { TRIGGER: 'CloudWatch Events' },
schedule: events.Schedule.expression('rate(1 minute)')
});

Expand All @@ -111,11 +111,7 @@ export = {
Cpu: 2,
Environment: [
{
Name: "name",
Value: "TRIGGER"
},
{
Name: "value",
Name: "TRIGGER",
Value: "CloudWatch Events"
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,7 @@
{
"Environment": [
{
"Name": "name",
"Value": "TRIGGER"
},
{
"Name": "value",
"Name": "TRIGGER",
"Value": "CloudWatch Events"
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class EventStack extends cdk.Stack {
desiredTaskCount: 2,
memoryLimitMiB: 512,
cpu: 256,
environment: { name: 'TRIGGER', value: 'CloudWatch Events' },
environment: { TRIGGER: 'CloudWatch Events' },
schedule: events.Schedule.rate(cdk.Duration.minutes(2)),
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export = {
desiredTaskCount: 2,
memoryLimitMiB: 512,
cpu: 2,
environment: { name: 'TRIGGER', value: 'CloudWatch Events' },
environment: { TRIGGER: 'CloudWatch Events' },
schedule: events.Schedule.expression('rate(1 minute)')
});

Expand All @@ -103,11 +103,7 @@ export = {
{
Environment: [
{
Name: "name",
Value: "TRIGGER"
},
{
Name: "value",
Name: "TRIGGER",
Value: "CloudWatch Events"
}
],
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export class TaskDefinition extends TaskDefinitionBase {
});

const taskDef = new CfnTaskDefinition(this, 'Resource', {
containerDefinitions: Lazy.anyValue({ produce: () => this.containers.map(x => x.renderContainerDefinition()) }),
containerDefinitions: Lazy.anyValue({ produce: () => this.containers.map(x => x.renderContainerDefinition(this)) }),
volumes: Lazy.anyValue({ produce: () => this.volumes }),
executionRoleArn: Lazy.stringValue({ produce: () => this.executionRole && this.executionRole.roleArn }),
family: this.family,
Expand Down
55 changes: 53 additions & 2 deletions packages/@aws-cdk/aws-ecs/lib/container-definition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import iam = require('@aws-cdk/aws-iam');
import secretsmanager = require('@aws-cdk/aws-secretsmanager');
import ssm = require('@aws-cdk/aws-ssm');
import cdk = require('@aws-cdk/core');
import { NetworkMode, TaskDefinition } from './base/task-definition';
import { ContainerImage, ContainerImageConfig } from './container-image';
Expand All @@ -7,6 +9,36 @@ import { LinuxParameters } from './linux-parameters';
import { LogDriver, LogDriverConfig } from './log-drivers/log-driver';

/**
* A secret environment variable.
*/
export abstract class Secret {
/**
* Creates an environment variable value from a parameter stored in AWS
* Systems Manager Parameter Store.
*/
public static fromSsmParameter(parameter: ssm.IParameter): Secret {
return {
arn: parameter.parameterArn,
grantRead: grantee => parameter.grantRead(grantee),
};
}

/**
* Creates a environment variable value from a secret stored in AWS Secrets
* Manager.
*/
public static fromSecretsManager(secret: secretsmanager.ISecret): Secret {
return {
arn: secret.secretArn,
grantRead: grantee => secret.grantRead(grantee),
};
}

public abstract readonly arn: string;
public abstract grantRead(grantee: iam.IGrantable): iam.Grant;
}

/*
* The options for creating a container definition.
*/
export interface ContainerDefinitionOptions {
Expand Down Expand Up @@ -89,6 +121,13 @@ export interface ContainerDefinitionOptions {
*/
readonly environment?: { [key: string]: string };

/**
* The secret environment variables to pass to the container.
*
* @default - No secret environment variables.
*/
readonly secrets?: { [key: string]: Secret };

/**
* Specifies whether the container is marked essential.
*
Expand Down Expand Up @@ -412,8 +451,10 @@ export class ContainerDefinition extends cdk.Construct {

/**
* Render this container definition to a CloudFormation object
*
* @param taskDefinition [disable-awslint:ref-via-interface] (made optional to avoid breaking change)
*/
public renderContainerDefinition(): CfnTaskDefinition.ContainerDefinitionProperty {
public renderContainerDefinition(taskDefinition?: TaskDefinition): CfnTaskDefinition.ContainerDefinitionProperty {
return {
command: this.props.command,
cpu: this.props.cpu,
Expand All @@ -439,7 +480,17 @@ export class ContainerDefinition extends cdk.Construct {
volumesFrom: this.volumesFrom.map(renderVolumeFrom),
workingDirectory: this.props.workingDirectory,
logConfiguration: this.logDriverConfig,
environment: this.props.environment && renderKV(this.props.environment, 'name', 'value'),
environment: this.props.environment && renderKV(this.props.environment, 'name', 'value'),
secrets: this.props.secrets && Object.entries(this.props.secrets)
.map(([k, v]) => {
if (taskDefinition) {
v.grantRead(taskDefinition.obtainExecutionRole());
}
return {
name: k,
valueFrom: v.arn
};
}),
extraHosts: this.props.extraHosts && renderKV(this.props.extraHosts, 'hostname', 'ipAddress'),
healthCheck: this.props.healthCheck && renderHealthCheck(this.props.healthCheck),
links: this.links,
Expand Down
Loading