@friggframework/devtools 2.0.0-next.27 â 2.0.0-next.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frigg-cli/build-command/index.js +4 -2
- package/frigg-cli/deploy-command/index.js +5 -2
- package/frigg-cli/generate-iam-command.js +115 -0
- package/frigg-cli/index.js +11 -1
- package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +596 -0
- package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
- package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
- package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
- package/infrastructure/README-TESTING.md +332 -0
- package/infrastructure/README.md +421 -0
- package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +277 -0
- package/infrastructure/aws-discovery.js +568 -0
- package/infrastructure/aws-discovery.test.js +373 -0
- package/infrastructure/build-time-discovery.js +206 -0
- package/infrastructure/build-time-discovery.test.js +375 -0
- package/infrastructure/create-frigg-infrastructure.js +2 -2
- package/infrastructure/frigg-deployment-iam-stack.yaml +379 -0
- package/infrastructure/iam-generator.js +687 -0
- package/infrastructure/iam-generator.test.js +169 -0
- package/infrastructure/iam-policy-basic.json +212 -0
- package/infrastructure/iam-policy-full.json +282 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/run-discovery.js +110 -0
- package/infrastructure/serverless-template.js +537 -212
- package/infrastructure/serverless-template.test.js +541 -0
- package/management-ui/dist/assets/FriggLogo-B7Xx8ZW1.svg +1 -0
- package/management-ui/dist/assets/index-BA21WgFa.js +1221 -0
- package/management-ui/dist/assets/index-CbM64Oba.js +1221 -0
- package/management-ui/dist/assets/index-CkvseXTC.css +1 -0
- package/management-ui/dist/index.html +14 -0
- package/package.json +9 -5
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { composeServerlessDefinition } = require('./serverless-template');
|
|
3
|
+
const { AWSDiscovery } = require('./aws-discovery');
|
|
4
|
+
const { BuildTimeDiscovery } = require('./build-time-discovery');
|
|
5
|
+
const FriggServerlessPlugin = require('../../serverless-plugin/index');
|
|
6
|
+
|
|
7
|
+
// Integration tests for end-to-end AWS discovery and serverless config generation
|
|
8
|
+
describe('VPC/KMS/SSM Integration Tests', () => {
|
|
9
|
+
let mockAWSDiscovery;
|
|
10
|
+
let buildTimeDiscovery;
|
|
11
|
+
|
|
12
|
+
const mockAWSResources = {
|
|
13
|
+
defaultVpcId: 'vpc-12345678',
|
|
14
|
+
defaultSecurityGroupId: 'sg-12345678',
|
|
15
|
+
privateSubnetId1: 'subnet-private-1',
|
|
16
|
+
privateSubnetId2: 'subnet-private-2',
|
|
17
|
+
privateRouteTableId: 'rtb-12345678',
|
|
18
|
+
defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
// Mock AWSDiscovery to return consistent test data
|
|
23
|
+
mockAWSDiscovery = {
|
|
24
|
+
discoverResources: jest.fn().mockResolvedValue(mockAWSResources),
|
|
25
|
+
findDefaultVpc: jest.fn().mockResolvedValue({ VpcId: mockAWSResources.defaultVpcId }),
|
|
26
|
+
findPrivateSubnets: jest.fn().mockResolvedValue([
|
|
27
|
+
{ SubnetId: mockAWSResources.privateSubnetId1 },
|
|
28
|
+
{ SubnetId: mockAWSResources.privateSubnetId2 }
|
|
29
|
+
]),
|
|
30
|
+
findDefaultSecurityGroup: jest.fn().mockResolvedValue({ GroupId: mockAWSResources.defaultSecurityGroupId }),
|
|
31
|
+
findPrivateRouteTable: jest.fn().mockResolvedValue({ RouteTableId: mockAWSResources.privateRouteTableId }),
|
|
32
|
+
findDefaultKmsKey: jest.fn().mockResolvedValue(mockAWSResources.defaultKmsKeyId)
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
jest.doMock('./aws-discovery', () => ({
|
|
36
|
+
AWSDiscovery: jest.fn(() => mockAWSDiscovery)
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
buildTimeDiscovery = new BuildTimeDiscovery('us-east-1');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
jest.clearAllMocks();
|
|
44
|
+
jest.resetModules();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('End-to-End Serverless Configuration Generation', () => {
|
|
48
|
+
it('should generate complete serverless config with VPC, KMS, and SSM enabled', async () => {
|
|
49
|
+
const appDefinition = {
|
|
50
|
+
name: 'test-frigg-app',
|
|
51
|
+
vpc: { enable: true },
|
|
52
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
53
|
+
ssm: { enable: true },
|
|
54
|
+
integrations: [{
|
|
55
|
+
Definition: {
|
|
56
|
+
name: 'testIntegration'
|
|
57
|
+
}
|
|
58
|
+
}]
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Run AWS discovery
|
|
62
|
+
const discoveredResources = await mockAWSDiscovery.discoverResources();
|
|
63
|
+
|
|
64
|
+
// Set environment variables as would happen in build
|
|
65
|
+
process.env.AWS_DISCOVERY_VPC_ID = discoveredResources.defaultVpcId;
|
|
66
|
+
process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = discoveredResources.defaultSecurityGroupId;
|
|
67
|
+
process.env.AWS_DISCOVERY_SUBNET_ID_1 = discoveredResources.privateSubnetId1;
|
|
68
|
+
process.env.AWS_DISCOVERY_SUBNET_ID_2 = discoveredResources.privateSubnetId2;
|
|
69
|
+
process.env.AWS_DISCOVERY_ROUTE_TABLE_ID = discoveredResources.privateRouteTableId;
|
|
70
|
+
process.env.AWS_DISCOVERY_KMS_KEY_ID = discoveredResources.defaultKmsKeyId;
|
|
71
|
+
|
|
72
|
+
// Generate serverless configuration
|
|
73
|
+
const serverlessConfig = composeServerlessDefinition(appDefinition);
|
|
74
|
+
|
|
75
|
+
// Verify VPC configuration
|
|
76
|
+
expect(serverlessConfig.provider.vpc).toBe('${self:custom.vpc.${self:provider.stage}}');
|
|
77
|
+
expect(serverlessConfig.custom.vpc).toEqual({
|
|
78
|
+
'${self:provider.stage}': {
|
|
79
|
+
securityGroupIds: ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}'],
|
|
80
|
+
subnetIds: [
|
|
81
|
+
'${env:AWS_DISCOVERY_SUBNET_ID_1}',
|
|
82
|
+
'${env:AWS_DISCOVERY_SUBNET_ID_2}'
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Verify VPC Endpoint
|
|
88
|
+
expect(serverlessConfig.resources.Resources.VPCEndpointS3).toEqual({
|
|
89
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
90
|
+
Properties: {
|
|
91
|
+
VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
|
|
92
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.s3',
|
|
93
|
+
VpcEndpointType: 'Gateway',
|
|
94
|
+
RouteTableIds: ['${env:AWS_DISCOVERY_ROUTE_TABLE_ID}']
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Verify KMS configuration
|
|
99
|
+
expect(serverlessConfig.plugins).toContain('serverless-kms-grants');
|
|
100
|
+
expect(serverlessConfig.provider.environment.KMS_KEY_ARN).toBe('${self:custom.kmsGrants.kmsKeyId}');
|
|
101
|
+
expect(serverlessConfig.custom.kmsGrants).toEqual({
|
|
102
|
+
kmsKeyId: '${env:AWS_DISCOVERY_KMS_KEY_ID}'
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Verify KMS IAM permissions
|
|
106
|
+
const kmsPermission = serverlessConfig.provider.iamRoleStatements.find(
|
|
107
|
+
statement => statement.Action.includes('kms:GenerateDataKey')
|
|
108
|
+
);
|
|
109
|
+
expect(kmsPermission).toBeDefined();
|
|
110
|
+
|
|
111
|
+
// Verify SSM configuration
|
|
112
|
+
expect(serverlessConfig.provider.layers).toEqual([
|
|
113
|
+
'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
|
|
114
|
+
]);
|
|
115
|
+
expect(serverlessConfig.provider.environment.SSM_PARAMETER_PREFIX).toBe('/${self:service}/${self:provider.stage}');
|
|
116
|
+
|
|
117
|
+
// Verify SSM IAM permissions
|
|
118
|
+
const ssmPermission = serverlessConfig.provider.iamRoleStatements.find(
|
|
119
|
+
statement => statement.Action.includes('ssm:GetParameter')
|
|
120
|
+
);
|
|
121
|
+
expect(ssmPermission).toBeDefined();
|
|
122
|
+
|
|
123
|
+
// Verify integration resources
|
|
124
|
+
expect(serverlessConfig.functions.testIntegration).toBeDefined();
|
|
125
|
+
expect(serverlessConfig.functions.testIntegrationQueueWorker).toBeDefined();
|
|
126
|
+
expect(serverlessConfig.resources.Resources.TestIntegrationQueue).toBeDefined();
|
|
127
|
+
|
|
128
|
+
// Clean up environment
|
|
129
|
+
delete process.env.AWS_DISCOVERY_VPC_ID;
|
|
130
|
+
delete process.env.AWS_DISCOVERY_SECURITY_GROUP_ID;
|
|
131
|
+
delete process.env.AWS_DISCOVERY_SUBNET_ID_1;
|
|
132
|
+
delete process.env.AWS_DISCOVERY_SUBNET_ID_2;
|
|
133
|
+
delete process.env.AWS_DISCOVERY_ROUTE_TABLE_ID;
|
|
134
|
+
delete process.env.AWS_DISCOVERY_KMS_KEY_ID;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should generate config with only VPC enabled', async () => {
|
|
138
|
+
const appDefinition = {
|
|
139
|
+
name: 'vpc-only-app',
|
|
140
|
+
vpc: { enable: true },
|
|
141
|
+
integrations: []
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
process.env.AWS_DISCOVERY_VPC_ID = mockAWSResources.defaultVpcId;
|
|
145
|
+
process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = mockAWSResources.defaultSecurityGroupId;
|
|
146
|
+
process.env.AWS_DISCOVERY_SUBNET_ID_1 = mockAWSResources.privateSubnetId1;
|
|
147
|
+
process.env.AWS_DISCOVERY_SUBNET_ID_2 = mockAWSResources.privateSubnetId2;
|
|
148
|
+
process.env.AWS_DISCOVERY_ROUTE_TABLE_ID = mockAWSResources.privateRouteTableId;
|
|
149
|
+
|
|
150
|
+
const serverlessConfig = composeServerlessDefinition(appDefinition);
|
|
151
|
+
|
|
152
|
+
// Should have VPC config
|
|
153
|
+
expect(serverlessConfig.provider.vpc).toBeDefined();
|
|
154
|
+
expect(serverlessConfig.custom.vpc).toBeDefined();
|
|
155
|
+
expect(serverlessConfig.resources.Resources.VPCEndpointS3).toBeDefined();
|
|
156
|
+
|
|
157
|
+
// Should not have KMS config
|
|
158
|
+
expect(serverlessConfig.plugins).not.toContain('serverless-kms-grants');
|
|
159
|
+
expect(serverlessConfig.provider.environment.KMS_KEY_ARN).toBeUndefined();
|
|
160
|
+
|
|
161
|
+
// Should not have SSM config
|
|
162
|
+
expect(serverlessConfig.provider.layers).toBeUndefined();
|
|
163
|
+
expect(serverlessConfig.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
164
|
+
|
|
165
|
+
// Clean up
|
|
166
|
+
delete process.env.AWS_DISCOVERY_VPC_ID;
|
|
167
|
+
delete process.env.AWS_DISCOVERY_SECURITY_GROUP_ID;
|
|
168
|
+
delete process.env.AWS_DISCOVERY_SUBNET_ID_1;
|
|
169
|
+
delete process.env.AWS_DISCOVERY_SUBNET_ID_2;
|
|
170
|
+
delete process.env.AWS_DISCOVERY_ROUTE_TABLE_ID;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should generate config with only KMS enabled', async () => {
|
|
174
|
+
const appDefinition = {
|
|
175
|
+
name: 'kms-only-app',
|
|
176
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
177
|
+
integrations: []
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
process.env.AWS_DISCOVERY_KMS_KEY_ID = mockAWSResources.defaultKmsKeyId;
|
|
181
|
+
|
|
182
|
+
const serverlessConfig = composeServerlessDefinition(appDefinition);
|
|
183
|
+
|
|
184
|
+
// Should have KMS config
|
|
185
|
+
expect(serverlessConfig.plugins).toContain('serverless-kms-grants');
|
|
186
|
+
expect(serverlessConfig.custom.kmsGrants).toBeDefined();
|
|
187
|
+
|
|
188
|
+
// Should not have VPC config
|
|
189
|
+
expect(serverlessConfig.provider.vpc).toBeUndefined();
|
|
190
|
+
|
|
191
|
+
// Should not have SSM config
|
|
192
|
+
expect(serverlessConfig.provider.layers).toBeUndefined();
|
|
193
|
+
|
|
194
|
+
delete process.env.AWS_DISCOVERY_KMS_KEY_ID;
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('Plugin Integration', () => {
|
|
199
|
+
it('should trigger AWS discovery through serverless plugin', async () => {
|
|
200
|
+
const mockServerless = {
|
|
201
|
+
cli: { log: jest.fn() },
|
|
202
|
+
service: {
|
|
203
|
+
provider: {
|
|
204
|
+
name: 'aws',
|
|
205
|
+
region: 'us-east-1',
|
|
206
|
+
vpc: '${self:custom.vpc.${self:provider.stage}}'
|
|
207
|
+
},
|
|
208
|
+
plugins: ['serverless-kms-grants'],
|
|
209
|
+
custom: {},
|
|
210
|
+
functions: {}
|
|
211
|
+
},
|
|
212
|
+
processedInput: { commands: [] },
|
|
213
|
+
getProvider: jest.fn(() => ({})),
|
|
214
|
+
extendConfiguration: jest.fn()
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const plugin = new FriggServerlessPlugin(mockServerless, { stage: 'test' });
|
|
218
|
+
|
|
219
|
+
// Mock BuildTimeDiscovery
|
|
220
|
+
const mockBuildTimeDiscovery = {
|
|
221
|
+
preBuildHook: jest.fn().mockResolvedValue(mockAWSResources)
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
jest.doMock('./build-time-discovery', () => ({
|
|
225
|
+
BuildTimeDiscovery: jest.fn(() => mockBuildTimeDiscovery)
|
|
226
|
+
}));
|
|
227
|
+
|
|
228
|
+
// Test the beforePackageInitialize hook
|
|
229
|
+
await plugin.beforePackageInitialize();
|
|
230
|
+
|
|
231
|
+
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
|
|
232
|
+
expect.objectContaining({
|
|
233
|
+
vpc: { enable: true },
|
|
234
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true }
|
|
235
|
+
}),
|
|
236
|
+
'us-east-1'
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
expect(mockServerless.cli.log).toHaveBeenCalledWith('AWS discovery completed successfully');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should handle plugin discovery failure gracefully', async () => {
|
|
243
|
+
const mockServerless = {
|
|
244
|
+
cli: { log: jest.fn() },
|
|
245
|
+
service: {
|
|
246
|
+
provider: {
|
|
247
|
+
name: 'aws',
|
|
248
|
+
region: 'us-east-1',
|
|
249
|
+
vpc: '${self:custom.vpc}'
|
|
250
|
+
},
|
|
251
|
+
plugins: [],
|
|
252
|
+
custom: {},
|
|
253
|
+
functions: {}
|
|
254
|
+
},
|
|
255
|
+
processedInput: { commands: [] },
|
|
256
|
+
getProvider: jest.fn(() => ({})),
|
|
257
|
+
extendConfiguration: jest.fn()
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const plugin = new FriggServerlessPlugin(mockServerless, { stage: 'test' });
|
|
261
|
+
|
|
262
|
+
// Mock BuildTimeDiscovery to fail
|
|
263
|
+
const mockBuildTimeDiscovery = {
|
|
264
|
+
preBuildHook: jest.fn().mockRejectedValue(new Error('AWS API Error'))
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
jest.doMock('./build-time-discovery', () => ({
|
|
268
|
+
BuildTimeDiscovery: jest.fn(() => mockBuildTimeDiscovery)
|
|
269
|
+
}));
|
|
270
|
+
|
|
271
|
+
await plugin.beforePackageInitialize();
|
|
272
|
+
|
|
273
|
+
expect(mockServerless.cli.log).toHaveBeenCalledWith('AWS discovery failed, continuing with deployment...');
|
|
274
|
+
expect(mockServerless.cli.log).toHaveBeenCalledWith('Using fallback values for AWS resources');
|
|
275
|
+
|
|
276
|
+
// Verify fallback values are set
|
|
277
|
+
expect(process.env.AWS_DISCOVERY_VPC_ID).toBe('vpc-fallback');
|
|
278
|
+
expect(process.env.AWS_DISCOVERY_KMS_KEY_ID).toBe('arn:aws:kms:*:*:key/*');
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe('Template Variable Replacement', () => {
|
|
283
|
+
it('should replace environment variable placeholders with actual values', () => {
|
|
284
|
+
const template = `
|
|
285
|
+
provider:
|
|
286
|
+
vpc:
|
|
287
|
+
securityGroupIds:
|
|
288
|
+
- \${env:AWS_DISCOVERY_SECURITY_GROUP_ID}
|
|
289
|
+
subnetIds:
|
|
290
|
+
- \${env:AWS_DISCOVERY_SUBNET_ID_1}
|
|
291
|
+
- \${env:AWS_DISCOVERY_SUBNET_ID_2}
|
|
292
|
+
environment:
|
|
293
|
+
KMS_KEY_ARN: \${env:AWS_DISCOVERY_KMS_KEY_ID}
|
|
294
|
+
resources:
|
|
295
|
+
VPCEndpoint:
|
|
296
|
+
Properties:
|
|
297
|
+
VpcId: \${env:AWS_DISCOVERY_VPC_ID}
|
|
298
|
+
`;
|
|
299
|
+
|
|
300
|
+
// Set environment variables
|
|
301
|
+
process.env.AWS_DISCOVERY_VPC_ID = mockAWSResources.defaultVpcId;
|
|
302
|
+
process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = mockAWSResources.defaultSecurityGroupId;
|
|
303
|
+
process.env.AWS_DISCOVERY_SUBNET_ID_1 = mockAWSResources.privateSubnetId1;
|
|
304
|
+
process.env.AWS_DISCOVERY_SUBNET_ID_2 = mockAWSResources.privateSubnetId2;
|
|
305
|
+
process.env.AWS_DISCOVERY_KMS_KEY_ID = mockAWSResources.defaultKmsKeyId;
|
|
306
|
+
|
|
307
|
+
// In a real deployment, serverless framework would resolve these environment variables
|
|
308
|
+
// For testing, we can verify the placeholders are correctly formatted
|
|
309
|
+
expect(template).toContain('${env:AWS_DISCOVERY_VPC_ID}');
|
|
310
|
+
expect(template).toContain('${env:AWS_DISCOVERY_SECURITY_GROUP_ID}');
|
|
311
|
+
expect(template).toContain('${env:AWS_DISCOVERY_SUBNET_ID_1}');
|
|
312
|
+
expect(template).toContain('${env:AWS_DISCOVERY_SUBNET_ID_2}');
|
|
313
|
+
expect(template).toContain('${env:AWS_DISCOVERY_KMS_KEY_ID}');
|
|
314
|
+
|
|
315
|
+
// Clean up
|
|
316
|
+
delete process.env.AWS_DISCOVERY_VPC_ID;
|
|
317
|
+
delete process.env.AWS_DISCOVERY_SECURITY_GROUP_ID;
|
|
318
|
+
delete process.env.AWS_DISCOVERY_SUBNET_ID_1;
|
|
319
|
+
delete process.env.AWS_DISCOVERY_SUBNET_ID_2;
|
|
320
|
+
delete process.env.AWS_DISCOVERY_KMS_KEY_ID;
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('Error Scenarios', () => {
|
|
325
|
+
it('should handle AWS discovery timeout gracefully', async () => {
|
|
326
|
+
const mockFailingDiscovery = {
|
|
327
|
+
discoverResources: jest.fn().mockRejectedValue(new Error('Request timeout'))
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
jest.doMock('./aws-discovery', () => ({
|
|
331
|
+
AWSDiscovery: jest.fn(() => mockFailingDiscovery)
|
|
332
|
+
}));
|
|
333
|
+
|
|
334
|
+
const appDefinition = {
|
|
335
|
+
vpc: { enable: true },
|
|
336
|
+
integrations: []
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
await expect(buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1')).rejects.toThrow('Request timeout');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should handle partial AWS resource discovery', async () => {
|
|
343
|
+
const partialResources = {
|
|
344
|
+
defaultVpcId: 'vpc-12345678',
|
|
345
|
+
defaultSecurityGroupId: 'sg-12345678',
|
|
346
|
+
privateSubnetId1: 'subnet-1',
|
|
347
|
+
privateSubnetId2: 'subnet-1', // Same subnet used twice
|
|
348
|
+
privateRouteTableId: 'rtb-12345678',
|
|
349
|
+
defaultKmsKeyId: '*' // Fallback KMS key
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
mockAWSDiscovery.discoverResources.mockResolvedValue(partialResources);
|
|
353
|
+
|
|
354
|
+
const appDefinition = {
|
|
355
|
+
vpc: { enable: true },
|
|
356
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
357
|
+
integrations: []
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
|
|
361
|
+
|
|
362
|
+
expect(result).toEqual(partialResources);
|
|
363
|
+
expect(result.privateSubnetId2).toBe(result.privateSubnetId1); // Should handle single subnet scenario
|
|
364
|
+
expect(result.defaultKmsKeyId).toBe('*'); // Should handle KMS fallback
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
describe('Multi-Region Support', () => {
|
|
369
|
+
it('should support different AWS regions', async () => {
|
|
370
|
+
const appDefinition = {
|
|
371
|
+
vpc: { enable: true },
|
|
372
|
+
integrations: []
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const euWestDiscovery = new BuildTimeDiscovery('eu-west-1');
|
|
376
|
+
|
|
377
|
+
await euWestDiscovery.preBuildHook(appDefinition, 'eu-west-1');
|
|
378
|
+
|
|
379
|
+
// Verify that AWSDiscovery was instantiated with correct region
|
|
380
|
+
expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-build script to run AWS discovery and set environment variables
|
|
5
|
+
* This should be run before serverless commands that need AWS resource discovery
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { BuildTimeDiscovery } = require('./build-time-discovery');
|
|
9
|
+
const { findNearestBackendPackageJson } = require('@friggframework/core');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
async function runDiscovery() {
|
|
13
|
+
let appDefinition;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
console.log('đ Starting AWS resource discovery...');
|
|
17
|
+
|
|
18
|
+
// Find the backend package.json to get AppDefinition
|
|
19
|
+
const backendPath = findNearestBackendPackageJson();
|
|
20
|
+
if (!backendPath) {
|
|
21
|
+
console.log('â ī¸ No backend package.json found, skipping discovery');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const backendDir = path.dirname(backendPath);
|
|
26
|
+
const backendFilePath = path.join(backendDir, 'index.js');
|
|
27
|
+
|
|
28
|
+
if (!require('fs').existsSync(backendFilePath)) {
|
|
29
|
+
console.log('â ī¸ No backend/index.js found, skipping discovery');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Load the app definition
|
|
34
|
+
const backend = require(backendFilePath);
|
|
35
|
+
appDefinition = backend.Definition;
|
|
36
|
+
|
|
37
|
+
if (!appDefinition) {
|
|
38
|
+
console.log('â ī¸ No Definition found in backend/index.js, skipping discovery');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check if discovery is needed
|
|
43
|
+
const needsDiscovery = appDefinition.vpc?.enable ||
|
|
44
|
+
appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
|
|
45
|
+
appDefinition.ssm?.enable;
|
|
46
|
+
|
|
47
|
+
if (!needsDiscovery) {
|
|
48
|
+
console.log('âšī¸ No AWS discovery needed based on app definition');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log('đ App requires AWS discovery for:');
|
|
53
|
+
if (appDefinition.vpc?.enable) console.log(' â
VPC support');
|
|
54
|
+
if (appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.log(' â
KMS encryption');
|
|
55
|
+
if (appDefinition.ssm?.enable) console.log(' â
SSM parameters');
|
|
56
|
+
|
|
57
|
+
// Run discovery
|
|
58
|
+
const discovery = new BuildTimeDiscovery();
|
|
59
|
+
const resources = await discovery.preBuildHook(appDefinition, process.env.AWS_REGION || 'us-east-1');
|
|
60
|
+
|
|
61
|
+
if (resources) {
|
|
62
|
+
console.log('â
AWS discovery completed successfully!');
|
|
63
|
+
console.log(` VPC: ${resources.defaultVpcId}`);
|
|
64
|
+
console.log(` Subnets: ${resources.privateSubnetId1}, ${resources.privateSubnetId2}`);
|
|
65
|
+
console.log(` Public Subnet: ${resources.publicSubnetId}`);
|
|
66
|
+
console.log(` Security Group: ${resources.defaultSecurityGroupId}`);
|
|
67
|
+
console.log(` Route Table: ${resources.privateRouteTableId}`);
|
|
68
|
+
console.log(` KMS Key: ${resources.defaultKmsKeyId}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('â AWS discovery failed:', error.message);
|
|
73
|
+
console.error('');
|
|
74
|
+
|
|
75
|
+
// Check if this is an AWS SDK missing error
|
|
76
|
+
if (error.message.includes('Cannot find module') && error.message.includes('@aws-sdk')) {
|
|
77
|
+
console.error('đ¨ AWS SDK not installed!');
|
|
78
|
+
console.error('');
|
|
79
|
+
console.error('đĄ Install AWS SDK dependencies:');
|
|
80
|
+
console.error(' npm install @aws-sdk/client-ec2 @aws-sdk/client-kms @aws-sdk/client-sts');
|
|
81
|
+
console.error('');
|
|
82
|
+
} else {
|
|
83
|
+
console.error('đ¨ Discovery is required because your AppDefinition has these features enabled:');
|
|
84
|
+
if (appDefinition.vpc?.enable) console.error(' â VPC support (vpc.enable: true)');
|
|
85
|
+
if (appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.error(' â KMS encryption (encryption.useDefaultKMSForFieldLevelEncryption: true)');
|
|
86
|
+
if (appDefinition.ssm?.enable) console.error(' â SSM parameters (ssm.enable: true)');
|
|
87
|
+
console.error('');
|
|
88
|
+
console.error('đĄ To fix this issue:');
|
|
89
|
+
console.error(' 1. Check AWS credentials: aws sts get-caller-identity');
|
|
90
|
+
console.error(' 2. Verify IAM permissions (see AWS-IAM-CREDENTIAL-NEEDS.md)');
|
|
91
|
+
console.error(' 3. Ensure default VPC exists: aws ec2 describe-vpcs');
|
|
92
|
+
console.error(' 4. Check AWS region: aws configure get region');
|
|
93
|
+
console.error('');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.error('đ§ Or disable features in backend/index.js:');
|
|
97
|
+
console.error(' vpc: { enable: false }');
|
|
98
|
+
console.error(' encryption: { useDefaultKMSForFieldLevelEncryption: false }');
|
|
99
|
+
console.error(' ssm: { enable: false }');
|
|
100
|
+
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Run if called directly
|
|
106
|
+
if (require.main === module) {
|
|
107
|
+
runDiscovery();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = { runDiscovery };
|