@adobe/helix-deploy 10.3.3 → 11.0.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/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ # [11.0.0](https://github.com/adobe/helix-deploy/compare/v10.4.0...v11.0.0) (2024-01-30)
2
+
3
+
4
+ ### Features
5
+
6
+ * move development server to own package ([#659](https://github.com/adobe/helix-deploy/issues/659)) ([43cbb16](https://github.com/adobe/helix-deploy/commit/43cbb16bb74ebfab1759b7b8ae0f842db6ec3ebe)), closes [#654](https://github.com/adobe/helix-deploy/issues/654)
7
+
8
+
9
+ ### BREAKING CHANGES
10
+
11
+ * import { DevelopmentServer } from @adobe/helix-universal-devserver
12
+
13
+ # [10.4.0](https://github.com/adobe/helix-deploy/compare/v10.3.3...v10.4.0) (2024-01-29)
14
+
15
+
16
+ ### Features
17
+
18
+ * add support for custom AWS tags which are set on the deployed Lambda. Fixes [#651](https://github.com/adobe/helix-deploy/issues/651) ([#652](https://github.com/adobe/helix-deploy/issues/652)) ([09e2055](https://github.com/adobe/helix-deploy/commit/09e2055928b7f67c70b64034c810a0ff48b5ce59))
19
+
1
20
  ## [10.3.3](https://github.com/adobe/helix-deploy/compare/v10.3.2...v10.3.3) (2024-01-29)
2
21
 
3
22
 
package/README.md CHANGED
@@ -132,7 +132,8 @@ AWS Deployment Options
132
132
  --aws-log-format The lambda log format. Can be either "JSON" or "Text". [string]
133
133
  --aws-layers List of layers ARNs to attach to the lambda function. [array]
134
134
  --aws-tracing-mode The lambda tracing mode. Can be either "Active" or "PassThrough". [string]
135
- --aws-extra-permissions A list fo additional invoke permissions to add to the lambda function in the form <SourceARN>@<Principal>. [array]
135
+ --aws-extra-permissions A list of additional invoke permissions to add to the lambda function in the form <SourceARN>@<Principal>. [array]
136
+ --aws-tags A list of additional tags to attach to the lambda function in the form key=value. To remove a tag, use key= (i.e. without a value).[array]
136
137
 
137
138
  Google Deployment Options
138
139
  --google-project-id the Google Cloud project to deploy to. Optional when the key file is a JSON file [string] [default: ""]
@@ -346,38 +347,25 @@ destination filename. eg:
346
347
 
347
348
  ## Using the development server
348
349
 
349
- Testing an openwhisk action that was _expressified_ using [ActionUtils.expressify()](https://github.com/adobe/openwhisk-action-utils/blob/main/src/expressify.js)
350
- can be done with the `DevelopmentServer`. Just create a `test/dev.js` file with:
350
+ Testing an universal function can be done with the [development server](https://github.com/adobe/helix-universal-devserver).
351
+
352
+ Just create a `test/dev.js` file with:
351
353
 
352
354
  ```js
353
- const { DevelopmentServer } = require('@adobe/helix-deploy');
354
- const App = require('../src/app.js');
355
+ import { DevelopmentServer } from '@adobe/helix-universal-devserver';
356
+ import { main } from '../src/index.js';
355
357
 
356
358
  async function run() {
357
- const devServer = await new DevelopmentServer(App).init();
358
- return devServer.start();
359
+ const devServer = await new DevelopmentServer(main).init();
360
+ await devServer.start();
359
361
  }
360
362
 
361
- // eslint-disable-next-line no-console
362
- run().catch(console.error);
363
+ run().then(process.stdout).catch(process.stderr);
363
364
  ```
364
365
 
365
366
  and run `node test/dev.js`.
366
367
 
367
- ### Using development params with the server
368
-
369
- Sometimes it might be useful to specify action params that would be provided during deployment
370
- but are not available during development. those can be specified by a `dev-params-file` `wsk`
371
- property. those parameters are loaded an applied to every action call. eg:
372
-
373
- ```json
374
- ...
375
- "wsk": {
376
- ...
377
- "dev-params-file": ".dev-secrets.env"
378
- }
379
- ...
380
- ```
368
+ for more information see https://github.com/adobe/helix-universal-devserver
381
369
 
382
370
  ## Notes
383
371
 
package/index.js CHANGED
@@ -13,4 +13,3 @@ export { default as ActionBuilder } from './src/ActionBuilder.js';
13
13
  export { default as Bundler } from './src/bundler/WebpackBundler.js';
14
14
  export { default as BaseConfig } from './src/BaseConfig.js';
15
15
  export { default as CLI } from './src/cli.js';
16
- export { default as DevelopmentServer } from './src/DevelopmentServer.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-deploy",
3
- "version": "10.3.3",
3
+ "version": "11.0.0",
4
4
  "description": "Library and Commandline Tools to build and deploy OpenWhisk Actions",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/adobe/helix-deploy#readme",
@@ -59,7 +59,6 @@
59
59
  "chalk-template": "1.1.0",
60
60
  "constants-browserify": "1.0.0",
61
61
  "dotenv": "16.4.1",
62
- "express": "4.18.2",
63
62
  "form-data": "4.0.0",
64
63
  "fs-extra": "11.2.0",
65
64
  "isomorphic-git": "1.25.3",
@@ -77,7 +76,7 @@
77
76
  "@semantic-release/git": "10.0.1",
78
77
  "c8": "9.1.0",
79
78
  "eslint": "8.56.0",
80
- "husky": "8.0.3",
79
+ "husky": "9.0.7",
81
80
  "lint-staged": "15.2.0",
82
81
  "mocha": "10.2.0",
83
82
  "mocha-junit-reporter": "2.2.1",
@@ -81,7 +81,7 @@ export default class WebpackBundler extends BaseBundler {
81
81
  },
82
82
  // use fixed conditions to omit the `development` condition.
83
83
  // see: https://webpack.js.org/guides/package-exports/#conditions
84
- conditionNames: ['node', 'require', 'import'],
84
+ conditionNames: ['node', 'require', 'import', 'module'],
85
85
  },
86
86
  node: {
87
87
  __dirname: true,
@@ -34,6 +34,7 @@ export default class AWSConfig {
34
34
  layers: undefined,
35
35
  tracingMode: undefined,
36
36
  extraPermissions: undefined,
37
+ tags: undefined,
37
38
  });
38
39
  }
39
40
 
@@ -56,7 +57,8 @@ export default class AWSConfig {
56
57
  .withAWSLogFormat(argv.awsLogFormat)
57
58
  .withAWSLayers(argv.awsLayers)
58
59
  .withAWSTracingMode(argv.awsTracingMode)
59
- .withAWSExtraPermissions(argv.awsExtraPermissions);
60
+ .withAWSExtraPermissions(argv.awsExtraPermissions)
61
+ .withAWSTags(argv.awsTags);
60
62
  }
61
63
 
62
64
  withAWSRegion(value) {
@@ -152,13 +154,21 @@ export default class AWSConfig {
152
154
  return this;
153
155
  }
154
156
 
157
+ withAWSTags(value) {
158
+ if (value && !Array.isArray(value)) {
159
+ throw new Error('awsTags must be an array');
160
+ }
161
+ this.tags = value;
162
+ return this;
163
+ }
164
+
155
165
  static yarg(yargs) {
156
166
  return yargs
157
167
  .group(['aws-region', 'aws-api', 'aws-role', 'aws-cleanup-buckets', 'aws-cleanup-integrations',
158
168
  'aws-cleanup-versions', 'aws-create-routes', 'aws-create-authorizer', 'aws-attach-authorizer',
159
169
  'aws-lambda-format', 'aws-parameter-manager', 'aws-deploy-template', 'aws-arch', 'aws-update-secrets',
160
170
  'aws-deploy-bucket', 'aws-identity-source', 'aws-log-format', 'aws-layers',
161
- 'aws-tracing-mode', 'aws-extra-permissions'], 'AWS Deployment Options')
171
+ 'aws-tracing-mode', 'aws-extra-permissions', 'aws-tags'], 'AWS Deployment Options')
162
172
  .option('aws-region', {
163
173
  description: 'the AWS region to deploy lambda functions to',
164
174
  type: 'string',
@@ -245,9 +255,14 @@ export default class AWSConfig {
245
255
  type: 'string',
246
256
  })
247
257
  .option('aws-extra-permissions', {
248
- description: 'A list fo additional invoke permissions to add to the lambda function in the form <SourceARN>@<Principal>.',
258
+ description: 'A list of additional invoke permissions to add to the lambda function in the form <SourceARN>@<Principal>.',
249
259
  type: 'string',
250
260
  array: true,
261
+ })
262
+ .option('aws-tags', {
263
+ description: 'A list of additional tags to attach to the lambda function in the form key=value. To remove a tag, use key= (i.e. without a value).',
264
+ type: 'array',
265
+ array: true,
251
266
  });
252
267
  }
253
268
  }
@@ -29,9 +29,9 @@ import {
29
29
  CreateAliasCommand,
30
30
  CreateFunctionCommand, DeleteAliasCommand, DeleteFunctionCommand, GetAliasCommand,
31
31
  GetFunctionCommand,
32
- LambdaClient, ListAliasesCommand, ListVersionsByFunctionCommand,
33
- PublishVersionCommand, UpdateAliasCommand, UpdateFunctionCodeCommand,
34
- UpdateFunctionConfigurationCommand,
32
+ LambdaClient, ListAliasesCommand, ListTagsCommand, ListVersionsByFunctionCommand,
33
+ PublishVersionCommand, TagResourceCommand, UntagResourceCommand, UpdateAliasCommand,
34
+ UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand,
35
35
  } from '@aws-sdk/client-lambda';
36
36
 
37
37
  import {
@@ -126,6 +126,16 @@ export default class AWSDeployer extends BaseDeployer {
126
126
  }`;
127
127
  }
128
128
 
129
+ get additionalTags() {
130
+ return (this._cfg.tags || []).map((tag) => tag.split('=')).reduce((acc, tagSplit) => {
131
+ if (tagSplit.length >= 2) {
132
+ const [key, ...value] = tagSplit;
133
+ acc[key] = value.join('=');
134
+ }
135
+ return acc;
136
+ }, {});
137
+ }
138
+
129
139
  validate() {
130
140
  const req = [];
131
141
  if (!this._cfg.role) {
@@ -206,7 +216,7 @@ export default class AWSDeployer extends BaseDeployer {
206
216
  }
207
217
 
208
218
  async createLambda() {
209
- const { cfg, functionName } = this;
219
+ const { cfg, functionName, additionalTags } = this;
210
220
  const functionVersion = cfg.version.replace(/\./g, '_');
211
221
 
212
222
  const functionConfig = {
@@ -243,6 +253,13 @@ export default class AWSDeployer extends BaseDeployer {
243
253
  TracingConfig: this._cfg.tracingMode ? { Mode: this._cfg.tracingMode } : undefined,
244
254
  };
245
255
 
256
+ // add additional tags which are not empty
257
+ Object.entries(additionalTags).forEach(([key, value]) => {
258
+ if (value !== '') {
259
+ functionConfig.Tags[key] = value;
260
+ }
261
+ });
262
+
246
263
  this.log.info(`--: using lambda role "${this._cfg.role}"`);
247
264
 
248
265
  // check if function already exists
@@ -272,6 +289,28 @@ export default class AWSDeployer extends BaseDeployer {
272
289
  this.log.info(chalk`--: updating existing Lambda function configuration {yellow ${functionName}}`);
273
290
  await this._lambda.send(new UpdateFunctionConfigurationCommand(functionConfig));
274
291
  await this.checkFunctionReady(baseARN);
292
+ this.log.info(chalk`--: updating existing Lambda function tags {yellow ${functionName}}`);
293
+ // set all the tags in the current configuration
294
+ await this._lambda.send(new TagResourceCommand({
295
+ Resource: baseARN,
296
+ Tags: functionConfig.Tags,
297
+ }));
298
+ // then remove any tags with a blank value in the configuration (and are currently set),
299
+ // leaving other tags alone
300
+ const tagsToPotentiallyRemove = Object.entries(additionalTags).filter(([_, value]) => value === '').map(([key]) => key);
301
+ if (tagsToPotentiallyRemove.length) {
302
+ const { Tags: currentTags } = await this._lambda.send(new ListTagsCommand({
303
+ Resource: baseARN,
304
+ }));
305
+ const tagsToRemove = tagsToPotentiallyRemove.filter((key) => currentTags[key]);
306
+ if (tagsToRemove.length) {
307
+ await this._lambda.send(new UntagResourceCommand({
308
+ Resource: baseARN,
309
+ TagKeys: tagsToRemove,
310
+ }));
311
+ }
312
+ }
313
+ await this.checkFunctionReady(baseARN);
275
314
  this.log.info('--: updating Lambda function code...');
276
315
  await this._lambda.send(new UpdateFunctionCodeCommand({
277
316
  FunctionName: functionName,
@@ -1,237 +0,0 @@
1
- /*
2
- * Copyright 2020 Adobe. All rights reserved.
3
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License. You may obtain a copy
5
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
- *
7
- * Unless required by applicable law or agreed to in writing, software distributed under
8
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
- * OF ANY KIND, either express or implied. See the License for the specific language
10
- * governing permissions and limitations under the License.
11
- */
12
- import crypto from 'crypto';
13
- import fse from 'fs-extra';
14
- import path from 'path';
15
- import express from 'express';
16
- import { createAdapter } from '@adobe/helix-universal/aws';
17
- import ActionBuilder from './ActionBuilder.js';
18
- import BaseConfig from './BaseConfig.js';
19
-
20
- function rawBody() {
21
- return (req, res, next) => {
22
- if (req.method === 'GET' || req.method === 'HEAD') {
23
- next();
24
- return;
25
- }
26
- const chunks = [];
27
- req.on('data', (chunk) => {
28
- chunks.push(chunk);
29
- });
30
- req.on('end', () => {
31
- req.rawBody = Buffer.concat(chunks);
32
- next();
33
- });
34
- };
35
- }
36
-
37
- function addRequestHeader(name, value) {
38
- return (req, res, next) => {
39
- req.headers[name] = value;
40
- next();
41
- };
42
- }
43
-
44
- /**
45
- * Development server for local development.
46
- *
47
- * Example:
48
- *
49
- * ```
50
- * // test/dev.js
51
- *
52
- * const { DevelopmentServer } = require('@adobe/helix-deploy');
53
- * const { main } = require('../src/index.js');
54
- *
55
- * async function run() {
56
- * const devServer = await new DevelopmentServer(main).init();
57
- * await devServer.start();
58
- * }
59
- *
60
- * run().then(process.stdout).catch(process.stderr);
61
- * ```
62
- *
63
- * @type {DevelopmentServer}
64
- */
65
- export default class DevelopmentServer {
66
- /**
67
- * Creates a new development server using the given universal function.
68
- * @param {UniversalFunction} main - The universal function
69
- */
70
- constructor(main) {
71
- this._main = main;
72
- this._cwd = process.cwd();
73
- this._port = process.env.WEBSERVER_PORT || 3000;
74
- this._headers = {};
75
- }
76
-
77
- withPort(value) {
78
- this._port = value;
79
- return this;
80
- }
81
-
82
- withXFH(value) {
83
- process.emitWarning('DevelopmentServer.withXFH is deprecated. Use withHeader(\'x-forwarded-host\') instead.', 'DeprecationWarning');
84
- this._headers['x-forwarded-host'] = value;
85
- return this;
86
- }
87
-
88
- withHeader(name, value) {
89
- this._headers[name] = value;
90
- return this;
91
- }
92
-
93
- withDirectory(value) {
94
- this._cwd = value;
95
- return this;
96
- }
97
-
98
- get port() {
99
- return this._port;
100
- }
101
-
102
- /**
103
- * Initializes the development server.
104
- * It uses the `wsk.package.params-file` and `wsk.params-file` to read the environment for
105
- * the action params.
106
- *
107
- * @returns this
108
- */
109
- async init() {
110
- // load the action params
111
- let pkgJson = {};
112
- try {
113
- pkgJson = await fse.readJson(path.resolve(this._cwd, 'package.json'));
114
- } catch (e) {
115
- // ignore
116
- }
117
- const config = new BaseConfig();
118
- if (pkgJson.wsk) {
119
- const withParamsFile = async (file) => {
120
- if (!file) {
121
- return;
122
- }
123
- // eslint-disable-next-line no-param-reassign
124
- const files = (Array.isArray(file) ? file : [file]).map((f) => path.resolve(this._cwd, f));
125
- await Promise.all(files.map(async (f) => {
126
- if (await fse.exists(f)) {
127
- config.withParamsFile(f);
128
- }
129
- }));
130
- };
131
-
132
- await withParamsFile(pkgJson.wsk?.package?.['params-file']);
133
- config.withParams(pkgJson.wsk?.package?.params);
134
- await withParamsFile(pkgJson.wsk?.['params-file']);
135
- config.withParams(pkgJson.wsk?.params);
136
- await withParamsFile(pkgJson.wsk?.dev?.['params-file']);
137
- config.withParams(pkgJson.wsk?.dev?.params);
138
- }
139
-
140
- const builder = new ActionBuilder().withConfig(config);
141
- await builder.validate();
142
-
143
- const region = process.env.AWS_REGION ?? 'us-east-1';
144
- const accountId = process.env.AWS_ACCOUNT_ID ?? 'no-account';
145
-
146
- const adapter = createAdapter({
147
- factory: () => (req, ctx) => {
148
- ctx.runtime.name = 'simulate';
149
- return this._main(req, ctx);
150
- },
151
- });
152
- this._handler = async (req, res) => {
153
- const [rawPath, ...rest] = req.originalUrl.split('?');
154
- const rawQueryString = rest.join('?');
155
- const method = req.headers['x-http-method'] || req.method;
156
- const event = {
157
- body: req.rawBody,
158
- headers: req.headers,
159
- pathParameters: {
160
- path: rawPath.substring(1),
161
- },
162
- requestContext: {
163
- domainName: req.hostname,
164
- http: {
165
- method,
166
- },
167
- },
168
- rawPath,
169
- rawQueryString,
170
- };
171
- const context = {
172
- awsRequestId: crypto.randomUUID(),
173
- invokedFunctionArn: `arn:aws:lambda:${region}:${accountId}:function:${config.name}:${config.version}`,
174
- getRemainingTimeInMillis: () => 60000,
175
- };
176
-
177
- const {
178
- statusCode,
179
- headers,
180
- isBase64Encoded,
181
- body,
182
- } = await adapter(event, context);
183
-
184
- res.status(statusCode);
185
- Object.entries(headers).forEach(([name, value]) => res.set(name, value));
186
- res.send(isBase64Encoded ? Buffer.from(body, 'base64') : body);
187
- };
188
- this.params = config.params;
189
- return this;
190
- }
191
-
192
- /**
193
- * Starts the development server
194
- * @returns {Promise<void>}
195
- */
196
- async start() {
197
- Object.entries(this.params).forEach(([key, value]) => {
198
- if (!(key in process.env)) {
199
- process.env[key] = value;
200
- }
201
- });
202
- this.app = express();
203
- await new Promise((resolve, reject) => {
204
- try {
205
- this.server = this.app.listen(this._port, () => {
206
- this._port = this.server.address().port;
207
- // eslint-disable-next-line no-console
208
- console.log(`Started development server at http://localhost:${this._port}/`);
209
- resolve();
210
- });
211
- } catch (e) {
212
- reject(e);
213
- }
214
- });
215
- this.app.use(rawBody());
216
- Object.entries(this._headers).forEach(([name, value]) => {
217
- this.app.use(addRequestHeader(name, value.replace('{port}', this._port)));
218
- });
219
- this.app.all('*', this._handler);
220
- }
221
-
222
- /**
223
- * Stops the development server.
224
- * @returns {Promise<void>}
225
- */
226
- async stop() {
227
- return new Promise((resolve, reject) => {
228
- this.server.close((err) => {
229
- if (err) {
230
- reject(err);
231
- } else {
232
- resolve();
233
- }
234
- });
235
- });
236
- }
237
- }