Skip to content

Commit b54e2d3

Browse files
author
Frank Schmid
committed
Fix DynamoDB streams
Resolve deferred outputs before uploading the template Modified logging to show resolutions Use non-locking deferred outputs for event references.
1 parent 09a9cfc commit b54e2d3

File tree

7 files changed

+170
-79
lines changed

7 files changed

+170
-79
lines changed

.eslintrc

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
env:
3+
es6: true
4+
node: true
5+
mocha: true
6+
7+
extends:
8+
- eslint:recommended
9+
- plugin:lodash/recommended
10+
- plugin:import/errors
11+
- plugin:import/warnings
12+
13+
plugins:
14+
- promise
15+
- lodash
16+
- import
17+
18+
rules:
19+
indent:
20+
- error
21+
- tab
22+
-
23+
MemberExpression: off
24+
linebreak-style:
25+
- error
26+
- unix
27+
semi:
28+
- error
29+
- always
30+
promise/always-return: error
31+
promise/no-return-wrap: error
32+
promise/param-names: error
33+
promise/catch-or-return: error
34+
promise/no-native: off
35+
promise/no-nesting: off
36+
promise/no-promise-in-callback: warn
37+
promise/no-callback-in-promise: warn
38+
promise/avoid-new: warn
39+
import/no-named-as-default: off
40+
lodash/import-scope: off
41+
lodash/preferred-alias: off
42+
lodash/prop-shorthand: off
43+
lodash/prefer-lodash-method:
44+
- error
45+
-
46+
ignoreObjects:
47+
- BbPromise

.eslintrc.json

-48
This file was deleted.

index.js

+30-27
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@
55
*/
66

77
const BbPromise = require('bluebird')
8-
, _ = require('lodash')
9-
, Path = require('path')
10-
, validate = require('./lib/validate')
11-
, configureAliasStack = require('./lib/configureAliasStack')
12-
, createAliasStack = require('./lib/createAliasStack')
13-
, updateAliasStack = require('./lib/updateAliasStack')
14-
, aliasRestructureStack = require('./lib/aliasRestructureStack')
15-
, stackInformation = require('./lib/stackInformation')
16-
, listAliases = require('./lib/listAliases')
17-
, removeAlias = require('./lib/removeAlias')
18-
, logs = require('./lib/logs')
19-
, collectUserResources = require('./lib/collectUserResources')
20-
, uploadAliasArtifacts = require('./lib/uploadAliasArtifacts')
21-
, updateFunctionAlias = require('./lib/updateFunctionAlias');
8+
, _ = require('lodash')
9+
, Path = require('path')
10+
, validate = require('./lib/validate')
11+
, configureAliasStack = require('./lib/configureAliasStack')
12+
, createAliasStack = require('./lib/createAliasStack')
13+
, updateAliasStack = require('./lib/updateAliasStack')
14+
, aliasRestructureStack = require('./lib/aliasRestructureStack')
15+
, stackInformation = require('./lib/stackInformation')
16+
, listAliases = require('./lib/listAliases')
17+
, removeAlias = require('./lib/removeAlias')
18+
, logs = require('./lib/logs')
19+
, collectUserResources = require('./lib/collectUserResources')
20+
, uploadAliasArtifacts = require('./lib/uploadAliasArtifacts')
21+
, updateFunctionAlias = require('./lib/updateFunctionAlias')
22+
, deferredOutputs = require('./lib/deferredOutputs');
2223

2324
class AwsAlias {
2425

@@ -40,18 +41,18 @@ class AwsAlias {
4041
* Load stack helpers from Serverless installation.
4142
*/
4243
const monitorStack = require(
43-
Path.join(this._serverless.config.serverlessPath,
44-
'plugins',
45-
'aws',
46-
'lib',
47-
'monitorStack')
44+
Path.join(this._serverless.config.serverlessPath,
45+
'plugins',
46+
'aws',
47+
'lib',
48+
'monitorStack')
4849
);
4950
const setBucketName = require(
50-
Path.join(this._serverless.config.serverlessPath,
51-
'plugins',
52-
'aws',
53-
'lib',
54-
'setBucketName')
51+
Path.join(this._serverless.config.serverlessPath,
52+
'plugins',
53+
'aws',
54+
'lib',
55+
'setBucketName')
5556
);
5657

5758
_.assign(
@@ -69,7 +70,8 @@ class AwsAlias {
6970
uploadAliasArtifacts,
7071
updateFunctionAlias,
7172
setBucketName,
72-
monitorStack
73+
monitorStack,
74+
deferredOutputs
7375
);
7476

7577
this._commands = {
@@ -116,10 +118,11 @@ class AwsAlias {
116118
.then(this.createAliasStack),
117119

118120
'after:aws:deploy:deploy:uploadArtifacts': () => BbPromise.bind(this)
119-
.then(this.setBucketName)
120-
.then(this.uploadAliasArtifacts),
121+
.then(() => BbPromise.resolve()),
121122

122123
'after:aws:deploy:deploy:updateStack': () => BbPromise.bind(this)
124+
.then(this.setBucketName)
125+
.then(this.uploadAliasArtifacts)
123126
.then(this.updateAliasStack),
124127

125128
'before:deploy:function:initialize': () => BbPromise.bind(this)

lib/deferredOutputs.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
3+
/**
4+
* Handle deferred output resolution.
5+
* Some references to outputs of the base stack cannot be done
6+
* by Fn::ImportValue because they will change from deployment to deployment.
7+
* So we resolve them after the base stack has been deployed and set their
8+
* values accordingly.
9+
*/
10+
11+
const _ = require('lodash');
12+
const BbPromise = require('bluebird');
13+
14+
const deferredOutputs = {};
15+
16+
module.exports = {
17+
18+
/**
19+
* Register a deferred output
20+
* @param {string} sourceOutput
21+
* @param {Object} targetObject
22+
* @param {string} targetPropertyName
23+
*/
24+
addDeferredOutput(sourceOutput, targetObject, targetPropertyName) {
25+
this.options.verbose && this.serverless.cli.log(`Register deferred output ${sourceOutput} -> ${targetPropertyName}`);
26+
27+
deferredOutputs[sourceOutput] = deferredOutputs[sourceOutput] || [];
28+
deferredOutputs[sourceOutput].push({
29+
target: targetObject,
30+
property: targetPropertyName
31+
});
32+
},
33+
34+
resolveDeferredOutputs() {
35+
this.options.verbose && this.serverless.cli.log('Resolving deferred outputs');
36+
37+
if (_.isEmpty(deferredOutputs)) {
38+
return BbPromise.resolve();
39+
}
40+
41+
return this.aliasGetExports()
42+
.then(cfExports => {
43+
_.forOwn(deferredOutputs, (references, output) => {
44+
if (_.has(cfExports, output)) {
45+
const value = cfExports[output];
46+
this.options.verbose && this.serverless.cli.log(` ${output} -> ${value}`);
47+
_.forEach(references, reference => {
48+
_.set(reference.target, reference.property, value);
49+
});
50+
}
51+
else {
52+
this.serverless.cli.log(`ERROR: Output ${output} not found.`);
53+
}
54+
});
55+
return null;
56+
});
57+
}
58+
59+
};

lib/stackInformation.js

+24
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,30 @@ module.exports = {
141141
});
142142
},
143143

144+
aliasGetExports() {
145+
const fetchExports = (result, token) => {
146+
const params = {};
147+
if (token) {
148+
params.NextToken = token;
149+
}
150+
return this._provider.request('CloudFormation', 'listExports', params)
151+
.then(cfData => {
152+
const newResult = _.reduce(cfData.Exports, (__, cfExport) => {
153+
__[cfExport.Name] = cfExport.Value;
154+
return __;
155+
}, result);
156+
157+
if (cfData.NextToken) {
158+
return fetchExports(newResult, cfData.NextToken);
159+
}
160+
161+
return newResult;
162+
});
163+
};
164+
165+
return fetchExports({});
166+
},
167+
144168
aliasStackLoadCurrentCFStackAndDependencies() {
145169
return BbPromise.join(
146170
BbPromise.bind(this).then(this.aliasStackLoadCurrentTemplate),

lib/stackops/events.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
1515

1616
const subscriptions = _.assign({}, _.pickBy(_.get(stageStack, 'Resources', {}), [ 'Type', 'AWS::Lambda::EventSourceMapping' ]));
1717

18+
this.options.verbose && this._serverless.cli.log('Processing event source subscriptions');
19+
1820
_.forOwn(subscriptions, (subscription, name) => {
1921
// Reference alias as FunctionName
2022
const functionNameRef = utils.findAllReferences(_.get(subscription, 'Properties.FunctionName'));
@@ -44,14 +46,17 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
4446
Name: `${stackName}-${resourceRefName}`
4547
}
4648
};
47-
// Add the outpur to the referenced alias outputs
49+
// Add the output to the referenced alias outputs
4850
const aliasOutputs = JSON.parse(aliasStack.Outputs.AliasOutputs.Value);
4951
aliasOutputs.push(resourceRefName);
5052
aliasStack.Outputs.AliasOutputs.Value = JSON.stringify(aliasOutputs);
5153
// Replace the reference with the cross stack reference
52-
subscription.Properties.EventSourceArn = {
53-
'Fn::ImportValue': `${stackName}-${resourceRefName}`
54-
};
54+
subscription.Properties.EventSourceArn = {};
55+
56+
// Event source ARNs can be volatile - e.g. DynamoDB and must not be referenced
57+
// with Fn::ImportValue which does not allow for changes. So we have to register
58+
// them for delayed lookup until the base stack has been updated.
59+
this.addDeferredOutput(`${stackName}-${resourceRefName}`, subscription.Properties, 'EventSourceArn');
5560

5661
// Remove mapping from stage stack
5762
delete stageStack.Resources[name];

lib/uploadAliasArtifacts.js

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ module.exports = {
3636
}
3737

3838
return BbPromise.bind(this)
39+
.then(this.resolveDeferredOutputs)
3940
.then(this.uploadAliasCloudFormationFile);
4041
},
4142

0 commit comments

Comments
 (0)