Skip to content

Commit

Permalink
feat: intrinsic func in compile sm
Browse files Browse the repository at this point in the history
  • Loading branch information
theburningmonk committed Apr 30, 2019
1 parent 2b006d6 commit 6c0128e
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 3 deletions.
55 changes: 52 additions & 3 deletions lib/deploy/stepFunctions/compileStateMachines.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
const _ = require('lodash');
const BbPromise = require('bluebird');

function randomName() {
const chars = 'abcdefghijklmnopqrstufwxyzABCDEFGHIJKLMNOPQRSTUFWXYZ1234567890';
const pwd = _.sampleSize(chars, 10);
return pwd.join('');
}

function isIntrinsic(obj) {
const isObject = typeof obj === 'object';
return isObject && Object.keys(obj).some((k) => k.startsWith('Fn::') || k.startsWith('Ref'));
return typeof obj === 'object' &&
Object.keys(obj).some((k) => k.startsWith('Fn::') || k.startsWith('Ref'));
}

function toTags(obj, serverless) {
Expand All @@ -26,6 +32,39 @@ function toTags(obj, serverless) {
return tags;
}

// return an iterable of
// [ ParamName, IntrinsicFunction ]
// e.g. [ 'mptFnX05Fb', { Ref: 'MyTopic' } ]
// this makes it easy to use _.fromPairs to construct an object afterwards
function* getIntrinsicFunctions(obj) {
// eslint-disable-next-line no-restricted-syntax
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const value = obj[key];

if (Array.isArray(value)) {
// eslint-disable-next-line guard-for-in, no-restricted-syntax
for (const idx in value) {
const innerFuncs = Array.from(getIntrinsicFunctions(value[idx]));
for (const x of innerFuncs) {
yield x;
}
}
} else if (isIntrinsic(value)) {
const paramName = randomName();
// eslint-disable-next-line no-param-reassign
obj[key] = `\${${paramName}}`;
yield [paramName, value];
} else if (typeof value === 'object') {
const innerFuncs = Array.from(getIntrinsicFunctions(value));
for (const x of innerFuncs) {
yield x;
}
}
}
}
}

module.exports = {
isIntrinsic,
compileStateMachines() {
Expand All @@ -42,7 +81,17 @@ module.exports = {
DefinitionString = JSON.stringify(stateMachineObj.definition)
.replace(/\\n|\\r|\\n\\r/g, '');
} else {
DefinitionString = JSON.stringify(stateMachineObj.definition, undefined, 2);
const functionMappings = Array.from(getIntrinsicFunctions(stateMachineObj.definition));
if (_.isEmpty(functionMappings)) {
DefinitionString = JSON.stringify(stateMachineObj.definition, undefined, 2);
} else {
DefinitionString = {
'Fn::Sub': [
JSON.stringify(stateMachineObj.definition, undefined, 2),
_.fromPairs(functionMappings),
],
};
}
}
} else {
const errorMessage = [
Expand Down
78 changes: 78 additions & 0 deletions lib/deploy/stepFunctions/compileStateMachines.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,4 +607,82 @@ describe('#compileStateMachines', () => {

expect(() => serverlessStepFunctions.compileStateMachines()).to.throw(Error);
});

it('should respect CloudFormation intrinsic functions for Resource', () => {
serverless.service.stepFunctions = {
stateMachines: {
myStateMachine: {
name: 'stateMachine',
definition: {
StartAt: 'Lambda',
States: {
Lambda: {
Type: 'Task',
Resource: {
Ref: 'MyFunction',
},
Next: 'Sns',
},
Sns: {
Type: 'Task',
Resource: 'arn:aws:states:::sns:publish',
Parameters: {
Message: {
'Fn::GetAtt': ['MyTopic', 'TopicName'],
},
TopicArn: {
Ref: 'MyTopic',
},
},
Next: 'Sqs',
},
Sqs: {
Type: 'Task',
Resource: 'arn:aws:states:::sqs:sendMessage',
Parameters: {
QueueUrl: {
Ref: 'MyQueue',
},
MessageBody: 'This is a static message',
},
End: true,
},
},
},
},
},
};

serverlessStepFunctions.compileStateMachines();
const stateMachine = serverlessStepFunctions.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.StateMachine;

expect(stateMachine.Properties.DefinitionString).to.haveOwnProperty('Fn::Sub');
expect(stateMachine.Properties.DefinitionString['Fn::Sub']).to.have.lengthOf(2);

const [json, params] = stateMachine.Properties.DefinitionString['Fn::Sub'];
const modifiedDefinition = JSON.parse(json);

const lambda = modifiedDefinition.States.Lambda;
expect(lambda.Resource.startsWith('${')).to.eq(true);
const functionParam = lambda.Resource.replace(/[${}]/g, '');
expect(params).to.haveOwnProperty(functionParam);
expect(params[functionParam]).to.eql({ Ref: 'MyFunction' });

const sns = modifiedDefinition.States.Sns;
expect(sns.Parameters.Message.startsWith('${')).to.eq(true);
const topicNameParam = sns.Parameters.Message.replace(/[${}]/g, '');
expect(params).to.haveOwnProperty(topicNameParam);
expect(params[topicNameParam]).to.eql({ 'Fn::GetAtt': ['MyTopic', 'TopicName'] });
expect(sns.Parameters.TopicArn.startsWith('${')).to.eq(true);
const topicArnParam = sns.Parameters.TopicArn.replace(/[${}]/g, '');
expect(params).to.haveOwnProperty(topicArnParam);
expect(params[topicArnParam]).to.eql({ Ref: 'MyTopic' });

const sqs = modifiedDefinition.States.Sqs;
expect(sqs.Parameters.QueueUrl.startsWith('${')).to.eq(true);
const queueUrlParam = sqs.Parameters.QueueUrl.replace(/[${}]/g, '');
expect(params[queueUrlParam]).to.eql({ Ref: 'MyQueue' });
});
});

0 comments on commit 6c0128e

Please sign in to comment.