Skip to content

Commit 02c254f

Browse files
committed
Re-add ${param:...} variable
It was part of the dashboard plugin because I guess some values might come from it. I extracted only the interesting part without the dashboard stuff. Unit tests are ok. I tested it on few projects and it works as expected.
1 parent 54ddd08 commit 02c254f

File tree

3 files changed

+183
-6
lines changed

3 files changed

+183
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const memoizee = require('memoizee');
5+
const ensureString = require('type/string/ensure');
6+
const ServerlessError = require('../../../../serverless-error');
7+
8+
const resolveParams = memoizee(async (stage, serverlessInstance) => {
9+
const configParams = new Map(
10+
Object.entries(_.get(serverlessInstance.configurationInput, 'params') || {})
11+
);
12+
13+
const resultParams = Object.create(null);
14+
15+
for (const [name, value] of Object.entries(configParams.get(stage) || {})) {
16+
if (value == null) continue;
17+
if (resultParams[name] != null) continue;
18+
resultParams[name] = { value, type: 'configServiceStage' };
19+
}
20+
21+
for (const [name, value] of new Map(Object.entries(configParams.get('default') || {}))) {
22+
if (value == null) continue;
23+
if (resultParams[name] != null) continue;
24+
resultParams[name] = { value, type: 'configService' };
25+
}
26+
27+
return resultParams;
28+
});
29+
30+
module.exports = (serverlessInstance) => {
31+
return {
32+
resolve: async ({ address, resolveConfigurationProperty, options }) => {
33+
if (!address) {
34+
throw new ServerlessError(
35+
'Missing address argument in variable "param" source',
36+
'MISSING_PARAM_SOURCE_ADDRESS'
37+
);
38+
}
39+
address = ensureString(address, {
40+
Error: ServerlessError,
41+
errorMessage: 'Non-string address argument in variable "param" source: %v',
42+
errorCode: 'INVALID_PARAM_SOURCE_ADDRESS',
43+
});
44+
if (!serverlessInstance) return { value: null, isPending: true };
45+
46+
let stage = options.stage;
47+
if (!stage) stage = await resolveConfigurationProperty(['provider', 'stage']);
48+
if (!stage) stage = 'dev';
49+
50+
const params = await resolveParams(stage, serverlessInstance);
51+
const value = params[address] ? params[address].value : null;
52+
const result = { value };
53+
54+
if (value == null) {
55+
throw new ServerlessError(
56+
`The param "${address}" cannot be resolved from stage params. If you are using Serverless Framework Compose, make sure to run commands via Compose so that all parameters can be resolved`,
57+
'MISSING_PARAM_SOURCE_ADDRESS'
58+
);
59+
}
60+
61+
return result;
62+
},
63+
};
64+
};

scripts/serverless.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const finalize = async ({ error, shouldBeSync } = {}) => {
4646
hasBeenFinalized = true;
4747
clearTimeout(keepAliveTimer);
4848
progress.clear();
49-
if (error) (handleError(error, { serverless }));
49+
if (error) handleError(error, { serverless });
5050
if (!shouldBeSync) {
5151
await logDeprecation.printSummary();
5252
}
@@ -474,11 +474,7 @@ process.once('uncaughtException', (error) => {
474474

475475
// Names of the commands which are configured independently in root `commands` folder
476476
// and not in Serverless class internals
477-
const notIntegratedCommands = new Set([
478-
'doctor',
479-
'plugin install',
480-
'plugin uninstall',
481-
]);
477+
const notIntegratedCommands = new Set(['doctor', 'plugin install', 'plugin uninstall']);
482478
const isStandaloneCommand = notIntegratedCommands.has(command);
483479

484480
if (!isHelpRequest) {
@@ -564,6 +560,7 @@ process.once('uncaughtException', (error) => {
564560
self: require('../lib/configuration/variables/sources/self'),
565561
strToBool: require('../lib/configuration/variables/sources/str-to-bool'),
566562
sls: require('../lib/configuration/variables/sources/instance-dependent/get-sls')(),
563+
param: require('../lib/configuration/variables/sources/instance-dependent/param')(),
567564
},
568565
options: filterSupportedOptions(options, { commandSchema, providerName }),
569566
fulfilledSources: new Set(['env', 'file', 'self', 'strToBool']),
@@ -588,6 +585,8 @@ process.once('uncaughtException', (error) => {
588585
require('../lib/configuration/variables/sources/instance-dependent/get-sls')(serverless);
589586
resolverConfiguration.fulfilledSources.add('sls');
590587

588+
resolverConfiguration.sources.param =
589+
require('../lib/configuration/variables/sources/instance-dependent/param')(serverless);
591590
resolverConfiguration.fulfilledSources.add('param');
592591

593592
// Register AWS provider specific variable sources
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
'use strict';
2+
3+
const { expect } = require('chai');
4+
const _ = require('lodash');
5+
6+
const resolveMeta = require('../../../../../../../lib/configuration/variables/resolve-meta');
7+
const resolve = require('../../../../../../../lib/configuration/variables/resolve');
8+
const selfSource = require('../../../../../../../lib/configuration/variables/sources/self');
9+
const getParamSource = require('../../../../../../../lib/configuration/variables/sources/instance-dependent/param');
10+
const Serverless = require('../../../../../../../lib/serverless');
11+
12+
describe('test/unit/lib/configuration/variables/sources/instance-dependent/param.test.js', () => {
13+
let configuration;
14+
let variablesMeta;
15+
let serverlessInstance;
16+
17+
const initializeServerless = async ({ configExt, options, setupOptions = {} } = {}) => {
18+
configuration = {
19+
service: 'foo',
20+
provider: {
21+
name: 'aws',
22+
deploymentBucket: '${param:bucket}',
23+
timeout: '${param:timeout}',
24+
},
25+
custom: {
26+
missingAddress: '${param:}',
27+
unsupportedAddress: '${param:foo}',
28+
nonStringAddress: '${param:${self:custom.someObject}}',
29+
someObject: {},
30+
},
31+
params: {
32+
default: {
33+
bucket: 'global.bucket',
34+
timeout: 10,
35+
},
36+
dev: {
37+
bucket: 'my.bucket',
38+
},
39+
},
40+
};
41+
if (configExt) {
42+
configuration = _.merge(configuration, configExt);
43+
}
44+
variablesMeta = resolveMeta(configuration);
45+
serverlessInstance = new Serverless({
46+
configuration,
47+
serviceDir: process.cwd(),
48+
configurationFilename: 'serverless.yml',
49+
commands: ['package'],
50+
options: options || {},
51+
});
52+
serverlessInstance.init();
53+
await resolve({
54+
serviceDir: process.cwd(),
55+
configuration,
56+
variablesMeta,
57+
sources: {
58+
self: selfSource,
59+
param: getParamSource(setupOptions.withoutInstance ? null : serverlessInstance),
60+
},
61+
options: options || {},
62+
fulfilledSources: new Set(['self', 'param']),
63+
});
64+
};
65+
66+
it('should resolve ${param:timeout}', async () => {
67+
await initializeServerless();
68+
if (variablesMeta.get('param\0timeout')) throw variablesMeta.get('param\0timeout').error;
69+
expect(configuration.provider.timeout).to.equal(10);
70+
});
71+
72+
it('should resolve ${param:bucket} for different stages', async () => {
73+
// Dev by default
74+
await initializeServerless();
75+
expect(configuration.provider.deploymentBucket).to.equal('my.bucket');
76+
77+
// Forced prod
78+
await initializeServerless({
79+
configExt: {
80+
provider: {
81+
stage: 'prod',
82+
},
83+
},
84+
});
85+
expect(configuration.provider.deploymentBucket).to.equal('global.bucket');
86+
});
87+
88+
it('should resolve ${param:bucket} when no serverless instance available', async () => {
89+
await initializeServerless({ setupOptions: { withoutInstance: true } });
90+
expect(variablesMeta.get('provider\0timeout')).to.have.property('variables');
91+
expect(variablesMeta.get('provider\0timeout')).to.not.have.property('error');
92+
});
93+
94+
it('should report with an error missing address', async () => {
95+
await initializeServerless();
96+
expect(variablesMeta.get('custom\0missingAddress').error.code).to.equal(
97+
'VARIABLE_RESOLUTION_ERROR'
98+
);
99+
});
100+
101+
it('should report with an error unsupported address', async () => {
102+
await initializeServerless();
103+
expect(variablesMeta.get('custom\0unsupportedAddress').error.code).to.equal(
104+
'VARIABLE_RESOLUTION_ERROR'
105+
);
106+
});
107+
108+
it('should report with an error a non-string address', async () => {
109+
await initializeServerless();
110+
expect(variablesMeta.get('custom\0nonStringAddress').error.code).to.equal(
111+
'VARIABLE_RESOLUTION_ERROR'
112+
);
113+
});
114+
});

0 commit comments

Comments
 (0)