@friggframework/devtools 2.0.0--canary.490.e6d93e8.0 → 2.0.0--canary.490.df708d7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/infrastructure/domains/networking/vpc-builder.test.js +22 -21
- package/infrastructure/domains/networking/vpc-resolver.js +31 -3
- package/infrastructure/domains/networking/vpc-resolver.test.js +76 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +1 -1
- package/package.json +7 -7
|
@@ -1348,18 +1348,19 @@ describe('VpcBuilder', () => {
|
|
|
1348
1348
|
});
|
|
1349
1349
|
});
|
|
1350
1350
|
|
|
1351
|
-
describe('
|
|
1352
|
-
it('should correctly handle
|
|
1353
|
-
// This
|
|
1351
|
+
describe('External VPC with stack-managed routing infrastructure pattern', () => {
|
|
1352
|
+
it('should correctly handle external VPC with stack-managed routing infrastructure', async () => {
|
|
1353
|
+
// This pattern occurs when VPC/subnets/NAT are external but routing (route tables,
|
|
1354
|
+
// VPC endpoints, security groups) are managed by CloudFormation stack
|
|
1354
1355
|
const appDefinition = {
|
|
1355
1356
|
vpc: { enable: true },
|
|
1356
1357
|
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
1357
1358
|
};
|
|
1358
1359
|
|
|
1359
|
-
//
|
|
1360
|
+
// Discovery results from real-world production scenario
|
|
1360
1361
|
const discoveredResources = {
|
|
1361
1362
|
fromCloudFormationStack: true,
|
|
1362
|
-
stackName: '
|
|
1363
|
+
stackName: 'test-production-stack',
|
|
1363
1364
|
existingLogicalIds: [
|
|
1364
1365
|
'FriggLambdaSecurityGroup',
|
|
1365
1366
|
'FriggLambdaRouteTable',
|
|
@@ -1655,10 +1656,10 @@ describe('VpcBuilder', () => {
|
|
|
1655
1656
|
expect(result.external.some(r => r.physicalId === 'subnet-in-stack')).toBe(false);
|
|
1656
1657
|
});
|
|
1657
1658
|
|
|
1658
|
-
it('should handle
|
|
1659
|
+
it('should handle external VPC pattern: stack resources + queried external references', () => {
|
|
1659
1660
|
const flatDiscovery = {
|
|
1660
1661
|
fromCloudFormationStack: true,
|
|
1661
|
-
stackName: '
|
|
1662
|
+
stackName: 'test-production-stack',
|
|
1662
1663
|
existingLogicalIds: [
|
|
1663
1664
|
'FriggLambdaSecurityGroup',
|
|
1664
1665
|
'FriggLambdaRouteTable',
|
|
@@ -1670,14 +1671,14 @@ describe('VpcBuilder', () => {
|
|
|
1670
1671
|
'FriggKMSVPCEndpoint'
|
|
1671
1672
|
],
|
|
1672
1673
|
// Stack resources
|
|
1673
|
-
lambdaSecurityGroupId: 'sg-
|
|
1674
|
-
routeTableId: 'rtb-
|
|
1675
|
-
s3VpcEndpointId: 'vpce-s3',
|
|
1674
|
+
lambdaSecurityGroupId: 'sg-stack-123',
|
|
1675
|
+
routeTableId: 'rtb-stack-456',
|
|
1676
|
+
s3VpcEndpointId: 'vpce-s3-stack',
|
|
1676
1677
|
// External resources (discovered via queries)
|
|
1677
|
-
defaultVpcId: 'vpc-
|
|
1678
|
-
privateSubnetId1: 'subnet-
|
|
1679
|
-
privateSubnetId2: 'subnet-
|
|
1680
|
-
existingNatGatewayId: 'nat-
|
|
1678
|
+
defaultVpcId: 'vpc-external-123',
|
|
1679
|
+
privateSubnetId1: 'subnet-external-1',
|
|
1680
|
+
privateSubnetId2: 'subnet-external-2',
|
|
1681
|
+
existingNatGatewayId: 'nat-external-789'
|
|
1681
1682
|
};
|
|
1682
1683
|
|
|
1683
1684
|
const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
|
|
@@ -1685,19 +1686,19 @@ describe('VpcBuilder', () => {
|
|
|
1685
1686
|
// Stack resources should be in stackManaged
|
|
1686
1687
|
expect(result.stackManaged).toEqual(
|
|
1687
1688
|
expect.arrayContaining([
|
|
1688
|
-
expect.objectContaining({ logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-
|
|
1689
|
-
expect.objectContaining({ logicalId: 'FriggLambdaRouteTable', physicalId: 'rtb-
|
|
1690
|
-
expect.objectContaining({ logicalId: 'FriggS3VPCEndpoint' })
|
|
1689
|
+
expect.objectContaining({ logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-stack-123' }),
|
|
1690
|
+
expect.objectContaining({ logicalId: 'FriggLambdaRouteTable', physicalId: 'rtb-stack-456' }),
|
|
1691
|
+
expect.objectContaining({ logicalId: 'FriggS3VPCEndpoint', physicalId: 'vpce-s3-stack' })
|
|
1691
1692
|
])
|
|
1692
1693
|
);
|
|
1693
1694
|
|
|
1694
1695
|
// External resources should be in external array
|
|
1695
1696
|
expect(result.external).toEqual(
|
|
1696
1697
|
expect.arrayContaining([
|
|
1697
|
-
expect.objectContaining({ physicalId: 'vpc-
|
|
1698
|
-
expect.objectContaining({ physicalId: 'subnet-
|
|
1699
|
-
expect.objectContaining({ physicalId: 'subnet-
|
|
1700
|
-
expect.objectContaining({ physicalId: 'nat-
|
|
1698
|
+
expect.objectContaining({ physicalId: 'vpc-external-123', resourceType: 'AWS::EC2::VPC', source: 'cloudformation-query' }),
|
|
1699
|
+
expect.objectContaining({ physicalId: 'subnet-external-1', resourceType: 'AWS::EC2::Subnet', source: 'cloudformation-query' }),
|
|
1700
|
+
expect.objectContaining({ physicalId: 'subnet-external-2', resourceType: 'AWS::EC2::Subnet', source: 'cloudformation-query' }),
|
|
1701
|
+
expect.objectContaining({ physicalId: 'nat-external-789', resourceType: 'AWS::EC2::NatGateway', source: 'cloudformation-query' })
|
|
1701
1702
|
])
|
|
1702
1703
|
);
|
|
1703
1704
|
});
|
|
@@ -300,9 +300,23 @@ class VpcResourceResolver extends BaseResourceResolver {
|
|
|
300
300
|
const encryptionMethod = appDefinition.encryption?.fieldLevelEncryptionMethod;
|
|
301
301
|
const needsKms = encryptionMethod === 'kms';
|
|
302
302
|
|
|
303
|
+
// DynamoDB endpoint only needed if using DynamoDB (not MongoDB or PostgreSQL)
|
|
304
|
+
// Currently framework only supports MongoDB (via Prisma) and PostgreSQL (via Aurora)
|
|
305
|
+
// DynamoDB support is not implemented, so endpoint is not needed
|
|
306
|
+
const usesDynamoDB = appDefinition.database?.dynamodb?.enable === true;
|
|
307
|
+
|
|
308
|
+
// Special case: If DynamoDB endpoint exists in stack but not needed, preserve it
|
|
309
|
+
// to avoid deletion (user may have enabled it previously)
|
|
310
|
+
const dynamoDbInStack = this.isInStack('FriggDynamoDBVPCEndpoint', discovery);
|
|
311
|
+
const shouldPreserveDynamoDB = !usesDynamoDB && dynamoDbInStack;
|
|
312
|
+
|
|
303
313
|
const endpoints = {
|
|
304
314
|
s3: this._resolveEndpoint('FriggS3VPCEndpoint', 's3', userIntent, appDefinition, discovery),
|
|
305
|
-
dynamodb:
|
|
315
|
+
dynamodb: usesDynamoDB
|
|
316
|
+
? this._resolveEndpoint('FriggDynamoDBVPCEndpoint', 'dynamodb', userIntent, appDefinition, discovery)
|
|
317
|
+
: shouldPreserveDynamoDB
|
|
318
|
+
? this._resolveEndpoint('FriggDynamoDBVPCEndpoint', 'dynamodb', userIntent, appDefinition, discovery)
|
|
319
|
+
: { ownership: null, reason: 'DynamoDB endpoint not needed (application uses MongoDB/PostgreSQL, not DynamoDB)' },
|
|
306
320
|
kms: needsKms
|
|
307
321
|
? this._resolveEndpoint('FriggKMSVPCEndpoint', 'kms', userIntent, appDefinition, discovery)
|
|
308
322
|
: { ownership: null, reason: 'KMS endpoint not needed (encryption method is not KMS)' },
|
|
@@ -340,13 +354,27 @@ class VpcResourceResolver extends BaseResourceResolver {
|
|
|
340
354
|
);
|
|
341
355
|
}
|
|
342
356
|
|
|
343
|
-
// Auto-decide
|
|
344
|
-
|
|
357
|
+
// Auto-decide - check if in stack first for more informative logging
|
|
358
|
+
const inStack = this.isInStack(logicalId, discovery);
|
|
359
|
+
const stackResource = inStack ? this.findInStack(logicalId, discovery) : null;
|
|
360
|
+
|
|
361
|
+
const decision = this.resolveResourceOwnership(
|
|
345
362
|
'auto',
|
|
346
363
|
logicalId,
|
|
347
364
|
'AWS::EC2::VPCEndpoint',
|
|
348
365
|
discovery
|
|
349
366
|
);
|
|
367
|
+
|
|
368
|
+
// Override reason with more detailed explanation
|
|
369
|
+
if (decision.ownership === 'stack' && decision.physicalId) {
|
|
370
|
+
decision.reason = `Found in CloudFormation stack (must keep in template to avoid deletion)`;
|
|
371
|
+
} else if (decision.ownership === 'stack' && !decision.physicalId) {
|
|
372
|
+
decision.reason = `No existing ${endpointType} endpoint found - will create in stack`;
|
|
373
|
+
} else if (decision.ownership === 'external') {
|
|
374
|
+
decision.reason = `Found external ${endpointType} endpoint via discovery`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return decision;
|
|
350
378
|
}
|
|
351
379
|
|
|
352
380
|
/**
|
|
@@ -300,6 +300,82 @@ describe('VpcResourceResolver', () => {
|
|
|
300
300
|
});
|
|
301
301
|
|
|
302
302
|
describe('resolveVpcEndpoints', () => {
|
|
303
|
+
it('should skip DynamoDB endpoint when application uses MongoDB', () => {
|
|
304
|
+
const appDefinition = {
|
|
305
|
+
vpc: { ownership: { vpcEndpoints: 'auto' } },
|
|
306
|
+
database: { mongoDB: { enable: true } }, // Using MongoDB, not DynamoDB
|
|
307
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' }
|
|
308
|
+
};
|
|
309
|
+
const discovery = {
|
|
310
|
+
stackManaged: [],
|
|
311
|
+
external: [],
|
|
312
|
+
fromCloudFormation: false
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
316
|
+
|
|
317
|
+
expect(decisions.s3.ownership).toBe('stack'); // S3 always needed
|
|
318
|
+
expect(decisions.dynamodb.ownership).toBeNull(); // DynamoDB NOT needed
|
|
319
|
+
expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
|
|
320
|
+
expect(decisions.kms.ownership).toBe('stack'); // KMS needed (encryption enabled)
|
|
321
|
+
expect(decisions.secretsManager.ownership).toBe('stack'); // SM always needed
|
|
322
|
+
expect(decisions.sqs.ownership).toBe('stack'); // SQS always needed
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should skip DynamoDB endpoint when application uses PostgreSQL', () => {
|
|
326
|
+
const appDefinition = {
|
|
327
|
+
vpc: { ownership: { vpcEndpoints: 'auto' } },
|
|
328
|
+
database: { postgres: { enable: true } }, // Using PostgreSQL, not DynamoDB
|
|
329
|
+
};
|
|
330
|
+
const discovery = {
|
|
331
|
+
stackManaged: [],
|
|
332
|
+
external: [],
|
|
333
|
+
fromCloudFormation: false
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
337
|
+
|
|
338
|
+
expect(decisions.dynamodb.ownership).toBeNull();
|
|
339
|
+
expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should create DynamoDB endpoint when explicitly enabled', () => {
|
|
343
|
+
const appDefinition = {
|
|
344
|
+
vpc: { ownership: { vpcEndpoints: 'auto' } },
|
|
345
|
+
database: { dynamodb: { enable: true } }, // Explicitly using DynamoDB
|
|
346
|
+
};
|
|
347
|
+
const discovery = {
|
|
348
|
+
stackManaged: [],
|
|
349
|
+
external: [],
|
|
350
|
+
fromCloudFormation: false
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
354
|
+
|
|
355
|
+
expect(decisions.dynamodb.ownership).toBe('stack'); // DynamoDB needed
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should preserve DynamoDB endpoint if exists in stack even when not needed', () => {
|
|
359
|
+
const appDefinition = {
|
|
360
|
+
vpc: { ownership: { vpcEndpoints: 'auto' } },
|
|
361
|
+
database: { mongoDB: { enable: true } }, // Using MongoDB (DynamoDB not needed)
|
|
362
|
+
};
|
|
363
|
+
const discovery = {
|
|
364
|
+
stackManaged: [
|
|
365
|
+
{ logicalId: 'FriggDynamoDBVPCEndpoint', physicalId: 'vpce-ddb-legacy', resourceType: 'AWS::EC2::VPCEndpoint' }
|
|
366
|
+
],
|
|
367
|
+
external: [],
|
|
368
|
+
fromCloudFormation: true
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
|
|
372
|
+
|
|
373
|
+
// Should preserve (not delete) even though not actively needed
|
|
374
|
+
expect(decisions.dynamodb.ownership).toBe('stack');
|
|
375
|
+
expect(decisions.dynamodb.physicalId).toBe('vpce-ddb-legacy');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
|
|
303
379
|
it('should return null decisions when endpoints disabled', () => {
|
|
304
380
|
const appDefinition = {
|
|
305
381
|
vpc: {
|
|
@@ -589,7 +589,7 @@ describe('CloudFormationDiscovery', () => {
|
|
|
589
589
|
|
|
590
590
|
describe('External VPC with routing infrastructure pattern', () => {
|
|
591
591
|
it('should discover routing resources when VPC is external', async () => {
|
|
592
|
-
// This tests the
|
|
592
|
+
// This tests the external VPC pattern: external VPC/subnets/KMS,
|
|
593
593
|
// but stack creates routing infrastructure (route table, NAT route, VPC endpoints)
|
|
594
594
|
const mockStack = {
|
|
595
595
|
StackName: 'create-frigg-app-production',
|
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.490.
|
|
4
|
+
"version": "2.0.0--canary.490.df708d7.0",
|
|
5
5
|
"bin": {
|
|
6
6
|
"frigg": "./frigg-cli/index.js"
|
|
7
7
|
},
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"@babel/eslint-parser": "^7.18.9",
|
|
17
17
|
"@babel/parser": "^7.25.3",
|
|
18
18
|
"@babel/traverse": "^7.25.3",
|
|
19
|
-
"@friggframework/core": "2.0.0--canary.490.
|
|
20
|
-
"@friggframework/schemas": "2.0.0--canary.490.
|
|
21
|
-
"@friggframework/test": "2.0.0--canary.490.
|
|
19
|
+
"@friggframework/core": "2.0.0--canary.490.df708d7.0",
|
|
20
|
+
"@friggframework/schemas": "2.0.0--canary.490.df708d7.0",
|
|
21
|
+
"@friggframework/test": "2.0.0--canary.490.df708d7.0",
|
|
22
22
|
"@hapi/boom": "^10.0.1",
|
|
23
23
|
"@inquirer/prompts": "^5.3.8",
|
|
24
24
|
"axios": "^1.7.2",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"validate-npm-package-name": "^5.0.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@friggframework/eslint-config": "2.0.0--canary.490.
|
|
50
|
-
"@friggframework/prettier-config": "2.0.0--canary.490.
|
|
49
|
+
"@friggframework/eslint-config": "2.0.0--canary.490.df708d7.0",
|
|
50
|
+
"@friggframework/prettier-config": "2.0.0--canary.490.df708d7.0",
|
|
51
51
|
"aws-sdk-client-mock": "^4.1.0",
|
|
52
52
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
53
53
|
"jest": "^30.1.3",
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
"publishConfig": {
|
|
80
80
|
"access": "public"
|
|
81
81
|
},
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "df708d7b3a84f180a3edc5267375f9210e6a585a"
|
|
83
83
|
}
|