Skip to content

Commit

Permalink
Multiple event definitions for existing S3 bucket
Browse files Browse the repository at this point in the history
  • Loading branch information
pmuens committed Jul 30, 2019
1 parent 9282618 commit c062763
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 76 deletions.
46 changes: 25 additions & 21 deletions lib/plugins/aws/customResources/resources/s3/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,55 +17,59 @@ function handler(event, context) {

function create(event, context) {
const { Region, AccountId } = getEnvironment(context);
const { FunctionName, BucketName, BucketConfig } = event.ResourceProperties;
const { ResourceLogicalId, FunctionName, BucketName, BucketConfigs } = event.ResourceProperties;

const lambdaArn = getLambdaArn(Region, AccountId, FunctionName);

return addPermission({
functionName: FunctionName,
bucketName: BucketName,
region: Region,
}).then(() =>
updateConfiguration({
lambdaArn,
region: Region,
functionName: FunctionName,
bucketName: BucketName,
bucketConfig: BucketConfig,
})
);
}) // TODO add more fine-grained error-handling
.catch(() => null)
.finally(() =>
updateConfiguration({
lambdaArn,
region: Region,
resourceLogicalId: ResourceLogicalId,
bucketName: BucketName,
bucketConfigs: BucketConfigs,
})
);
}

function update(event, context) {
const { Region, AccountId } = getEnvironment(context);
const { FunctionName, BucketName, BucketConfig } = event.ResourceProperties;
const { ResourceLogicalId, FunctionName, BucketName, BucketConfigs } = event.ResourceProperties;

const lambdaArn = getLambdaArn(Region, AccountId, FunctionName);

return updateConfiguration({
lambdaArn,
region: Region,
functionName: FunctionName,
resourceLogicalId: ResourceLogicalId,
bucketName: BucketName,
bucketConfig: BucketConfig,
bucketConfigs: BucketConfigs,
});
}

function remove(event, context) {
const { Region } = getEnvironment(context);
const { FunctionName, BucketName } = event.ResourceProperties;
const { ResourceLogicalId, FunctionName, BucketName } = event.ResourceProperties;

return removePermission({
functionName: FunctionName,
bucketName: BucketName,
region: Region,
}).then(() =>
removeConfiguration({
region: Region,
functionName: FunctionName,
bucketName: BucketName,
})
);
}) // TODO add more fine-grained error-handling
.catch(() => null)
.finally(() =>
removeConfiguration({
region: Region,
resourceLogicalId: ResourceLogicalId,
bucketName: BucketName,
})
);
}

module.exports = {
Expand Down
34 changes: 18 additions & 16 deletions lib/plugins/aws/customResources/resources/s3/lib/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
const crypto = require('crypto');
const AWS = require('aws-sdk');

function generateId(functionName, bucketConfig) {
function generateId(resourceLogicalId, bucketConfig) {
const md5 = crypto
.createHash('md5')
.update(JSON.stringify(bucketConfig))
.digest('hex');
return `${functionName}-${md5}`;
return `${resourceLogicalId}-${md5}`;
}

function createFilter(config) {
Expand Down Expand Up @@ -46,26 +46,28 @@ function getConfiguration(config) {
}

function updateConfiguration(config) {
const { lambdaArn, functionName, bucketName, bucketConfig, region } = config;
const { lambdaArn, resourceLogicalId, bucketName, bucketConfigs, region } = config;
const s3 = new AWS.S3({ region });
const Bucket = bucketName;

return getConfiguration(config).then(NotificationConfiguration => {
// remove configurations for this specific function
NotificationConfiguration.LambdaFunctionConfigurations = NotificationConfiguration.LambdaFunctionConfigurations.filter(
conf => !conf.Id.startsWith(functionName)
conf => !conf.Id.startsWith(resourceLogicalId)
);

// add the event to the existing NotificationConfiguration
const Events = [bucketConfig.Event];
const LambdaFunctionArn = lambdaArn;
const Id = generateId(functionName, bucketConfig);
const Filter = createFilter(bucketConfig);
NotificationConfiguration.LambdaFunctionConfigurations.push({
Events,
LambdaFunctionArn,
Filter,
Id,
// add the events to the existing NotificationConfiguration
bucketConfigs.forEach(bucketConfig => {
const Events = [bucketConfig.Event];
const LambdaFunctionArn = lambdaArn;
const Id = generateId(resourceLogicalId, bucketConfig);
const Filter = createFilter(bucketConfig);
NotificationConfiguration.LambdaFunctionConfigurations.push({
Events,
LambdaFunctionArn,
Filter,
Id,
});
});

const payload = {
Expand All @@ -77,14 +79,14 @@ function updateConfiguration(config) {
}

function removeConfiguration(config) {
const { functionName, bucketName, region } = config;
const { resourceLogicalId, bucketName, region } = config;
const s3 = new AWS.S3({ region });
const Bucket = bucketName;

return getConfiguration(config).then(NotificationConfiguration => {
// remove configurations for this specific function
NotificationConfiguration.LambdaFunctionConfigurations = NotificationConfiguration.LambdaFunctionConfigurations.filter(
conf => !conf.Id.startsWith(functionName)
conf => !conf.Id.startsWith(resourceLogicalId)
);

const payload = {
Expand Down
4 changes: 2 additions & 2 deletions lib/plugins/aws/lib/naming.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,8 +468,8 @@ module.exports = {
`${this.getNormalizedFunctionName(this.getCustomResourceS3HandlerFunctionName())}`
);
},
getCustomResourceS3ResourceLogicalId(functionName, idx) {
return `${this.getNormalizedFunctionName(functionName)}CustomS3${idx}`;
getCustomResourceS3ResourceLogicalId(functionName) {
return `${this.getNormalizedFunctionName(functionName)}CustomS3`;
},
// Cognito User Pool
getCustomResourceCognitoUserPoolHandlerFunctionName() {
Expand Down
5 changes: 2 additions & 3 deletions lib/plugins/aws/lib/naming.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -755,9 +755,8 @@ describe('#naming()', () => {
describe('#getCustomResourceS3ResourceLogicalId()', () => {
it('should return the logical id of the S3 custom resouce', () => {
const functionName = 'my-function';
const index = 1;
expect(sdk.naming.getCustomResourceS3ResourceLogicalId(functionName, index)).to.equal(
'MyDashfunctionCustomS31'
expect(sdk.naming.getCustomResourceS3ResourceLogicalId(functionName)).to.equal(
'MyDashfunctionCustomS3'
);
});
});
Expand Down
68 changes: 40 additions & 28 deletions lib/plugins/aws/package/compile/events/s3/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,13 @@ class AwsCompileS3Events {
const functionObj = service.getFunction(functionName);
const FunctionName = functionObj.name;

let numEventsForFunc = 0;
if (functionObj.events) {
functionObj.events.forEach((event, idx) => {
functionObj.events.forEach(event => {
if (event.s3 && _.isObject(event.s3) && event.s3.existing) {
let rules = null;
idx = idx += 1;
numEventsForFunc++;
const { Resources } = compiledCloudFormationTemplate;
const bucket = event.s3.bucket;
const notificationEvent = event.s3.event || 's3:ObjectCreated:*';
funcUsesExistingS3Bucket = true;
Expand All @@ -227,38 +229,48 @@ class AwsCompileS3Events {
const eventFunctionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
const customResourceFunctionLogicalId = this.provider.naming.getCustomResourceS3HandlerFunctionLogicalId();
const customS3ResourceLogicalId = this.provider.naming.getCustomResourceS3ResourceLogicalId(
functionName,
idx
functionName
);

const customS3Resource = {
[customS3ResourceLogicalId]: {
Type: 'Custom::S3',
Version: 1.0,
DependsOn: [eventFunctionLogicalId, customResourceFunctionLogicalId],
Properties: {
ServiceToken: {
'Fn::GetAtt': [customResourceFunctionLogicalId, 'Arn'],
},
FunctionName,
BucketName: bucket,
BucketConfig: {
Event: notificationEvent,
Rules: rules,
let customS3Resource;
if (numEventsForFunc === 1) {
customS3Resource = {
[customS3ResourceLogicalId]: {
Type: 'Custom::S3',
Version: 1.0,
DependsOn: [eventFunctionLogicalId, customResourceFunctionLogicalId],
Properties: {
ServiceToken: {
'Fn::GetAtt': [customResourceFunctionLogicalId, 'Arn'],
},
FunctionName,
ResourceLogicalId: customS3ResourceLogicalId,
BucketName: bucket,
BucketConfigs: [
{
Event: notificationEvent,
Rules: rules,
},
],
},
},
},
};
};

_.merge(compiledCloudFormationTemplate.Resources, customS3Resource);
iamRoleStatements.push({
Effect: 'Allow',
Resource: {
'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, `:s3:::${bucket}`]],
},
Action: ['s3:PutBucketNotification', 's3:GetBucketNotification'],
});
} else {
Resources[customS3ResourceLogicalId].Properties.BucketConfigs.push({
Event: notificationEvent,
Rules: rules,
});
}

iamRoleStatements.push({
Effect: 'Allow',
Resource: {
'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, `:s3:::${bucket}`]],
},
Action: ['s3:PutBucketNotification', 's3:GetBucketNotification'],
});
_.merge(Resources, customS3Resource);
}
});
}
Expand Down

0 comments on commit c062763

Please sign in to comment.