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 +1 -1
- package/README.md +64 -33
- package/dist/src/aws/ec2-wrapper.js +30 -15
- package/dist/src/common/lambda-function.js +52 -10
- package/dist/src/globals.js +26 -34
- package/dist/src/index.js +51 -16
- package/dist/src/logging.js +45 -0
- package/dist/src/utils.js +10 -45
- package/package.json +26 -19
package/.eslintrc.json
CHANGED
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://
|
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
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
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.
|
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
|
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
|
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
|
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
|
99
|
+
const input = { Filters: [{ Name: "vpc-id", Values: [vpcId] }] };
|
85
100
|
// update filters with names if specified
|
86
101
|
if (names) {
|
87
|
-
|
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
|
-
|
106
|
+
input.Filters.push({ Name: `tag:${tagKey}`, Values: tagValues });
|
92
107
|
}
|
93
|
-
const securityGroups = yield utils_1.getAWSPagedResults(this.ec2, "
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
69
|
-
|
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.
|
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.
|
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;
|
package/dist/src/globals.js
CHANGED
@@ -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
|
5
|
-
Globals.
|
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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
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
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
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(
|
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
|
28
|
-
results = results.concat(response[resultsKey]);
|
29
|
-
|
30
|
-
|
31
|
-
|
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(
|
83
|
-
return Object.keys(
|
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": "
|
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
|
-
"@
|
52
|
-
"@types/
|
53
|
-
"@
|
54
|
-
"@
|
55
|
-
"
|
56
|
-
"
|
57
|
-
"
|
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.
|
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": "^
|
69
|
+
"mocha": "^10.2.0",
|
69
70
|
"mocha-param": "^2.0.1",
|
70
71
|
"nyc": "^15.1.0",
|
71
|
-
"randomstring": "^1.
|
72
|
-
"serverless": "^3.
|
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": "
|
76
|
+
"typescript": "5.1.6"
|
76
77
|
},
|
77
78
|
"dependencies": {
|
78
|
-
"aws-sdk": "^
|
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
|
}
|