@friggframework/devtools 2.0.0--canary.454.25d396a.0 → 2.0.0--canary.463.62579dd.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/__tests__/unit/commands/db-setup.test.js +1 -1
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +486 -0
- package/frigg-cli/db-setup-command/index.js +1 -1
- package/frigg-cli/utils/prisma-runner.js +280 -0
- package/infrastructure/README.md +0 -51
- package/infrastructure/aws-discovery.js +2 -504
- package/infrastructure/aws-discovery.test.js +1 -447
- package/infrastructure/serverless-template.js +80 -465
- package/infrastructure/serverless-template.test.js +0 -91
- package/package.json +6 -8
- package/infrastructure/POSTGRES-CONFIGURATION.md +0 -630
- package/infrastructure/__tests__/postgres-config.test.js +0 -914
- package/infrastructure/scripts/build-prisma-layer.js +0 -394
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const { AWSDiscovery } = require('./aws-discovery');
|
|
4
|
-
const { buildPrismaLayer } = require('./scripts/build-prisma-layer');
|
|
5
4
|
|
|
6
5
|
const shouldRunDiscovery = (AppDefinition) => {
|
|
7
6
|
console.log(
|
|
@@ -18,8 +17,7 @@ const shouldRunDiscovery = (AppDefinition) => {
|
|
|
18
17
|
return (
|
|
19
18
|
AppDefinition.vpc?.enable === true ||
|
|
20
19
|
AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
21
|
-
AppDefinition.ssm?.enable === true
|
|
22
|
-
AppDefinition.database?.postgres?.enable === true
|
|
20
|
+
AppDefinition.ssm?.enable === true
|
|
23
21
|
);
|
|
24
22
|
};
|
|
25
23
|
|
|
@@ -70,7 +68,8 @@ const getAppEnvironmentVars = (AppDefinition) => {
|
|
|
70
68
|
}
|
|
71
69
|
if (skippedKeys.length > 0) {
|
|
72
70
|
console.log(
|
|
73
|
-
` ⚠️ Skipped ${
|
|
71
|
+
` ⚠️ Skipped ${
|
|
72
|
+
skippedKeys.length
|
|
74
73
|
} reserved AWS Lambda variables: ${skippedKeys.join(', ')}`
|
|
75
74
|
);
|
|
76
75
|
}
|
|
@@ -545,17 +544,10 @@ const gatherDiscoveredResources = async (AppDefinition) => {
|
|
|
545
544
|
try {
|
|
546
545
|
const region = process.env.AWS_REGION || 'us-east-1';
|
|
547
546
|
const discovery = new AWSDiscovery(region);
|
|
548
|
-
// Use Serverless Framework's stage resolution (opt:stage with 'dev' as default)
|
|
549
|
-
// This matches how serverless.yml resolves ${opt:stage, "dev"}
|
|
550
|
-
// IMPORTANT: Use SLS_STAGE (not STAGE) to match actual deployment stage
|
|
551
|
-
const stage = process.env.SLS_STAGE || 'dev';
|
|
552
|
-
|
|
553
547
|
const config = {
|
|
554
548
|
vpc: AppDefinition.vpc || {},
|
|
555
549
|
encryption: AppDefinition.encryption || {},
|
|
556
550
|
ssm: AppDefinition.ssm || {},
|
|
557
|
-
serviceName: AppDefinition.name || 'create-frigg-app',
|
|
558
|
-
stage: stage,
|
|
559
551
|
};
|
|
560
552
|
|
|
561
553
|
const discoveredResources = await discovery.discoverResources(config);
|
|
@@ -611,22 +603,6 @@ const buildEnvironment = (appEnvironmentVars, discoveredResources) => {
|
|
|
611
603
|
}
|
|
612
604
|
}
|
|
613
605
|
|
|
614
|
-
// Add Aurora discovery mappings
|
|
615
|
-
if (discoveredResources.aurora) {
|
|
616
|
-
if (discoveredResources.aurora.clusterIdentifier) {
|
|
617
|
-
environment.AWS_DISCOVERY_AURORA_CLUSTER_ID = discoveredResources.aurora.clusterIdentifier;
|
|
618
|
-
}
|
|
619
|
-
if (discoveredResources.aurora.endpoint) {
|
|
620
|
-
environment.AWS_DISCOVERY_AURORA_ENDPOINT = discoveredResources.aurora.endpoint;
|
|
621
|
-
}
|
|
622
|
-
if (discoveredResources.aurora.port) {
|
|
623
|
-
environment.AWS_DISCOVERY_AURORA_PORT = discoveredResources.aurora.port.toString();
|
|
624
|
-
}
|
|
625
|
-
if (discoveredResources.aurora.secretArn) {
|
|
626
|
-
environment.AWS_DISCOVERY_AURORA_SECRET_ARN = discoveredResources.aurora.secretArn;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
606
|
return environment;
|
|
631
607
|
};
|
|
632
608
|
|
|
@@ -642,27 +618,15 @@ const createBaseDefinition = (
|
|
|
642
618
|
service: AppDefinition.name || 'create-frigg-app',
|
|
643
619
|
package: {
|
|
644
620
|
individually: true,
|
|
645
|
-
|
|
646
|
-
// Existing AWS SDK exclusions
|
|
621
|
+
exclude: [
|
|
647
622
|
'!**/node_modules/aws-sdk/**',
|
|
648
623
|
'!**/node_modules/@aws-sdk/**',
|
|
649
624
|
'!package.json',
|
|
650
|
-
|
|
651
|
-
// GLOBAL Prisma exclusions - all Prisma packages moved to Lambda Layer
|
|
652
|
-
// This reduces each function from ~120MB to ~45MB (60% reduction)
|
|
653
|
-
'!node_modules/@prisma/**',
|
|
654
|
-
'!node_modules/.prisma/**',
|
|
655
|
-
'!node_modules/@prisma-mongodb/**',
|
|
656
|
-
'!node_modules/@prisma-postgresql/**',
|
|
657
|
-
'!node_modules/prisma/**',
|
|
658
|
-
// Prisma packages will be provided at runtime via Lambda Layer
|
|
659
|
-
// See: LAMBDA-LAYER-PRISMA.md for complete documentation
|
|
660
625
|
],
|
|
661
626
|
},
|
|
662
627
|
useDotenv: true,
|
|
663
628
|
provider: {
|
|
664
629
|
name: AppDefinition.provider || 'aws',
|
|
665
|
-
...(process.env.AWS_PROFILE && { profile: process.env.AWS_PROFILE }),
|
|
666
630
|
runtime: 'nodejs20.x',
|
|
667
631
|
timeout: 30,
|
|
668
632
|
region,
|
|
@@ -711,8 +675,7 @@ const createBaseDefinition = (
|
|
|
711
675
|
},
|
|
712
676
|
},
|
|
713
677
|
plugins: [
|
|
714
|
-
|
|
715
|
-
// 'serverless-jetpack',
|
|
678
|
+
'serverless-jetpack',
|
|
716
679
|
'serverless-dotenv-plugin',
|
|
717
680
|
'serverless-offline-sqs',
|
|
718
681
|
'serverless-offline',
|
|
@@ -733,15 +696,14 @@ const createBaseDefinition = (
|
|
|
733
696
|
secretAccessKey: 'root',
|
|
734
697
|
skipCacheInvalidation: false,
|
|
735
698
|
},
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
// },
|
|
699
|
+
jetpack: {
|
|
700
|
+
base: '..',
|
|
701
|
+
},
|
|
740
702
|
},
|
|
741
703
|
functions: {
|
|
742
704
|
auth: {
|
|
743
|
-
handler:
|
|
744
|
-
|
|
705
|
+
handler:
|
|
706
|
+
'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
745
707
|
events: [
|
|
746
708
|
{ httpApi: { path: '/api/integrations', method: 'ANY' } },
|
|
747
709
|
{
|
|
@@ -754,57 +716,20 @@ const createBaseDefinition = (
|
|
|
754
716
|
],
|
|
755
717
|
},
|
|
756
718
|
user: {
|
|
757
|
-
handler:
|
|
758
|
-
|
|
759
|
-
events: [
|
|
719
|
+
handler:
|
|
720
|
+
'node_modules/@friggframework/core/handlers/routers/user.handler',
|
|
721
|
+
events: [
|
|
722
|
+
{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } },
|
|
723
|
+
],
|
|
760
724
|
},
|
|
761
725
|
health: {
|
|
762
|
-
handler:
|
|
763
|
-
|
|
726
|
+
handler:
|
|
727
|
+
'node_modules/@friggframework/core/handlers/routers/health.handler',
|
|
764
728
|
events: [
|
|
765
729
|
{ httpApi: { path: '/health', method: 'GET' } },
|
|
766
730
|
{ httpApi: { path: '/health/{proxy+}', method: 'GET' } },
|
|
767
731
|
],
|
|
768
732
|
},
|
|
769
|
-
dbMigrate: {
|
|
770
|
-
handler: 'node_modules/@friggframework/core/handlers/workers/db-migration.handler',
|
|
771
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
772
|
-
timeout: 300, // 5 minutes for long-running migrations
|
|
773
|
-
memorySize: 512, // Extra memory for Prisma CLI operations
|
|
774
|
-
reservedConcurrency: 1, // Prevent concurrent migrations
|
|
775
|
-
description: 'Runs database migrations via Prisma (invoke manually from CI/CD)',
|
|
776
|
-
// No events - this function is invoked manually via AWS CLI
|
|
777
|
-
maximumEventAge: 60, // Don't retry old migration requests (60 seconds)
|
|
778
|
-
maximumRetryAttempts: 0, // Don't auto-retry failed migrations
|
|
779
|
-
tags: {
|
|
780
|
-
Purpose: 'DatabaseMigration',
|
|
781
|
-
ManagedBy: 'Frigg',
|
|
782
|
-
},
|
|
783
|
-
// Environment variables for non-interactive Prisma CLI operation
|
|
784
|
-
environment: {
|
|
785
|
-
CI: '1', // Forces Prisma to non-interactive mode
|
|
786
|
-
PRISMA_HIDE_UPDATE_MESSAGE: '1', // Suppress update messages
|
|
787
|
-
PRISMA_MIGRATE_SKIP_SEED: '1', // Skip seeding during migrations
|
|
788
|
-
},
|
|
789
|
-
// Function-specific packaging: Include Prisma schemas (CLI from layer)
|
|
790
|
-
package: {
|
|
791
|
-
patterns: [
|
|
792
|
-
// Include Prisma schemas from @friggframework/core
|
|
793
|
-
// Note: Prisma CLI and clients come from Lambda Layer
|
|
794
|
-
'node_modules/@friggframework/core/prisma-mongodb/**',
|
|
795
|
-
'node_modules/@friggframework/core/prisma-postgresql/**',
|
|
796
|
-
],
|
|
797
|
-
},
|
|
798
|
-
},
|
|
799
|
-
},
|
|
800
|
-
layers: {
|
|
801
|
-
prisma: {
|
|
802
|
-
path: 'layers/prisma',
|
|
803
|
-
name: '${self:service}-prisma-${sls:stage}',
|
|
804
|
-
description: 'Prisma ORM clients for MongoDB and PostgreSQL with rhel-openssl-3.0.x binaries. Reduces function sizes by ~60% (120MB → 45MB). See LAMBDA-LAYER-PRISMA.md for details.',
|
|
805
|
-
compatibleRuntimes: ['nodejs18.x', 'nodejs20.x'],
|
|
806
|
-
retain: false, // Don't retain old layer versions
|
|
807
|
-
},
|
|
808
733
|
},
|
|
809
734
|
resources: {
|
|
810
735
|
Resources: {
|
|
@@ -903,22 +828,18 @@ const applyKmsConfiguration = (
|
|
|
903
828
|
}
|
|
904
829
|
|
|
905
830
|
if (discoveredResources.defaultKmsKeyId) {
|
|
906
|
-
console.log(
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
};
|
|
919
|
-
} else {
|
|
920
|
-
console.log('KMS alias already exists, skipping alias creation');
|
|
921
|
-
}
|
|
831
|
+
console.log(
|
|
832
|
+
`Using existing KMS key: ${discoveredResources.defaultKmsKeyId}`
|
|
833
|
+
);
|
|
834
|
+
definition.resources.Resources.FriggKMSKeyAlias = {
|
|
835
|
+
Type: 'AWS::KMS::Alias',
|
|
836
|
+
DeletionPolicy: 'Retain',
|
|
837
|
+
Properties: {
|
|
838
|
+
AliasName:
|
|
839
|
+
'alias/${self:service}-${self:provider.stage}-frigg-kms',
|
|
840
|
+
TargetKeyId: discoveredResources.defaultKmsKeyId,
|
|
841
|
+
},
|
|
842
|
+
};
|
|
922
843
|
|
|
923
844
|
definition.provider.iamRoleStatements.push({
|
|
924
845
|
Effect: 'Allow',
|
|
@@ -929,7 +850,7 @@ const applyKmsConfiguration = (
|
|
|
929
850
|
if (AppDefinition.encryption?.createResourceIfNoneFound !== true) {
|
|
930
851
|
throw new Error(
|
|
931
852
|
'KMS field-level encryption is enabled but no KMS key was found. ' +
|
|
932
|
-
|
|
853
|
+
'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
|
|
933
854
|
);
|
|
934
855
|
}
|
|
935
856
|
|
|
@@ -968,8 +889,9 @@ const applyKmsConfiguration = (
|
|
|
968
889
|
Resource: '*',
|
|
969
890
|
Condition: {
|
|
970
891
|
StringEquals: {
|
|
971
|
-
'kms:ViaService': `lambda.${
|
|
972
|
-
|
|
892
|
+
'kms:ViaService': `lambda.${
|
|
893
|
+
process.env.AWS_REGION || 'us-east-1'
|
|
894
|
+
}.amazonaws.com`,
|
|
973
895
|
},
|
|
974
896
|
},
|
|
975
897
|
},
|
|
@@ -1371,13 +1293,13 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1371
1293
|
};
|
|
1372
1294
|
|
|
1373
1295
|
definition.resources.Resources.FriggPublicSubnetRouteTableAssociation =
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1296
|
+
{
|
|
1297
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1298
|
+
Properties: {
|
|
1299
|
+
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
1300
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
1301
|
+
},
|
|
1302
|
+
};
|
|
1381
1303
|
|
|
1382
1304
|
definition.resources.Resources.FriggLambdaRouteTable = {
|
|
1383
1305
|
Type: 'AWS::EC2::RouteTable',
|
|
@@ -1394,22 +1316,22 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1394
1316
|
};
|
|
1395
1317
|
|
|
1396
1318
|
definition.resources.Resources.FriggPrivateSubnet1RouteTableAssociation =
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1319
|
+
{
|
|
1320
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1321
|
+
Properties: {
|
|
1322
|
+
SubnetId: { Ref: 'FriggPrivateSubnet1' },
|
|
1323
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' },
|
|
1324
|
+
},
|
|
1325
|
+
};
|
|
1404
1326
|
|
|
1405
1327
|
definition.resources.Resources.FriggPrivateSubnet2RouteTableAssociation =
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1328
|
+
{
|
|
1329
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1330
|
+
Properties: {
|
|
1331
|
+
SubnetId: { Ref: 'FriggPrivateSubnet2' },
|
|
1332
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' },
|
|
1333
|
+
},
|
|
1334
|
+
};
|
|
1413
1335
|
}
|
|
1414
1336
|
} else if (subnetManagement === 'use-existing') {
|
|
1415
1337
|
if (
|
|
@@ -1426,12 +1348,12 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1426
1348
|
AppDefinition.vpc.subnets?.ids?.length > 0
|
|
1427
1349
|
? AppDefinition.vpc.subnets.ids
|
|
1428
1350
|
: discoveredResources.privateSubnetId1 &&
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1351
|
+
discoveredResources.privateSubnetId2
|
|
1352
|
+
? [
|
|
1353
|
+
discoveredResources.privateSubnetId1,
|
|
1354
|
+
discoveredResources.privateSubnetId2,
|
|
1355
|
+
]
|
|
1356
|
+
: [];
|
|
1435
1357
|
|
|
1436
1358
|
if (vpcConfig.subnetIds.length < 2) {
|
|
1437
1359
|
if (AppDefinition.vpc.selfHeal) {
|
|
@@ -1638,13 +1560,13 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1638
1560
|
};
|
|
1639
1561
|
|
|
1640
1562
|
definition.resources.Resources.FriggPublicSubnetRouteTableAssociation =
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1563
|
+
{
|
|
1564
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1565
|
+
Properties: {
|
|
1566
|
+
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
1567
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
1568
|
+
},
|
|
1569
|
+
};
|
|
1648
1570
|
}
|
|
1649
1571
|
|
|
1650
1572
|
definition.resources.Resources.FriggNATGateway = {
|
|
@@ -1655,11 +1577,11 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1655
1577
|
AllocationId: useExistingEip
|
|
1656
1578
|
? discoveredResources.existingElasticIpAllocationId
|
|
1657
1579
|
: {
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1580
|
+
'Fn::GetAtt': [
|
|
1581
|
+
'FriggNATGatewayEIP',
|
|
1582
|
+
'AllocationId',
|
|
1583
|
+
],
|
|
1584
|
+
},
|
|
1663
1585
|
SubnetId: discoveredResources.publicSubnetId || {
|
|
1664
1586
|
Ref: 'FriggPublicSubnet',
|
|
1665
1587
|
},
|
|
@@ -1972,9 +1894,7 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1972
1894
|
},
|
|
1973
1895
|
};
|
|
1974
1896
|
|
|
1975
|
-
|
|
1976
|
-
// (Aurora requires Secrets Manager access for credential retrieval)
|
|
1977
|
-
if (AppDefinition.secretsManager?.enable === true || AppDefinition.database?.postgres?.enable === true) {
|
|
1897
|
+
if (AppDefinition.secretsManager?.enable === true) {
|
|
1978
1898
|
definition.resources.Resources.VPCEndpointSecretsManager = {
|
|
1979
1899
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
1980
1900
|
Properties: {
|
|
@@ -1992,283 +1912,6 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1992
1912
|
}
|
|
1993
1913
|
};
|
|
1994
1914
|
|
|
1995
|
-
const createAuroraInfrastructure = (definition, AppDefinition, discoveredResources) => {
|
|
1996
|
-
const dbConfig = AppDefinition.database.postgres;
|
|
1997
|
-
|
|
1998
|
-
console.log('🔧 Creating Aurora Serverless v2 infrastructure...');
|
|
1999
|
-
|
|
2000
|
-
// 1. DB Subnet Group (using Lambda private subnets)
|
|
2001
|
-
definition.resources.Resources.FriggDBSubnetGroup = {
|
|
2002
|
-
Type: 'AWS::RDS::DBSubnetGroup',
|
|
2003
|
-
Properties: {
|
|
2004
|
-
DBSubnetGroupDescription: 'Subnet group for Frigg Aurora cluster',
|
|
2005
|
-
SubnetIds: [
|
|
2006
|
-
discoveredResources.privateSubnetId1,
|
|
2007
|
-
discoveredResources.privateSubnetId2
|
|
2008
|
-
],
|
|
2009
|
-
Tags: [
|
|
2010
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-db-subnet-group' },
|
|
2011
|
-
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2012
|
-
{ Key: 'Service', Value: '${self:service}' },
|
|
2013
|
-
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2014
|
-
]
|
|
2015
|
-
}
|
|
2016
|
-
};
|
|
2017
|
-
|
|
2018
|
-
// 2. Security Group (allow Lambda SG to access 5432)
|
|
2019
|
-
// In create-new VPC mode, Lambda uses FriggLambdaSecurityGroup
|
|
2020
|
-
// In other modes, use discovered default security group
|
|
2021
|
-
const lambdaSecurityGroupId = AppDefinition.vpc?.management === 'create-new'
|
|
2022
|
-
? { Ref: 'FriggLambdaSecurityGroup' }
|
|
2023
|
-
: discoveredResources.defaultSecurityGroupId;
|
|
2024
|
-
|
|
2025
|
-
definition.resources.Resources.FriggAuroraSecurityGroup = {
|
|
2026
|
-
Type: 'AWS::EC2::SecurityGroup',
|
|
2027
|
-
Properties: {
|
|
2028
|
-
GroupDescription: 'Security group for Frigg Aurora PostgreSQL',
|
|
2029
|
-
VpcId: discoveredResources.defaultVpcId,
|
|
2030
|
-
SecurityGroupIngress: [
|
|
2031
|
-
{
|
|
2032
|
-
IpProtocol: 'tcp',
|
|
2033
|
-
FromPort: 5432,
|
|
2034
|
-
ToPort: 5432,
|
|
2035
|
-
SourceSecurityGroupId: lambdaSecurityGroupId,
|
|
2036
|
-
Description: 'PostgreSQL access from Lambda functions'
|
|
2037
|
-
}
|
|
2038
|
-
],
|
|
2039
|
-
Tags: [
|
|
2040
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-sg' },
|
|
2041
|
-
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2042
|
-
{ Key: 'Service', Value: '${self:service}' },
|
|
2043
|
-
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2044
|
-
]
|
|
2045
|
-
}
|
|
2046
|
-
};
|
|
2047
|
-
|
|
2048
|
-
// 3. Secrets Manager Secret (database credentials)
|
|
2049
|
-
definition.resources.Resources.FriggDatabaseSecret = {
|
|
2050
|
-
Type: 'AWS::SecretsManager::Secret',
|
|
2051
|
-
Properties: {
|
|
2052
|
-
Name: '${self:service}-${self:provider.stage}-aurora-credentials',
|
|
2053
|
-
Description: 'Aurora PostgreSQL credentials for Frigg application',
|
|
2054
|
-
GenerateSecretString: {
|
|
2055
|
-
SecretStringTemplate: JSON.stringify({
|
|
2056
|
-
username: dbConfig.masterUsername || 'frigg_admin'
|
|
2057
|
-
}),
|
|
2058
|
-
GenerateStringKey: 'password',
|
|
2059
|
-
PasswordLength: 32,
|
|
2060
|
-
ExcludeCharacters: '"@/\\'
|
|
2061
|
-
},
|
|
2062
|
-
Tags: [
|
|
2063
|
-
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2064
|
-
{ Key: 'Service', Value: '${self:service}' },
|
|
2065
|
-
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2066
|
-
]
|
|
2067
|
-
}
|
|
2068
|
-
};
|
|
2069
|
-
|
|
2070
|
-
// 4. Aurora Serverless v2 Cluster
|
|
2071
|
-
definition.resources.Resources.FriggAuroraCluster = {
|
|
2072
|
-
Type: 'AWS::RDS::DBCluster',
|
|
2073
|
-
DeletionPolicy: 'Snapshot',
|
|
2074
|
-
UpdateReplacePolicy: 'Snapshot',
|
|
2075
|
-
Properties: {
|
|
2076
|
-
Engine: 'aurora-postgresql',
|
|
2077
|
-
EngineVersion: dbConfig.engineVersion || '15.3',
|
|
2078
|
-
EngineMode: 'provisioned', // Required for Serverless v2
|
|
2079
|
-
DatabaseName: dbConfig.databaseName || 'frigg_db',
|
|
2080
|
-
MasterUsername: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:username}}' },
|
|
2081
|
-
MasterUserPassword: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:password}}' },
|
|
2082
|
-
DBSubnetGroupName: { Ref: 'FriggDBSubnetGroup' },
|
|
2083
|
-
VpcSecurityGroupIds: [{ Ref: 'FriggAuroraSecurityGroup' }],
|
|
2084
|
-
ServerlessV2ScalingConfiguration: {
|
|
2085
|
-
MinCapacity: dbConfig.scaling?.minCapacity || 0.5,
|
|
2086
|
-
MaxCapacity: dbConfig.scaling?.maxCapacity || 1.0
|
|
2087
|
-
},
|
|
2088
|
-
BackupRetentionPeriod: dbConfig.backupRetentionDays || 7,
|
|
2089
|
-
PreferredBackupWindow: dbConfig.preferredBackupWindow || '03:00-04:00',
|
|
2090
|
-
DeletionProtection: dbConfig.deletionProtection !== false,
|
|
2091
|
-
EnableCloudwatchLogsExports: ['postgresql'],
|
|
2092
|
-
Tags: [
|
|
2093
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-cluster' },
|
|
2094
|
-
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2095
|
-
{ Key: 'Service', Value: '${self:service}' },
|
|
2096
|
-
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2097
|
-
]
|
|
2098
|
-
}
|
|
2099
|
-
};
|
|
2100
|
-
|
|
2101
|
-
// 5. Aurora Serverless v2 Instance
|
|
2102
|
-
definition.resources.Resources.FriggAuroraInstance = {
|
|
2103
|
-
Type: 'AWS::RDS::DBInstance',
|
|
2104
|
-
Properties: {
|
|
2105
|
-
Engine: 'aurora-postgresql',
|
|
2106
|
-
DBInstanceClass: 'db.serverless',
|
|
2107
|
-
DBClusterIdentifier: { Ref: 'FriggAuroraCluster' },
|
|
2108
|
-
PubliclyAccessible: false,
|
|
2109
|
-
EnablePerformanceInsights: dbConfig.enablePerformanceInsights || false,
|
|
2110
|
-
Tags: [
|
|
2111
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-instance' },
|
|
2112
|
-
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2113
|
-
{ Key: 'Service', Value: '${self:service}' },
|
|
2114
|
-
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2115
|
-
]
|
|
2116
|
-
}
|
|
2117
|
-
};
|
|
2118
|
-
|
|
2119
|
-
// 6. Secret Attachment (links cluster to secret)
|
|
2120
|
-
definition.resources.Resources.FriggSecretAttachment = {
|
|
2121
|
-
Type: 'AWS::SecretsManager::SecretTargetAttachment',
|
|
2122
|
-
Properties: {
|
|
2123
|
-
SecretId: { Ref: 'FriggDatabaseSecret' },
|
|
2124
|
-
TargetId: { Ref: 'FriggAuroraCluster' },
|
|
2125
|
-
TargetType: 'AWS::RDS::DBCluster'
|
|
2126
|
-
}
|
|
2127
|
-
};
|
|
2128
|
-
|
|
2129
|
-
// 7. Add IAM permissions for Secrets Manager
|
|
2130
|
-
definition.provider.iamRoleStatements.push({
|
|
2131
|
-
Effect: 'Allow',
|
|
2132
|
-
Action: [
|
|
2133
|
-
'secretsmanager:GetSecretValue',
|
|
2134
|
-
'secretsmanager:DescribeSecret'
|
|
2135
|
-
],
|
|
2136
|
-
Resource: { Ref: 'FriggDatabaseSecret' }
|
|
2137
|
-
});
|
|
2138
|
-
|
|
2139
|
-
// 8. Set DATABASE_URL environment variable
|
|
2140
|
-
definition.provider.environment.DATABASE_URL = {
|
|
2141
|
-
'Fn::Sub': [
|
|
2142
|
-
'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
|
|
2143
|
-
{
|
|
2144
|
-
Username: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:username}}' },
|
|
2145
|
-
Password: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:password}}' },
|
|
2146
|
-
Endpoint: { 'Fn::GetAtt': ['FriggAuroraCluster', 'Endpoint'] },
|
|
2147
|
-
Port: { 'Fn::GetAtt': ['FriggAuroraCluster', 'Port'] },
|
|
2148
|
-
DatabaseName: dbConfig.databaseName || 'frigg_db'
|
|
2149
|
-
}
|
|
2150
|
-
]
|
|
2151
|
-
};
|
|
2152
|
-
|
|
2153
|
-
// 9. Set DB_TYPE for Prisma client selection
|
|
2154
|
-
definition.provider.environment.DB_TYPE = 'postgresql';
|
|
2155
|
-
|
|
2156
|
-
console.log('✅ Aurora infrastructure resources created');
|
|
2157
|
-
};
|
|
2158
|
-
|
|
2159
|
-
const useExistingAurora = (definition, AppDefinition, discoveredResources) => {
|
|
2160
|
-
const dbConfig = AppDefinition.database.postgres;
|
|
2161
|
-
|
|
2162
|
-
console.log(`🔗 Using existing Aurora cluster: ${discoveredResources.aurora.clusterIdentifier}`);
|
|
2163
|
-
|
|
2164
|
-
// Add IAM permissions for Secrets Manager if secret exists
|
|
2165
|
-
if (discoveredResources.aurora.secretArn) {
|
|
2166
|
-
definition.provider.iamRoleStatements.push({
|
|
2167
|
-
Effect: 'Allow',
|
|
2168
|
-
Action: [
|
|
2169
|
-
'secretsmanager:GetSecretValue',
|
|
2170
|
-
'secretsmanager:DescribeSecret'
|
|
2171
|
-
],
|
|
2172
|
-
Resource: discoveredResources.aurora.secretArn
|
|
2173
|
-
});
|
|
2174
|
-
|
|
2175
|
-
// Set DATABASE_URL from discovered secret
|
|
2176
|
-
definition.provider.environment.DATABASE_URL = {
|
|
2177
|
-
'Fn::Sub': [
|
|
2178
|
-
'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
|
|
2179
|
-
{
|
|
2180
|
-
Username: { 'Fn::Sub': `{{resolve:secretsmanager:${discoveredResources.aurora.secretArn}:SecretString:username}}` },
|
|
2181
|
-
Password: { 'Fn::Sub': `{{resolve:secretsmanager:${discoveredResources.aurora.secretArn}:SecretString:password}}` },
|
|
2182
|
-
Endpoint: discoveredResources.aurora.endpoint,
|
|
2183
|
-
Port: discoveredResources.aurora.port,
|
|
2184
|
-
DatabaseName: dbConfig.databaseName || 'frigg_db'
|
|
2185
|
-
}
|
|
2186
|
-
]
|
|
2187
|
-
};
|
|
2188
|
-
} else if (dbConfig.secretArn) {
|
|
2189
|
-
// Use user-provided secret ARN
|
|
2190
|
-
definition.provider.iamRoleStatements.push({
|
|
2191
|
-
Effect: 'Allow',
|
|
2192
|
-
Action: [
|
|
2193
|
-
'secretsmanager:GetSecretValue',
|
|
2194
|
-
'secretsmanager:DescribeSecret'
|
|
2195
|
-
],
|
|
2196
|
-
Resource: dbConfig.secretArn
|
|
2197
|
-
});
|
|
2198
|
-
|
|
2199
|
-
definition.provider.environment.DATABASE_URL = {
|
|
2200
|
-
'Fn::Sub': [
|
|
2201
|
-
'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
|
|
2202
|
-
{
|
|
2203
|
-
Username: { 'Fn::Sub': `{{resolve:secretsmanager:${dbConfig.secretArn}:SecretString:username}}` },
|
|
2204
|
-
Password: { 'Fn::Sub': `{{resolve:secretsmanager:${dbConfig.secretArn}:SecretString:password}}` },
|
|
2205
|
-
Endpoint: discoveredResources.aurora.endpoint,
|
|
2206
|
-
Port: discoveredResources.aurora.port,
|
|
2207
|
-
DatabaseName: dbConfig.databaseName || 'frigg_db'
|
|
2208
|
-
}
|
|
2209
|
-
]
|
|
2210
|
-
};
|
|
2211
|
-
} else {
|
|
2212
|
-
throw new Error('No database secret found. Provide secretArn in database.postgres configuration or ensure Secrets Manager secret exists.');
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
// Set DB_TYPE for Prisma client selection
|
|
2216
|
-
definition.provider.environment.DB_TYPE = 'postgresql';
|
|
2217
|
-
|
|
2218
|
-
console.log('✅ Existing Aurora cluster configured');
|
|
2219
|
-
};
|
|
2220
|
-
|
|
2221
|
-
const useDiscoveredAurora = (definition, AppDefinition, discoveredResources) => {
|
|
2222
|
-
console.log(`🔍 Using discovered Aurora cluster: ${discoveredResources.aurora.clusterIdentifier}`);
|
|
2223
|
-
useExistingAurora(definition, AppDefinition, discoveredResources);
|
|
2224
|
-
};
|
|
2225
|
-
|
|
2226
|
-
const configurePostgres = (definition, AppDefinition, discoveredResources) => {
|
|
2227
|
-
if (!AppDefinition.database?.postgres?.enable) {
|
|
2228
|
-
return;
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
// Validate VPC is enabled (required for Aurora deployment)
|
|
2232
|
-
if (!AppDefinition.vpc?.enable) {
|
|
2233
|
-
throw new Error(
|
|
2234
|
-
'Aurora PostgreSQL requires VPC deployment. ' +
|
|
2235
|
-
'Set vpc.enable to true in your app definition.'
|
|
2236
|
-
);
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2239
|
-
// Validate private subnets exist (Aurora requires at least 2 subnets in different AZs)
|
|
2240
|
-
// Skip validation if VPC management is 'create-new' (subnets will be created)
|
|
2241
|
-
const vpcManagement = AppDefinition.vpc?.management || 'discover';
|
|
2242
|
-
if (vpcManagement !== 'create-new' && (!discoveredResources.privateSubnetId1 || !discoveredResources.privateSubnetId2)) {
|
|
2243
|
-
throw new Error(
|
|
2244
|
-
'Aurora PostgreSQL requires at least 2 private subnets in different availability zones. ' +
|
|
2245
|
-
'No private subnets were discovered in your VPC. ' +
|
|
2246
|
-
'Please create private subnets or use VPC management mode "create-new".'
|
|
2247
|
-
);
|
|
2248
|
-
}
|
|
2249
|
-
|
|
2250
|
-
const dbConfig = AppDefinition.database.postgres;
|
|
2251
|
-
const management = dbConfig.management || 'discover';
|
|
2252
|
-
|
|
2253
|
-
console.log(`\n🐘 PostgreSQL Management Mode: ${management}`);
|
|
2254
|
-
|
|
2255
|
-
if (management === 'create-new' || discoveredResources.aurora?.needsCreation) {
|
|
2256
|
-
createAuroraInfrastructure(definition, AppDefinition, discoveredResources);
|
|
2257
|
-
} else if (management === 'use-existing') {
|
|
2258
|
-
if (!discoveredResources.aurora?.clusterIdentifier && !dbConfig.clusterIdentifier) {
|
|
2259
|
-
throw new Error('PostgreSQL management is set to "use-existing" but no clusterIdentifier was found or provided');
|
|
2260
|
-
}
|
|
2261
|
-
useExistingAurora(definition, AppDefinition, discoveredResources);
|
|
2262
|
-
} else {
|
|
2263
|
-
// discover mode
|
|
2264
|
-
if (discoveredResources.aurora?.clusterIdentifier) {
|
|
2265
|
-
useDiscoveredAurora(definition, AppDefinition, discoveredResources);
|
|
2266
|
-
} else {
|
|
2267
|
-
throw new Error('No Aurora cluster found in discovery mode. Set management to "create-new" or provide clusterIdentifier with "use-existing".');
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
};
|
|
2271
|
-
|
|
2272
1915
|
const configureSsm = (definition, AppDefinition) => {
|
|
2273
1916
|
if (AppDefinition.ssm?.enable !== true) {
|
|
2274
1917
|
return;
|
|
@@ -2312,8 +1955,9 @@ const attachIntegrations = (definition, AppDefinition) => {
|
|
|
2312
1955
|
}
|
|
2313
1956
|
|
|
2314
1957
|
const integrationName = integration.Definition.name;
|
|
2315
|
-
const queueReference = `${
|
|
2316
|
-
|
|
1958
|
+
const queueReference = `${
|
|
1959
|
+
integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
|
|
1960
|
+
}Queue`;
|
|
2317
1961
|
const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
|
|
2318
1962
|
|
|
2319
1963
|
definition.functions[integrationName] = {
|
|
@@ -2369,7 +2013,10 @@ const attachIntegrations = (definition, AppDefinition) => {
|
|
|
2369
2013
|
|
|
2370
2014
|
// Add webhook handler if enabled
|
|
2371
2015
|
const webhookConfig = integration.Definition.webhooks;
|
|
2372
|
-
if (
|
|
2016
|
+
if (
|
|
2017
|
+
webhookConfig &&
|
|
2018
|
+
(webhookConfig === true || webhookConfig.enabled === true)
|
|
2019
|
+
) {
|
|
2373
2020
|
const webhookFunctionName = `${integrationName}Webhook`;
|
|
2374
2021
|
|
|
2375
2022
|
definition.functions[webhookFunctionName] = {
|
|
@@ -2409,40 +2056,9 @@ const configureWebsockets = (definition, AppDefinition) => {
|
|
|
2409
2056
|
};
|
|
2410
2057
|
};
|
|
2411
2058
|
|
|
2412
|
-
/**
|
|
2413
|
-
* Ensure Prisma Lambda Layer exists
|
|
2414
|
-
* Automatically builds the layer if it doesn't exist in the project root
|
|
2415
|
-
*/
|
|
2416
|
-
async function ensurePrismaLayerExists() {
|
|
2417
|
-
const projectRoot = process.cwd();
|
|
2418
|
-
const layerPath = path.join(projectRoot, 'layers/prisma');
|
|
2419
|
-
|
|
2420
|
-
// Check if layer already exists
|
|
2421
|
-
if (fs.existsSync(layerPath)) {
|
|
2422
|
-
console.log('✓ Prisma Lambda Layer already exists at', layerPath);
|
|
2423
|
-
return;
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
// Layer doesn't exist - build it automatically
|
|
2427
|
-
console.log('📦 Prisma Lambda Layer not found - building automatically...');
|
|
2428
|
-
console.log(' This may take a minute on first deployment.\n');
|
|
2429
|
-
|
|
2430
|
-
try {
|
|
2431
|
-
await buildPrismaLayer();
|
|
2432
|
-
console.log('✓ Prisma Lambda Layer built successfully\n');
|
|
2433
|
-
} catch (error) {
|
|
2434
|
-
console.error('✗ Failed to build Prisma Lambda Layer:', error.message);
|
|
2435
|
-
console.error(' You may need to run: npm install @friggframework/core\n');
|
|
2436
|
-
throw error;
|
|
2437
|
-
}
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
2059
|
const composeServerlessDefinition = async (AppDefinition) => {
|
|
2441
2060
|
console.log('composeServerlessDefinition', AppDefinition);
|
|
2442
2061
|
|
|
2443
|
-
// Ensure Prisma layer exists before generating serverless config
|
|
2444
|
-
await ensurePrismaLayerExists();
|
|
2445
|
-
|
|
2446
2062
|
const discoveredResources = await gatherDiscoveredResources(AppDefinition);
|
|
2447
2063
|
const appEnvironmentVars = getAppEnvironmentVars(AppDefinition);
|
|
2448
2064
|
const definition = createBaseDefinition(
|
|
@@ -2464,7 +2080,6 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
2464
2080
|
if (!isLocalBuild) {
|
|
2465
2081
|
applyKmsConfiguration(definition, AppDefinition, discoveredResources);
|
|
2466
2082
|
configureVpc(definition, AppDefinition, discoveredResources);
|
|
2467
|
-
configurePostgres(definition, AppDefinition, discoveredResources);
|
|
2468
2083
|
configureSsm(definition, AppDefinition);
|
|
2469
2084
|
} else {
|
|
2470
2085
|
console.log(
|