@friggframework/devtools 2.0.0--canary.464.f9d3fc0.0 ā 2.0.0--canary.454.25d396a.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/db-setup-command/index.js +1 -1
- package/infrastructure/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/README.md +51 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/aws-discovery.js +504 -2
- package/infrastructure/aws-discovery.test.js +447 -1
- package/infrastructure/scripts/build-prisma-layer.js +394 -0
- package/infrastructure/serverless-template.js +437 -48
- package/infrastructure/serverless-template.test.js +91 -0
- package/package.json +8 -6
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
- package/frigg-cli/utils/prisma-runner.js +0 -280
|
@@ -1,6 +1,7 @@
|
|
|
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');
|
|
4
5
|
|
|
5
6
|
const shouldRunDiscovery = (AppDefinition) => {
|
|
6
7
|
console.log(
|
|
@@ -17,7 +18,8 @@ const shouldRunDiscovery = (AppDefinition) => {
|
|
|
17
18
|
return (
|
|
18
19
|
AppDefinition.vpc?.enable === true ||
|
|
19
20
|
AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
20
|
-
AppDefinition.ssm?.enable === true
|
|
21
|
+
AppDefinition.ssm?.enable === true ||
|
|
22
|
+
AppDefinition.database?.postgres?.enable === true
|
|
21
23
|
);
|
|
22
24
|
};
|
|
23
25
|
|
|
@@ -543,10 +545,17 @@ const gatherDiscoveredResources = async (AppDefinition) => {
|
|
|
543
545
|
try {
|
|
544
546
|
const region = process.env.AWS_REGION || 'us-east-1';
|
|
545
547
|
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
|
+
|
|
546
553
|
const config = {
|
|
547
554
|
vpc: AppDefinition.vpc || {},
|
|
548
555
|
encryption: AppDefinition.encryption || {},
|
|
549
556
|
ssm: AppDefinition.ssm || {},
|
|
557
|
+
serviceName: AppDefinition.name || 'create-frigg-app',
|
|
558
|
+
stage: stage,
|
|
550
559
|
};
|
|
551
560
|
|
|
552
561
|
const discoveredResources = await discovery.discoverResources(config);
|
|
@@ -602,6 +611,22 @@ const buildEnvironment = (appEnvironmentVars, discoveredResources) => {
|
|
|
602
611
|
}
|
|
603
612
|
}
|
|
604
613
|
|
|
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
|
+
|
|
605
630
|
return environment;
|
|
606
631
|
};
|
|
607
632
|
|
|
@@ -617,15 +642,27 @@ const createBaseDefinition = (
|
|
|
617
642
|
service: AppDefinition.name || 'create-frigg-app',
|
|
618
643
|
package: {
|
|
619
644
|
individually: true,
|
|
620
|
-
|
|
645
|
+
patterns: [
|
|
646
|
+
// Existing AWS SDK exclusions
|
|
621
647
|
'!**/node_modules/aws-sdk/**',
|
|
622
648
|
'!**/node_modules/@aws-sdk/**',
|
|
623
649
|
'!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
|
|
624
660
|
],
|
|
625
661
|
},
|
|
626
662
|
useDotenv: true,
|
|
627
663
|
provider: {
|
|
628
664
|
name: AppDefinition.provider || 'aws',
|
|
665
|
+
...(process.env.AWS_PROFILE && { profile: process.env.AWS_PROFILE }),
|
|
629
666
|
runtime: 'nodejs20.x',
|
|
630
667
|
timeout: 30,
|
|
631
668
|
region,
|
|
@@ -703,8 +740,8 @@ const createBaseDefinition = (
|
|
|
703
740
|
},
|
|
704
741
|
functions: {
|
|
705
742
|
auth: {
|
|
706
|
-
handler:
|
|
707
|
-
|
|
743
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
744
|
+
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
708
745
|
events: [
|
|
709
746
|
{ httpApi: { path: '/api/integrations', method: 'ANY' } },
|
|
710
747
|
{
|
|
@@ -717,20 +754,57 @@ const createBaseDefinition = (
|
|
|
717
754
|
],
|
|
718
755
|
},
|
|
719
756
|
user: {
|
|
720
|
-
handler:
|
|
721
|
-
|
|
722
|
-
events: [
|
|
723
|
-
{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } },
|
|
724
|
-
],
|
|
757
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
|
|
758
|
+
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
759
|
+
events: [{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } }],
|
|
725
760
|
},
|
|
726
761
|
health: {
|
|
727
|
-
handler:
|
|
728
|
-
|
|
762
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
|
|
763
|
+
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
729
764
|
events: [
|
|
730
765
|
{ httpApi: { path: '/health', method: 'GET' } },
|
|
731
766
|
{ httpApi: { path: '/health/{proxy+}', method: 'GET' } },
|
|
732
767
|
],
|
|
733
768
|
},
|
|
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
|
+
},
|
|
734
808
|
},
|
|
735
809
|
resources: {
|
|
736
810
|
Resources: {
|
|
@@ -829,18 +903,22 @@ const applyKmsConfiguration = (
|
|
|
829
903
|
}
|
|
830
904
|
|
|
831
905
|
if (discoveredResources.defaultKmsKeyId) {
|
|
832
|
-
console.log(
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
906
|
+
console.log(`Using existing KMS key: ${discoveredResources.defaultKmsKeyId}`);
|
|
907
|
+
|
|
908
|
+
// Only create alias if it doesn't already exist
|
|
909
|
+
if (!discoveredResources.kmsAliasExists) {
|
|
910
|
+
console.log('Creating KMS alias for discovered key...');
|
|
911
|
+
definition.resources.Resources.FriggKMSKeyAlias = {
|
|
912
|
+
Type: 'AWS::KMS::Alias',
|
|
913
|
+
DeletionPolicy: 'Retain',
|
|
914
|
+
Properties: {
|
|
915
|
+
AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
|
|
916
|
+
TargetKeyId: discoveredResources.defaultKmsKeyId,
|
|
917
|
+
},
|
|
918
|
+
};
|
|
919
|
+
} else {
|
|
920
|
+
console.log('KMS alias already exists, skipping alias creation');
|
|
921
|
+
}
|
|
844
922
|
|
|
845
923
|
definition.provider.iamRoleStatements.push({
|
|
846
924
|
Effect: 'Allow',
|
|
@@ -1894,7 +1972,9 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1894
1972
|
},
|
|
1895
1973
|
};
|
|
1896
1974
|
|
|
1897
|
-
if
|
|
1975
|
+
// Create Secrets Manager VPC Endpoint if explicitly enabled OR if Aurora is enabled
|
|
1976
|
+
// (Aurora requires Secrets Manager access for credential retrieval)
|
|
1977
|
+
if (AppDefinition.secretsManager?.enable === true || AppDefinition.database?.postgres?.enable === true) {
|
|
1898
1978
|
definition.resources.Resources.VPCEndpointSecretsManager = {
|
|
1899
1979
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
1900
1980
|
Properties: {
|
|
@@ -1912,6 +1992,283 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1912
1992
|
}
|
|
1913
1993
|
};
|
|
1914
1994
|
|
|
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
|
+
|
|
1915
2272
|
const configureSsm = (definition, AppDefinition) => {
|
|
1916
2273
|
if (AppDefinition.ssm?.enable !== true) {
|
|
1917
2274
|
return;
|
|
@@ -1959,30 +2316,6 @@ const attachIntegrations = (definition, AppDefinition) => {
|
|
|
1959
2316
|
}Queue`;
|
|
1960
2317
|
const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
|
|
1961
2318
|
|
|
1962
|
-
// Add webhook handler if enabled (BEFORE catch-all proxy route)
|
|
1963
|
-
const webhookConfig = integration.Definition.webhooks;
|
|
1964
|
-
if (webhookConfig && (webhookConfig === true || webhookConfig.enabled === true)) {
|
|
1965
|
-
const webhookFunctionName = `${integrationName}Webhook`;
|
|
1966
|
-
|
|
1967
|
-
definition.functions[webhookFunctionName] = {
|
|
1968
|
-
handler: `node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.${integrationName}Webhook.handler`,
|
|
1969
|
-
events: [
|
|
1970
|
-
{
|
|
1971
|
-
httpApi: {
|
|
1972
|
-
path: `/api/${integrationName}-integration/webhooks`,
|
|
1973
|
-
method: 'POST',
|
|
1974
|
-
},
|
|
1975
|
-
},
|
|
1976
|
-
{
|
|
1977
|
-
httpApi: {
|
|
1978
|
-
path: `/api/${integrationName}-integration/webhooks/{integrationId}`,
|
|
1979
|
-
method: 'POST',
|
|
1980
|
-
},
|
|
1981
|
-
},
|
|
1982
|
-
],
|
|
1983
|
-
};
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
2319
|
definition.functions[integrationName] = {
|
|
1987
2320
|
handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
|
|
1988
2321
|
events: [
|
|
@@ -2033,6 +2366,30 @@ const attachIntegrations = (definition, AppDefinition) => {
|
|
|
2033
2366
|
};
|
|
2034
2367
|
|
|
2035
2368
|
definition.custom[queueReference] = queueName;
|
|
2369
|
+
|
|
2370
|
+
// Add webhook handler if enabled
|
|
2371
|
+
const webhookConfig = integration.Definition.webhooks;
|
|
2372
|
+
if (webhookConfig && (webhookConfig === true || webhookConfig.enabled === true)) {
|
|
2373
|
+
const webhookFunctionName = `${integrationName}Webhook`;
|
|
2374
|
+
|
|
2375
|
+
definition.functions[webhookFunctionName] = {
|
|
2376
|
+
handler: `node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.${integrationName}Webhook.handler`,
|
|
2377
|
+
events: [
|
|
2378
|
+
{
|
|
2379
|
+
httpApi: {
|
|
2380
|
+
path: `/api/${integrationName}-integration/webhooks`,
|
|
2381
|
+
method: 'POST',
|
|
2382
|
+
},
|
|
2383
|
+
},
|
|
2384
|
+
{
|
|
2385
|
+
httpApi: {
|
|
2386
|
+
path: `/api/${integrationName}-integration/webhooks/{integrationId}`,
|
|
2387
|
+
method: 'POST',
|
|
2388
|
+
},
|
|
2389
|
+
},
|
|
2390
|
+
],
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2036
2393
|
}
|
|
2037
2394
|
};
|
|
2038
2395
|
|
|
@@ -2052,9 +2409,40 @@ const configureWebsockets = (definition, AppDefinition) => {
|
|
|
2052
2409
|
};
|
|
2053
2410
|
};
|
|
2054
2411
|
|
|
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
|
+
|
|
2055
2440
|
const composeServerlessDefinition = async (AppDefinition) => {
|
|
2056
2441
|
console.log('composeServerlessDefinition', AppDefinition);
|
|
2057
2442
|
|
|
2443
|
+
// Ensure Prisma layer exists before generating serverless config
|
|
2444
|
+
await ensurePrismaLayerExists();
|
|
2445
|
+
|
|
2058
2446
|
const discoveredResources = await gatherDiscoveredResources(AppDefinition);
|
|
2059
2447
|
const appEnvironmentVars = getAppEnvironmentVars(AppDefinition);
|
|
2060
2448
|
const definition = createBaseDefinition(
|
|
@@ -2076,6 +2464,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
2076
2464
|
if (!isLocalBuild) {
|
|
2077
2465
|
applyKmsConfiguration(definition, AppDefinition, discoveredResources);
|
|
2078
2466
|
configureVpc(definition, AppDefinition, discoveredResources);
|
|
2467
|
+
configurePostgres(definition, AppDefinition, discoveredResources);
|
|
2079
2468
|
configureSsm(definition, AppDefinition);
|
|
2080
2469
|
} else {
|
|
2081
2470
|
console.log(
|