Skip to content

Commit

Permalink
commit fix
Browse files Browse the repository at this point in the history
  • Loading branch information
comcalvi committed Oct 29, 2021
1 parent 7f19400 commit 8815c83
Show file tree
Hide file tree
Showing 14 changed files with 1,353 additions and 93 deletions.
11 changes: 7 additions & 4 deletions packages/@aws-cdk/aws-autoscaling-hooktargets/lib/lambda-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@ export class FunctionHook implements autoscaling.ILifecycleHookTarget {
constructor(private readonly fn: lambda.IFunction, private readonly encryptionKey?: kms.IKey) {
}

public bind(scope: Construct, lifecycleHook: autoscaling.ILifecycleHook): autoscaling.LifecycleHookTargetConfig {
const topic = new sns.Topic(scope, 'Topic', {
public bind(_scope: Construct, options: autoscaling.BindHookTargetOptions): autoscaling.LifecycleHookTargetConfig {
const topic = new sns.Topic(_scope, 'Topic', {
masterKey: this.encryptionKey,
});

const role = autoscaling.createRole(_scope, options.role);

// Per: https://docs.aws.amazon.com/sns/latest/dg/sns-key-management.html#sns-what-permissions-for-sse
// Topic's grantPublish() is in a base class that does not know there is a kms key, and so does not
// grant appropriate permissions to the kms key. We do that here to ensure the correct permissions
// are in place.
this.encryptionKey?.grant(lifecycleHook.role, 'kms:Decrypt', 'kms:GenerateDataKey');
this.encryptionKey?.grant(role, 'kms:Decrypt', 'kms:GenerateDataKey');
topic.addSubscription(new subs.LambdaSubscription(this.fn));
return new TopicHook(topic).bind(scope, lifecycleHook);
return new TopicHook(topic).bind(_scope, { lifecycleHook: options.lifecycleHook, role });
}
}
11 changes: 8 additions & 3 deletions packages/@aws-cdk/aws-autoscaling-hooktargets/lib/queue-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ export class QueueHook implements autoscaling.ILifecycleHookTarget {
constructor(private readonly queue: sqs.IQueue) {
}

public bind(_scope: Construct, lifecycleHook: autoscaling.ILifecycleHook): autoscaling.LifecycleHookTargetConfig {
this.queue.grantSendMessages(lifecycleHook.role);
return { notificationTargetArn: this.queue.queueArn };
public bind(_scope: Construct, options: autoscaling.BindHookTargetOptions): autoscaling.LifecycleHookTargetConfig {
const role = autoscaling.createRole(_scope, options.role);
this.queue.grantSendMessages(role);

return {
notificationTargetArn: this.queue.queueArn,
createdRole: role,
};
}
}
11 changes: 8 additions & 3 deletions packages/@aws-cdk/aws-autoscaling-hooktargets/lib/topic-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ export class TopicHook implements autoscaling.ILifecycleHookTarget {
constructor(private readonly topic: sns.ITopic) {
}

public bind(_scope: Construct, lifecycleHook: autoscaling.ILifecycleHook): autoscaling.LifecycleHookTargetConfig {
this.topic.grantPublish(lifecycleHook.role);
return { notificationTargetArn: this.topic.topicArn };
public bind(_scope: Construct, options: autoscaling.BindHookTargetOptions): autoscaling.LifecycleHookTargetConfig {
const role = autoscaling.createRole(_scope, options.role);
this.topic.grantPublish(role);

return {
notificationTargetArn: this.topic.topicArn,
createdRole: role,
};
}
}
233 changes: 224 additions & 9 deletions packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '@aws-cdk/assert-internal/jest';
import { arrayWith } from '@aws-cdk/assert-internal';
import * as autoscaling from '@aws-cdk/aws-autoscaling';
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 lambda from '@aws-cdk/aws-lambda';
import * as sns from '@aws-cdk/aws-sns';
Expand All @@ -10,7 +11,7 @@ import { Stack } from '@aws-cdk/core';
import * as hooks from '../lib';


describe('given an AutoScalingGroup', () => {
describe('given an AutoScalingGroup and no role', () => {
let stack: Stack;
let asg: autoscaling.AutoScalingGroup;

Expand All @@ -25,7 +26,24 @@ describe('given an AutoScalingGroup', () => {
});
});

test('can use queue as hook target', () => {
afterEach(() => {
expect(stack).toHaveResource('AWS::IAM::Role', {
AssumeRolePolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'autoscaling.amazonaws.com',
},
},
],
},
});
});

test('can use queue as hook target without providing a role', () => {
// GIVEN
const queue = new sqs.Queue(stack, 'Queue');

Expand All @@ -37,9 +55,36 @@ describe('given an AutoScalingGroup', () => {

// THEN
expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] } });
expect(stack).toHaveResource('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: [
'sqs:SendMessage',
'sqs:GetQueueAttributes',
'sqs:GetQueueUrl',
],
Effect: 'Allow',
Resource: {
'Fn::GetAtt': [
'Queue4A7E3555',
'Arn',
],
},
},
],
Version: '2012-10-17',
},
PolicyName: 'ASGLifecycleHookTransRoleDefaultPolicy43D7C82A',
Roles: [
{
Ref: 'ASGLifecycleHookTransRole71E0A219',
},
],
});
});

test('can use topic as hook target', () => {
test('can use topic as hook target without providing a role', () => {
// GIVEN
const topic = new sns.Topic(stack, 'Topic');

Expand All @@ -50,12 +95,30 @@ describe('given an AutoScalingGroup', () => {
});

// THEN
expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', {
NotificationTargetARN: { Ref: 'TopicBFC7AF6E' },
expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'TopicBFC7AF6E' } });
expect(stack).toHaveResource('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 'sns:Publish',
Effect: 'Allow',
Resource: {
Ref: 'TopicBFC7AF6E',
},
},
],
Version: '2012-10-17',
},
PolicyName: 'ASGLifecycleHookTransRoleDefaultPolicy43D7C82A',
Roles: [
{
Ref: 'ASGLifecycleHookTransRole71E0A219',
},
],
});
});

test('can use Lambda function as hook target', () => {
test('can use Lambda function as hook target without providing a role', () => {
// GIVEN
const fn = new lambda.Function(stack, 'Fn', {
code: lambda.Code.fromInline('foo'),
Expand All @@ -70,14 +133,32 @@ describe('given an AutoScalingGroup', () => {
});

// THEN
expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', {
NotificationTargetARN: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' },
});
expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' } });
expect(stack).toHaveResource('AWS::SNS::Subscription', {
Protocol: 'lambda',
TopicArn: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' },
Endpoint: { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] },
});
expect(stack).toHaveResource('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 'sns:Publish',
Effect: 'Allow',
Resource: {
Ref: 'ASGLifecycleHookTransTopic9B0D4842',
},
},
],
Version: '2012-10-17',
},
PolicyName: 'ASGLifecycleHookTransRoleDefaultPolicy43D7C82A',
Roles: [
{
Ref: 'ASGLifecycleHookTransRole71E0A219',
},
],
});
});

test('can use Lambda function as hook target with encrypted SNS', () => {
Expand Down Expand Up @@ -124,5 +205,139 @@ describe('given an AutoScalingGroup', () => {
},
});
});
});

describe('given an AutoScalingGroup and a role', () => {
let stack: Stack;
let asg: autoscaling.AutoScalingGroup;

beforeEach(() => {
stack = new Stack();

const vpc = new ec2.Vpc(stack, 'VPC');
asg = new autoscaling.AutoScalingGroup(stack, 'ASG', {
vpc,
instanceType: new ec2.InstanceType('t2.micro'),
machineImage: new ec2.AmazonLinuxImage(),
});
});

afterEach(() => {
expect(stack).toHaveResource('AWS::IAM::Role', {
AssumeRolePolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'custom.role.domain.com',
},
},
],
},
});
});

test('can use queue as hook target with a role', () => {
// GIVEN
const queue = new sqs.Queue(stack, 'Queue');
const myrole = new iam.Role(stack, 'MyRole', {
assumedBy: new iam.ServicePrincipal('custom.role.domain.com'),
});
// WHEN
asg.addLifecycleHook('Trans', {
lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING,
notificationTarget: new hooks.QueueHook(queue),
role: myrole,
});

// THEN
expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] } });
});

test('can use topic as hook target with a role', () => {
// GIVEN
const topic = new sns.Topic(stack, 'Topic');
const myrole = new iam.Role(stack, 'MyRole', {
assumedBy: new iam.ServicePrincipal('custom.role.domain.com'),
});

// WHEN
asg.addLifecycleHook('Trans', {
lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING,
notificationTarget: new hooks.TopicHook(topic),
role: myrole,
});

// THEN
expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'TopicBFC7AF6E' } });
expect(stack).toHaveResource('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 'sns:Publish',
Effect: 'Allow',
Resource: {
Ref: 'TopicBFC7AF6E',
},
},
],
Version: '2012-10-17',
},
PolicyName: 'MyRoleDefaultPolicyA36BE1DD',
Roles: [
{
Ref: 'MyRoleF48FFE04',
},
],
});
});

test('can use Lambda function as hook target with a role', () => {
// GIVEN
const fn = new lambda.Function(stack, 'Fn', {
code: lambda.Code.fromInline('foo'),
runtime: lambda.Runtime.NODEJS_10_X,
handler: 'index.index',
});
const myrole = new iam.Role(stack, 'MyRole', {
assumedBy: new iam.ServicePrincipal('custom.role.domain.com'),
});

// WHEN
asg.addLifecycleHook('Trans', {
lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING,
notificationTarget: new hooks.FunctionHook(fn),
role: myrole,
});

// THEN
expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' } });
expect(stack).toHaveResource('AWS::SNS::Subscription', {
Protocol: 'lambda',
TopicArn: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' },
Endpoint: { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] },
});
expect(stack).toHaveResource('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 'sns:Publish',
Effect: 'Allow',
Resource: {
Ref: 'ASGLifecycleHookTransTopic9B0D4842',
},
},
],
Version: '2012-10-17',
},
PolicyName: 'MyRoleDefaultPolicyA36BE1DD',
Roles: [
{
Ref: 'MyRoleF48FFE04',
},
],
});
});
});
Loading

0 comments on commit 8815c83

Please sign in to comment.