Skip to content

Commit

Permalink
feat(AWS Deploy): Retry retryable SDK errors in custom resources (#8338)
Browse files Browse the repository at this point in the history
  • Loading branch information
pratik-vii committed Oct 7, 2020
1 parent 60cfa75 commit a3ebc01
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 142 deletions.
@@ -1,8 +1,7 @@
'use strict';

const ApiGateway = require('aws-sdk/clients/apigateway');
const Iam = require('aws-sdk/clients/iam');
const { getEnvironment, handlerWrapper, wait } = require('../utils');
const { awsRequest, wait } = require('../utils');
const { getEnvironment, handlerWrapper } = require('../utils');

function handler(event, context) {
if (event.RequestType === 'Create') {
Expand All @@ -19,8 +18,9 @@ async function create(event, context) {
const { RoleArn } = event.ResourceProperties;
const { Partition: partition, AccountId: accountId, Region: region } = getEnvironment(context);

const apiGateway = new ApiGateway({ region });
const assignedRoleArn = (await apiGateway.getAccount().promise()).cloudwatchRoleArn;
const assignedRoleArn = (
await awsRequest({ name: 'APIGateway', params: { region } }, 'getAccount')
).cloudwatchRoleArn;

let roleArn = `arn:${partition}:iam::${accountId}:role/serverlessApiGatewayCloudWatchRole`;
if (RoleArn) {
Expand All @@ -32,33 +32,29 @@ async function create(event, context) {

const roleName = roleArn.slice(roleArn.lastIndexOf('/') + 1);

const iam = new Iam();

const attachedPolicies = await (async () => {
try {
return (await iam.listAttachedRolePolicies({ RoleName: roleName }).promise())
return (await awsRequest('IAM', 'listAttachedRolePolicies', { RoleName: roleName }))
.AttachedPolicies;
} catch (error) {
if (error.code === 'NoSuchEntity') {
// Role doesn't exist yet, create;
await iam
.createRole({
AssumeRolePolicyDocument: JSON.stringify({
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: {
Service: ['apigateway.amazonaws.com'],
},
Action: ['sts:AssumeRole'],
await awsRequest('IAM', 'createRole', {
AssumeRolePolicyDocument: JSON.stringify({
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: {
Service: ['apigateway.amazonaws.com'],
},
],
}),
Path: '/',
RoleName: roleName,
})
.promise();
Action: ['sts:AssumeRole'],
},
],
}),
Path: '/',
RoleName: roleName,
});
return [];
}
throw error;
Expand All @@ -68,12 +64,10 @@ async function create(event, context) {
if (
!attachedPolicies.some(policy => policy.PolicyArn === apiGatewayPushToCloudWatchLogsPolicyArn)
) {
await iam
.attachRolePolicy({
PolicyArn: apiGatewayPushToCloudWatchLogsPolicyArn,
RoleName: roleName,
})
.promise();
await awsRequest('IAM', 'attachRolePolicy', {
PolicyArn: apiGatewayPushToCloudWatchLogsPolicyArn,
RoleName: roleName,
});
}
}

Expand All @@ -82,17 +76,15 @@ async function create(event, context) {

const updateAccount = async (counter = 1) => {
try {
await apiGateway
.updateAccount({
patchOperations: [
{
op: 'replace',
path: '/cloudwatchRoleArn',
value: roleArn,
},
],
})
.promise();
await awsRequest({ name: 'APIGateway', params: { region } }, 'updateAccount', {
patchOperations: [
{
op: 'replace',
path: '/cloudwatchRoleArn',
value: roleArn,
},
],
});
} catch (error) {
if (counter < 10) {
// Observed fails with errors marked as non-retryable. Still they're outcome of
Expand Down
@@ -1,6 +1,6 @@
'use strict';

const Lambda = require('aws-sdk/clients/lambda');
const { awsRequest } = require('../../utils');

function getStatementId(functionName, userPoolName) {
const normalizedUserPoolName = userPoolName.toLowerCase().replace(/[.:*\s]/g, '');
Expand All @@ -13,25 +13,23 @@ function getStatementId(functionName, userPoolName) {

function addPermission(config) {
const { functionName, userPoolName, partition, region, accountId, userPoolId } = config;
const lambda = new Lambda({ region });
const params = {
const payload = {
Action: 'lambda:InvokeFunction',
FunctionName: functionName,
Principal: 'cognito-idp.amazonaws.com',
StatementId: getStatementId(functionName, userPoolName),
SourceArn: `arn:${partition}:cognito-idp:${region}:${accountId}:userpool/${userPoolId}`,
};
return lambda.addPermission(params).promise();
return awsRequest({ name: 'Lambda', params: { region } }, 'addPermission', payload);
}

function removePermission(config) {
const { functionName, userPoolName, region } = config;
const lambda = new Lambda({ region });
const params = {
const payload = {
FunctionName: functionName,
StatementId: getStatementId(functionName, userPoolName),
};
return lambda.removePermission(params).promise();
return awsRequest({ name: 'Lambda', params: { region } }, 'removePermission', payload);
}

module.exports = {
Expand Down
@@ -1,6 +1,6 @@
'use strict';

const CognitoIdentityServiceProvider = require('aws-sdk/clients/cognitoidentityserviceprovider');
const { awsRequest } = require('../../utils');

function getUpdateConfigFromCurrentSetup(currentSetup) {
const updatedConfig = Object.assign({}, currentSetup);
Expand Down Expand Up @@ -28,46 +28,42 @@ function getUpdateConfigFromCurrentSetup(currentSetup) {

function findUserPoolByName(config) {
const { userPoolName, region } = config;
const cognito = new CognitoIdentityServiceProvider({ region });

const params = {
const payload = {
MaxResults: 60,
};

function recursiveFind(nextToken) {
if (nextToken) params.NextToken = nextToken;
return cognito
.listUserPools(params)
.promise()
.then(result => {
const matches = result.UserPools.filter(pool => pool.Name === userPoolName);
if (matches.length) {
return matches.shift();
}
if (result.NextToken) return recursiveFind(result.NextToken);
return null;
});
if (nextToken) payload.NextToken = nextToken;
return awsRequest(
{ name: 'CognitoIdentityServiceProvider', params: { region } },
'listUserPools',
payload
).then(result => {
const matches = result.UserPools.filter(pool => pool.Name === userPoolName);
if (matches.length) {
return matches.shift();
}
if (result.NextToken) return recursiveFind(result.NextToken);
return null;
});
}

return recursiveFind();
}

function getConfiguration(config) {
const { region } = config;
const cognito = new CognitoIdentityServiceProvider({ region });

return findUserPoolByName(config).then(userPool =>
cognito
.describeUserPool({
UserPoolId: userPool.Id,
})
.promise()
awsRequest({ name: 'CognitoIdentityServiceProvider', params: { region } }, 'describeUserPool', {
UserPoolId: userPool.Id,
})
);
}

function updateConfiguration(config) {
const { lambdaArn, userPoolConfigs, region } = config;
const cognito = new CognitoIdentityServiceProvider({ region });

return getConfiguration(config).then(res => {
const UserPoolId = res.UserPool.Id;
Expand All @@ -88,13 +84,16 @@ function updateConfiguration(config) {
LambdaConfig,
});

return cognito.updateUserPool(updatedConfig).promise();
return awsRequest(
{ name: 'CognitoIdentityServiceProvider', params: { region } },
'updateUserPool',
updatedConfig
);
});
}

function removeConfiguration(config) {
const { lambdaArn, region } = config;
const cognito = new CognitoIdentityServiceProvider({ region });

return getConfiguration(config).then(res => {
const UserPoolId = res.UserPool.Id;
Expand All @@ -111,7 +110,11 @@ function removeConfiguration(config) {
LambdaConfig,
});

return cognito.updateUserPool(updatedConfig).promise();
return awsRequest(
{ name: 'CognitoIdentityServiceProvider', params: { region } },
'updateUserPool',
updatedConfig
);
});
}

Expand Down
@@ -1,6 +1,6 @@
'use strict';

const EventBridge = require('aws-sdk/clients/eventbridge');
const { awsRequest } = require('../../utils');
const { getEventBusName, getEventBusTargetId } = require('./utils');

function createEventBus(config) {
Expand All @@ -10,12 +10,9 @@ function createEventBus(config) {
if (eventBus.startsWith('arn')) {
return Promise.resolve();
}
const eventBridge = new EventBridge({ region });
return eventBridge
.createEventBus({
Name: eventBus,
})
.promise();
return awsRequest({ name: 'EventBridge', params: { region } }, 'createEventBus', {
Name: eventBus,
});
}
return Promise.resolve();
}
Expand All @@ -28,50 +25,40 @@ function deleteEventBus(config) {
return Promise.resolve();
}

const eventBridge = new EventBridge({ region });
return eventBridge
.deleteEventBus({
Name: eventBus,
})
.promise();
return awsRequest({ name: 'EventBridge', params: { region } }, 'deleteEventBus', {
Name: eventBus,
});
}
return Promise.resolve();
}

function updateRuleConfiguration(config) {
const { ruleName, eventBus, pattern, schedule, region } = config;
const eventBridge = new EventBridge({ region });

const EventBusName = getEventBusName(eventBus);

return eventBridge
.putRule({
Name: ruleName,
EventBusName,
EventPattern: JSON.stringify(pattern),
ScheduleExpression: schedule,
State: 'ENABLED',
})
.promise();
return awsRequest({ name: 'EventBridge', params: { region } }, 'putRule', {
Name: ruleName,
EventBusName,
EventPattern: JSON.stringify(pattern),
ScheduleExpression: schedule,
State: 'ENABLED',
});
}

function removeRuleConfiguration(config) {
const { ruleName, eventBus, region } = config;
const eventBridge = new EventBridge({ region });

const EventBusName = getEventBusName(eventBus);

return eventBridge
.deleteRule({
Name: ruleName,
EventBusName,
})
.promise();
return awsRequest({ name: 'EventBridge', params: { region } }, 'deleteRule', {
Name: ruleName,
EventBusName,
});
}

function updateTargetConfiguration(config) {
const { lambdaArn, ruleName, eventBus, input, inputPath, inputTransformer, region } = config;
const eventBridge = new EventBridge({ region });

const EventBusName = getEventBusName(eventBus);

Expand All @@ -89,29 +76,24 @@ function updateTargetConfiguration(config) {
}

return removeTargetConfiguration(config).then(() =>
eventBridge
.putTargets({
Rule: ruleName,
EventBusName,
Targets: [target],
})
.promise()
awsRequest({ name: 'EventBridge', params: { region } }, 'putTargets', {
Rule: ruleName,
EventBusName,
Targets: [target],
})
);
}

function removeTargetConfiguration(config) {
const { ruleName, eventBus, region } = config;
const eventBridge = new EventBridge({ region });

const EventBusName = getEventBusName(eventBus);

return eventBridge
.removeTargets({
Ids: [getEventBusTargetId(ruleName)],
Rule: ruleName,
EventBusName,
})
.promise();
return awsRequest({ name: 'EventBridge', params: { region } }, 'removeTargets', {
Ids: [getEventBusTargetId(ruleName)],
Rule: ruleName,
EventBusName,
});
}

module.exports = {
Expand Down

0 comments on commit a3ebc01

Please sign in to comment.