@friggframework/devtools 2.0.0--canary.428.7665532.0 โ 2.0.0--canary.428.6b04c24.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.
|
@@ -510,6 +510,8 @@ class AWSDiscovery {
|
|
|
510
510
|
console.warn('This is a Frigg-managed NAT Gateway that may have been misconfigured by route table changes');
|
|
511
511
|
console.warn('Consider enabling selfHeal: true to fix this automatically');
|
|
512
512
|
// Return it anyway if it's Frigg-managed - we can fix the routes
|
|
513
|
+
// Mark that it's in a private subnet
|
|
514
|
+
natGateway._isInPrivateSubnet = true;
|
|
513
515
|
return natGateway;
|
|
514
516
|
} else {
|
|
515
517
|
console.warn('NAT Gateways MUST be placed in public subnets with Internet Gateway routes');
|
|
@@ -520,11 +522,13 @@ class AWSDiscovery {
|
|
|
520
522
|
|
|
521
523
|
if (isFriggNat) {
|
|
522
524
|
console.log(`Found existing Frigg-managed NAT Gateway: ${natGateway.NatGatewayId}`);
|
|
525
|
+
natGateway._isInPrivateSubnet = false;
|
|
523
526
|
return natGateway;
|
|
524
527
|
}
|
|
525
528
|
|
|
526
529
|
// Return first valid NAT Gateway that's in a public subnet
|
|
527
530
|
console.log(`Found existing NAT Gateway in public subnet: ${natGateway.NatGatewayId}`);
|
|
531
|
+
natGateway._isInPrivateSubnet = false;
|
|
528
532
|
return natGateway;
|
|
529
533
|
}
|
|
530
534
|
|
|
@@ -888,9 +892,13 @@ class AWSDiscovery {
|
|
|
888
892
|
const existingNatGateway = await this.findExistingNatGateway(vpc.VpcId);
|
|
889
893
|
let natGatewayId = null;
|
|
890
894
|
let elasticIpAllocationId = null;
|
|
891
|
-
|
|
895
|
+
let natGatewayInPrivateSubnet = false;
|
|
896
|
+
|
|
892
897
|
if (existingNatGateway) {
|
|
893
898
|
natGatewayId = existingNatGateway.NatGatewayId;
|
|
899
|
+
// Check if NAT Gateway is in a private subnet (from our detection)
|
|
900
|
+
natGatewayInPrivateSubnet = existingNatGateway._isInPrivateSubnet || false;
|
|
901
|
+
|
|
894
902
|
// Get the EIP allocation ID from the NAT Gateway
|
|
895
903
|
if (existingNatGateway.NatGatewayAddresses && existingNatGateway.NatGatewayAddresses.length > 0) {
|
|
896
904
|
elasticIpAllocationId = existingNatGateway.NatGatewayAddresses[0].AllocationId;
|
|
@@ -949,9 +957,18 @@ class AWSDiscovery {
|
|
|
949
957
|
defaultKmsKeyId: kmsKeyArn,
|
|
950
958
|
existingNatGatewayId: natGatewayId,
|
|
951
959
|
existingElasticIpAllocationId: elasticIpAllocationId,
|
|
960
|
+
natGatewayInPrivateSubnet: natGatewayInPrivateSubnet,
|
|
952
961
|
subnetConversionRequired: subnetStatus.requiresConversion,
|
|
953
|
-
privateSubnetsWithWrongRoutes:
|
|
954
|
-
|
|
962
|
+
privateSubnetsWithWrongRoutes: (() => {
|
|
963
|
+
const wrongRoutes = [];
|
|
964
|
+
if (subnetStatus.subnet1NeedsConversion && privateSubnets[0]) {
|
|
965
|
+
wrongRoutes.push(privateSubnets[0].SubnetId);
|
|
966
|
+
}
|
|
967
|
+
if (subnetStatus.subnet2NeedsConversion && privateSubnets[1]) {
|
|
968
|
+
wrongRoutes.push(privateSubnets[1].SubnetId);
|
|
969
|
+
}
|
|
970
|
+
return wrongRoutes;
|
|
971
|
+
})()
|
|
955
972
|
};
|
|
956
973
|
} catch (error) {
|
|
957
974
|
console.error('Error discovering AWS resources:', error);
|
|
@@ -1,446 +1,545 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { mockClient } = require('aws-sdk-client-mock');
|
|
2
2
|
const { AWSDiscovery } = require('./aws-discovery');
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
// Import AWS SDK commands
|
|
5
|
+
const {
|
|
6
|
+
EC2Client,
|
|
7
|
+
DescribeVpcsCommand,
|
|
8
|
+
DescribeSubnetsCommand,
|
|
9
|
+
DescribeSecurityGroupsCommand,
|
|
10
|
+
DescribeRouteTablesCommand,
|
|
11
|
+
DescribeNatGatewaysCommand,
|
|
12
|
+
DescribeAddressesCommand
|
|
13
|
+
} = require('@aws-sdk/client-ec2');
|
|
14
|
+
const {
|
|
15
|
+
KMSClient,
|
|
16
|
+
ListKeysCommand,
|
|
17
|
+
DescribeKeyCommand
|
|
18
|
+
} = require('@aws-sdk/client-kms');
|
|
19
|
+
const {
|
|
20
|
+
STSClient,
|
|
21
|
+
GetCallerIdentityCommand
|
|
22
|
+
} = require('@aws-sdk/client-sts');
|
|
23
|
+
|
|
24
|
+
// Create mock clients
|
|
25
|
+
const ec2Mock = mockClient(EC2Client);
|
|
26
|
+
const kmsMock = mockClient(KMSClient);
|
|
27
|
+
const stsMock = mockClient(STSClient);
|
|
12
28
|
|
|
13
29
|
describe('AWSDiscovery', () => {
|
|
14
30
|
let discovery;
|
|
15
|
-
let mockEC2Send;
|
|
16
|
-
let mockKMSSend;
|
|
17
|
-
let mockSTSSend;
|
|
18
31
|
|
|
19
32
|
beforeEach(() => {
|
|
33
|
+
// Reset all mocks before each test
|
|
34
|
+
ec2Mock.reset();
|
|
35
|
+
kmsMock.reset();
|
|
36
|
+
stsMock.reset();
|
|
37
|
+
|
|
20
38
|
discovery = new AWSDiscovery('us-east-1');
|
|
21
|
-
|
|
22
|
-
// Create mock send functions
|
|
23
|
-
mockEC2Send = jest.fn();
|
|
24
|
-
mockKMSSend = jest.fn();
|
|
25
|
-
mockSTSSend = jest.fn();
|
|
26
|
-
|
|
27
|
-
// Mock the client constructors and send methods
|
|
28
|
-
EC2Client.mockImplementation(() => ({
|
|
29
|
-
send: mockEC2Send
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
KMSClient.mockImplementation(() => ({
|
|
33
|
-
send: mockKMSSend
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
STSClient.mockImplementation(() => ({
|
|
37
|
-
send: mockSTSSend
|
|
38
|
-
}));
|
|
39
|
-
|
|
40
|
-
// Reset mocks
|
|
41
|
-
jest.clearAllMocks();
|
|
42
39
|
});
|
|
43
40
|
|
|
44
41
|
describe('getAccountId', () => {
|
|
45
42
|
it('should return AWS account ID', async () => {
|
|
46
43
|
const mockAccountId = '123456789012';
|
|
47
|
-
|
|
44
|
+
stsMock.on(GetCallerIdentityCommand).resolves({
|
|
48
45
|
Account: mockAccountId
|
|
49
46
|
});
|
|
50
47
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
expect(result).toBe(mockAccountId);
|
|
54
|
-
expect(mockSTSSend).toHaveBeenCalledWith(expect.any(GetCallerIdentityCommand));
|
|
48
|
+
const accountId = await discovery.getAccountId();
|
|
49
|
+
expect(accountId).toBe(mockAccountId);
|
|
55
50
|
});
|
|
56
51
|
|
|
57
52
|
it('should throw error when STS call fails', async () => {
|
|
58
|
-
|
|
59
|
-
mockSTSSend.mockRejectedValue(error);
|
|
53
|
+
stsMock.on(GetCallerIdentityCommand).rejects(new Error('STS error'));
|
|
60
54
|
|
|
61
|
-
await expect(discovery.getAccountId()).rejects.toThrow('STS
|
|
55
|
+
await expect(discovery.getAccountId()).rejects.toThrow('STS error');
|
|
62
56
|
});
|
|
63
57
|
});
|
|
64
58
|
|
|
65
59
|
describe('findDefaultVpc', () => {
|
|
66
|
-
it('should return default VPC when
|
|
67
|
-
const mockVpc = {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
60
|
+
it('should return default VPC when available', async () => {
|
|
61
|
+
const mockVpc = { VpcId: 'vpc-12345678', IsDefault: true };
|
|
62
|
+
ec2Mock.on(DescribeVpcsCommand).resolves({
|
|
63
|
+
Vpcs: [mockVpc]
|
|
64
|
+
});
|
|
72
65
|
|
|
73
|
-
|
|
66
|
+
const vpc = await discovery.findDefaultVpc();
|
|
67
|
+
expect(vpc).toEqual(mockVpc);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should return first VPC when no default VPC exists', async () => {
|
|
71
|
+
const mockVpc = { VpcId: 'vpc-12345678', IsDefault: false };
|
|
72
|
+
ec2Mock.on(DescribeVpcsCommand).resolves({
|
|
74
73
|
Vpcs: [mockVpc]
|
|
75
74
|
});
|
|
76
75
|
|
|
77
|
-
const
|
|
76
|
+
const vpc = await discovery.findDefaultVpc();
|
|
77
|
+
expect(vpc).toEqual(mockVpc);
|
|
78
|
+
});
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}]
|
|
86
|
-
}
|
|
87
|
-
}));
|
|
80
|
+
it('should throw error when no VPCs found', async () => {
|
|
81
|
+
ec2Mock.on(DescribeVpcsCommand).resolves({
|
|
82
|
+
Vpcs: []
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await expect(discovery.findDefaultVpc()).rejects.toThrow('No VPC found in the account');
|
|
88
86
|
});
|
|
87
|
+
});
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
IsDefault: false,
|
|
94
|
-
State: 'available'
|
|
95
|
-
};
|
|
89
|
+
describe('isSubnetPrivate', () => {
|
|
90
|
+
const mockVpcId = 'vpc-12345678';
|
|
91
|
+
const mockSubnetId = 'subnet-12345678';
|
|
96
92
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
it('should return true for private subnet', async () => {
|
|
94
|
+
// Mock subnet lookup first
|
|
95
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
96
|
+
Subnets: [{ SubnetId: mockSubnetId, VpcId: mockVpcId }]
|
|
97
|
+
});
|
|
100
98
|
|
|
101
|
-
|
|
99
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
100
|
+
RouteTables: [{
|
|
101
|
+
Associations: [{ SubnetId: mockSubnetId }],
|
|
102
|
+
Routes: [
|
|
103
|
+
{ GatewayId: 'local', DestinationCidrBlock: '10.0.0.0/16' }
|
|
104
|
+
]
|
|
105
|
+
}]
|
|
106
|
+
});
|
|
102
107
|
|
|
103
|
-
|
|
104
|
-
expect(
|
|
108
|
+
const isPrivate = await discovery.isSubnetPrivate(mockSubnetId, mockVpcId);
|
|
109
|
+
expect(isPrivate).toBe(true);
|
|
105
110
|
});
|
|
106
111
|
|
|
107
|
-
it('should
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
it('should return false for public subnet', async () => {
|
|
113
|
+
// Mock subnet lookup first
|
|
114
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
115
|
+
Subnets: [{ SubnetId: mockSubnetId, VpcId: mockVpcId }]
|
|
116
|
+
});
|
|
111
117
|
|
|
112
|
-
|
|
118
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
119
|
+
RouteTables: [{
|
|
120
|
+
Associations: [{ SubnetId: mockSubnetId }],
|
|
121
|
+
Routes: [
|
|
122
|
+
{ GatewayId: 'igw-12345', DestinationCidrBlock: '0.0.0.0/0' }
|
|
123
|
+
]
|
|
124
|
+
}]
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const isPrivate = await discovery.isSubnetPrivate(mockSubnetId, mockVpcId);
|
|
128
|
+
expect(isPrivate).toBe(false);
|
|
113
129
|
});
|
|
114
130
|
});
|
|
115
131
|
|
|
116
132
|
describe('findPrivateSubnets', () => {
|
|
117
133
|
const mockVpcId = 'vpc-12345678';
|
|
118
134
|
|
|
119
|
-
it('should return private subnets
|
|
135
|
+
it('should return private subnets', async () => {
|
|
120
136
|
const mockSubnets = [
|
|
121
|
-
{ SubnetId: 'subnet-private-1',
|
|
122
|
-
{ SubnetId: 'subnet-private-2',
|
|
137
|
+
{ SubnetId: 'subnet-private-1', AvailabilityZone: 'us-east-1a' },
|
|
138
|
+
{ SubnetId: 'subnet-private-2', AvailabilityZone: 'us-east-1b' }
|
|
123
139
|
];
|
|
124
140
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
Subnets: [{ SubnetId: 'subnet-private-1', VpcId: mockVpcId }]
|
|
129
|
-
})
|
|
130
|
-
.mockResolvedValueOnce({ // Route tables for private-1
|
|
131
|
-
RouteTables: [{
|
|
132
|
-
Associations: [{ SubnetId: 'subnet-private-1' }],
|
|
133
|
-
Routes: [{ GatewayId: 'local' }] // No IGW = private
|
|
134
|
-
}]
|
|
135
|
-
})
|
|
136
|
-
.mockResolvedValueOnce({ // Check subnet-private-2
|
|
137
|
-
Subnets: [{ SubnetId: 'subnet-private-2', VpcId: mockVpcId }]
|
|
138
|
-
})
|
|
139
|
-
.mockResolvedValueOnce({ // Route tables for private-2
|
|
140
|
-
RouteTables: [{
|
|
141
|
-
Associations: [{ SubnetId: 'subnet-private-2' }],
|
|
142
|
-
Routes: [{ GatewayId: 'local' }] // No IGW = private
|
|
143
|
-
}]
|
|
144
|
-
});
|
|
141
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
142
|
+
Subnets: mockSubnets
|
|
143
|
+
});
|
|
145
144
|
|
|
146
|
-
|
|
145
|
+
// Mock route tables - no IGW routes (private)
|
|
146
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
147
|
+
RouteTables: [{
|
|
148
|
+
Associations: [
|
|
149
|
+
{ SubnetId: 'subnet-private-1' },
|
|
150
|
+
{ SubnetId: 'subnet-private-2' }
|
|
151
|
+
],
|
|
152
|
+
Routes: [
|
|
153
|
+
{ GatewayId: 'local', DestinationCidrBlock: '10.0.0.0/16' }
|
|
154
|
+
]
|
|
155
|
+
}]
|
|
156
|
+
});
|
|
147
157
|
|
|
148
|
-
|
|
149
|
-
expect(
|
|
150
|
-
expect(result[1].SubnetId).toBe('subnet-private-2');
|
|
158
|
+
const subnets = await discovery.findPrivateSubnets(mockVpcId);
|
|
159
|
+
expect(subnets).toEqual(mockSubnets);
|
|
151
160
|
});
|
|
152
161
|
|
|
153
|
-
it('should
|
|
162
|
+
it('should throw error when no private subnets found and autoConvert is false', async () => {
|
|
154
163
|
const mockSubnets = [
|
|
155
|
-
{ SubnetId: 'subnet-public-1',
|
|
156
|
-
{ SubnetId: 'subnet-public-2',
|
|
157
|
-
{ SubnetId: 'subnet-public-3',
|
|
164
|
+
{ SubnetId: 'subnet-public-1', AvailabilityZone: 'us-east-1a' },
|
|
165
|
+
{ SubnetId: 'subnet-public-2', AvailabilityZone: 'us-east-1b' },
|
|
166
|
+
{ SubnetId: 'subnet-public-3', AvailabilityZone: 'us-east-1c' }
|
|
158
167
|
];
|
|
159
168
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
.mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-1', VpcId: mockVpcId }] })
|
|
164
|
-
.mockResolvedValueOnce({ // Route table for public-1
|
|
165
|
-
RouteTables: [{
|
|
166
|
-
Associations: [{ SubnetId: 'subnet-public-1' }],
|
|
167
|
-
Routes: [{ GatewayId: 'igw-12345' }] // IGW = public
|
|
168
|
-
}]
|
|
169
|
-
})
|
|
170
|
-
.mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-2', VpcId: mockVpcId }] })
|
|
171
|
-
.mockResolvedValueOnce({ // Route table for public-2
|
|
172
|
-
RouteTables: [{
|
|
173
|
-
Associations: [{ SubnetId: 'subnet-public-2' }],
|
|
174
|
-
Routes: [{ GatewayId: 'igw-12345' }] // IGW = public
|
|
175
|
-
}]
|
|
176
|
-
})
|
|
177
|
-
.mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-3', VpcId: mockVpcId }] })
|
|
178
|
-
.mockResolvedValueOnce({ // Route table for public-3
|
|
179
|
-
RouteTables: [{
|
|
180
|
-
Associations: [{ SubnetId: 'subnet-public-3' }],
|
|
181
|
-
Routes: [{ GatewayId: 'igw-12345' }] // IGW = public
|
|
182
|
-
}]
|
|
183
|
-
});
|
|
169
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
170
|
+
Subnets: mockSubnets
|
|
171
|
+
});
|
|
184
172
|
|
|
185
|
-
//
|
|
186
|
-
|
|
173
|
+
// Mock route tables - has IGW routes (public)
|
|
174
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
175
|
+
RouteTables: [{
|
|
176
|
+
Associations: [
|
|
177
|
+
{ SubnetId: 'subnet-public-1' },
|
|
178
|
+
{ SubnetId: 'subnet-public-2' },
|
|
179
|
+
{ SubnetId: 'subnet-public-3' }
|
|
180
|
+
],
|
|
181
|
+
Routes: [
|
|
182
|
+
{ GatewayId: 'igw-12345', DestinationCidrBlock: '0.0.0.0/0' }
|
|
183
|
+
]
|
|
184
|
+
}]
|
|
185
|
+
});
|
|
187
186
|
|
|
188
|
-
expect(
|
|
189
|
-
|
|
190
|
-
expect(result[0].SubnetId).toBe('subnet-public-2');
|
|
191
|
-
expect(result[1].SubnetId).toBe('subnet-public-3');
|
|
187
|
+
await expect(discovery.findPrivateSubnets(mockVpcId, false))
|
|
188
|
+
.rejects.toThrow('No private subnets found in VPC');
|
|
192
189
|
});
|
|
193
190
|
|
|
194
|
-
it('should
|
|
191
|
+
it('should return public subnets with warning when autoConvert is true', async () => {
|
|
195
192
|
const mockSubnets = [
|
|
196
|
-
{ SubnetId: 'subnet-public-1',
|
|
197
|
-
{ SubnetId: 'subnet-public-2',
|
|
193
|
+
{ SubnetId: 'subnet-public-1', AvailabilityZone: 'us-east-1a' },
|
|
194
|
+
{ SubnetId: 'subnet-public-2', AvailabilityZone: 'us-east-1b' }
|
|
198
195
|
];
|
|
199
196
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
197
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
198
|
+
Subnets: mockSubnets
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Mock route tables - has IGW routes (public)
|
|
202
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
203
|
+
RouteTables: [{
|
|
204
|
+
Associations: [
|
|
205
|
+
{ SubnetId: 'subnet-public-1' },
|
|
206
|
+
{ SubnetId: 'subnet-public-2' }
|
|
207
|
+
],
|
|
208
|
+
Routes: [
|
|
209
|
+
{ GatewayId: 'igw-12345', DestinationCidrBlock: '0.0.0.0/0' }
|
|
210
|
+
]
|
|
211
|
+
}]
|
|
212
|
+
});
|
|
216
213
|
|
|
217
|
-
await
|
|
218
|
-
|
|
219
|
-
);
|
|
214
|
+
const subnets = await discovery.findPrivateSubnets(mockVpcId, true);
|
|
215
|
+
expect(subnets).toHaveLength(2);
|
|
216
|
+
expect(subnets[0].SubnetId).toBe('subnet-public-1');
|
|
220
217
|
});
|
|
218
|
+
});
|
|
221
219
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
{ SubnetId: 'subnet-private-1', VpcId: mockVpcId, AvailabilityZone: 'us-east-1a' },
|
|
225
|
-
{ SubnetId: 'subnet-public-1', VpcId: mockVpcId, AvailabilityZone: 'us-east-1b' },
|
|
226
|
-
{ SubnetId: 'subnet-public-2', VpcId: mockVpcId, AvailabilityZone: 'us-east-1c' }
|
|
227
|
-
];
|
|
220
|
+
describe('findPublicSubnets', () => {
|
|
221
|
+
const mockVpcId = 'vpc-12345678';
|
|
228
222
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
Associations: [{ SubnetId: 'subnet-private-1' }],
|
|
235
|
-
Routes: [{ GatewayId: 'local' }] // No IGW
|
|
236
|
-
}]
|
|
237
|
-
})
|
|
238
|
-
.mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-1', VpcId: mockVpcId }] })
|
|
239
|
-
.mockResolvedValueOnce({ // Public route table
|
|
240
|
-
RouteTables: [{
|
|
241
|
-
Associations: [{ SubnetId: 'subnet-public-1' }],
|
|
242
|
-
Routes: [{ GatewayId: 'igw-12345' }]
|
|
243
|
-
}]
|
|
244
|
-
})
|
|
245
|
-
.mockResolvedValueOnce({ Subnets: [{ SubnetId: 'subnet-public-2', VpcId: mockVpcId }] })
|
|
246
|
-
.mockResolvedValueOnce({ // Public route table
|
|
247
|
-
RouteTables: [{
|
|
248
|
-
Associations: [{ SubnetId: 'subnet-public-2' }],
|
|
249
|
-
Routes: [{ GatewayId: 'igw-12345' }]
|
|
250
|
-
}]
|
|
251
|
-
});
|
|
223
|
+
it('should return public subnet', async () => {
|
|
224
|
+
const mockSubnet = { SubnetId: 'subnet-public-1' };
|
|
225
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
226
|
+
Subnets: [mockSubnet]
|
|
227
|
+
});
|
|
252
228
|
|
|
253
|
-
|
|
229
|
+
// Mock route table check to show it's public
|
|
230
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
231
|
+
RouteTables: [{
|
|
232
|
+
Associations: [{ SubnetId: 'subnet-public-1' }],
|
|
233
|
+
Routes: [{ GatewayId: 'igw-12345' }] // Has IGW = public
|
|
234
|
+
}]
|
|
235
|
+
});
|
|
254
236
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
expect(result[0].SubnetId).toBe('subnet-private-1');
|
|
258
|
-
expect(result[1].SubnetId).toBe('subnet-public-1');
|
|
237
|
+
const subnet = await discovery.findPublicSubnets(mockVpcId);
|
|
238
|
+
expect(subnet).toEqual(mockSubnet);
|
|
259
239
|
});
|
|
260
240
|
|
|
261
241
|
it('should throw error when no subnets found', async () => {
|
|
262
|
-
|
|
242
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
243
|
+
Subnets: []
|
|
244
|
+
});
|
|
263
245
|
|
|
264
|
-
await expect(discovery.
|
|
246
|
+
await expect(discovery.findPublicSubnets(mockVpcId))
|
|
247
|
+
.rejects.toThrow('No subnets found in VPC');
|
|
265
248
|
});
|
|
266
249
|
});
|
|
267
250
|
|
|
268
|
-
describe('
|
|
269
|
-
const mockSubnetId = 'subnet-12345678';
|
|
251
|
+
describe('findDefaultSecurityGroup', () => {
|
|
270
252
|
const mockVpcId = 'vpc-12345678';
|
|
271
253
|
|
|
272
|
-
it('should return
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
.mockResolvedValueOnce({ // DescribeRouteTables
|
|
278
|
-
RouteTables: [{
|
|
279
|
-
Associations: [{ SubnetId: mockSubnetId }],
|
|
280
|
-
Routes: [{
|
|
281
|
-
GatewayId: 'igw-12345678',
|
|
282
|
-
DestinationCidrBlock: '0.0.0.0/0'
|
|
283
|
-
}]
|
|
284
|
-
}]
|
|
285
|
-
});
|
|
254
|
+
it('should return default security group', async () => {
|
|
255
|
+
const mockSecurityGroup = {
|
|
256
|
+
GroupId: 'sg-12345678',
|
|
257
|
+
GroupName: 'default'
|
|
258
|
+
};
|
|
286
259
|
|
|
287
|
-
|
|
260
|
+
ec2Mock.on(DescribeSecurityGroupsCommand).resolves({
|
|
261
|
+
SecurityGroups: [mockSecurityGroup]
|
|
262
|
+
});
|
|
288
263
|
|
|
289
|
-
|
|
264
|
+
const sg = await discovery.findDefaultSecurityGroup(mockVpcId);
|
|
265
|
+
expect(sg).toEqual(mockSecurityGroup);
|
|
290
266
|
});
|
|
291
267
|
|
|
292
|
-
it('should
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
268
|
+
it('should throw error when no default security group found', async () => {
|
|
269
|
+
ec2Mock.on(DescribeSecurityGroupsCommand).resolves({
|
|
270
|
+
SecurityGroups: []
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
await expect(discovery.findDefaultSecurityGroup(mockVpcId))
|
|
274
|
+
.rejects.toThrow('No security group found for VPC');
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('findPrivateRouteTable', () => {
|
|
279
|
+
const mockVpcId = 'vpc-12345678';
|
|
280
|
+
|
|
281
|
+
it('should return private route table', async () => {
|
|
282
|
+
const mockRouteTable = {
|
|
283
|
+
RouteTableId: 'rtb-12345678',
|
|
284
|
+
Routes: [
|
|
285
|
+
{ GatewayId: 'local', DestinationCidrBlock: '10.0.0.0/16' }
|
|
286
|
+
]
|
|
287
|
+
};
|
|
306
288
|
|
|
307
|
-
|
|
289
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
290
|
+
RouteTables: [mockRouteTable]
|
|
291
|
+
});
|
|
308
292
|
|
|
309
|
-
|
|
293
|
+
const rt = await discovery.findPrivateRouteTable(mockVpcId);
|
|
294
|
+
expect(rt).toEqual(mockRouteTable);
|
|
310
295
|
});
|
|
311
296
|
|
|
312
|
-
it('should
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
Associations: [{ Main: true }], // Main route table
|
|
320
|
-
Routes: [{
|
|
321
|
-
GatewayId: 'igw-12345678',
|
|
322
|
-
DestinationCidrBlock: '0.0.0.0/0'
|
|
323
|
-
}]
|
|
324
|
-
}]
|
|
325
|
-
});
|
|
297
|
+
it('should return first route table when no private route table found', async () => {
|
|
298
|
+
const mockRouteTable = {
|
|
299
|
+
RouteTableId: 'rtb-12345678',
|
|
300
|
+
Routes: [
|
|
301
|
+
{ GatewayId: 'igw-12345', DestinationCidrBlock: '0.0.0.0/0' }
|
|
302
|
+
]
|
|
303
|
+
};
|
|
326
304
|
|
|
327
|
-
|
|
305
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
306
|
+
RouteTables: [mockRouteTable]
|
|
307
|
+
});
|
|
328
308
|
|
|
329
|
-
|
|
309
|
+
const rt = await discovery.findPrivateRouteTable(mockVpcId);
|
|
310
|
+
expect(rt).toEqual(mockRouteTable);
|
|
330
311
|
});
|
|
312
|
+
});
|
|
331
313
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
314
|
+
describe('findDefaultKmsKey', () => {
|
|
315
|
+
it('should return default KMS key ARN', async () => {
|
|
316
|
+
const mockKeyId = '12345678-1234-1234-1234-123456789012';
|
|
317
|
+
const mockKeyArn = `arn:aws:kms:us-east-1:123456789012:key/${mockKeyId}`;
|
|
318
|
+
|
|
319
|
+
kmsMock.on(ListKeysCommand).resolves({
|
|
320
|
+
Keys: [{ KeyId: mockKeyId }]
|
|
321
|
+
});
|
|
338
322
|
|
|
339
|
-
|
|
323
|
+
kmsMock.on(DescribeKeyCommand).resolves({
|
|
324
|
+
KeyMetadata: {
|
|
325
|
+
Arn: mockKeyArn,
|
|
326
|
+
KeyManager: 'CUSTOMER',
|
|
327
|
+
KeyState: 'Enabled'
|
|
328
|
+
}
|
|
329
|
+
});
|
|
340
330
|
|
|
341
|
-
|
|
331
|
+
const keyArn = await discovery.findDefaultKmsKey();
|
|
332
|
+
expect(keyArn).toBe(mockKeyArn);
|
|
342
333
|
});
|
|
343
334
|
|
|
344
|
-
it('should
|
|
345
|
-
|
|
335
|
+
it('should return null when no AWS-managed keys found', async () => {
|
|
336
|
+
kmsMock.on(ListKeysCommand).resolves({
|
|
337
|
+
Keys: []
|
|
338
|
+
});
|
|
346
339
|
|
|
347
|
-
await
|
|
348
|
-
|
|
349
|
-
);
|
|
340
|
+
const keyArn = await discovery.findDefaultKmsKey();
|
|
341
|
+
expect(keyArn).toBeNull();
|
|
350
342
|
});
|
|
351
343
|
});
|
|
352
344
|
|
|
353
|
-
describe('
|
|
345
|
+
describe('findAvailableElasticIP', () => {
|
|
346
|
+
it('should return available Elastic IP', async () => {
|
|
347
|
+
const mockElasticIP = {
|
|
348
|
+
AllocationId: 'eipalloc-12345',
|
|
349
|
+
PublicIp: '52.1.2.3'
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
ec2Mock.on(DescribeAddressesCommand).resolves({
|
|
353
|
+
Addresses: [mockElasticIP]
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const eip = await discovery.findAvailableElasticIP();
|
|
357
|
+
expect(eip).toEqual(mockElasticIP);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('should return null when no available Elastic IPs', async () => {
|
|
361
|
+
ec2Mock.on(DescribeAddressesCommand).resolves({
|
|
362
|
+
Addresses: []
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const eip = await discovery.findAvailableElasticIP();
|
|
366
|
+
expect(eip).toBeNull();
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
describe('findExistingNatGateway', () => {
|
|
354
371
|
const mockVpcId = 'vpc-12345678';
|
|
355
372
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
373
|
+
beforeEach(() => {
|
|
374
|
+
// Create a fresh discovery instance for each test
|
|
375
|
+
discovery = new AWSDiscovery('us-east-1');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should return NAT Gateway in public subnet', async () => {
|
|
379
|
+
const mockNatGateway = {
|
|
380
|
+
NatGatewayId: 'nat-12345678',
|
|
381
|
+
SubnetId: 'subnet-public-1',
|
|
382
|
+
State: 'available',
|
|
383
|
+
NatGatewayAddresses: [{ AllocationId: 'eipalloc-12345' }],
|
|
384
|
+
Tags: []
|
|
361
385
|
};
|
|
362
386
|
|
|
363
|
-
|
|
364
|
-
|
|
387
|
+
ec2Mock.on(DescribeNatGatewaysCommand).resolves({
|
|
388
|
+
NatGateways: [mockNatGateway]
|
|
365
389
|
});
|
|
366
390
|
|
|
367
|
-
|
|
391
|
+
// Mock subnet lookup
|
|
392
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
393
|
+
Subnets: [{ SubnetId: 'subnet-public-1', VpcId: mockVpcId }]
|
|
394
|
+
});
|
|
368
395
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
396
|
+
// Mock route table - has IGW (public)
|
|
397
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
398
|
+
RouteTables: [{
|
|
399
|
+
Associations: [{ SubnetId: 'subnet-public-1' }],
|
|
400
|
+
Routes: [{ GatewayId: 'igw-12345' }]
|
|
401
|
+
}]
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const result = await discovery.findExistingNatGateway(mockVpcId);
|
|
405
|
+
|
|
406
|
+
expect(result).toBeDefined();
|
|
407
|
+
expect(result.NatGatewayId).toBe('nat-12345678');
|
|
408
|
+
expect(result._isInPrivateSubnet).toBe(false);
|
|
378
409
|
});
|
|
379
410
|
|
|
380
|
-
it('should
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
411
|
+
it('should detect NAT Gateway in private subnet', async () => {
|
|
412
|
+
const mockNatGateway = {
|
|
413
|
+
NatGatewayId: 'nat-12345678',
|
|
414
|
+
SubnetId: 'subnet-private-1',
|
|
415
|
+
State: 'available',
|
|
416
|
+
NatGatewayAddresses: [{ AllocationId: 'eipalloc-12345' }],
|
|
417
|
+
Tags: [
|
|
418
|
+
{ Key: 'ManagedBy', Value: 'Frigg' }
|
|
419
|
+
]
|
|
385
420
|
};
|
|
386
421
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
422
|
+
ec2Mock.on(DescribeNatGatewaysCommand).resolves({
|
|
423
|
+
NatGateways: [mockNatGateway]
|
|
424
|
+
});
|
|
390
425
|
|
|
391
|
-
|
|
426
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
427
|
+
Subnets: [{ SubnetId: 'subnet-private-1', VpcId: mockVpcId }]
|
|
428
|
+
});
|
|
392
429
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
430
|
+
// Mock route table - no IGW (private)
|
|
431
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
432
|
+
RouteTables: [{
|
|
433
|
+
Associations: [{ SubnetId: 'subnet-private-1' }],
|
|
434
|
+
Routes: [{ GatewayId: 'local' }]
|
|
435
|
+
}]
|
|
436
|
+
});
|
|
396
437
|
|
|
397
|
-
|
|
398
|
-
mockEC2Send.mockResolvedValue({ SecurityGroups: [] });
|
|
438
|
+
const result = await discovery.findExistingNatGateway(mockVpcId);
|
|
399
439
|
|
|
400
|
-
|
|
440
|
+
expect(result).toBeDefined();
|
|
441
|
+
expect(result.NatGatewayId).toBe('nat-12345678');
|
|
442
|
+
expect(result._isInPrivateSubnet).toBe(true);
|
|
401
443
|
});
|
|
402
|
-
});
|
|
403
444
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
445
|
+
it('should skip non-Frigg NAT Gateway in private subnet', async () => {
|
|
446
|
+
const mockNatGateways = [
|
|
447
|
+
{
|
|
448
|
+
NatGatewayId: 'nat-other-12345',
|
|
449
|
+
SubnetId: 'subnet-private-1',
|
|
450
|
+
State: 'available',
|
|
451
|
+
Tags: [] // No Frigg tags
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
NatGatewayId: 'nat-good-12345',
|
|
455
|
+
SubnetId: 'subnet-public-1',
|
|
456
|
+
State: 'available',
|
|
457
|
+
Tags: []
|
|
458
|
+
}
|
|
459
|
+
];
|
|
460
|
+
|
|
461
|
+
ec2Mock.on(DescribeNatGatewaysCommand).resolves({
|
|
462
|
+
NatGateways: mockNatGateways
|
|
463
|
+
});
|
|
408
464
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
465
|
+
// First call for subnet-private-1
|
|
466
|
+
ec2Mock.on(DescribeSubnetsCommand)
|
|
467
|
+
.resolvesOnce({
|
|
468
|
+
Subnets: [{ SubnetId: 'subnet-private-1', VpcId: mockVpcId }]
|
|
412
469
|
})
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
Arn: mockKeyArn,
|
|
417
|
-
KeyManager: 'CUSTOMER',
|
|
418
|
-
KeyState: 'Enabled'
|
|
419
|
-
}
|
|
470
|
+
// Second call for subnet-public-1
|
|
471
|
+
.resolvesOnce({
|
|
472
|
+
Subnets: [{ SubnetId: 'subnet-public-1', VpcId: mockVpcId }]
|
|
420
473
|
});
|
|
421
474
|
|
|
422
|
-
|
|
475
|
+
// First call for private subnet route table
|
|
476
|
+
ec2Mock.on(DescribeRouteTablesCommand)
|
|
477
|
+
.resolvesOnce({
|
|
478
|
+
RouteTables: [{
|
|
479
|
+
Associations: [{ SubnetId: 'subnet-private-1' }],
|
|
480
|
+
Routes: [{ GatewayId: 'local' }] // Private
|
|
481
|
+
}]
|
|
482
|
+
})
|
|
483
|
+
// Second call for public subnet route table
|
|
484
|
+
.resolvesOnce({
|
|
485
|
+
RouteTables: [{
|
|
486
|
+
Associations: [{ SubnetId: 'subnet-public-1' }],
|
|
487
|
+
Routes: [{ GatewayId: 'igw-12345' }] // Public
|
|
488
|
+
}]
|
|
489
|
+
});
|
|
423
490
|
|
|
424
|
-
const result = await discovery.
|
|
491
|
+
const result = await discovery.findExistingNatGateway(mockVpcId);
|
|
425
492
|
|
|
426
|
-
expect(result).
|
|
493
|
+
expect(result).toBeDefined();
|
|
494
|
+
expect(result.NatGatewayId).toBe('nat-good-12345');
|
|
495
|
+
expect(result._isInPrivateSubnet).toBe(false);
|
|
427
496
|
});
|
|
428
497
|
|
|
429
|
-
it('should
|
|
430
|
-
|
|
431
|
-
|
|
498
|
+
it('should prioritize Frigg-managed NAT Gateways', async () => {
|
|
499
|
+
const mockNatGateways = [
|
|
500
|
+
{
|
|
501
|
+
NatGatewayId: 'nat-other-12345',
|
|
502
|
+
SubnetId: 'subnet-public-1',
|
|
503
|
+
State: 'available',
|
|
504
|
+
Tags: []
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
NatGatewayId: 'nat-frigg-12345',
|
|
508
|
+
SubnetId: 'subnet-public-2',
|
|
509
|
+
State: 'available',
|
|
510
|
+
Tags: [{ Key: 'ManagedBy', Value: 'Frigg' }]
|
|
511
|
+
}
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
ec2Mock.on(DescribeNatGatewaysCommand).resolves({
|
|
515
|
+
NatGateways: mockNatGateways
|
|
516
|
+
});
|
|
432
517
|
|
|
433
|
-
|
|
518
|
+
ec2Mock.on(DescribeSubnetsCommand).resolves({
|
|
519
|
+
Subnets: [{ SubnetId: 'subnet-public-2', VpcId: mockVpcId }]
|
|
520
|
+
});
|
|
434
521
|
|
|
435
|
-
|
|
436
|
-
|
|
522
|
+
ec2Mock.on(DescribeRouteTablesCommand).resolves({
|
|
523
|
+
RouteTables: [{
|
|
524
|
+
Associations: [{ SubnetId: 'subnet-public-2' }],
|
|
525
|
+
Routes: [{ GatewayId: 'igw-12345' }] // Public
|
|
526
|
+
}]
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const result = await discovery.findExistingNatGateway(mockVpcId);
|
|
437
530
|
|
|
438
|
-
|
|
439
|
-
|
|
531
|
+
expect(result).toBeDefined();
|
|
532
|
+
expect(result.NatGatewayId).toBe('nat-frigg-12345');
|
|
533
|
+
expect(result._isInPrivateSubnet).toBe(false);
|
|
534
|
+
});
|
|
440
535
|
|
|
441
|
-
|
|
536
|
+
it('should return null when no NAT Gateways found', async () => {
|
|
537
|
+
ec2Mock.on(DescribeNatGatewaysCommand).resolves({
|
|
538
|
+
NatGateways: []
|
|
539
|
+
});
|
|
442
540
|
|
|
443
|
-
|
|
541
|
+
const result = await discovery.findExistingNatGateway(mockVpcId);
|
|
542
|
+
expect(result).toBeNull();
|
|
444
543
|
});
|
|
445
544
|
});
|
|
446
545
|
|
|
@@ -457,7 +556,9 @@ describe('AWSDiscovery', () => {
|
|
|
457
556
|
const mockKmsArn = 'arn:aws:kms:us-east-1:123456789012:key/12345678';
|
|
458
557
|
const mockNatGateway = {
|
|
459
558
|
NatGatewayId: 'nat-12345678',
|
|
460
|
-
|
|
559
|
+
SubnetId: 'subnet-public-1',
|
|
560
|
+
NatGatewayAddresses: [{ AllocationId: 'eipalloc-12345' }],
|
|
561
|
+
_isInPrivateSubnet: false
|
|
461
562
|
};
|
|
462
563
|
|
|
463
564
|
// Mock all the discovery methods
|
|
@@ -484,6 +585,7 @@ describe('AWSDiscovery', () => {
|
|
|
484
585
|
defaultKmsKeyId: mockKmsArn,
|
|
485
586
|
existingNatGatewayId: 'nat-12345678',
|
|
486
587
|
existingElasticIpAllocationId: 'eipalloc-12345',
|
|
588
|
+
natGatewayInPrivateSubnet: false,
|
|
487
589
|
subnetConversionRequired: false,
|
|
488
590
|
privateSubnetsWithWrongRoutes: []
|
|
489
591
|
});
|
|
@@ -517,8 +619,10 @@ describe('AWSDiscovery', () => {
|
|
|
517
619
|
jest.spyOn(discovery, 'findExistingNatGateway').mockResolvedValue(null);
|
|
518
620
|
jest.spyOn(discovery, 'findAvailableElasticIP').mockResolvedValue(null);
|
|
519
621
|
jest.spyOn(discovery, 'isSubnetPrivate')
|
|
520
|
-
.
|
|
521
|
-
|
|
622
|
+
.mockImplementation((subnetId) => {
|
|
623
|
+
// subnet-1 is public, subnet-2 is private
|
|
624
|
+
return Promise.resolve(subnetId === 'subnet-2');
|
|
625
|
+
});
|
|
522
626
|
|
|
523
627
|
const result = await discovery.discoverResources({ selfHeal: true });
|
|
524
628
|
|
|
@@ -560,15 +664,54 @@ describe('AWSDiscovery', () => {
|
|
|
560
664
|
});
|
|
561
665
|
});
|
|
562
666
|
|
|
667
|
+
it('should detect NAT Gateway in private subnet in discoverResources', async () => {
|
|
668
|
+
const mockVpc = { VpcId: 'vpc-12345678' };
|
|
669
|
+
const mockSubnets = [
|
|
670
|
+
{ SubnetId: 'subnet-1' },
|
|
671
|
+
{ SubnetId: 'subnet-2' }
|
|
672
|
+
];
|
|
673
|
+
const mockPublicSubnet = { SubnetId: 'subnet-public-1' };
|
|
674
|
+
const mockSecurityGroup = { GroupId: 'sg-12345678' };
|
|
675
|
+
const mockRouteTable = { RouteTableId: 'rtb-12345678' };
|
|
676
|
+
const mockNatGateway = {
|
|
677
|
+
NatGatewayId: 'nat-12345678',
|
|
678
|
+
NatGatewayAddresses: [{ AllocationId: 'eipalloc-12345' }],
|
|
679
|
+
_isInPrivateSubnet: true // NAT is in private subnet
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
jest.spyOn(discovery, 'findDefaultVpc').mockResolvedValue(mockVpc);
|
|
683
|
+
jest.spyOn(discovery, 'findPrivateSubnets').mockResolvedValue(mockSubnets);
|
|
684
|
+
jest.spyOn(discovery, 'findPublicSubnets').mockResolvedValue(mockPublicSubnet);
|
|
685
|
+
jest.spyOn(discovery, 'findDefaultSecurityGroup').mockResolvedValue(mockSecurityGroup);
|
|
686
|
+
jest.spyOn(discovery, 'findPrivateRouteTable').mockResolvedValue(mockRouteTable);
|
|
687
|
+
jest.spyOn(discovery, 'findDefaultKmsKey').mockResolvedValue(null);
|
|
688
|
+
jest.spyOn(discovery, 'findExistingNatGateway').mockResolvedValue(mockNatGateway);
|
|
689
|
+
jest.spyOn(discovery, 'isSubnetPrivate')
|
|
690
|
+
.mockResolvedValueOnce(true)
|
|
691
|
+
.mockResolvedValueOnce(true);
|
|
692
|
+
|
|
693
|
+
const result = await discovery.discoverResources();
|
|
694
|
+
|
|
695
|
+
expect(result).toMatchObject({
|
|
696
|
+
defaultVpcId: 'vpc-12345678',
|
|
697
|
+
existingNatGatewayId: 'nat-12345678',
|
|
698
|
+
natGatewayInPrivateSubnet: true, // Should be true
|
|
699
|
+
subnetConversionRequired: false,
|
|
700
|
+
privateSubnetsWithWrongRoutes: []
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
|
|
563
704
|
it('should handle single subnet scenario', async () => {
|
|
564
705
|
const mockVpc = { VpcId: 'vpc-12345678' };
|
|
565
706
|
const mockSubnets = [{ SubnetId: 'subnet-1' }]; // Only one subnet
|
|
707
|
+
const mockPublicSubnet = { SubnetId: 'subnet-public-1' };
|
|
566
708
|
const mockSecurityGroup = { GroupId: 'sg-12345678' };
|
|
567
709
|
const mockRouteTable = { RouteTableId: 'rtb-12345678' };
|
|
568
710
|
const mockKmsArn = 'arn:aws:kms:us-east-1:123456789012:key/12345678';
|
|
569
711
|
|
|
570
712
|
jest.spyOn(discovery, 'findDefaultVpc').mockResolvedValue(mockVpc);
|
|
571
713
|
jest.spyOn(discovery, 'findPrivateSubnets').mockResolvedValue(mockSubnets);
|
|
714
|
+
jest.spyOn(discovery, 'findPublicSubnets').mockResolvedValue(mockPublicSubnet);
|
|
572
715
|
jest.spyOn(discovery, 'findDefaultSecurityGroup').mockResolvedValue(mockSecurityGroup);
|
|
573
716
|
jest.spyOn(discovery, 'findPrivateRouteTable').mockResolvedValue(mockRouteTable);
|
|
574
717
|
jest.spyOn(discovery, 'findDefaultKmsKey').mockResolvedValue(mockKmsArn);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test script to verify subnet detection and classification logic
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { AWSDiscovery } = require('./aws-discovery');
|
|
8
|
+
|
|
9
|
+
async function testSubnetLogic() {
|
|
10
|
+
console.log('๐งช Testing Subnet Detection and Classification Logic');
|
|
11
|
+
console.log('โ'.repeat(60));
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const discovery = new AWSDiscovery();
|
|
15
|
+
|
|
16
|
+
// Test with selfHeal enabled
|
|
17
|
+
console.log('\n๐ Testing with selfHeal: true');
|
|
18
|
+
console.log('-'.repeat(40));
|
|
19
|
+
|
|
20
|
+
const resources = await discovery.discoverResources({ selfHeal: true });
|
|
21
|
+
|
|
22
|
+
console.log('\n๐ Results:');
|
|
23
|
+
console.log(` VPC ID: ${resources.defaultVpcId}`);
|
|
24
|
+
console.log(` Private Subnet 1: ${resources.privateSubnetId1}`);
|
|
25
|
+
console.log(` Private Subnet 2: ${resources.privateSubnetId2}`);
|
|
26
|
+
console.log(` Public Subnet: ${resources.publicSubnetId}`);
|
|
27
|
+
console.log(` Conversion Required: ${resources.subnetConversionRequired}`);
|
|
28
|
+
|
|
29
|
+
if (resources.privateSubnetsWithWrongRoutes && resources.privateSubnetsWithWrongRoutes.length > 0) {
|
|
30
|
+
console.log(`\nโ ๏ธ Subnets needing conversion:`);
|
|
31
|
+
resources.privateSubnetsWithWrongRoutes.forEach(subnet => {
|
|
32
|
+
console.log(` - ${subnet}`);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('\nโ
Test completed successfully!');
|
|
37
|
+
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('\nโ Test failed:', error.message);
|
|
40
|
+
console.error('\nStack trace:', error.stack);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Run the test
|
|
46
|
+
testSubnetLogic().catch(error => {
|
|
47
|
+
console.error('Unhandled error:', error);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.428.
|
|
4
|
+
"version": "2.0.0--canary.428.6b04c24.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-ec2": "^3.835.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.835.0",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"@babel/eslint-parser": "^7.18.9",
|
|
10
10
|
"@babel/parser": "^7.25.3",
|
|
11
11
|
"@babel/traverse": "^7.25.3",
|
|
12
|
-
"@friggframework/schemas": "2.0.0--canary.428.
|
|
13
|
-
"@friggframework/test": "2.0.0--canary.428.
|
|
12
|
+
"@friggframework/schemas": "2.0.0--canary.428.6b04c24.0",
|
|
13
|
+
"@friggframework/test": "2.0.0--canary.428.6b04c24.0",
|
|
14
14
|
"@hapi/boom": "^10.0.1",
|
|
15
15
|
"@inquirer/prompts": "^5.3.8",
|
|
16
16
|
"axios": "^1.7.2",
|
|
@@ -32,8 +32,10 @@
|
|
|
32
32
|
"serverless-http": "^2.7.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@friggframework/eslint-config": "2.0.0--canary.428.
|
|
36
|
-
"@friggframework/prettier-config": "2.0.0--canary.428.
|
|
35
|
+
"@friggframework/eslint-config": "2.0.0--canary.428.6b04c24.0",
|
|
36
|
+
"@friggframework/prettier-config": "2.0.0--canary.428.6b04c24.0",
|
|
37
|
+
"aws-sdk-client-mock": "^4.1.0",
|
|
38
|
+
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
37
39
|
"jest": "^30.1.3",
|
|
38
40
|
"prettier": "^2.7.1",
|
|
39
41
|
"serverless": "3.39.0",
|
|
@@ -66,5 +68,5 @@
|
|
|
66
68
|
"publishConfig": {
|
|
67
69
|
"access": "public"
|
|
68
70
|
},
|
|
69
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "6b04c24df1e253fd23afd6acb39ec9b5ad61456f"
|
|
70
72
|
}
|