Skip to content

Commit

Permalink
Validate before deploy (#27)
Browse files Browse the repository at this point in the history
* Added validation that runs before compile #22

* Added validation for empty string on sqs and sns. #22 

* Switched normalize methods to use serverless provider naming #26

* Refactor for building resource logical ids #26
  • Loading branch information
gmetzker committed Jan 30, 2017
1 parent 2fd481c commit f8df164
Show file tree
Hide file tree
Showing 2 changed files with 507 additions and 271 deletions.
125 changes: 75 additions & 50 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ class Plugin {
}
};


}

resolveTargetArn(functionName, deadLetter) {
resolveTargetArn(functionName, deadLetter, resolveStackResources) {

let targetDefCount = 0;

Expand All @@ -52,14 +53,14 @@ class Plugin {

if (deadLetter.sqs !== undefined) {
return this.resolveTargetArnFromObject(functionName, {
GetResourceArn: `${Plugin.normalize(functionName)}DeadLetterQueue`
});
GetResourceArn: this.getLogicalIdForDlQueue(functionName)
}, resolveStackResources);
}

if (deadLetter.sns !== undefined) {
return this.resolveTargetArnFromObject(functionName, {
GetResourceArn: `${Plugin.normalize(functionName)}DeadLetterTopic`
});
GetResourceArn: this.getLogicalIdForDlTopic(functionName)
}, resolveStackResources);
}

const targetArn = deadLetter.targetArn;
Expand All @@ -73,7 +74,7 @@ class Plugin {

} else if (typeof targetArn === 'object') {

return this.resolveTargetArnFromObject(functionName, targetArn);
return this.resolveTargetArnFromObject(functionName, targetArn, resolveStackResources);
}

throw new Error(`Function property ${functionName}.deadLetter.targetArn is an unexpected type. This must be an object or string.`);
Expand All @@ -99,17 +100,18 @@ class Plugin {
return BbPromise.resolve(targetArn);
}

resolveTargetArnFromObject(functionName, targetArn) {
resolveTargetArnFromObject(functionName, targetArn, resolveStackResources) {

if (!targetArn.GetResourceArn) {
throw new Error(`Function property ${functionName}.deadLetter.targetArn object is missing GetResourceArn property.`);
}

if (!this.deploy) {
// If noDeploy options is set then there is no guarantee that the stack exists
// and no guarantee that the serverless.yml even matches the a previously deploy stack.
// So instead of returning the real arn we'll return a string that
// can be used for logging but not an actual ARN.
if (!resolveStackResources) {
// If the stack has not been deployed this we cannot get the resource arn
// from cloudformation yet so just return a display string (not a real arn).
// This can be used when:
// a) the --noDeploy option is set
// b) before stack deployment when we want to run this for simple validations.
return BbPromise.resolve(`\${GetResourceArn: ${targetArn.GetResourceArn}}`);
}

Expand Down Expand Up @@ -143,7 +145,7 @@ class Plugin {
});
}

buildDeadLetterUpdateParams(functionName) {
buildDeadLetterUpdateParams(functionName, resolveStackResources) {

const functionObj = this.serverless.service.getFunction(functionName);

Expand All @@ -153,7 +155,8 @@ class Plugin {

return BbPromise.bind(this)

.then(() => this.resolveTargetArn(functionName, functionObj.deadLetter))
.then(() => this.resolveTargetArn(functionName, functionObj.deadLetter,
resolveStackResources))

.then(targetArnString => BbPromise.resolve({
FunctionName: functionObj.name,
Expand All @@ -166,15 +169,14 @@ class Plugin {
setLambdaDeadLetterConfig() {

return BbPromise.mapSeries(this.serverless.service.getAllFunctions(), functionName =>
this.buildDeadLetterUpdateParams(functionName)
this.buildDeadLetterUpdateParams(functionName, this.deploy)

.then((deadLetterUpdateParams) => {

if (!deadLetterUpdateParams) {
return BbPromise.resolve();
}

const arnStr = deadLetterUpdateParams.DeadLetterConfig.TargetArn || '{none}';
let logPrefix;
let updateStep;

Expand All @@ -189,8 +191,10 @@ class Plugin {

return updateStep.then(() => {

const arnDisplayStr = deadLetterUpdateParams.DeadLetterConfig.TargetArn || '{none}';

this.serverless.cli.log(`${logPrefix} Function '${functionName}' ` +
`DeadLetterConfig.TargetArn: ${arnStr}`);
`DeadLetterConfig.TargetArn: ${arnDisplayStr}`);
});

}));
Expand All @@ -206,55 +210,73 @@ class Plugin {
return ['arn', 'aws', 'sqs', region, account, queueName].join(':');
}

validate(functionName) {
return this.buildDeadLetterUpdateParams(functionName, false);
}

compileFunctionDeadLetterResources() {
return BbPromise.mapSeries(this.serverless.service.getAllFunctions(), (functionName) => {
return BbPromise.mapSeries(this.serverless.service.getAllFunctions(), functionName =>

const functionObj = this.serverless.service.getFunction(functionName);
BbPromise.resolve()
.then(() => this.validate(functionName))
.then(() => this.compileFunctionDeadLetterResource(functionName))

if (functionObj.deadLetter === undefined) {
return BbPromise.resolve();
}
if (functionObj.deadLetter.sqs !== undefined) {
return this.compileFunctionDeadLetterQueue(functionName,
functionObj.deadLetter.sqs);
}
);
}

if (functionObj.deadLetter.sns !== undefined) {
return this.compileFunctionDeadLetterTopic(functionName,
functionObj.deadLetter.sns);
}
compileFunctionDeadLetterResource(functionName) {
const functionObj = this.serverless.service.getFunction(functionName);

if (functionObj.deadLetter === null) {
return BbPromise.resolve();
}

});


}
if (functionObj.deadLetter === undefined) {
return BbPromise.resolve();
}
if (functionObj.deadLetter.sqs !== undefined) {
return this.compileFunctionDeadLetterQueue(functionName,
functionObj.deadLetter.sqs);
}

static normalize(s) {
if (s === undefined || s === '') {
return '';
if (functionObj.deadLetter.sns !== undefined) {
return this.compileFunctionDeadLetterTopic(functionName,
functionObj.deadLetter.sns);
}

return s[0].toUpperCase() + s.substr(1);
return BbPromise.resolve();

}

static normalizeResourceName(name) {
return Plugin.normalize(name.replace(/[^0-9A-Za-z]/g, ''));
normalizeFunctionName(functionName) {
return this.provider.naming.getNormalizedFunctionName(functionName);
}

getLogicalIdForDlQueue(functionName) {
return `${this.normalizeFunctionName(functionName)}DeadLetterQueue`;
}
getLogicalIdForDlQueuePolicy(functionName) {
return `${this.normalizeFunctionName(functionName)}DeadLetterQueuePolicy`;
}
getLogicalIdForDlTopic(functionName) {
return `${this.normalizeFunctionName(functionName)}DeadLetterTopic`;
}

compileFunctionDeadLetterQueue(functionName, queueConfig) {

if (typeof queueConfig !== 'string') {
if (typeof queueConfig !== 'string' && queueConfig !== null) {
throw new Error(`Function property ${functionName}.deadLetter.sqs is an unexpected type. This must be a or string.`);
}

const queueName = queueConfig;
const queueName = (queueConfig || '').trim();

if (queueName.length < 1) {
throw new Error(`Function property ${functionName}.deadLetter.sqs must contain one or more characters.`);
}

const normalizedFnName = Plugin.normalize(functionName);
const queueLogicalId = `${normalizedFnName}DeadLetterQueue`;
const queuePolicyLogicalId = `${normalizedFnName}DeadLetterQueuePolicy`;
const functionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
const queueLogicalId = this.getLogicalIdForDlQueue(functionName);
const queuePolicyLogicalId = this.getLogicalIdForDlQueuePolicy(functionName);
const resources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources;

const queueResource = {
Expand Down Expand Up @@ -283,7 +305,7 @@ class Plugin {
Condition: {
ArnEquals: {
'aws:SourceArn': {
'Fn::GetAtt': [`${normalizedFnName}LambdaFunction`, 'Arn']
'Fn::GetAtt': [functionLogicalId, 'Arn']
}
}
}
Expand All @@ -298,14 +320,17 @@ class Plugin {

compileFunctionDeadLetterTopic(functionName, topicConfig) {

if (typeof topicConfig !== 'string') {
if (typeof topicConfig !== 'string' && topicConfig !== null) {
throw new Error(`Function property ${functionName}.deadLetter.sns is an unexpected type. This must be a or string.`);
}

const topicName = topicConfig;
const topicName = (topicConfig || '').trim();

if (topicName.length < 1) {
throw new Error(`Function property ${functionName}.deadLetter.sns must contain one or more characters.`);
}

const normalizedFnName = Plugin.normalize(functionName);
const topicLogicalId = `${normalizedFnName}DeadLetterTopic`;
const topicLogicalId = this.getLogicalIdForDlTopic(functionName);
const resources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources;

const topicResource = {
Expand Down
Loading

0 comments on commit f8df164

Please sign in to comment.