@friggframework/devtools 2.0.0--canary.406.78e2685.0 → 2.0.0--canary.398.dd443c7.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/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 +514 -167
- 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
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
+
const { AWSDiscovery } = require('./aws-discovery');
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Check if AWS discovery should run based on AppDefinition
|
|
7
|
+
* @param {Object} AppDefinition - Application definition
|
|
8
|
+
* @returns {boolean} True if discovery should run
|
|
9
|
+
*/
|
|
10
|
+
const shouldRunDiscovery = (AppDefinition) => {
|
|
11
|
+
return AppDefinition.vpc?.enable === true ||
|
|
12
|
+
AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true ||
|
|
13
|
+
AppDefinition.ssm?.enable === true;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find the actual path to node_modules directory
|
|
18
|
+
* Tries multiple methods to locate node_modules:
|
|
19
|
+
* 1. Traversing up from current directory
|
|
20
|
+
* 2. Using npm root command
|
|
21
|
+
* 3. Looking for package.json and adjacent node_modules
|
|
22
|
+
* @returns {string} Path to node_modules directory
|
|
23
|
+
*/
|
|
5
24
|
const findNodeModulesPath = () => {
|
|
6
25
|
try {
|
|
7
26
|
// Method 1: Try to find node_modules by traversing up from current directory
|
|
@@ -75,7 +94,18 @@ const findNodeModulesPath = () => {
|
|
|
75
94
|
}
|
|
76
95
|
};
|
|
77
96
|
|
|
78
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Modify handler paths to point to the correct node_modules location
|
|
99
|
+
* Only modifies paths when running in offline mode
|
|
100
|
+
* @param {Object} functions - Serverless functions configuration object
|
|
101
|
+
* @returns {Object} Modified functions object with updated handler paths
|
|
102
|
+
*/
|
|
103
|
+
/**
|
|
104
|
+
* Modify handler paths to point to the correct node_modules location
|
|
105
|
+
* Only modifies paths when running in offline mode
|
|
106
|
+
* @param {Object} functions - Serverless functions configuration object
|
|
107
|
+
* @returns {Object} Modified functions object with updated handler paths
|
|
108
|
+
*/
|
|
79
109
|
const modifyHandlerPaths = (functions) => {
|
|
80
110
|
// Check if we're running in offline mode
|
|
81
111
|
const isOffline = process.argv.includes('offline');
|
|
@@ -94,7 +124,10 @@ const modifyHandlerPaths = (functions) => {
|
|
|
94
124
|
const functionDef = modifiedFunctions[functionName];
|
|
95
125
|
if (functionDef?.handler?.includes('node_modules/')) {
|
|
96
126
|
// Replace node_modules/ with the actual path to node_modules/
|
|
97
|
-
|
|
127
|
+
const relativePath = path.relative(process.cwd(), nodeModulesPath);
|
|
128
|
+
functionDef.handler = functionDef.handler.replace('node_modules/', `${relativePath}/`);
|
|
129
|
+
const relativePath = path.relative(process.cwd(), nodeModulesPath);
|
|
130
|
+
functionDef.handler = functionDef.handler.replace('node_modules/', `${relativePath}/`);
|
|
98
131
|
console.log(`Updated handler for ${functionName}: ${functionDef.handler}`);
|
|
99
132
|
}
|
|
100
133
|
}
|
|
@@ -102,7 +135,22 @@ const modifyHandlerPaths = (functions) => {
|
|
|
102
135
|
return modifiedFunctions;
|
|
103
136
|
};
|
|
104
137
|
|
|
105
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Create VPC infrastructure resources for CloudFormation
|
|
140
|
+
* Creates VPC, subnets, NAT gateway, route tables, and security groups
|
|
141
|
+
* @param {Object} AppDefinition - Application definition object
|
|
142
|
+
* @param {Object} AppDefinition.vpc - VPC configuration
|
|
143
|
+
* @param {string} [AppDefinition.vpc.cidrBlock='10.0.0.0/16'] - CIDR block for VPC
|
|
144
|
+
* @returns {Object} CloudFormation resources for VPC infrastructure
|
|
145
|
+
*/
|
|
146
|
+
/**
|
|
147
|
+
* Create VPC infrastructure resources for CloudFormation
|
|
148
|
+
* Creates VPC, subnets, NAT gateway, route tables, and security groups
|
|
149
|
+
* @param {Object} AppDefinition - Application definition object
|
|
150
|
+
* @param {Object} AppDefinition.vpc - VPC configuration
|
|
151
|
+
* @param {string} [AppDefinition.vpc.cidrBlock='10.0.0.0/16'] - CIDR block for VPC
|
|
152
|
+
* @returns {Object} CloudFormation resources for VPC infrastructure
|
|
153
|
+
*/
|
|
106
154
|
const createVPCInfrastructure = (AppDefinition) => {
|
|
107
155
|
const vpcResources = {
|
|
108
156
|
// VPC
|
|
@@ -292,13 +340,6 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
292
340
|
CidrIp: '0.0.0.0/0',
|
|
293
341
|
Description: 'HTTP outbound'
|
|
294
342
|
},
|
|
295
|
-
{
|
|
296
|
-
IpProtocol: 'tcp',
|
|
297
|
-
FromPort: 27017,
|
|
298
|
-
ToPort: 27017,
|
|
299
|
-
CidrIp: '0.0.0.0/0',
|
|
300
|
-
Description: 'MongoDB Atlas TLS outbound'
|
|
301
|
-
},
|
|
302
343
|
{
|
|
303
344
|
IpProtocol: 'tcp',
|
|
304
345
|
FromPort: 53,
|
|
@@ -412,7 +453,57 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
412
453
|
return vpcResources;
|
|
413
454
|
};
|
|
414
455
|
|
|
415
|
-
|
|
456
|
+
/**
|
|
457
|
+
* Compose a complete serverless framework configuration from app definition
|
|
458
|
+
* @param {Object} AppDefinition - Application definition object
|
|
459
|
+
* @param {string} [AppDefinition.name] - Application name
|
|
460
|
+
* @param {string} [AppDefinition.provider='aws'] - Cloud provider
|
|
461
|
+
* @param {Array} AppDefinition.integrations - Array of integration definitions
|
|
462
|
+
* @param {Object} [AppDefinition.vpc] - VPC configuration
|
|
463
|
+
* @param {Object} [AppDefinition.encryption] - KMS encryption configuration
|
|
464
|
+
* @param {Object} [AppDefinition.ssm] - SSM parameter store configuration
|
|
465
|
+
* @param {Object} [AppDefinition.websockets] - WebSocket configuration
|
|
466
|
+
* @param {boolean} [AppDefinition.websockets.enable=false] - Enable WebSocket support for live update streaming
|
|
467
|
+
* @returns {Object} Complete serverless framework configuration
|
|
468
|
+
*/
|
|
469
|
+
const composeServerlessDefinition = async (AppDefinition) => {
|
|
470
|
+
// Store discovered resources
|
|
471
|
+
let discoveredResources = {};
|
|
472
|
+
|
|
473
|
+
// Run AWS discovery if needed
|
|
474
|
+
if (shouldRunDiscovery(AppDefinition)) {
|
|
475
|
+
console.log('🔍 Running AWS resource discovery for serverless template...');
|
|
476
|
+
try {
|
|
477
|
+
const region = process.env.AWS_REGION || 'us-east-1';
|
|
478
|
+
const discovery = new AWSDiscovery(region);
|
|
479
|
+
|
|
480
|
+
const config = {
|
|
481
|
+
vpc: AppDefinition.vpc || {},
|
|
482
|
+
encryption: AppDefinition.encryption || {},
|
|
483
|
+
ssm: AppDefinition.ssm || {}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
discoveredResources = await discovery.discoverResources(config);
|
|
487
|
+
|
|
488
|
+
console.log('✅ AWS discovery completed successfully!');
|
|
489
|
+
if (discoveredResources.defaultVpcId) {
|
|
490
|
+
console.log(` VPC: ${discoveredResources.defaultVpcId}`);
|
|
491
|
+
}
|
|
492
|
+
if (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2) {
|
|
493
|
+
console.log(` Subnets: ${discoveredResources.privateSubnetId1}, ${discoveredResources.privateSubnetId2}`);
|
|
494
|
+
}
|
|
495
|
+
if (discoveredResources.defaultSecurityGroupId) {
|
|
496
|
+
console.log(` Security Group: ${discoveredResources.defaultSecurityGroupId}`);
|
|
497
|
+
}
|
|
498
|
+
if (discoveredResources.defaultKmsKeyId) {
|
|
499
|
+
console.log(` KMS Key: ${discoveredResources.defaultKmsKeyId}`);
|
|
500
|
+
}
|
|
501
|
+
} catch (error) {
|
|
502
|
+
console.error('❌ AWS discovery failed:', error.message);
|
|
503
|
+
throw new Error(`AWS discovery failed: ${error.message}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
416
507
|
const definition = {
|
|
417
508
|
frameworkVersion: '>=3.17.0',
|
|
418
509
|
service: AppDefinition.name || 'create-frigg-app',
|
|
@@ -425,11 +516,28 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
425
516
|
name: AppDefinition.provider || 'aws',
|
|
426
517
|
runtime: 'nodejs20.x',
|
|
427
518
|
timeout: 30,
|
|
428
|
-
region: 'us-east-1',
|
|
429
|
-
|
|
519
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
520
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
521
|
+
stage: '${opt:stage}',
|
|
430
522
|
environment: {
|
|
431
523
|
STAGE: '${opt:stage, "dev"}',
|
|
432
524
|
AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1,
|
|
525
|
+
// Add discovered resources to environment if available
|
|
526
|
+
...(discoveredResources.defaultVpcId && { AWS_DISCOVERY_VPC_ID: discoveredResources.defaultVpcId }),
|
|
527
|
+
...(discoveredResources.defaultSecurityGroupId && { AWS_DISCOVERY_SECURITY_GROUP_ID: discoveredResources.defaultSecurityGroupId }),
|
|
528
|
+
...(discoveredResources.privateSubnetId1 && { AWS_DISCOVERY_SUBNET_ID_1: discoveredResources.privateSubnetId1 }),
|
|
529
|
+
...(discoveredResources.privateSubnetId2 && { AWS_DISCOVERY_SUBNET_ID_2: discoveredResources.privateSubnetId2 }),
|
|
530
|
+
...(discoveredResources.publicSubnetId && { AWS_DISCOVERY_PUBLIC_SUBNET_ID: discoveredResources.publicSubnetId }),
|
|
531
|
+
...(discoveredResources.defaultRouteTableId && { AWS_DISCOVERY_ROUTE_TABLE_ID: discoveredResources.defaultRouteTableId }),
|
|
532
|
+
...(discoveredResources.defaultKmsKeyId && { AWS_DISCOVERY_KMS_KEY_ID: discoveredResources.defaultKmsKeyId }),
|
|
533
|
+
// Add discovered resources to environment if available
|
|
534
|
+
...(discoveredResources.defaultVpcId && { AWS_DISCOVERY_VPC_ID: discoveredResources.defaultVpcId }),
|
|
535
|
+
...(discoveredResources.defaultSecurityGroupId && { AWS_DISCOVERY_SECURITY_GROUP_ID: discoveredResources.defaultSecurityGroupId }),
|
|
536
|
+
...(discoveredResources.privateSubnetId1 && { AWS_DISCOVERY_SUBNET_ID_1: discoveredResources.privateSubnetId1 }),
|
|
537
|
+
...(discoveredResources.privateSubnetId2 && { AWS_DISCOVERY_SUBNET_ID_2: discoveredResources.privateSubnetId2 }),
|
|
538
|
+
...(discoveredResources.publicSubnetId && { AWS_DISCOVERY_PUBLIC_SUBNET_ID: discoveredResources.publicSubnetId }),
|
|
539
|
+
...(discoveredResources.defaultRouteTableId && { AWS_DISCOVERY_ROUTE_TABLE_ID: discoveredResources.defaultRouteTableId }),
|
|
540
|
+
...(discoveredResources.defaultKmsKeyId && { AWS_DISCOVERY_KMS_KEY_ID: discoveredResources.defaultKmsKeyId }),
|
|
433
541
|
},
|
|
434
542
|
iamRoleStatements: [
|
|
435
543
|
{
|
|
@@ -491,7 +599,8 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
491
599
|
autoCreate: false,
|
|
492
600
|
apiVersion: '2012-11-05',
|
|
493
601
|
endpoint: 'http://localhost:4566',
|
|
494
|
-
region: 'us-east-1',
|
|
602
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
603
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
495
604
|
accessKeyId: 'root',
|
|
496
605
|
secretAccessKey: 'root',
|
|
497
606
|
skipCacheInvalidation: false,
|
|
@@ -501,26 +610,6 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
501
610
|
},
|
|
502
611
|
},
|
|
503
612
|
functions: {
|
|
504
|
-
defaultWebsocket: {
|
|
505
|
-
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
506
|
-
events: [
|
|
507
|
-
{
|
|
508
|
-
websocket: {
|
|
509
|
-
route: '$connect',
|
|
510
|
-
},
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
websocket: {
|
|
514
|
-
route: '$default',
|
|
515
|
-
},
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
websocket: {
|
|
519
|
-
route: '$disconnect',
|
|
520
|
-
},
|
|
521
|
-
},
|
|
522
|
-
],
|
|
523
|
-
},
|
|
524
613
|
auth: {
|
|
525
614
|
handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
526
615
|
events: [
|
|
@@ -579,7 +668,8 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
579
668
|
Type: 'AWS::SQS::Queue',
|
|
580
669
|
Properties: {
|
|
581
670
|
QueueName:
|
|
582
|
-
'internal-error-queue-${self:provider.stage}',
|
|
671
|
+
'${self:service}-internal-error-queue-${self:provider.stage}',
|
|
672
|
+
'${self:service}-internal-error-queue-${self:provider.stage}',
|
|
583
673
|
MessageRetentionPeriod: 300,
|
|
584
674
|
},
|
|
585
675
|
},
|
|
@@ -659,62 +749,7 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
659
749
|
},
|
|
660
750
|
};
|
|
661
751
|
|
|
662
|
-
|
|
663
|
-
if (process.env.CUSTOM_DOMAIN) {
|
|
664
|
-
|
|
665
|
-
// Configure custom domain
|
|
666
|
-
definition.custom.customDomain = {
|
|
667
|
-
domainName: process.env.CUSTOM_DOMAIN,
|
|
668
|
-
basePath: process.env.CUSTOM_BASE_PATH || '',
|
|
669
|
-
stage: '${opt:stage, "dev"}',
|
|
670
|
-
createRoute53Record: process.env.CREATE_ROUTE53_RECORD !== 'false', // Default true
|
|
671
|
-
certificateName: process.env.CERTIFICATE_NAME || process.env.CUSTOM_DOMAIN,
|
|
672
|
-
endpointType: process.env.ENDPOINT_TYPE || 'edge', // edge, regional, or private
|
|
673
|
-
securityPolicy: process.env.SECURITY_POLICY || 'tls_1_2',
|
|
674
|
-
apiType: 'http',
|
|
675
|
-
autoDomain: process.env.AUTO_DOMAIN === 'true', // Auto create domain if it doesn't exist
|
|
676
|
-
};
|
|
677
|
-
|
|
678
|
-
// Set BASE_URL to custom domain
|
|
679
|
-
definition.provider.environment.BASE_URL = `https://${process.env.CUSTOM_DOMAIN}`;
|
|
680
|
-
} else {
|
|
681
|
-
// Default BASE_URL using API Gateway generated URL
|
|
682
|
-
// For HTTP API, don't include stage as it uses $default behavior
|
|
683
|
-
definition.provider.environment.BASE_URL = {
|
|
684
|
-
'Fn::GetAtt': ['HttpApi', 'ApiEndpoint']
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// REDIRECT_PATH is required for OAuth integrations
|
|
689
|
-
if (!process.env.REDIRECT_PATH) {
|
|
690
|
-
throw new Error(
|
|
691
|
-
'REDIRECT_PATH environment variable is required. ' +
|
|
692
|
-
'Please set REDIRECT_PATH in your .env file (e.g., REDIRECT_PATH=/oauth/callback)'
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// Set REDIRECT_URI based on domain configuration
|
|
697
|
-
if (process.env.CUSTOM_DOMAIN) {
|
|
698
|
-
definition.provider.environment.REDIRECT_URI = `https://${process.env.CUSTOM_DOMAIN}${process.env.REDIRECT_PATH}`;
|
|
699
|
-
} else {
|
|
700
|
-
definition.provider.environment.REDIRECT_URI = {
|
|
701
|
-
'Fn::Join': [
|
|
702
|
-
'',
|
|
703
|
-
[
|
|
704
|
-
{ 'Fn::GetAtt': ['HttpApi', 'ApiEndpoint'] },
|
|
705
|
-
process.env.REDIRECT_PATH,
|
|
706
|
-
],
|
|
707
|
-
],
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// Add REDIRECT_URI to CloudFormation outputs
|
|
712
|
-
definition.resources.Outputs = {
|
|
713
|
-
RedirectURI: {
|
|
714
|
-
Description: 'OAuth Redirect URI to register with providers',
|
|
715
|
-
Value: definition.provider.environment.REDIRECT_URI,
|
|
716
|
-
},
|
|
717
|
-
};
|
|
752
|
+
|
|
718
753
|
|
|
719
754
|
// KMS Configuration based on App Definition
|
|
720
755
|
if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
|
|
@@ -747,36 +782,17 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
747
782
|
definition.provider.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
|
|
748
783
|
|
|
749
784
|
definition.plugins.push('serverless-kms-grants');
|
|
750
|
-
|
|
785
|
+
|
|
786
|
+
// Configure KMS grants with discovered default key
|
|
787
|
+
// Configure KMS grants with discovered default key
|
|
788
|
+
definition.custom.kmsGrants = {
|
|
789
|
+
kmsKeyId: discoveredResources.defaultKmsKeyId || '${env:AWS_DISCOVERY_KMS_KEY_ID}'
|
|
790
|
+
kmsKeyId: discoveredResources.defaultKmsKeyId || '${env:AWS_DISCOVERY_KMS_KEY_ID}'
|
|
791
|
+
};
|
|
751
792
|
}
|
|
752
793
|
|
|
753
794
|
// VPC Configuration based on App Definition
|
|
754
795
|
if (AppDefinition.vpc?.enable === true) {
|
|
755
|
-
// Create VPC config from App Definition or use auto-created resources
|
|
756
|
-
const vpcConfig = {};
|
|
757
|
-
|
|
758
|
-
if (AppDefinition.vpc.securityGroupIds) {
|
|
759
|
-
// User provided custom security groups
|
|
760
|
-
vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
|
|
761
|
-
} else {
|
|
762
|
-
// Use auto-created security group
|
|
763
|
-
vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
if (AppDefinition.vpc.subnetIds) {
|
|
767
|
-
// User provided custom subnets
|
|
768
|
-
vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
|
|
769
|
-
} else {
|
|
770
|
-
// Use auto-created private subnets
|
|
771
|
-
vpcConfig.subnetIds = [
|
|
772
|
-
{ Ref: 'FriggPrivateSubnet1' },
|
|
773
|
-
{ Ref: 'FriggPrivateSubnet2' }
|
|
774
|
-
];
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// Set VPC config for Lambda functions
|
|
778
|
-
definition.provider.vpc = vpcConfig;
|
|
779
|
-
|
|
780
796
|
// Add VPC-related IAM permissions
|
|
781
797
|
definition.provider.iamRoleStatements.push({
|
|
782
798
|
Effect: 'Allow',
|
|
@@ -790,74 +806,405 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
790
806
|
Resource: '*'
|
|
791
807
|
});
|
|
792
808
|
|
|
793
|
-
//
|
|
794
|
-
|
|
795
|
-
|
|
809
|
+
// Default approach: Use AWS Discovery to find existing VPC resources
|
|
810
|
+
if (AppDefinition.vpc.createNew === true) {
|
|
811
|
+
// Option 1: Create new VPC infrastructure (explicit opt-in)
|
|
812
|
+
const vpcConfig = {};
|
|
813
|
+
// Add VPC-related IAM permissions
|
|
814
|
+
definition.provider.iamRoleStatements.push({
|
|
815
|
+
Effect: 'Allow',
|
|
816
|
+
Action: [
|
|
817
|
+
'ec2:CreateNetworkInterface',
|
|
818
|
+
'ec2:DescribeNetworkInterfaces',
|
|
819
|
+
'ec2:DeleteNetworkInterface',
|
|
820
|
+
'ec2:AttachNetworkInterface',
|
|
821
|
+
'ec2:DetachNetworkInterface'
|
|
822
|
+
],
|
|
823
|
+
Resource: '*'
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
// Default approach: Use AWS Discovery to find existing VPC resources
|
|
827
|
+
if (AppDefinition.vpc.createNew === true) {
|
|
828
|
+
// Option 1: Create new VPC infrastructure (explicit opt-in)
|
|
829
|
+
const vpcConfig = {};
|
|
830
|
+
|
|
831
|
+
if (AppDefinition.vpc.securityGroupIds) {
|
|
832
|
+
// User provided custom security groups
|
|
833
|
+
vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
|
|
834
|
+
} else {
|
|
835
|
+
// Use auto-created security group
|
|
836
|
+
vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
|
|
837
|
+
}
|
|
838
|
+
if (AppDefinition.vpc.securityGroupIds) {
|
|
839
|
+
// User provided custom security groups
|
|
840
|
+
vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
|
|
841
|
+
} else {
|
|
842
|
+
// Use auto-created security group
|
|
843
|
+
vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (AppDefinition.vpc.subnetIds) {
|
|
847
|
+
// User provided custom subnets
|
|
848
|
+
vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
|
|
849
|
+
} else {
|
|
850
|
+
// Use auto-created private subnets
|
|
851
|
+
vpcConfig.subnetIds = [
|
|
852
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
853
|
+
{ Ref: 'FriggPrivateSubnet2' }
|
|
854
|
+
];
|
|
855
|
+
}
|
|
856
|
+
if (AppDefinition.vpc.subnetIds) {
|
|
857
|
+
// User provided custom subnets
|
|
858
|
+
vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
|
|
859
|
+
} else {
|
|
860
|
+
// Use auto-created private subnets
|
|
861
|
+
vpcConfig.subnetIds = [
|
|
862
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
863
|
+
{ Ref: 'FriggPrivateSubnet2' }
|
|
864
|
+
];
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Set VPC config for Lambda functions
|
|
868
|
+
definition.provider.vpc = vpcConfig;
|
|
869
|
+
// Set VPC config for Lambda functions
|
|
870
|
+
definition.provider.vpc = vpcConfig;
|
|
871
|
+
|
|
872
|
+
// Add VPC infrastructure resources to CloudFormation
|
|
873
|
+
const vpcResources = createVPCInfrastructure(AppDefinition);
|
|
874
|
+
Object.assign(definition.resources.Resources, vpcResources);
|
|
875
|
+
} else {
|
|
876
|
+
// Option 2: Use AWS Discovery (default behavior)
|
|
877
|
+
// VPC configuration using discovered or explicitly provided resources
|
|
878
|
+
const vpcConfig = {
|
|
879
|
+
securityGroupIds: AppDefinition.vpc.securityGroupIds ||
|
|
880
|
+
(discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []),
|
|
881
|
+
subnetIds: AppDefinition.vpc.subnetIds ||
|
|
882
|
+
(discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2 ?
|
|
883
|
+
[discoveredResources.privateSubnetId1, discoveredResources.privateSubnetId2] :
|
|
884
|
+
[])
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
// Set VPC config for Lambda functions only if we have valid subnet IDs
|
|
888
|
+
if (vpcConfig.subnetIds.length >= 2 && vpcConfig.securityGroupIds.length > 0) {
|
|
889
|
+
definition.provider.vpc = vpcConfig;
|
|
890
|
+
|
|
891
|
+
// Check if we have an existing NAT Gateway to use
|
|
892
|
+
if (!discoveredResources.existingNatGatewayId) {
|
|
893
|
+
// No existing NAT Gateway, create new resources
|
|
894
|
+
|
|
895
|
+
// Only create EIP if we don't have an existing one available
|
|
896
|
+
if (!discoveredResources.existingElasticIpAllocationId) {
|
|
897
|
+
definition.resources.Resources.FriggNATGatewayEIP = {
|
|
898
|
+
Type: 'AWS::EC2::EIP',
|
|
899
|
+
Properties: {
|
|
900
|
+
Domain: 'vpc',
|
|
901
|
+
Tags: [
|
|
902
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
|
|
903
|
+
]
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
definition.resources.Resources.FriggNATGateway = {
|
|
909
|
+
Type: 'AWS::EC2::NatGateway',
|
|
910
|
+
Properties: {
|
|
911
|
+
AllocationId: discoveredResources.existingElasticIpAllocationId ||
|
|
912
|
+
{ 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
|
|
913
|
+
SubnetId: discoveredResources.publicSubnetId || discoveredResources.privateSubnetId1, // Use first discovered subnet if no public subnet found
|
|
914
|
+
Tags: [
|
|
915
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
|
|
916
|
+
]
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Create route table for Lambda subnets to use NAT Gateway
|
|
922
|
+
definition.resources.Resources.FriggLambdaRouteTable = {
|
|
923
|
+
Type: 'AWS::EC2::RouteTable',
|
|
924
|
+
Properties: {
|
|
925
|
+
VpcId: discoveredResources.defaultVpcId || { Ref: 'FriggVPC' },
|
|
926
|
+
Tags: [
|
|
927
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' }
|
|
928
|
+
]
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
definition.resources.Resources.FriggNATRoute = {
|
|
933
|
+
Type: 'AWS::EC2::Route',
|
|
934
|
+
Properties: {
|
|
935
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' },
|
|
936
|
+
DestinationCidrBlock: '0.0.0.0/0',
|
|
937
|
+
NatGatewayId: discoveredResources.existingNatGatewayId || { Ref: 'FriggNATGateway' }
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
// Associate Lambda subnets with NAT Gateway route table
|
|
942
|
+
definition.resources.Resources.FriggSubnet1RouteAssociation = {
|
|
943
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
944
|
+
Properties: {
|
|
945
|
+
SubnetId: vpcConfig.subnetIds[0],
|
|
946
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' }
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
definition.resources.Resources.FriggSubnet2RouteAssociation = {
|
|
951
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
952
|
+
Properties: {
|
|
953
|
+
SubnetId: vpcConfig.subnetIds[1],
|
|
954
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' }
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// Add VPC endpoints for AWS service optimization (optional but recommended)
|
|
959
|
+
if (AppDefinition.vpc.enableVPCEndpoints !== false) {
|
|
960
|
+
definition.resources.Resources.VPCEndpointS3 = {
|
|
961
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
962
|
+
Properties: {
|
|
963
|
+
VpcId: discoveredResources.defaultVpcId,
|
|
964
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.s3',
|
|
965
|
+
VpcEndpointType: 'Gateway',
|
|
966
|
+
RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
definition.resources.Resources.VPCEndpointDynamoDB = {
|
|
971
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
972
|
+
Properties: {
|
|
973
|
+
VpcId: discoveredResources.defaultVpcId,
|
|
974
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
|
|
975
|
+
VpcEndpointType: 'Gateway',
|
|
976
|
+
RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// SSM Parameter Store Configuration based on App Definition
|
|
985
|
+
if (AppDefinition.ssm?.enable === true) {
|
|
986
|
+
// Add AWS Parameters and Secrets Lambda Extension layer
|
|
987
|
+
definition.provider.layers = [
|
|
988
|
+
'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
|
|
989
|
+
];
|
|
990
|
+
|
|
991
|
+
// Add SSM IAM permissions
|
|
992
|
+
definition.provider.iamRoleStatements.push({
|
|
993
|
+
Effect: 'Allow',
|
|
994
|
+
Action: [
|
|
995
|
+
'ssm:GetParameter',
|
|
996
|
+
'ssm:GetParameters',
|
|
997
|
+
'ssm:GetParametersByPath'
|
|
998
|
+
],
|
|
999
|
+
Resource: [
|
|
1000
|
+
'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'
|
|
1001
|
+
]
|
|
1002
|
+
'ssm:GetParameter',
|
|
1003
|
+
'ssm:GetParameters',
|
|
1004
|
+
'ssm:GetParametersByPath'
|
|
1005
|
+
],
|
|
1006
|
+
Resource: [
|
|
1007
|
+
'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'
|
|
1008
|
+
]
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
// Add environment variable for SSM parameter prefix
|
|
1012
|
+
definition.provider.environment.SSM_PARAMETER_PREFIX = '/${self:service}/${self:provider.stage}';
|
|
1013
|
+
// Add environment variable for SSM parameter prefix
|
|
1014
|
+
definition.provider.environment.SSM_PARAMETER_PREFIX = '/${self:service}/${self:provider.stage}';
|
|
796
1015
|
}
|
|
797
1016
|
|
|
798
|
-
|
|
1017
|
+
// Add integration-specific functions and resources
|
|
1018
|
+
if (AppDefinition.integrations && Array.isArray(AppDefinition.integrations)) {
|
|
799
1019
|
for (const integration of AppDefinition.integrations) {
|
|
1020
|
+
if (!integration || !integration.Definition || !integration.Definition.name) {
|
|
1021
|
+
throw new Error('Invalid integration: missing Definition or name');
|
|
1022
|
+
}
|
|
800
1023
|
const integrationName = integration.Definition.name;
|
|
1024
|
+
if (AppDefinition.integrations && Array.isArray(AppDefinition.integrations)) {
|
|
1025
|
+
for (const integration of AppDefinition.integrations) {
|
|
1026
|
+
if (!integration || !integration.Definition || !integration.Definition.name) {
|
|
1027
|
+
throw new Error('Invalid integration: missing Definition or name');
|
|
1028
|
+
}
|
|
1029
|
+
const integrationName = integration.Definition.name;
|
|
1030
|
+
|
|
1031
|
+
// Add function for the integration
|
|
1032
|
+
definition.functions[integrationName] = {
|
|
1033
|
+
handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
|
|
1034
|
+
events: [
|
|
1035
|
+
{
|
|
1036
|
+
httpApi: {
|
|
1037
|
+
path: `/api/${integrationName}-integration/{proxy+}`,
|
|
1038
|
+
method: 'ANY',
|
|
1039
|
+
},
|
|
1040
|
+
},
|
|
1041
|
+
],
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
// Add SQS Queue for the integration
|
|
1045
|
+
const queueReference = `${integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
|
|
1046
|
+
}Queue`;
|
|
1047
|
+
const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
|
|
1048
|
+
definition.resources.Resources[queueReference] = {
|
|
1049
|
+
Type: 'AWS::SQS::Queue',
|
|
1050
|
+
Properties: {
|
|
1051
|
+
QueueName: `\${self:custom.${queueReference}}`,
|
|
1052
|
+
MessageRetentionPeriod: 60,
|
|
1053
|
+
VisibilityTimeout: 1800, // 30 minutes
|
|
1054
|
+
RedrivePolicy: {
|
|
1055
|
+
maxReceiveCount: 1,
|
|
1056
|
+
deadLetterTargetArn: {
|
|
1057
|
+
'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
|
|
1058
|
+
},
|
|
1059
|
+
},
|
|
1060
|
+
},
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
// Add Queue Worker for the integration
|
|
1064
|
+
const queueWorkerName = `${integrationName}QueueWorker`;
|
|
1065
|
+
definition.functions[queueWorkerName] = {
|
|
1066
|
+
handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
|
|
1067
|
+
reservedConcurrency: 5,
|
|
1068
|
+
events: [
|
|
1069
|
+
{
|
|
1070
|
+
sqs: {
|
|
1071
|
+
arn: {
|
|
1072
|
+
'Fn::GetAtt': [queueReference, 'Arn'],
|
|
1073
|
+
},
|
|
1074
|
+
batchSize: 1,
|
|
1075
|
+
},
|
|
1076
|
+
},
|
|
1077
|
+
],
|
|
1078
|
+
timeout: 600,
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
// Add Queue URL for the integration to the ENVironment variables
|
|
1082
|
+
definition.provider.environment = {
|
|
1083
|
+
...definition.provider.environment,
|
|
1084
|
+
[`${integrationName.toUpperCase()}_QUEUE_URL`]: {
|
|
1085
|
+
Ref: queueReference,
|
|
1086
|
+
},
|
|
1087
|
+
};
|
|
1088
|
+
// Add Queue URL for the integration to the ENVironment variables
|
|
1089
|
+
definition.provider.environment = {
|
|
1090
|
+
...definition.provider.environment,
|
|
1091
|
+
[`${integrationName.toUpperCase()}_QUEUE_URL`]: {
|
|
1092
|
+
Ref: queueReference,
|
|
1093
|
+
},
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
definition.custom[queueReference] = queueName;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Discovery has already run successfully at this point if needed
|
|
1101
|
+
// The discoveredResources object contains all the necessary AWS resources
|
|
1102
|
+
|
|
1103
|
+
// Add websocket function if enabled
|
|
1104
|
+
if (AppDefinition.websockets?.enable === true) {
|
|
1105
|
+
definition.functions.defaultWebsocket = {
|
|
1106
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
1107
|
+
events: [
|
|
1108
|
+
{
|
|
1109
|
+
websocket: {
|
|
1110
|
+
route: '$connect',
|
|
1111
|
+
},
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
websocket: {
|
|
1115
|
+
route: '$default',
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
websocket: {
|
|
1120
|
+
route: '$disconnect',
|
|
1121
|
+
},
|
|
1122
|
+
},
|
|
1123
|
+
],
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
801
1126
|
|
|
802
|
-
//
|
|
803
|
-
|
|
804
|
-
|
|
1127
|
+
// Discovery has already run successfully at this point if needed
|
|
1128
|
+
// The discoveredResources object contains all the necessary AWS resources
|
|
1129
|
+
|
|
1130
|
+
// Add websocket function if enabled
|
|
1131
|
+
if (AppDefinition.websockets?.enable === true) {
|
|
1132
|
+
definition.functions.defaultWebsocket = {
|
|
1133
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
1134
|
+
events: [
|
|
1135
|
+
{
|
|
1136
|
+
websocket: {
|
|
1137
|
+
route: '$connect',
|
|
1138
|
+
},
|
|
1139
|
+
},
|
|
1140
|
+
{
|
|
1141
|
+
websocket: {
|
|
1142
|
+
route: '$default',
|
|
1143
|
+
},
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
websocket: {
|
|
1147
|
+
route: '$disconnect',
|
|
1148
|
+
},
|
|
1149
|
+
},
|
|
1150
|
+
],
|
|
1151
|
+
};
|
|
1152
|
+
definition.custom[queueReference] = queueName;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Discovery has already run successfully at this point if needed
|
|
1157
|
+
// The discoveredResources object contains all the necessary AWS resources
|
|
1158
|
+
|
|
1159
|
+
// Add websocket function if enabled
|
|
1160
|
+
if (AppDefinition.websockets?.enable === true) {
|
|
1161
|
+
definition.functions.defaultWebsocket = {
|
|
1162
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
805
1163
|
events: [
|
|
806
1164
|
{
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
method: 'ANY',
|
|
1165
|
+
websocket: {
|
|
1166
|
+
route: '$connect',
|
|
810
1167
|
},
|
|
811
1168
|
},
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
// Add SQS Queue for the integration
|
|
816
|
-
const queueReference = `${integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
|
|
817
|
-
}Queue`;
|
|
818
|
-
const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
|
|
819
|
-
definition.resources.Resources[queueReference] = {
|
|
820
|
-
Type: 'AWS::SQS::Queue',
|
|
821
|
-
Properties: {
|
|
822
|
-
QueueName: `\${self:custom.${queueReference}}`,
|
|
823
|
-
MessageRetentionPeriod: 60,
|
|
824
|
-
VisibilityTimeout: 1800, // 30 minutes
|
|
825
|
-
RedrivePolicy: {
|
|
826
|
-
maxReceiveCount: 1,
|
|
827
|
-
deadLetterTargetArn: {
|
|
828
|
-
'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
|
|
1169
|
+
{
|
|
1170
|
+
websocket: {
|
|
1171
|
+
route: '$default',
|
|
829
1172
|
},
|
|
830
1173
|
},
|
|
831
|
-
|
|
1174
|
+
{
|
|
1175
|
+
websocket: {
|
|
1176
|
+
route: '$disconnect',
|
|
1177
|
+
},
|
|
1178
|
+
},
|
|
1179
|
+
],
|
|
832
1180
|
};
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Discovery has already run successfully at this point if needed
|
|
1184
|
+
// The discoveredResources object contains all the necessary AWS resources
|
|
833
1185
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
definition.functions
|
|
837
|
-
handler:
|
|
838
|
-
reservedConcurrency: 5,
|
|
1186
|
+
// Add websocket function if enabled
|
|
1187
|
+
if (AppDefinition.websockets?.enable === true) {
|
|
1188
|
+
definition.functions.defaultWebsocket = {
|
|
1189
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
839
1190
|
events: [
|
|
840
1191
|
{
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1192
|
+
websocket: {
|
|
1193
|
+
route: '$connect',
|
|
1194
|
+
},
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
websocket: {
|
|
1198
|
+
route: '$default',
|
|
1199
|
+
},
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
websocket: {
|
|
1203
|
+
route: '$disconnect',
|
|
846
1204
|
},
|
|
847
1205
|
},
|
|
848
1206
|
],
|
|
849
|
-
timeout: 600,
|
|
850
1207
|
};
|
|
851
|
-
|
|
852
|
-
// Add Queue URL for the integration to the ENVironment variables
|
|
853
|
-
definition.provider.environment = {
|
|
854
|
-
...definition.provider.environment,
|
|
855
|
-
[`${integrationName.toUpperCase()}_QUEUE_URL`]: {
|
|
856
|
-
Ref: queueReference,
|
|
857
|
-
},
|
|
858
|
-
};
|
|
859
|
-
|
|
860
|
-
definition.custom[queueReference] = queueName;
|
|
861
1208
|
}
|
|
862
1209
|
|
|
863
1210
|
// Modify handler paths to point to the correct node_modules location
|