mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
Merge pull request #3527 from HyperBrain/filter-self-reference
Persist self references with "${self:}" and restore it correctly
This commit is contained in:
commit
4e752fbc5e
@ -3,6 +3,7 @@
|
||||
const path = require('path');
|
||||
const BbPromise = require('bluebird');
|
||||
const _ = require('lodash');
|
||||
const findReferences = require('../../utils/findReferences');
|
||||
|
||||
module.exports = {
|
||||
extendedValidate() {
|
||||
@ -17,7 +18,11 @@ module.exports = {
|
||||
}
|
||||
const state = this.serverless
|
||||
.utils.readFileSync(serviceStateFilePath);
|
||||
const selfReferences = findReferences(state.service, '${self:}');
|
||||
_.forEach(selfReferences, ref => _.set(state.service, ref, this.serverless.service));
|
||||
|
||||
_.assign(this.serverless.service, state.service);
|
||||
|
||||
this.serverless.service.package.artifactDirectoryName = state.package.artifactDirectoryName;
|
||||
// only restore the default artifact path if the user is not using a custom path
|
||||
if (!_.isEmpty(state.package.artifact) && this.serverless.service.artifact) {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
const BbPromise = require('bluebird');
|
||||
const path = require('path');
|
||||
const _ = require('lodash');
|
||||
const findReferences = require('../../utils/findReferences');
|
||||
|
||||
module.exports = {
|
||||
saveServiceState() {
|
||||
@ -20,8 +21,14 @@ module.exports = {
|
||||
)
|
||||
);
|
||||
|
||||
const strippedService = _.assign(
|
||||
{}, _.omit(this.serverless.service, ['serverless', 'package'])
|
||||
);
|
||||
const selfReferences = findReferences(strippedService, this.serverless.service);
|
||||
_.forEach(selfReferences, refPath => _.set(strippedService, refPath, '${self:}'));
|
||||
|
||||
const state = {
|
||||
service: _.assign({}, _.omit(this.serverless.service, ['serverless', 'package'])),
|
||||
service: strippedService,
|
||||
package: {
|
||||
individually: this.serverless.service.package.individually,
|
||||
artifactDirectoryName: this.serverless.service.package.artifactDirectoryName,
|
||||
|
||||
@ -66,4 +66,38 @@ describe('#saveServiceState()', () => {
|
||||
.to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove self references correctly', () => {
|
||||
const filePath = path.join(
|
||||
awsPackage.serverless.config.servicePath,
|
||||
'.serverless',
|
||||
'service-state.json'
|
||||
);
|
||||
|
||||
serverless.service.custom = {
|
||||
mySelfRef: serverless.service,
|
||||
};
|
||||
|
||||
return awsPackage.saveServiceState().then(() => {
|
||||
const expectedStateFileContent = {
|
||||
service: {
|
||||
provider: {
|
||||
compiledCloudFormationTemplate: 'compiled content',
|
||||
},
|
||||
custom: {
|
||||
mySelfRef: '${self:}',
|
||||
},
|
||||
},
|
||||
package: {
|
||||
individually: false,
|
||||
artifactDirectoryName: 'artifact-directory',
|
||||
artifact: 'service.zip',
|
||||
},
|
||||
};
|
||||
|
||||
expect(getServiceStateFileNameStub.calledOnce).to.equal(true);
|
||||
expect(writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent))
|
||||
.to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
44
lib/plugins/aws/utils/findReferences.js
Normal file
44
lib/plugins/aws/utils/findReferences.js
Normal file
@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Find all objects with a given value within a given root object.
|
||||
* The search is implemented non-recursive to prevent stackoverflows and will
|
||||
* do a complete deep search including arrays.
|
||||
* @param root {Object} Root object for search
|
||||
* @param value {Object} Value to search
|
||||
* @returns {Array<String>} Paths to all self references found within the object
|
||||
*/
|
||||
function findReferences(root, value) {
|
||||
const visitedObjects = [];
|
||||
const resourcePaths = [];
|
||||
const stack = [{ propValue: root, path: '' }];
|
||||
|
||||
while (!_.isEmpty(stack)) {
|
||||
const property = stack.pop();
|
||||
|
||||
_.forOwn(property.propValue, (propValue, key) => {
|
||||
let propKey;
|
||||
if (_.isArray(property.propValue)) {
|
||||
propKey = `[${key}]`;
|
||||
} else {
|
||||
propKey = _.isEmpty(property.path) ? `${key}` : `.${key}`;
|
||||
}
|
||||
if (propValue === value) {
|
||||
resourcePaths.push(`${property.path}${propKey}`);
|
||||
} else if (_.isObject(propValue)) {
|
||||
// Prevent circular references
|
||||
if (_.includes(visitedObjects, propValue)) {
|
||||
return;
|
||||
}
|
||||
visitedObjects.push(propValue);
|
||||
stack.push({ propValue, path: `${property.path}${propKey}` });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return resourcePaths;
|
||||
}
|
||||
|
||||
module.exports = findReferences;
|
||||
88
lib/plugins/aws/utils/findReferences.test.js
Normal file
88
lib/plugins/aws/utils/findReferences.test.js
Normal file
@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const _ = require('lodash');
|
||||
const findReferences = require('./findReferences');
|
||||
|
||||
describe('#findReferences()', () => {
|
||||
it('should succeed on invalid input', () => {
|
||||
const withoutArgs = findReferences();
|
||||
const nullArgs = findReferences(null);
|
||||
|
||||
expect(withoutArgs).to.be.a('Array').to.have.lengthOf(0);
|
||||
expect(nullArgs).to.be.a('Array').have.lengthOf(0);
|
||||
});
|
||||
|
||||
it('should return paths', () => {
|
||||
const testObject = {
|
||||
prop1: 'test',
|
||||
array1: [
|
||||
{
|
||||
prop1: 'hit',
|
||||
prop2: 4,
|
||||
},
|
||||
'hit',
|
||||
[
|
||||
{
|
||||
prop1: null,
|
||||
prop2: 'hit',
|
||||
},
|
||||
],
|
||||
],
|
||||
prop2: {
|
||||
prop1: 'foo',
|
||||
prop2: {
|
||||
prop1: 'hit',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const expectedResult = [
|
||||
'array1[0].prop1',
|
||||
'array1[1]',
|
||||
'array1[2][0].prop2',
|
||||
'prop2.prop2.prop1',
|
||||
];
|
||||
const paths = findReferences(testObject, 'hit');
|
||||
|
||||
expect(paths).to.be.a('Array').to.have.lengthOf(4);
|
||||
expect(_.every(paths, path => _.includes(expectedResult, path))).to.equal(true);
|
||||
});
|
||||
|
||||
it('should not fail with circular references', () => {
|
||||
const testObject = {
|
||||
prop1: 'test',
|
||||
array1: [
|
||||
{
|
||||
prop1: 'hit',
|
||||
prop2: 4,
|
||||
},
|
||||
'hit',
|
||||
[
|
||||
{
|
||||
prop1: null,
|
||||
prop2: 'hit',
|
||||
},
|
||||
],
|
||||
],
|
||||
prop2: {
|
||||
prop1: 'foo',
|
||||
prop2: {
|
||||
prop1: 'hit',
|
||||
},
|
||||
},
|
||||
};
|
||||
testObject.array1.push(testObject.prop2);
|
||||
|
||||
const expectedResult = [
|
||||
'array1[0].prop1',
|
||||
'array1[1]',
|
||||
'array1[2][0].prop2',
|
||||
'prop2.prop2.prop1',
|
||||
];
|
||||
const paths = findReferences(testObject, 'hit');
|
||||
|
||||
expect(paths).to.be.a('Array').to.have.lengthOf(4);
|
||||
expect(_.every(paths, path => _.includes(expectedResult, path))).to.equal(true);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user