Skip to content

Commit

Permalink
Merge branch 'master' into aws-iotevents-actions
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] committed Feb 17, 2022
2 parents 0657c89 + 60d6e66 commit 4892208
Show file tree
Hide file tree
Showing 42 changed files with 2,092 additions and 776 deletions.
11 changes: 8 additions & 3 deletions packages/@aws-cdk/assertions/lib/private/cyclic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,16 @@ function findExpressionDependencies(obj: any): Set<string> {
} else if (keys.length === 1 && keys[0] === 'Fn::Sub') {
const argument = x[keys[0]];
const pattern = Array.isArray(argument) ? argument[0] : argument;
for (const logId of logicalIdsInSubString(pattern)) {
ret.add(logId);

// pattern should always be a string, but we've encountered some cases in which
// it isn't. Better safeguard.
if (typeof pattern === 'string') {
for (const logId of logicalIdsInSubString(pattern)) {
ret.add(logId);
}
}
const contextDict = Array.isArray(argument) ? argument[1] : undefined;
if (contextDict) {
if (contextDict && typeof contextDict === 'object') {
Object.values(contextDict).forEach(recurse);
}
} else {
Expand Down
1 change: 0 additions & 1 deletion packages/@aws-cdk/assertions/lib/private/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export function findMessage(messages: Messages, constructPath: string, props: an
return {};
}

Object.values(result.matches).forEach((m) => handleTrace(m));
return result.matches;
}

Expand Down
18 changes: 2 additions & 16 deletions packages/@aws-cdk/assertions/test/annotations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ describe('Messages', () => {
let annotations: _Annotations;
beforeAll(() => {
stack = new Stack();
const foo = new CfnResource(stack, 'Foo', {
new CfnResource(stack, 'Foo', {
type: 'Foo::Bar',
properties: {
Fred: 'Thud',
},
});
foo.node.setContext('disable-stack-trace', false);

new CfnResource(stack, 'Bar', {
type: 'Foo::Bar',
Expand Down Expand Up @@ -61,11 +60,6 @@ describe('Messages', () => {
const result = annotations.findError('*', 'no message looks like this');
expect(result.length).toEqual(0);
});

test('trace is redacted', () => {
const result = annotations.findError('/Default/Foo', Match.anyValue());
expect(result[0].entry.trace).toEqual('redacted');
});
});

describe('hasWarning', () => {
Expand Down Expand Up @@ -165,15 +159,7 @@ describe('Multiple Messages on the Resource', () => {
const result2 = annotations.findError('/Default/Bar', Match.stringLikeRegexp('error:.*'));
expect(result2.length).toEqual(2);
const result3 = annotations.findWarning('/Default/Bar', 'warning: Foo::Bar is deprecated');
expect(result3).toEqual([{
level: 'warning',
entry: {
type: 'aws:cdk:warning',
data: 'warning: Foo::Bar is deprecated',
trace: 'redacted',
},
id: '/Default/Bar',
}]);
expect(result3[0].entry.data).toEqual('warning: Foo::Bar is deprecated');
});
});
class MyAspect implements IAspect {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class SingletonPolicy extends Construct implements iam.IGrantable {
private statements: { [key: string]: iam.PolicyStatement } = {};

private constructor(private readonly role: iam.IRole) {
super(role as unknown as cdk.Construct, SingletonPolicy.UUID);
super(role as unknown as Construct, SingletonPolicy.UUID);
this.grantPrincipal = role;
}

Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ const newContainer = taskDefinition.addContainer('container', {
secrets: { // Retrieved from AWS Secrets Manager or AWS Systems Manager Parameter Store at container start-up.
SECRET: ecs.Secret.fromSecretsManager(secret),
DB_PASSWORD: ecs.Secret.fromSecretsManager(dbSecret, 'password'), // Reference a specific JSON field, (requires platform version 1.4.0 or later for Fargate tasks)
API_KEY: ecs.Secret.fromSecretsManagerVersion(secret, { versionId: '12345' }, 'apiKey'), // Reference a specific version of the secret by its version id or version stage (requires platform version 1.4.0 or later for Fargate tasks)
PARAMETER: ecs.Secret.fromSsmParameter(parameter),
},
});
Expand Down
37 changes: 37 additions & 0 deletions packages/@aws-cdk/aws-ecs/lib/container-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ import { LogDriver, LogDriverConfig } from './log-drivers/log-driver';
// eslint-disable-next-line no-duplicate-imports, import/order
import { Construct as CoreConstruct } from '@aws-cdk/core';

/**
* Specify the secret's version id or version stage
*/
export interface SecretVersionInfo {
/**
* version id of the secret
*
* @default - use default version id
*/
readonly versionId?: string;
/**
* version stage of the secret
*
* @default - use default version stage
*/
readonly versionStage?: string;
}

/**
* A secret environment variable.
*/
Expand Down Expand Up @@ -47,6 +65,25 @@ export abstract class Secret {
};
}

/**
* Creates a environment variable value from a secret stored in AWS Secrets
* Manager.
*
* @param secret the secret stored in AWS Secrets Manager
* @param versionInfo the version information to reference the secret
* @param field the name of the field with the value that you want to set as
* the environment variable value. Only values in JSON format are supported.
* If you do not specify a JSON field, then the full content of the secret is
* used.
*/
public static fromSecretsManagerVersion(secret: secretsmanager.ISecret, versionInfo: SecretVersionInfo, field?: string): Secret {
return {
arn: `${secret.secretArn}:${field ?? ''}:${versionInfo.versionStage ?? ''}:${versionInfo.versionId ?? ''}`,
hasField: !!field,
grantRead: grantee => secret.grantRead(grantee),
};
}

/**
* The ARN of the secret
*/
Expand Down
60 changes: 60 additions & 0 deletions packages/@aws-cdk/aws-ecs/test/container-definition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,8 @@ describe('container definition', () => {
secrets: {
SECRET: ecs.Secret.fromSecretsManager(secret),
PARAMETER: ecs.Secret.fromSsmParameter(parameter),
SECRET_ID: ecs.Secret.fromSecretsManagerVersion(secret, { versionId: 'version-id' }),
SECRET_STAGE: ecs.Secret.fromSecretsManagerVersion(secret, { versionStage: 'version-stage' }),
},
});

Expand Down Expand Up @@ -1298,6 +1300,34 @@ describe('container definition', () => {
],
},
},
{
Name: 'SECRET_ID',
ValueFrom: {
'Fn::Join': [
'',
[
{
Ref: 'SecretA720EF05',
},
':::version-id',
],
],
},
},
{
Name: 'SECRET_STAGE',
ValueFrom: {
'Fn::Join': [
'',
[
{
Ref: 'SecretA720EF05',
},
'::version-stage:',
],
],
},
},
],
}),
],
Expand Down Expand Up @@ -1405,6 +1435,8 @@ describe('container definition', () => {
memoryLimitMiB: 1024,
secrets: {
SECRET_KEY: ecs.Secret.fromSecretsManager(secret, 'specificKey'),
SECRET_KEY_ID: ecs.Secret.fromSecretsManagerVersion(secret, { versionId: 'version-id' }, 'specificKey'),
SECRET_KEY_STAGE: ecs.Secret.fromSecretsManagerVersion(secret, { versionStage: 'version-stage' }, 'specificKey'),
},
});

Expand All @@ -1427,6 +1459,34 @@ describe('container definition', () => {
],
},
},
{
Name: 'SECRET_KEY_ID',
ValueFrom: {
'Fn::Join': [
'',
[
{
Ref: 'SecretA720EF05',
},
':specificKey::version-id',
],
],
},
},
{
Name: 'SECRET_KEY_STAGE',
ValueFrom: {
'Fn::Join': [
'',
[
{
Ref: 'SecretA720EF05',
},
':specificKey:version-stage:',
],
],
},
},
],
}),
],
Expand Down
17 changes: 16 additions & 1 deletion packages/@aws-cdk/aws-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,8 @@ const fn = lambda.Function.fromFunctionAttributes(this, 'Function', {
## Lambda with DLQ

A dead-letter queue can be automatically created for a Lambda function by
setting the `deadLetterQueueEnabled: true` configuration.
setting the `deadLetterQueueEnabled: true` configuration. In such case CDK creates
a `sqs.Queue` as `deadLetterQueue`.

```ts
const fn = new lambda.Function(this, 'MyFunction', {
Expand All @@ -537,6 +538,20 @@ const fn = new lambda.Function(this, 'MyFunction', {
});
```

You can also use a `sns.Topic` instead of an `sqs.Queue` as dead-letter queue:

```ts
import * as sns from '@aws-cdk/aws-sns';

const dlt = new sns.Topic(this, 'DLQ');
const fn = new lambda.Function(this, 'MyFunction', {
runtime: lambda.Runtime.NODEJS_12_X,
handler: 'index.handler',
code: lambda.Code.fromInline('// your code here'),
deadLetterTopic: dlt,
});
```

See [the AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/dlq.html)
to learn more about AWS Lambdas and DLQs.

Expand Down
72 changes: 55 additions & 17 deletions packages/@aws-cdk/aws-lambda/lib/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
import * as logs from '@aws-cdk/aws-logs';
import * as sns from '@aws-cdk/aws-sns';
import * as sqs from '@aws-cdk/aws-sqs';
import { Annotations, ArnFormat, CfnResource, Duration, Fn, Lazy, Names, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
Expand Down Expand Up @@ -188,11 +189,21 @@ export interface FunctionOptions extends EventInvokeConfigOptions {

/**
* The SQS queue to use if DLQ is enabled.
* If SNS topic is desired, specify `deadLetterTopic` property instead.
*
* @default - SQS queue with 14 day retention period if `deadLetterQueueEnabled` is `true`
*/
readonly deadLetterQueue?: sqs.IQueue;

/**
* The SNS topic to use as a DLQ.
* Note that if `deadLetterQueueEnabled` is set to `true`, an SQS queue will be created
* rather than an SNS topic. Using an SNS topic as a DLQ requires this property to be set explicitly.
*
* @default - no SNS topic
*/
readonly deadLetterTopic?: sns.ITopic;

/**
* Enable AWS X-Ray Tracing for Lambda Function.
*
Expand Down Expand Up @@ -573,10 +584,15 @@ export class Function extends FunctionBase {
public readonly grantPrincipal: iam.IPrincipal;

/**
* The DLQ associated with this Lambda Function (this is an optional attribute).
* The DLQ (as queue) associated with this Lambda Function (this is an optional attribute).
*/
public readonly deadLetterQueue?: sqs.IQueue;

/**
* The DLQ (as topic) associated with this Lambda Function (this is an optional attribute).
*/
public readonly deadLetterTopic?: sns.ITopic;

/**
* The architecture of this Lambda Function (this is an optional attribute and defaults to X86_64).
*/
Expand Down Expand Up @@ -673,7 +689,15 @@ export class Function extends FunctionBase {
this.addEnvironment(key, value);
}

this.deadLetterQueue = this.buildDeadLetterQueue(props);
// DLQ can be either sns.ITopic or sqs.IQueue
const dlqTopicOrQueue = this.buildDeadLetterQueue(props);
if (dlqTopicOrQueue !== undefined) {
if (this.isQueue(dlqTopicOrQueue)) {
this.deadLetterQueue = dlqTopicOrQueue;
} else {
this.deadLetterTopic = dlqTopicOrQueue;
}
}

let fileSystemConfigs: CfnFunction.FileSystemConfigProperty[] | undefined = undefined;
if (props.filesystem) {
Expand Down Expand Up @@ -712,7 +736,7 @@ export class Function extends FunctionBase {
environment: Lazy.uncachedAny({ produce: () => this.renderEnvironment() }),
memorySize: props.memorySize,
vpcConfig: this.configureVpc(props),
deadLetterConfig: this.buildDeadLetterConfig(this.deadLetterQueue),
deadLetterConfig: this.buildDeadLetterConfig(dlqTopicOrQueue),
tracingConfig: this.buildTracingConfig(props),
reservedConcurrentExecutions: props.reservedConcurrentExecutions,
imageConfig: undefinedIfNoKeys({
Expand Down Expand Up @@ -1031,31 +1055,45 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett
};
}

private buildDeadLetterQueue(props: FunctionProps) {
private isQueue(deadLetterQueue: sqs.IQueue | sns.ITopic): deadLetterQueue is sqs.IQueue {
return (<sqs.IQueue>deadLetterQueue).queueArn !== undefined;
}

private buildDeadLetterQueue(props: FunctionProps): sqs.IQueue | sns.ITopic | undefined {
if (!props.deadLetterQueue && !props.deadLetterQueueEnabled && !props.deadLetterTopic) {
return undefined;
}
if (props.deadLetterQueue && props.deadLetterQueueEnabled === false) {
throw Error('deadLetterQueue defined but deadLetterQueueEnabled explicitly set to false');
}

if (!props.deadLetterQueue && !props.deadLetterQueueEnabled) {
return undefined;
if (props.deadLetterTopic && (props.deadLetterQueue || props.deadLetterQueueEnabled !== undefined)) {
throw new Error('deadLetterQueue and deadLetterTopic cannot be specified together at the same time');
}

const deadLetterQueue = props.deadLetterQueue || new sqs.Queue(this, 'DeadLetterQueue', {
retentionPeriod: Duration.days(14),
});

this.addToRolePolicy(new iam.PolicyStatement({
actions: ['sqs:SendMessage'],
resources: [deadLetterQueue.queueArn],
}));
let deadLetterQueue: sqs.IQueue | sns.ITopic;
if (props.deadLetterTopic) {
deadLetterQueue = props.deadLetterTopic;
this.addToRolePolicy(new iam.PolicyStatement({
actions: ['sns:Publish'],
resources: [deadLetterQueue.topicArn],
}));
} else {
deadLetterQueue = props.deadLetterQueue || new sqs.Queue(this, 'DeadLetterQueue', {
retentionPeriod: Duration.days(14),
});
this.addToRolePolicy(new iam.PolicyStatement({
actions: ['sqs:SendMessage'],
resources: [deadLetterQueue.queueArn],
}));
}

return deadLetterQueue;
}

private buildDeadLetterConfig(deadLetterQueue?: sqs.IQueue) {
private buildDeadLetterConfig(deadLetterQueue?: sqs.IQueue | sns.ITopic) {
if (deadLetterQueue) {
return {
targetArn: deadLetterQueue.queueArn,
targetArn: this.isQueue(deadLetterQueue) ? deadLetterQueue.queueArn : deadLetterQueue.topicArn,
};
} else {
return undefined;
Expand Down
Loading

0 comments on commit 4892208

Please sign in to comment.