serverless-vpc-discovery 4.1.0 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
package/.eslintrc.json CHANGED
@@ -38,4 +38,4 @@
38
38
  ],
39
39
  "guard-for-in": "error"
40
40
  }
41
- }
41
+ }
package/README.md CHANGED
@@ -1,14 +1,17 @@
1
1
  # serverless-vpc-discovery
2
+
2
3
  [![serverless](http://public.serverless.com/badges/v3.svg)](http://www.serverless.com)
3
4
  [![Build Status](https://travis-ci.org/amplify-education/serverless-vpc-discovery.svg?branch=master)](https://travis-ci.org/amplify-education/serverless-vpc-discovery)
4
5
  [![npm version](https://badge.fury.io/js/serverless-vpc-discovery.svg)](https://badge.fury.io/js/serverless-vpc-discovery)
5
6
  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/amplify-education/serverless-vpc-discovery/master/LICENSE)
6
- [![Codacy Badge](https://api.codacy.com/project/badge/Grade/c3ba87d04fe24b8f881252705e51cc29)](https://www.codacy.com/app/CFER/serverless-vpc-discovery?utm_source=github.com&utm_medium=referral&utm_content=amplify-education/serverless-vpc-discovery&utm_campaign=badger)
7
+ [![Codacy Badge](https://app.codacy.com/project/badge/Grade/8135a66ac35648758d22e242fbdbd732)](https://app.codacy.com/gh/amplify-education/serverless-vpc-discovery/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
7
8
  [![npm downloads](https://img.shields.io/npm/dt/serverless-vpc-discovery.svg?style=flat)](https://www.npmjs.com/package/serverless-vpc-discovery)
8
9
 
9
- The vpc discovery plugin takes the given vpc name, subnet tag key/value, and security group tag key/value or names in the serverless file to setup the vpc configuration for the lambda.
10
+ The vpc discovery plugin takes the given vpc name, subnet tag key/value, and security group tag key/value or names in
11
+ the serverless file to setup the vpc configuration for the lambda.
10
12
 
11
13
  Basically we use this config:
14
+
12
15
  ```
13
16
  vpcDiscovery:
14
17
  vpcName: '<vpc_name>'
@@ -21,7 +24,9 @@ vpcDiscovery:
21
24
  tagValues:
22
25
  - '<tag_value>'
23
26
  ```
27
+
24
28
  To generate this config:
29
+
25
30
  ```
26
31
  vpc:
27
32
  subnetIds:
@@ -31,30 +36,43 @@ vpc:
31
36
  - sg-123456789
32
37
  ...
33
38
  ```
39
+
34
40
  For each lambda function.
35
-
36
- > Note: The core serverless `provider.vpc` settings will be used, if they are set, instead of `vpcDiscovery`. You can use also mix settings. For example you may set `provider.vpc.subnetIds` while using `vpcDiscovery` to set the `securityGroupIds`. Take a look at [official documentation](https://www.serverless.com/framework/docs/providers/aws/guide/functions#vpc-configuration).
41
+
42
+ > Note: The core serverless `provider.vpc` settings will be used, if they are set, instead of `vpcDiscovery`. You can
43
+ > use also mix settings. For example you may set `provider.vpc.subnetIds` while using `vpcDiscovery` to set
44
+ > the `securityGroupIds`. Take a look
45
+ > at [official documentation](https://www.serverless.com/framework/docs/providers/aws/guide/functions#vpc-configuration).
37
46
 
38
47
  # About Amplify
39
- Amplify builds innovative and compelling digital educational products that empower teachers and students across the country. We have a long history as the leading innovator in K-12 education - and have been described as the best tech company in education and the best education company in tech. While others try to shrink the learning experience into the technology, we use technology to expand what is possible in real classrooms with real students and teachers.
48
+
49
+ Amplify builds innovative and compelling digital educational products that empower teachers and students across the
50
+ country. We have a long history as the leading innovator in K-12 education - and have been described as the best tech
51
+ company in education and the best education company in tech. While others try to shrink the learning experience into the
52
+ technology, we use technology to expand what is possible in real classrooms with real students and teachers.
40
53
 
41
54
  Learn more at https://www.amplify.com
42
55
 
43
56
  # Getting Started
44
57
 
45
58
  ## Prerequisites
59
+
46
60
  Make sure you have the following installed before starting:
61
+
47
62
  * [nodejs](https://nodejs.org/en/download/)
48
- * [npm](https://www.npmjs.com/get-npm?utm_source=house&utm_medium=homepage&utm_campaign=free%20orgs&utm_term=Install%20npm)
63
+ * [npm](https://www.npmjs.com/get-npm?utm_source=house\&utm_medium=homepage\&utm_campaign=free%20orgs\&utm_term=Install%20npm)
49
64
  * [serverless](https://serverless.com/framework/docs/providers/aws/guide/installation/)
50
65
 
51
66
  Also allow the lambda to have the following IAM permissions:
67
+
52
68
  * ec2:CreateNetworkInterface
53
69
  * ec2:DescribeNetworkInterfaces
54
70
  * ec2:DeleteNetworkInterface
55
71
 
56
72
  ## Installation
73
+
57
74
  Run:
75
+
58
76
  ```
59
77
  # From npm (recommended)
60
78
  npm install serverless-vpc-discovery
@@ -62,7 +80,9 @@ npm install serverless-vpc-discovery
62
80
  # From github
63
81
  npm install https://github.com/amplify-education/serverless-vpc-discovery.git
64
82
  ```
83
+
65
84
  Then make the following edits to your serverless.yaml file:
85
+
66
86
  ```yaml
67
87
  plugins:
68
88
  - serverless-vpc-discovery
@@ -71,12 +91,12 @@ plugins:
71
91
  custom:
72
92
  vpcDiscovery:
73
93
  vpcName: '<vpc_name>'
74
-
94
+
75
95
  # optional if `securityGroups` option is specified
76
96
  # list of tag key and values
77
97
  subnets:
78
98
  - tagKey: <tag_name>
79
-
99
+
80
100
  # an array of values
81
101
  tagValues:
82
102
  - '<tag_value>'
@@ -85,96 +105,103 @@ custom:
85
105
  # list of tag key and value or names
86
106
  securityGroups:
87
107
  - tagKey: <tag_name>
88
-
108
+
89
109
  # an array of values
90
110
  tagValues:
91
111
  - '<tag_value>'
92
-
112
+
93
113
  # optional if `tagKey` and `tagValues` are specified
94
114
  # an array of values
95
115
  - names:
96
- - '<security_group_name>'
116
+ - '<security_group_name>'
97
117
 
98
118
  # Optional: Either set `custom.vpcDiscovery` or `functions.<function name>.vpcDiscovery`
99
119
  functions:
100
120
  example:
101
121
  handler: handler.example
102
122
  # inherit `custom.vpcDiscovery` config in case `custom.vpcDiscovery` is specified
103
-
123
+
104
124
  example2:
105
125
  handler: handler.example
106
-
126
+
107
127
  # skip vpc configuration for the current function
108
128
  vpcDiscovery: false
109
-
129
+
110
130
  example3:
111
131
  handler: handler.example
112
-
132
+
113
133
  # inherit `custom.vpcDiscovery` config in case `custom.vpcDiscovery` is specified and override security group names
114
134
  vpcDiscovery:
115
135
  vpcName: '<vpc_name>'
116
136
  securityGroups:
117
137
  - tagKey: <tag_name>
118
-
138
+
119
139
  # an array of values
120
140
  tagValues:
121
141
  - '<tag_value>'
122
-
142
+
123
143
  example4:
124
144
  handler: handler.example
125
145
  # override or set basic subnets and security groups items
126
146
  vpcDiscovery:
127
147
  vpcName: '<vpc_name>'
128
-
148
+
129
149
  # optional if `custom.vpcDiscovery.securityGroups` option is specified
130
- subnets:
150
+ subnets:
131
151
  - tagKey: <tag_name>
132
-
152
+
133
153
  # an array of values
134
154
  tagValues:
135
155
  - '<tag_value>'
136
156
 
137
157
  # optional if `custom.vpcDiscovery.subnets` option is specified
138
- securityGroups:
139
-
158
+ securityGroups:
159
+
140
160
  # optional if `names` option is specified
141
161
  - tagKey: <tag_name>
142
-
162
+
143
163
  # an array of values
144
164
  tagValues:
145
165
  - '<tag_value>'
146
-
166
+
147
167
  # optional if `tagKey` and `tagValues` are specified
148
168
  # an array of values
149
- - names:
150
- - '<security_group_name>'
169
+ - names:
170
+ - '<security_group_name>'
151
171
  ```
152
172
 
153
173
  ## Running Tests
174
+
154
175
  To run the test:
176
+
155
177
  ```
156
178
  npm test
157
179
  ```
180
+
158
181
  All tests should pass.
159
182
 
160
- To run integration tests, set an environment variable TEST_VPC_NAME to the VPC you will be testing for. Then,
183
+ To run integration tests, set an environment variable TEST\_VPC\_NAME to the VPC you will be testing for. Then,
184
+
161
185
  ```
162
186
  export AWS_PROFILE=your_profile
163
187
  export TEST_VPC_NAME=vpc_name
164
- npm build
188
+ npm run build
165
189
  npm run integration-test
166
190
  ```
167
191
 
168
- If there is an error build and install the node_module inside the serverless-vpc-discovery folder:
192
+ If there is an error build and install the node\_module inside the serverless-vpc-discovery folder:
193
+
169
194
  ```
170
195
  npm build
171
196
  npm install .
172
197
  ```
173
198
 
174
199
  ## Deploying with the plugin
200
+
175
201
  When deploying run:
202
+
176
203
  ```
177
- serverless deploy --env 'VPC Name'
204
+ serverless deploy
178
205
  ```
179
206
 
180
207
  And that should be it! Good Luck!
@@ -185,14 +212,18 @@ The vpc, subnets, and security groups are found by filtering based on a specifie
185
212
  Vpc and subnets are found under the tag name `tag:Name`.
186
213
  Security groups are found by the name of the group under `group-name`.
187
214
 
188
- The vpc is found first as it is used to find the subnets and security groups. Once all of the subnets and security groups are found the serverless service provider creates a vpc object and stores the subnets and security groups.
215
+ The vpc is found first as it is used to find the subnets and security groups. Once all of the subnets and security
216
+ groups are found the serverless service provider creates a vpc object and stores the subnets and security groups.
189
217
 
190
218
  # Responsible Disclosure
219
+
191
220
  If you have any security issue to report, contact project maintainers privately.
192
221
  You can reach us at <github@amplify.com>
193
222
 
194
223
  # Contributing
224
+
195
225
  We welcome pull requests! For your pull request to be accepted smoothly, we suggest that you:
226
+
196
227
  1. For any sizable change, first open a GitHub issue to discuss your idea.
197
- 2. Create a pull request. Explain why you want to make the change and what it’s for.
198
- We’ll try to answer any PR’s promptly.
228
+ 2. Create a pull request. Explain why you want to make the change and what it’s for.
229
+ We’ll try to answer any PR’s promptly.
@@ -8,13 +8,30 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.EC2Wrapper = void 0;
13
- const aws_sdk_1 = require("aws-sdk");
14
16
  const utils_1 = require("../utils");
17
+ const client_ec2_1 = require("@aws-sdk/client-ec2");
18
+ const globals_1 = __importDefault(require("../globals"));
15
19
  class EC2Wrapper {
16
20
  constructor(credentials) {
17
- this.ec2 = new aws_sdk_1.EC2(credentials);
21
+ this.ec2 = new client_ec2_1.EC2Client([{
22
+ credentials,
23
+ region: globals_1.default.getRegion(),
24
+ retryStrategy: globals_1.default.getRetryStrategy()
25
+ }]);
26
+ }
27
+ /**
28
+ * Returns the promise that contains the vpc list
29
+ * @returns {Promise.<Vpc[]>}
30
+ */
31
+ getVpcs() {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ return yield (0, utils_1.getAWSPagedResults)(this.ec2, "Vpcs", "NextToken", "NextToken", new client_ec2_1.DescribeVpcsCommand({}));
34
+ });
18
35
  }
19
36
  /**
20
37
  * Returns the promise that contains the vpc-id
@@ -23,13 +40,12 @@ class EC2Wrapper {
23
40
  */
24
41
  getVpcId(vpcName) {
25
42
  return __awaiter(this, void 0, void 0, function* () {
26
- const params = {
43
+ const vpcItems = yield (0, utils_1.getAWSPagedResults)(this.ec2, "Vpcs", "NextToken", "NextToken", new client_ec2_1.DescribeVpcsCommand({
27
44
  Filters: [{
28
45
  Name: "tag:Name",
29
46
  Values: [vpcName]
30
47
  }]
31
- };
32
- const vpcItems = yield utils_1.getAWSPagedResults(this.ec2, "describeVpcs", "Vpcs", "NextToken", "NextToken", params);
48
+ }));
33
49
  if (vpcItems.length === 0) {
34
50
  throw new Error(`VPC with tag key 'Name' and tag value '${vpcName}' does not exist.`);
35
51
  }
@@ -46,7 +62,7 @@ class EC2Wrapper {
46
62
  */
47
63
  getSubnetIds(vpcId, tagKey, tagValues) {
48
64
  return __awaiter(this, void 0, void 0, function* () {
49
- const params = {
65
+ const subnets = yield (0, utils_1.getAWSPagedResults)(this.ec2, "Subnets", "NextToken", "NextToken", new client_ec2_1.DescribeSubnetsCommand({
50
66
  Filters: [{
51
67
  Name: "vpc-id",
52
68
  Values: [vpcId]
@@ -54,12 +70,11 @@ class EC2Wrapper {
54
70
  Name: `tag:${tagKey}`,
55
71
  Values: tagValues
56
72
  }]
57
- };
58
- const subnets = yield utils_1.getAWSPagedResults(this.ec2, "describeSubnets", "Subnets", "NextToken", "NextToken", params);
73
+ }));
59
74
  const missingSubnetValues = tagValues.filter((tagValue) => {
60
75
  // collect subnets by name
61
76
  const subnetsByName = subnets.filter((subnet) => {
62
- return utils_1.wildcardMatches(tagValue, utils_1.getValueFromTags(subnet.Tags, tagKey));
77
+ return (0, utils_1.wildcardMatches)(tagValue, (0, utils_1.getValueFromTags)(subnet.Tags, tagKey));
63
78
  });
64
79
  return subnetsByName.length === 0;
65
80
  });
@@ -81,16 +96,16 @@ class EC2Wrapper {
81
96
  getSecurityGroupIds(vpcId, names, tagKey, tagValues) {
82
97
  return __awaiter(this, void 0, void 0, function* () {
83
98
  // init filter by vpc id
84
- const params = { Filters: [{ Name: "vpc-id", Values: [vpcId] }] };
99
+ const input = { Filters: [{ Name: "vpc-id", Values: [vpcId] }] };
85
100
  // update filters with names if specified
86
101
  if (names) {
87
- params.Filters.push({ Name: "group-name", Values: names });
102
+ input.Filters.push({ Name: "group-name", Values: names });
88
103
  }
89
104
  // update filters with tag and values if specified
90
105
  if (tagKey && tagValues) {
91
- params.Filters.push({ Name: `tag:${tagKey}`, Values: tagValues });
106
+ input.Filters.push({ Name: `tag:${tagKey}`, Values: tagValues });
92
107
  }
93
- const securityGroups = yield utils_1.getAWSPagedResults(this.ec2, "describeSecurityGroups", "SecurityGroups", "NextToken", "NextToken", params);
108
+ const securityGroups = yield (0, utils_1.getAWSPagedResults)(this.ec2, "SecurityGroups", "NextToken", "NextToken", new client_ec2_1.DescribeSecurityGroupsCommand(input));
94
109
  if (securityGroups.length === 0) {
95
110
  const namesErrorText = names ? `, names '${names}'` : "";
96
111
  const tagErrorText = tagKey && tagValues ? `, tag key '${tagKey}' and tag values '${tagValues}'` : "";
@@ -100,7 +115,7 @@ class EC2Wrapper {
100
115
  const missingGroupsNames = names.filter((groupName) => {
101
116
  // collect security groups by name
102
117
  const securityGroupsByName = securityGroups.filter((securityGroup) => {
103
- return utils_1.wildcardMatches(groupName, securityGroup.GroupName);
118
+ return (0, utils_1.wildcardMatches)(groupName, securityGroup.GroupName);
104
119
  });
105
120
  return securityGroupsByName.length === 0;
106
121
  });
@@ -114,7 +129,7 @@ class EC2Wrapper {
114
129
  const missingGroupsTagNames = tagValues.filter((tagValue) => {
115
130
  // collect subnets by name
116
131
  const groupsByName = securityGroups.filter((securityGroup) => {
117
- const groupTagValue = utils_1.getValueFromTags(securityGroup.Tags, tagKey);
132
+ const groupTagValue = (0, utils_1.getValueFromTags)(securityGroup.Tags, tagKey);
118
133
  return groupTagValue === tagValue;
119
134
  });
120
135
  return groupsByName.length === 0;
@@ -14,11 +14,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.LambdaFunction = void 0;
16
16
  const ec2_wrapper_1 = require("../aws/ec2-wrapper");
17
- const globals_1 = __importDefault(require("../globals"));
18
17
  const utils_1 = require("../utils");
19
18
  const validation_1 = require("../validation");
19
+ const logging_1 = __importDefault(require("../logging"));
20
+ const ts_md5_1 = require("ts-md5");
20
21
  class LambdaFunction {
21
22
  constructor(credentials, basicVPCDiscovery) {
23
+ this.vpcIdsCache = {};
24
+ this.subnetsIdsCache = {};
25
+ this.SGIdsCache = {};
22
26
  this.ec2Wrapper = new ec2_wrapper_1.EC2Wrapper(credentials);
23
27
  this.basicVPCDiscovery = basicVPCDiscovery;
24
28
  }
@@ -30,29 +34,29 @@ class LambdaFunction {
30
34
  return __awaiter(this, void 0, void 0, function* () {
31
35
  if (typeof funcVPCDiscovery === "boolean" && !funcVPCDiscovery) {
32
36
  // skip vpc setup for `vpcDiscovery=false` option
33
- globals_1.default.logInfo(`Skipping VPC config for the function '${funcName}'`);
37
+ logging_1.default.logInfo(`Skipping VPC config for the function '${funcName}'`);
34
38
  return null;
35
39
  }
36
40
  // inherit the `custom.vpcDiscovery`
37
41
  const vpcDiscovery = Object.assign({}, this.basicVPCDiscovery, funcVPCDiscovery);
38
42
  // return null in case vpcDiscovery not setup
39
- if (utils_1.isObjectEmpty(vpcDiscovery)) {
43
+ if ((0, utils_1.isObjectEmpty)(vpcDiscovery)) {
40
44
  return null;
41
45
  }
42
46
  // validate func vpcDiscovery config
43
47
  try {
44
- validation_1.validateVPCDiscoveryConfig(vpcDiscovery);
48
+ (0, validation_1.validateVPCDiscoveryConfig)(vpcDiscovery);
45
49
  }
46
50
  catch (e) {
47
51
  throw new Error(`Function '${funcName}' is not configured correctly: ${e} VPC not configured. ` +
48
52
  "Please see the README for the proper setup.");
49
53
  }
50
54
  try {
51
- globals_1.default.logInfo(`Getting VPC config for the function: '${funcName}'\n`);
55
+ logging_1.default.logInfo(`Getting VPC config for the function: '${funcName}'\n`);
52
56
  return yield this.getVpcConfig(vpcDiscovery);
53
57
  }
54
58
  catch (e) {
55
- globals_1.default.logError(`Function '${funcName}' VPC not configured based on the error: ${e}`);
59
+ logging_1.default.logError(`Function '${funcName}' VPC not configured based on the error: ${e}`);
56
60
  }
57
61
  return null;
58
62
  });
@@ -65,12 +69,12 @@ class LambdaFunction {
65
69
  getVpcConfig(vpcDiscovery) {
66
70
  return __awaiter(this, void 0, void 0, function* () {
67
71
  const vpc = {};
68
- const vpcId = yield this.ec2Wrapper.getVpcId(vpcDiscovery.vpcName);
69
- globals_1.default.logInfo(`Found VPC with id '${vpcId}'`);
72
+ const vpcId = yield this.getVPCId(vpcDiscovery.vpcName);
73
+ logging_1.default.logInfo(`Found VPC with id '${vpcId}'`);
70
74
  if (vpcDiscovery.subnets) {
71
75
  vpc.subnetIds = [];
72
76
  for (const subnet of vpcDiscovery.subnets) {
73
- const subnetIds = yield this.ec2Wrapper.getSubnetIds(vpcId, subnet.tagKey, subnet.tagValues);
77
+ const subnetIds = yield this.getVPCSubnets(vpcId, subnet.tagKey, subnet.tagValues);
74
78
  vpc.subnetIds = vpc.subnetIds.concat(subnetIds);
75
79
  }
76
80
  // remove duplicate elements from the array
@@ -79,7 +83,7 @@ class LambdaFunction {
79
83
  if (vpcDiscovery.securityGroups) {
80
84
  vpc.securityGroupIds = [];
81
85
  for (const group of vpcDiscovery.securityGroups) {
82
- const groupIds = yield this.ec2Wrapper.getSecurityGroupIds(vpcId, group.names, group.tagKey, group.tagValues);
86
+ const groupIds = yield this.getVPCSecurityGroups(vpcId, group.names, group.tagKey, group.tagValues);
83
87
  vpc.securityGroupIds = vpc.securityGroupIds.concat(groupIds);
84
88
  }
85
89
  // remove duplicate elements from the array
@@ -88,5 +92,43 @@ class LambdaFunction {
88
92
  return vpc;
89
93
  });
90
94
  }
95
+ /**
96
+ * Get the VPC id from cache or read from AWS
97
+ * @returns {Promise<object>}
98
+ */
99
+ getVPCId(vpcName) {
100
+ return __awaiter(this, void 0, void 0, function* () {
101
+ if (this.vpcIdsCache[vpcName] === undefined) {
102
+ this.vpcIdsCache[vpcName] = yield this.ec2Wrapper.getVpcId(vpcName);
103
+ }
104
+ return this.vpcIdsCache[vpcName];
105
+ });
106
+ }
107
+ /**
108
+ * Get the subnet ids from cache or read from AWS
109
+ * @returns {Promise<object>}
110
+ */
111
+ getVPCSubnets(vpcId, tagKey, tagValues) {
112
+ return __awaiter(this, void 0, void 0, function* () {
113
+ const hash = ts_md5_1.Md5.hashStr(vpcId + tagKey + tagValues.join());
114
+ if (!this.subnetsIdsCache[hash]) {
115
+ this.subnetsIdsCache[hash] = yield this.ec2Wrapper.getSubnetIds(vpcId, tagKey, tagValues);
116
+ }
117
+ return this.subnetsIdsCache[hash];
118
+ });
119
+ }
120
+ /**
121
+ * Get the security group ids from cache or read from AWS
122
+ * @returns {Promise<object>}
123
+ */
124
+ getVPCSecurityGroups(vpcId, names, tagKey, tagValues) {
125
+ return __awaiter(this, void 0, void 0, function* () {
126
+ const hash = ts_md5_1.Md5.hashStr(vpcId + (names || []).join() + tagKey + (tagValues || []).join());
127
+ if (!this.SGIdsCache[hash]) {
128
+ this.SGIdsCache[hash] = yield this.ec2Wrapper.getSecurityGroupIds(vpcId, names, tagKey, tagValues);
129
+ }
130
+ return this.SGIdsCache[hash];
131
+ });
132
+ }
91
133
  }
92
134
  exports.LambdaFunction = LambdaFunction;
@@ -1,42 +1,34 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ const util_retry_1 = require("@smithy/util-retry");
13
+ const credential_providers_1 = require("@aws-sdk/credential-providers");
3
14
  class Globals {
4
- static cliLog(prefix, message) {
5
- Globals.serverless.cli.log(`${prefix} ${message}`, Globals.pluginName);
15
+ static getRegion() {
16
+ const slsRegion = Globals.options.region || Globals.serverless.service.provider.region;
17
+ return slsRegion || Globals.currentRegion || Globals.defaultRegion;
6
18
  }
7
- /**
8
- * Logs error message
9
- */
10
- static logError(message) {
11
- if (Globals.v3Utils) {
12
- Globals.v3Utils.log.error(message);
13
- }
14
- else {
15
- Globals.cliLog("[Error]", message);
16
- }
19
+ static getProfileCreds(profile) {
20
+ return __awaiter(this, void 0, void 0, function* () {
21
+ return yield (0, credential_providers_1.fromIni)({ profile })();
22
+ });
17
23
  }
18
- /**
19
- * Logs info message
20
- */
21
- static logInfo(message) {
22
- if (Globals.v3Utils) {
23
- Globals.v3Utils.log.verbose(message);
24
- }
25
- else {
26
- Globals.cliLog("[Info]", message);
27
- }
28
- }
29
- /**
30
- * Logs warning message
31
- */
32
- static logWarning(message) {
33
- if (Globals.v3Utils) {
34
- Globals.v3Utils.log.warning(message);
35
- }
36
- else {
37
- Globals.cliLog("[WARNING]", message);
38
- }
24
+ static getRetryStrategy(attempts = 3, delay = 3000, backoff = 500) {
25
+ return new util_retry_1.ConfiguredRetryStrategy(attempts, // max attempts.
26
+ // This example sets the backoff at 500ms plus 3s per attempt.
27
+ // https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_util_retry.html#aws-sdkutil-retry
28
+ (attempt) => backoff + attempt * delay // backoff function.
29
+ );
39
30
  }
40
31
  }
41
- exports.default = Globals;
42
32
  Globals.pluginName = "Serverless VPC Discovery";
33
+ Globals.defaultRegion = "us-east-1";
34
+ exports.default = Globals;
package/dist/src/index.js CHANGED
@@ -15,11 +15,15 @@ const lambda_function_1 = require("./common/lambda-function");
15
15
  const globals_1 = __importDefault(require("./globals"));
16
16
  const validation_1 = require("./validation");
17
17
  const schema_1 = require("./schema");
18
+ const logging_1 = __importDefault(require("./logging"));
19
+ const node_config_provider_1 = require("@smithy/node-config-provider");
20
+ const config_resolver_1 = require("@smithy/config-resolver");
18
21
  class VPCPlugin {
19
22
  constructor(serverless, options, v3Utils) {
20
23
  this.serverless = serverless;
21
24
  globals_1.default.serverless = serverless;
22
- if (v3Utils) {
25
+ globals_1.default.options = options;
26
+ if (v3Utils === null || v3Utils === void 0 ? void 0 : v3Utils.log) {
23
27
  globals_1.default.v3Utils = v3Utils;
24
28
  }
25
29
  /* hooks are the actual code that will run when called */
@@ -36,7 +40,7 @@ class VPCPlugin {
36
40
  hookWrapper(lifecycleFunc) {
37
41
  return __awaiter(this, void 0, void 0, function* () {
38
42
  this.validateCustomVPCDiscoveryConfig();
39
- this.initResources();
43
+ yield this.initResources();
40
44
  return yield lifecycleFunc.call(this);
41
45
  });
42
46
  }
@@ -53,7 +57,7 @@ class VPCPlugin {
53
57
  try {
54
58
  // the validateVPCDiscoveryConfig is general for custom and func configs
55
59
  // so try catch for extend error message with `custom.vpcDiscovery` as a source
56
- validation_1.validateVPCDiscoveryConfig(vpcDiscovery);
60
+ (0, validation_1.validateVPCDiscoveryConfig)(vpcDiscovery);
57
61
  }
58
62
  catch (e) {
59
63
  throw new Error(`The \`custom.vpcDiscovery\` is not configured correctly: \n${e} ` + " Please see README for proper setup.");
@@ -74,24 +78,55 @@ class VPCPlugin {
74
78
  if (vpcDiscovery.securityGroupNames) {
75
79
  vpcDiscovery.securityGroups = [{ names: vpcDiscovery.securityGroupNames }];
76
80
  }
77
- globals_1.default.logWarning("The `vpcDiscovery.subnetNames` and `vpcDiscovery.securityGroupNames` options are deprecated " +
81
+ logging_1.default.logWarning("The `vpcDiscovery.subnetNames` and `vpcDiscovery.securityGroupNames` options are deprecated " +
78
82
  "and will be removed in the future. Please see README for proper setup.");
79
83
  }
80
- else {
81
- // log warning in case mixed config are specified
82
- globals_1.default.logWarning("The `vpcDiscovery.subnetNames` and `vpcDiscovery.securityGroupNames` are deprecated " +
83
- "and will not be applied. Please remove mentioned option to not see this warning message.");
84
- }
85
84
  }
86
85
  }
87
86
  /**
88
87
  * Setup AWS resources
89
88
  */
90
89
  initResources() {
91
- this.awsCredentials = this.serverless.providers.aws.getCredentials();
92
- this.awsCredentials.region = this.serverless.providers.aws.getRegion();
93
- const baseVPCDiscovery = this.serverless.service.custom ? this.serverless.service.custom.vpcDiscovery : null;
94
- this.lambdaFunction = new lambda_function_1.LambdaFunction(this.awsCredentials, baseVPCDiscovery);
90
+ return __awaiter(this, void 0, void 0, function* () {
91
+ // setup AWS resources
92
+ yield this.initSLSCredentials();
93
+ yield this.initAWSRegion();
94
+ const baseVPCDiscovery = this.serverless.service.custom ? this.serverless.service.custom.vpcDiscovery : null;
95
+ this.lambdaFunction = new lambda_function_1.LambdaFunction(globals_1.default.credentials, baseVPCDiscovery);
96
+ // start of the legacy AWS SDK V2 creds support
97
+ // TODO: remove it in case serverless will add V3 support
98
+ try {
99
+ yield this.lambdaFunction.ec2Wrapper.getVpcs();
100
+ }
101
+ catch (error) {
102
+ if (error.message.includes("Could not load credentials from any providers")) {
103
+ globals_1.default.credentials = this.serverless.providers.aws.getCredentials();
104
+ this.lambdaFunction = new lambda_function_1.LambdaFunction(globals_1.default.credentials, baseVPCDiscovery);
105
+ }
106
+ }
107
+ });
108
+ }
109
+ /**
110
+ * Init AWS credentials based on sls `provider.profile`
111
+ */
112
+ initSLSCredentials() {
113
+ return __awaiter(this, void 0, void 0, function* () {
114
+ const slsProfile = globals_1.default.options["aws-profile"] || globals_1.default.serverless.service.provider.profile;
115
+ globals_1.default.credentials = slsProfile ? yield globals_1.default.getProfileCreds(slsProfile) : null;
116
+ });
117
+ }
118
+ /**
119
+ * Init AWS current region based on Node options
120
+ */
121
+ initAWSRegion() {
122
+ return __awaiter(this, void 0, void 0, function* () {
123
+ try {
124
+ globals_1.default.currentRegion = yield (0, node_config_provider_1.loadConfig)(config_resolver_1.NODE_REGION_CONFIG_OPTIONS, config_resolver_1.NODE_REGION_CONFIG_FILE_OPTIONS)();
125
+ }
126
+ catch (err) {
127
+ logging_1.default.logInfo("Node region was not found.");
128
+ }
129
+ });
95
130
  }
96
131
  /**
97
132
  * Updates functions vpc config
@@ -99,7 +134,7 @@ class VPCPlugin {
99
134
  */
100
135
  updateFunctionsVpcConfig() {
101
136
  return __awaiter(this, void 0, void 0, function* () {
102
- globals_1.default.logInfo("Updating VPC config...");
137
+ logging_1.default.logInfo("Updating VPC config...");
103
138
  const service = this.serverless.service;
104
139
  const functions = service.functions || {};
105
140
  // Sets the serverless's vpc config
@@ -115,12 +150,12 @@ class VPCPlugin {
115
150
  func.vpc = func.vpc || {};
116
151
  // log warning in case vpc.subnetIds and vpcDiscovery.subnetNames are specified.
117
152
  if (func.vpc.subnetIds && func.vpcDiscovery && func.vpcDiscovery.subnets) {
118
- globals_1.default.logWarning(`vpc.subnetIds' are specified for the function '${funcName}' ` +
153
+ logging_1.default.logWarning(`vpc.subnetIds' are specified for the function '${funcName}' ` +
119
154
  "and overrides 'vpcDiscovery.subnets' discovery config.");
120
155
  }
121
156
  // log warning in case vpc.securityGroupIds and vpcDiscovery.securityGroupNames are specified.
122
157
  if (func.vpc.securityGroupIds && func.vpcDiscovery && func.vpcDiscovery.securityGroups) {
123
- globals_1.default.logWarning(`vpc.securityGroupIds' are specified for the function '${funcName}' ` +
158
+ logging_1.default.logWarning(`vpc.securityGroupIds' are specified for the function '${funcName}' ` +
124
159
  "and overrides 'vpcDiscovery.securityGroups' discovery config.");
125
160
  }
126
161
  // set vpc.subnetIds if it does not exists
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const globals_1 = __importDefault(require("./globals"));
7
+ class Logging {
8
+ static cliLog(prefix, message) {
9
+ globals_1.default.serverless.cli.log(`${prefix} ${message}`, globals_1.default.pluginName);
10
+ }
11
+ /**
12
+ * Logs error message
13
+ */
14
+ static logError(message) {
15
+ if (globals_1.default.v3Utils) {
16
+ globals_1.default.v3Utils.log.error(message);
17
+ }
18
+ else {
19
+ Logging.cliLog("[Error]", message);
20
+ }
21
+ }
22
+ /**
23
+ * Logs info message
24
+ */
25
+ static logInfo(message) {
26
+ if (globals_1.default.v3Utils) {
27
+ globals_1.default.v3Utils.log.verbose(message);
28
+ }
29
+ else {
30
+ Logging.cliLog("[Info]", message);
31
+ }
32
+ }
33
+ /**
34
+ * Logs warning message
35
+ */
36
+ static logWarning(message) {
37
+ if (globals_1.default.v3Utils) {
38
+ globals_1.default.v3Utils.log.warning(message);
39
+ }
40
+ else {
41
+ Logging.cliLog("[WARNING]", message);
42
+ }
43
+ }
44
+ }
45
+ exports.default = Logging;
package/dist/src/utils.js CHANGED
@@ -9,65 +9,30 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.getValueFromTags = exports.wildcardMatches = exports.isObjectEmpty = exports.throttledCall = exports.getAWSPagedResults = exports.sleep = void 0;
13
- const RETRYABLE_ERRORS = ["Throttling", "RequestLimitExceeded", "TooManyRequestsException"];
12
+ exports.getValueFromTags = exports.wildcardMatches = exports.isObjectEmpty = exports.getAWSPagedResults = exports.sleep = void 0;
14
13
  /**
15
14
  * Iterate through the pages of a AWS SDK response and collect them into a single array
16
15
  *
17
- * @param service - The AWS service instance to use to make the calls
18
- * @param funcName - The function name in the service to call
16
+ * @param client - The AWS service instance to use to make the calls
19
17
  * @param resultsKey - The key name in the response that contains the items to return
20
18
  * @param nextTokenKey - The request key name to append to the request that has the paging token value
21
19
  * @param nextRequestTokenKey - The response key name that has the next paging token value
22
20
  * @param params - Parameters to send in the request
23
21
  */
24
- function getAWSPagedResults(service, funcName, resultsKey, nextTokenKey, nextRequestTokenKey, params) {
22
+ function getAWSPagedResults(client, resultsKey, nextTokenKey, nextRequestTokenKey, params) {
25
23
  return __awaiter(this, void 0, void 0, function* () {
26
24
  let results = [];
27
- let response = yield throttledCall(service, funcName, params);
28
- results = results.concat(response[resultsKey]);
29
- // eslint-disable-next-line no-prototype-builtins
30
- while (response.hasOwnProperty(nextRequestTokenKey) && response[nextRequestTokenKey]) {
31
- params[nextTokenKey] = response[nextRequestTokenKey];
32
- response = yield service[funcName](params).promise();
25
+ let response = yield client.send(params);
26
+ results = results.concat(response[resultsKey] || results);
27
+ while (nextRequestTokenKey in response && response[nextRequestTokenKey]) {
28
+ params.input[nextTokenKey] = response[nextRequestTokenKey];
29
+ response = yield client.send(params);
33
30
  results = results.concat(response[resultsKey]);
34
31
  }
35
32
  return results;
36
33
  });
37
34
  }
38
35
  exports.getAWSPagedResults = getAWSPagedResults;
39
- function throttledCall(service, funcName, params) {
40
- return __awaiter(this, void 0, void 0, function* () {
41
- const maxTimePassed = 5 * 60;
42
- let timePassed = 0;
43
- let previousInterval = 0;
44
- const minWait = 3;
45
- const maxWait = 60;
46
- while (true) {
47
- try {
48
- return yield service[funcName](params).promise();
49
- }
50
- catch (ex) {
51
- // rethrow the exception if it is not a type of retryable exception
52
- if (RETRYABLE_ERRORS.indexOf(ex.code) === -1) {
53
- throw ex;
54
- }
55
- // rethrow the exception if we have waited too long
56
- if (timePassed >= maxTimePassed) {
57
- throw ex;
58
- }
59
- // Sleep using the Decorrelated Jitter algorithm recommended by AWS
60
- // https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
61
- let newInterval = Math.random() * Math.min(maxWait, previousInterval * 3);
62
- newInterval = Math.max(minWait, newInterval);
63
- yield sleep(newInterval);
64
- previousInterval = newInterval;
65
- timePassed += previousInterval;
66
- }
67
- }
68
- });
69
- }
70
- exports.throttledCall = throttledCall;
71
36
  /**
72
37
  * Stops event thread execution for given number of seconds.
73
38
  * @param seconds
@@ -79,8 +44,8 @@ function sleep(seconds) {
79
44
  });
80
45
  }
81
46
  exports.sleep = sleep;
82
- function isObjectEmpty(object) {
83
- return Object.keys(object).length === 0;
47
+ function isObjectEmpty(value) {
48
+ return Object.keys(value).length === 0;
84
49
  }
85
50
  exports.isObjectEmpty = isObjectEmpty;
86
51
  function replaceAll(input, search, replace) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-vpc-discovery",
3
- "version": "4.1.0",
3
+ "version": "5.0.1",
4
4
  "engines": {
5
5
  "node": ">=14"
6
6
  },
@@ -34,7 +34,8 @@
34
34
  "integration-test": "nyc mocha -r ts-node/register --project tsconfig.json test/integration-tests/*.test.ts && nyc report --reporter=text-summary",
35
35
  "lint": "eslint src --ext .ts",
36
36
  "lint:fix": "npm run lint -- --fix",
37
- "build": "tsc --project ."
37
+ "build": "tsc --project .",
38
+ "prepare": "npm run build"
38
39
  },
39
40
  "files": [
40
41
  "*.js",
@@ -48,33 +49,39 @@
48
49
  ]
49
50
  },
50
51
  "devDependencies": {
51
- "@types/mocha": "^9.1.1",
52
- "@types/node": "^16.18.14",
53
- "@typescript-eslint/eslint-plugin": "^4.33.0",
54
- "@typescript-eslint/parser": "^4.33.0",
55
- "aws-sdk-mock": "^5.8.0",
56
- "chai": "^4.3.7",
57
- "chai-spies": "^1.0.0",
52
+ "@aws-sdk/client-lambda": "^3.465.0",
53
+ "@types/js-yaml": "^4.0.9",
54
+ "@types/mocha": "^10.0.6",
55
+ "@types/node": "^20.10.3",
56
+ "@types/randomstring": "^1.1.11",
57
+ "@types/shelljs": "^0.8.15",
58
+ "@typescript-eslint/eslint-plugin": "^5.62.0",
59
+ "@typescript-eslint/parser": "^5.62.0",
60
+ "aws-sdk-client-mock": "^3.0.0",
61
+ "chai": "^4.3.10",
62
+ "chai-spies": "^1.1.0",
58
63
  "eslint": "^7.32.0",
59
- "eslint-config-airbnb": "^18.2.1",
60
64
  "eslint-config-standard": "^16.0.3",
61
- "eslint-plugin-import": "^2.27.5",
62
- "eslint-plugin-jsx-a11y": "6.4.1",
65
+ "eslint-plugin-import": "^2.29.0",
63
66
  "eslint-plugin-node": "^11.1.0",
64
67
  "eslint-plugin-promise": "^5.2.0",
65
- "eslint-plugin-react": "^7.32.2",
66
- "eslint-plugin-react-hooks": "^4.6.0",
67
68
  "js-yaml": "^4.1.0",
68
- "mocha": "^9.2.2",
69
+ "mocha": "^10.2.0",
69
70
  "mocha-param": "^2.0.1",
70
71
  "nyc": "^15.1.0",
71
- "randomstring": "^1.2.3",
72
- "serverless": "^3.28.1",
72
+ "randomstring": "^1.3.0",
73
+ "serverless": "^3.38.0",
73
74
  "shelljs": "^0.8.5",
74
75
  "ts-node": "^10.9.1",
75
- "typescript": "~4.3.5"
76
+ "typescript": "5.1.6"
76
77
  },
77
78
  "dependencies": {
78
- "aws-sdk": "^2.1331.0"
79
+ "@aws-sdk/client-ec2": "^3.467.0",
80
+ "@aws-sdk/credential-providers": "^3.495.0",
81
+ "@smithy/config-resolver": "^2.1.0",
82
+ "@smithy/node-config-provider": "^2.2.0",
83
+ "@smithy/smithy-client": "^2.1.18",
84
+ "@smithy/util-retry": "^2.0.8",
85
+ "ts-md5": "^1.3.1"
79
86
  }
80
87
  }