Skip to content

Instantly share code, notes, and snippets.

@lusentis
Last active September 29, 2016 16:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lusentis/c140b86c0942343dadb0fcd61ee7637c to your computer and use it in GitHub Desktop.
Save lusentis/c140b86c0942343dadb0fcd61ee7637c to your computer and use it in GitHub Desktop.
CloudFormation snippet to create a Lambda function which replicates a DynamoDB table
function replicator(replicatorInternalId, sourceLogicalName, destinationPhysicalName, destinationArn) {
const lambdaInlineCode = `
'use strict';
console.log('Loading function');
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB({});
exports.handler = (event, context, callback) => {
console.log('Received event:', JSON.stringify(event, null, 2));
Promise.all(event.Records.map(record => {
if (record.eventName !== 'MODIFY' && record.eventName !== 'INSERT') {
return;
}
const image = record.dynamodb.NewImage;
const key = record.dynamodb.Keys;
const updates = {};
Object.keys(image).forEach(attributeName => {
if (Object.keys(key).indexOf(attributeName) !== -1) {
// skip the primary key
return;
}
updates[attributeName] = {
Action: 'PUT', // @BUG handle "REMOVE" event by diffing with OldImage
Value: image[attributeName]
};
});
return dynamo.updateItem({
TableName: '${destinationPhysicalName}',
Key: key,
AttributeUpdates: updates,
}).promise();
}))
.then(result => {
console.log('final result', result);
callback(null, 'Successfully processed', event.Records.length, 'records.');
})
.catch(err => {
console.log('Error running replicator:', err);
callback(err.message);
});
};
`;
const resourcesSuffix = sourceLogicalName[0].toUpperCase() + sourceLogicalName.slice(1) + '' + replicatorInternalId;
const roleTemplate = {
[`LambdaRoleForReplicator${resourcesSuffix}`]: {
'Type': 'AWS::IAM::Role',
'Properties': {
'AssumeRolePolicyDocument': {
'Version': '2012-10-17',
'Statement': [{
'Effect': 'Allow',
'Principal': {
'Service': ['lambda.amazonaws.com']
},
'Action': ['sts:AssumeRole']
}]
},
'Path': '/',
'Policies': [{
'PolicyName': 'dawson-policy',
'PolicyDocument': {
'Version': '2012-10-17',
'Statement': [{
'Effect': 'Allow',
'Action': [
'dynamodb:DescribeStream',
'dynamodb:GetRecords',
'dynamodb:GetShardIterator',
'dynamodb:ListStreams'
],
'Resource': [{ 'Fn::GetAtt': [sourceLogicalName, 'StreamArn'] }]
}, {
'Effect': 'Allow',
'Action': [
'dynamodb:UpdateItem'
],
'Resource': [destinationArn]
}]
}
}]
}
}
};
const lambdaTemplate = {
[`LambdaReplicator${resourcesSuffix}`]: {
'Type': 'AWS::Lambda::Function',
'Properties': {
'Handler': 'index.handler',
'Role': { 'Fn::GetAtt': [`LambdaRoleForReplicator${resourcesSuffix}`, 'Arn'] },
'Code': { ZipFile: lambdaInlineCode },
'Runtime': 'nodejs4.3',
'MemorySize': 256,
'Timeout': 10
}
}
};
const eventMappingTemplate = {
[`LambdaEventSourceMappingReplicator${resourcesSuffix}`]: {
"Type": "AWS::Lambda::EventSourceMapping",
"Properties": {
"BatchSize": 1,
"Enabled": true,
"EventSourceArn": {
"Fn::GetAtt": [sourceLogicalName, "StreamArn"]
},
"FunctionName": { "Ref": `LambdaReplicator${resourcesSuffix}` },
"StartingPosition": "TRIM_HORIZON"
}
}
};
return {
...roleTemplate,
...lambdaTemplate,
...eventMappingTemplate
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment