serverless-plugin-warmup 8.3.0 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -12,9 +12,9 @@ const path = require('path');
12
12
  const { extendServerlessSchema } = require('./schema');
13
13
  const { getConfigsByWarmer } = require('./config');
14
14
  const {
15
- addWarmUpFunctionRoleToResources,
16
- createWarmUpFunctionArtifact,
17
- addWarmUpFunctionToService,
15
+ addWarmUpFunctionRoleToResources,
16
+ createWarmUpFunctionArtifact,
17
+ addWarmUpFunctionToService,
18
18
  } = require('./warmer');
19
19
  const { capitalize } = require('./utils');
20
20
 
@@ -23,228 +23,248 @@ const { capitalize } = require('./utils');
23
23
  * @class WarmUp
24
24
  * */
25
25
  class WarmUp {
26
- /**
27
- * @description Serverless Warm Up
28
- * @constructor
29
- *
30
- * @param {!Object} serverless - Serverless object
31
- * @param {!Object} cliOptions - Serverless cliOptions
32
- * */
33
- constructor(serverless, cliOptions, { log }) {
34
- /** Serverless variables */
35
- this.serverless = serverless;
36
- this.cliOptions = cliOptions;
37
- this.log = log;
38
-
39
- this.provider = this.serverless.getProvider('aws');
40
-
41
- extendServerlessSchema(this.serverless);
42
-
43
- this.commands = {
44
- warmup: {
45
- commands: {
46
- addWarmers: { lifecycleEvents: ['addWarmers'] },
47
- cleanupTempDir: { lifecycleEvents: ['cleanup'] },
48
- prewarm: {
49
- lifecycleEvents: ['start', 'end'],
50
- cliOptions: {
51
- warmers: {
52
- shortcut: 'w',
53
- usage: 'Comma-separated list of warmer names to prewarm.',
54
- type: 'string',
55
- },
56
- },
57
- usage: 'Invoke a warmer to warm the functions on demand.',
58
- },
59
- },
60
- },
61
- };
62
-
63
- this.hooks = {
64
- 'after:package:initialize': () => this.serverless.pluginManager.spawn('warmup:addWarmers'),
65
- 'after:package:createDeploymentArtifacts': () => this.serverless.pluginManager.spawn('warmup:cleanupTempDir'),
66
- 'after:deploy:deploy': () => this.serverless.pluginManager.spawn('warmup:prewarm'),
67
- 'after:deploy:function:deploy': () => this.serverless.pluginManager.spawn('warmup:prewarm'),
68
- 'before:warmup:addWarmers:addWarmers': this.configPlugin.bind(this),
69
- 'warmup:addWarmers:addWarmers': this.initializeWarmers.bind(this),
70
- 'before:warmup:cleanupTempDir:cleanup': this.configPlugin.bind(this),
71
- 'warmup:cleanupTempDir:cleanup': this.cleanUp.bind(this),
72
- 'before:warmup:prewarm:start': this.configPlugin.bind(this),
73
- 'warmup:prewarm:start': this.prewarmFunctions.bind(this),
74
- // Workaround webpack/bundle plugins and serverless_sdk
75
- 'before:package:createDeploymentArtifacts': this.resetWarmerConfigs.bind(this),
76
- };
77
-
78
- // Fixed for issues in Serverles
79
- // https://github.com/serverless/serverless/pull/9307
80
- this.serviceDir = this.serverless.serviceDir || this.serverless.config.servicePath || '';
81
- }
82
-
83
- /**
84
- * @description Configures the plugin if needed or do nothing if already configured.
85
- * */
86
- configPlugin() {
87
- this.stage = this.stage || this.provider.getStage();
88
- this.configsByWarmer = this.configsByWarmer
89
- || getConfigsByWarmer(this.serverless, this.stage);
90
- }
91
-
92
- /**
93
- * @description Warm up initialize hook. Create warmer function and add it to the service.
94
- *
95
- * @fulfil {} — Warm up set
96
- * @reject {Error} Warm up error
97
- *
98
- * @return {Promise}
99
- * */
100
- async initializeWarmers() {
101
- await Promise.all(Object.entries(this.configsByWarmer)
102
- .map(([warmerName, warmerConfig]) => this.configureWarmer(warmerName, warmerConfig)));
103
- }
104
-
105
- /**
106
- * @description Workaround webpack/bundle plugins and serverless_sdk.
107
- * Reset the plugin and ignore changes
108
- *
109
- * @fulfil {} Warm up set
110
- * @reject {Error} Warm up error
111
- *
112
- * @return {Promise}
113
- * */
114
- async resetWarmerConfigs() {
115
- Object.entries(this.configsByWarmer)
116
- .forEach(([warmerName, warmerConfig]) => {
117
- if (warmerConfig.functions.length === 0) return;
118
- addWarmUpFunctionToService(this.serverless.service, warmerName, warmerConfig);
119
- });
120
- }
121
-
122
- /**
123
- * @description Warmup cleanup hook.
124
- *
125
- * @fulfil {} Temp folders cleaned up
126
- * @reject {Error} Couldn't cleaned up temp folders
127
- *
128
- * @return {Promise}
129
- * */
130
- async cleanUp() {
131
- const foldersToClean = Array.from(new Set(Object.values(this.configsByWarmer)
132
- .filter((config) => config.cleanFolder)
133
- .map((config) => config.folderName)));
134
-
135
- await Promise.all(foldersToClean.map(async (folderToClean) => {
136
- try {
137
- await fs.rm(
138
- path.join(this.serviceDir, folderToClean),
139
- { recursive: true },
140
- );
141
- } catch (err) {
142
- if (err.code !== 'ENOENT') {
143
- this.log.error(`WarmUp: Couldn't clean up temporary folder ${folderToClean}.`);
144
- }
145
- }
146
- }));
147
-
148
- try {
149
- const defaultDir = path.join(this.serviceDir, '.warmup');
150
- if (
151
- foldersToClean.some((folder) => folder.startsWith('.warmup'))
152
- && (await fs.readdir(defaultDir)).length === 0
153
- ) {
154
- await fs.rm(defaultDir, { recursive: true });
155
- }
156
- } catch (err) {
157
- if (err.code !== 'ENOENT') {
158
- this.log.error('WarmUp: Couldn\'t clean up temporary folder .warmup.');
159
- }
160
- }
161
- }
162
-
163
- /**
164
- * @description Warmer prewarm functions hook
165
- *
166
- * @fulfil {} — Functions warmed up sucessfuly
167
- * @reject {Error} Functions couldn't be warmed up
168
- *
169
- * @return {Promise}
170
- * */
171
- async prewarmFunctions() {
172
- const warmerNames = (this.cliOptions.warmers)
173
- ? this.cliOptions.warmers.split(',')
174
- : Object.entries(this.configsByWarmer)
175
- .filter(([, warmerConfig]) => warmerConfig.prewarm)
176
- .map(([warmerName]) => warmerName);
177
-
178
- await Promise.all(warmerNames.map(async (warmerName) => {
179
- const warmerConfig = this.configsByWarmer[warmerName];
180
- if (!warmerConfig) {
181
- throw new this.serverless.classes.Error(`Warmer names ${warmerName} doesn't exist.`);
182
- }
183
- addWarmUpFunctionToService(this.serverless.service, warmerName, warmerConfig);
184
- await this.invokeWarmer(warmerName, warmerConfig);
185
- }));
186
- }
187
-
188
- /**
189
- * @description Create warm up function code and write it to the handler file
190
- * and add warm up function to the service
191
- * */
192
- async configureWarmer(warmerName, warmerConfig) {
193
- if (warmerConfig.functions.length === 0) {
194
- this.log.warning(`WarmUp: Skipping warmer "${warmerName}" creation. No functions to warm up.`);
195
- return;
196
- }
197
-
198
- this.log.notice(`WarmUp: Creating warmer "${warmerName}" to warm up ${warmerConfig.functions.length} function${warmerConfig.functions.length === 1 ? '' : 's'}`);
199
- this.log.info(':');
200
- warmerConfig.functions.forEach((func) => this.log.info(` * ${func.name}`));
201
-
202
- const handlerFolder = path.join(this.serviceDir, warmerConfig.folderName);
203
-
204
- await createWarmUpFunctionArtifact(
205
- warmerConfig.functions,
206
- warmerConfig.tracing,
207
- warmerConfig.verbose,
208
- this.provider.getRegion(),
209
- handlerFolder,
210
- );
211
-
212
- if (warmerConfig.role === undefined) {
213
- addWarmUpFunctionRoleToResources(
214
- this.serverless.service,
215
- this.stage,
216
- warmerName,
217
- warmerConfig,
218
- );
219
- }
220
-
221
- addWarmUpFunctionToService(this.serverless.service, warmerName, warmerConfig);
222
- }
223
-
224
- async invokeWarmer(warmerName, warmerConfig) {
225
- if (warmerConfig.functions.length === 0) {
226
- this.log.warning(`WarmUp: Skipping prewarming using warmer "${warmerName}". No functions to warm up.`);
227
- return;
228
- }
229
-
230
- this.log.notice(`WarmUp: Prewarming up your functions using warmer "${warmerName}".`);
231
-
232
- try {
233
- const { SERVERLESS_ALIAS } = this.serverless.service.getFunction(`warmUpPlugin${capitalize(warmerName)}`).environment || {};
234
- const params = {
235
- FunctionName: warmerConfig.name,
236
- InvocationType: 'RequestResponse',
237
- LogType: 'None',
238
- Qualifier: SERVERLESS_ALIAS,
239
- Payload: warmerConfig.payload,
240
- };
241
-
242
- await this.provider.request('Lambda', 'invoke', params);
243
- this.log.notice(`WarmUp: Warmer "${warmerName}" successfully prewarmed your functions.`);
244
- } catch (err) {
245
- this.log.error(`WarmUp: Error while prewarming your functions using warmer "${warmerName}".`, err);
246
- }
247
- }
26
+ /**
27
+ * @description Serverless Warm Up
28
+ * @constructor
29
+ *
30
+ * @param {!Object} serverless - Serverless object
31
+ * @param {!Object} cliOptions - Serverless cliOptions
32
+ * */
33
+ constructor(serverless, cliOptions, { log }) {
34
+ /** Serverless variables */
35
+ this.serverless = serverless;
36
+ this.cliOptions = cliOptions;
37
+ this.log = log;
38
+
39
+ this.provider = this.serverless.getProvider('aws');
40
+
41
+ extendServerlessSchema(this.serverless);
42
+
43
+ this.commands = {
44
+ warmup: {
45
+ commands: {
46
+ addWarmers: { lifecycleEvents: ['addWarmers'] },
47
+ cleanupTempDir: { lifecycleEvents: ['cleanup'] },
48
+ prewarm: {
49
+ lifecycleEvents: ['start', 'end'],
50
+ cliOptions: {
51
+ warmers: {
52
+ shortcut: 'w',
53
+ usage: 'Comma-separated list of warmer names to prewarm.',
54
+ type: 'string',
55
+ },
56
+ },
57
+ usage: 'Invoke a warmer to warm the functions on demand.',
58
+ },
59
+ },
60
+ },
61
+ };
62
+
63
+ this.hooks = {
64
+ 'after:package:initialize': () => this.serverless.pluginManager.spawn('warmup:addWarmers'),
65
+ 'after:package:createDeploymentArtifacts': () =>
66
+ this.serverless.pluginManager.spawn('warmup:cleanupTempDir'),
67
+ 'after:deploy:deploy': () => this.serverless.pluginManager.spawn('warmup:prewarm'),
68
+ 'after:deploy:function:deploy': () => this.serverless.pluginManager.spawn('warmup:prewarm'),
69
+ 'before:warmup:addWarmers:addWarmers': this.configPlugin.bind(this),
70
+ 'warmup:addWarmers:addWarmers': this.initializeWarmers.bind(this),
71
+ 'before:warmup:cleanupTempDir:cleanup': this.configPlugin.bind(this),
72
+ 'warmup:cleanupTempDir:cleanup': this.cleanUp.bind(this),
73
+ 'before:warmup:prewarm:start': this.configPlugin.bind(this),
74
+ 'warmup:prewarm:start': this.prewarmFunctions.bind(this),
75
+ // Workaround webpack/bundle plugins and serverless_sdk
76
+ 'before:package:createDeploymentArtifacts': this.resetWarmerConfigs.bind(this),
77
+ };
78
+
79
+ // Fixed for issues in Serverles
80
+ // https://github.com/serverless/serverless/pull/9307
81
+ this.serviceDir = this.serverless.serviceDir || this.serverless.config.servicePath || '';
82
+ }
83
+
84
+ /**
85
+ * @description Configures the plugin if needed or do nothing if already configured.
86
+ * */
87
+ configPlugin() {
88
+ this.stage = this.stage || this.provider.getStage();
89
+ this.configsByWarmer = this.configsByWarmer || getConfigsByWarmer(this.serverless, this.stage);
90
+ }
91
+
92
+ /**
93
+ * @description Warm up initialize hook. Create warmer function and add it to the service.
94
+ *
95
+ * @fulfil {} — Warm up set
96
+ * @reject {Error} Warm up error
97
+ *
98
+ * @return {Promise}
99
+ * */
100
+ async initializeWarmers() {
101
+ await Promise.all(
102
+ Object.entries(this.configsByWarmer).map(([warmerName, warmerConfig]) =>
103
+ this.configureWarmer(warmerName, warmerConfig),
104
+ ),
105
+ );
106
+ }
107
+
108
+ /**
109
+ * @description Workaround webpack/bundle plugins and serverless_sdk.
110
+ * Reset the plugin and ignore changes
111
+ *
112
+ * @fulfil {} — Warm up set
113
+ * @reject {Error} Warm up error
114
+ *
115
+ * @return {Promise}
116
+ * */
117
+ async resetWarmerConfigs() {
118
+ Object.entries(this.configsByWarmer).forEach(([warmerName, warmerConfig]) => {
119
+ if (warmerConfig.functions.length === 0) return;
120
+ addWarmUpFunctionToService(this.serverless.service, warmerName, warmerConfig);
121
+ });
122
+ }
123
+
124
+ /**
125
+ * @description Warmup cleanup hook.
126
+ *
127
+ * @fulfil {} — Temp folders cleaned up
128
+ * @reject {Error} Couldn't cleaned up temp folders
129
+ *
130
+ * @return {Promise}
131
+ * */
132
+ async cleanUp() {
133
+ const foldersToClean = Array.from(
134
+ new Set(
135
+ Object.values(this.configsByWarmer)
136
+ .filter((config) => config.cleanFolder)
137
+ .map((config) => config.folderName),
138
+ ),
139
+ );
140
+
141
+ await Promise.all(
142
+ foldersToClean.map(async (folderToClean) => {
143
+ try {
144
+ await fs.rm(path.join(this.serviceDir, folderToClean), { recursive: true });
145
+ } catch (err) {
146
+ if (err.code !== 'ENOENT') {
147
+ this.log.error(`WarmUp: Couldn't clean up temporary folder ${folderToClean}.`);
148
+ }
149
+ }
150
+ }),
151
+ );
152
+
153
+ try {
154
+ const defaultDir = path.join(this.serviceDir, '.warmup');
155
+ if (
156
+ foldersToClean.some((folder) => folder.startsWith('.warmup')) &&
157
+ (await fs.readdir(defaultDir)).length === 0
158
+ ) {
159
+ await fs.rm(defaultDir, { recursive: true });
160
+ }
161
+ } catch (err) {
162
+ if (err.code !== 'ENOENT') {
163
+ this.log.error("WarmUp: Couldn't clean up temporary folder .warmup.");
164
+ }
165
+ }
166
+ }
167
+
168
+ /**
169
+ * @description Warmer prewarm functions hook
170
+ *
171
+ * @fulfil {} — Functions warmed up sucessfuly
172
+ * @reject {Error} Functions couldn't be warmed up
173
+ *
174
+ * @return {Promise}
175
+ * */
176
+ async prewarmFunctions() {
177
+ const warmerNames = this.cliOptions.warmers
178
+ ? this.cliOptions.warmers.split(',')
179
+ : Object.entries(this.configsByWarmer)
180
+ .filter(([, warmerConfig]) => warmerConfig.prewarm)
181
+ .map(([warmerName]) => warmerName);
182
+
183
+ await Promise.all(
184
+ warmerNames.map(async (warmerName) => {
185
+ const warmerConfig = this.configsByWarmer[warmerName];
186
+ if (!warmerConfig) {
187
+ throw new this.serverless.classes.Error(`Warmer names ${warmerName} doesn't exist.`);
188
+ }
189
+ addWarmUpFunctionToService(this.serverless.service, warmerName, warmerConfig);
190
+ await this.invokeWarmer(warmerName, warmerConfig);
191
+ }),
192
+ );
193
+ }
194
+
195
+ /**
196
+ * @description Create warm up function code and write it to the handler file
197
+ * and add warm up function to the service
198
+ * */
199
+ async configureWarmer(warmerName, warmerConfig) {
200
+ if (warmerConfig.functions.length === 0) {
201
+ this.log.warning(
202
+ `WarmUp: Skipping warmer "${warmerName}" creation. No functions to warm up.`,
203
+ );
204
+ return;
205
+ }
206
+
207
+ this.log.notice(
208
+ `WarmUp: Creating warmer "${warmerName}" to warm up ${warmerConfig.functions.length} function${warmerConfig.functions.length === 1 ? '' : 's'}`,
209
+ );
210
+ this.log.info(':');
211
+ warmerConfig.functions.forEach((func) => {
212
+ this.log.info(` * ${func.name}`);
213
+ });
214
+
215
+ const handlerFolder = path.join(this.serviceDir, warmerConfig.folderName);
216
+
217
+ await createWarmUpFunctionArtifact(
218
+ warmerConfig.functions,
219
+ warmerConfig.tracing,
220
+ warmerConfig.verbose,
221
+ this.provider.getRegion(),
222
+ handlerFolder,
223
+ );
224
+
225
+ if (warmerConfig.role === undefined) {
226
+ addWarmUpFunctionRoleToResources(
227
+ this.serverless.service,
228
+ this.stage,
229
+ warmerName,
230
+ warmerConfig,
231
+ );
232
+ }
233
+
234
+ addWarmUpFunctionToService(this.serverless.service, warmerName, warmerConfig);
235
+ }
236
+
237
+ async invokeWarmer(warmerName, warmerConfig) {
238
+ if (warmerConfig.functions.length === 0) {
239
+ this.log.warning(
240
+ `WarmUp: Skipping prewarming using warmer "${warmerName}". No functions to warm up.`,
241
+ );
242
+ return;
243
+ }
244
+
245
+ this.log.notice(`WarmUp: Prewarming up your functions using warmer "${warmerName}".`);
246
+
247
+ try {
248
+ const { SERVERLESS_ALIAS } =
249
+ this.serverless.service.getFunction(`warmUpPlugin${capitalize(warmerName)}`).environment ||
250
+ {};
251
+ const params = {
252
+ FunctionName: warmerConfig.name,
253
+ InvocationType: 'RequestResponse',
254
+ LogType: 'None',
255
+ Qualifier: SERVERLESS_ALIAS,
256
+ Payload: warmerConfig.payload,
257
+ };
258
+
259
+ await this.provider.request('Lambda', 'invoke', params);
260
+ this.log.notice(`WarmUp: Warmer "${warmerName}" successfully prewarmed your functions.`);
261
+ } catch (err) {
262
+ this.log.error(
263
+ `WarmUp: Error while prewarming your functions using warmer "${warmerName}".`,
264
+ err,
265
+ );
266
+ }
267
+ }
248
268
  }
249
269
 
250
270
  /** Export WarmUp class */