@friggframework/devtools 2.0.0--canary.454.16de0f8.0 → 2.0.0--canary.458.c150d9a.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 -8
- package/frigg-cli/utils/database-validator.js +1 -4
- 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 +11 -412
- package/infrastructure/serverless-template.test.js +0 -91
- package/management-ui/src/App.jsx +1 -85
- package/management-ui/src/hooks/useFrigg.jsx +1 -215
- 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('⚙️ Checking FRIGG_SKIP_AWS_DISCOVERY:', process.env.FRIGG_SKIP_AWS_DISCOVERY);
|
|
@@ -13,8 +12,7 @@ const shouldRunDiscovery = (AppDefinition) => {
|
|
|
13
12
|
return (
|
|
14
13
|
AppDefinition.vpc?.enable === true ||
|
|
15
14
|
AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
16
|
-
AppDefinition.ssm?.enable === true
|
|
17
|
-
AppDefinition.database?.postgres?.enable === true
|
|
15
|
+
AppDefinition.ssm?.enable === true
|
|
18
16
|
);
|
|
19
17
|
};
|
|
20
18
|
|
|
@@ -437,17 +435,10 @@ const gatherDiscoveredResources = async (AppDefinition) => {
|
|
|
437
435
|
try {
|
|
438
436
|
const region = process.env.AWS_REGION || 'us-east-1';
|
|
439
437
|
const discovery = new AWSDiscovery(region);
|
|
440
|
-
// Use Serverless Framework's stage resolution (opt:stage with 'dev' as default)
|
|
441
|
-
// This matches how serverless.yml resolves ${opt:stage, "dev"}
|
|
442
|
-
// IMPORTANT: Use SLS_STAGE (not STAGE) to match actual deployment stage
|
|
443
|
-
const stage = process.env.SLS_STAGE || 'dev';
|
|
444
|
-
|
|
445
438
|
const config = {
|
|
446
439
|
vpc: AppDefinition.vpc || {},
|
|
447
440
|
encryption: AppDefinition.encryption || {},
|
|
448
441
|
ssm: AppDefinition.ssm || {},
|
|
449
|
-
serviceName: AppDefinition.name || 'create-frigg-app',
|
|
450
|
-
stage: stage,
|
|
451
442
|
};
|
|
452
443
|
|
|
453
444
|
const discoveredResources = await discovery.discoverResources(config);
|
|
@@ -498,22 +489,6 @@ const buildEnvironment = (appEnvironmentVars, discoveredResources) => {
|
|
|
498
489
|
}
|
|
499
490
|
}
|
|
500
491
|
|
|
501
|
-
// Add Aurora discovery mappings
|
|
502
|
-
if (discoveredResources.aurora) {
|
|
503
|
-
if (discoveredResources.aurora.clusterIdentifier) {
|
|
504
|
-
environment.AWS_DISCOVERY_AURORA_CLUSTER_ID = discoveredResources.aurora.clusterIdentifier;
|
|
505
|
-
}
|
|
506
|
-
if (discoveredResources.aurora.endpoint) {
|
|
507
|
-
environment.AWS_DISCOVERY_AURORA_ENDPOINT = discoveredResources.aurora.endpoint;
|
|
508
|
-
}
|
|
509
|
-
if (discoveredResources.aurora.port) {
|
|
510
|
-
environment.AWS_DISCOVERY_AURORA_PORT = discoveredResources.aurora.port.toString();
|
|
511
|
-
}
|
|
512
|
-
if (discoveredResources.aurora.secretArn) {
|
|
513
|
-
environment.AWS_DISCOVERY_AURORA_SECRET_ARN = discoveredResources.aurora.secretArn;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
492
|
return environment;
|
|
518
493
|
};
|
|
519
494
|
|
|
@@ -525,27 +500,11 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
|
|
|
525
500
|
service: AppDefinition.name || 'create-frigg-app',
|
|
526
501
|
package: {
|
|
527
502
|
individually: true,
|
|
528
|
-
|
|
529
|
-
// Existing AWS SDK exclusions
|
|
530
|
-
'!**/node_modules/aws-sdk/**',
|
|
531
|
-
'!**/node_modules/@aws-sdk/**',
|
|
532
|
-
'!package.json',
|
|
533
|
-
|
|
534
|
-
// GLOBAL Prisma exclusions - all Prisma packages moved to Lambda Layer
|
|
535
|
-
// This reduces each function from ~120MB to ~45MB (60% reduction)
|
|
536
|
-
'!node_modules/@prisma/**',
|
|
537
|
-
'!node_modules/.prisma/**',
|
|
538
|
-
'!node_modules/@prisma-mongodb/**',
|
|
539
|
-
'!node_modules/@prisma-postgresql/**',
|
|
540
|
-
'!node_modules/prisma/**',
|
|
541
|
-
// Prisma packages will be provided at runtime via Lambda Layer
|
|
542
|
-
// See: LAMBDA-LAYER-PRISMA.md for complete documentation
|
|
543
|
-
],
|
|
503
|
+
exclude: ['!**/node_modules/aws-sdk/**', '!**/node_modules/@aws-sdk/**', '!package.json'],
|
|
544
504
|
},
|
|
545
505
|
useDotenv: true,
|
|
546
506
|
provider: {
|
|
547
507
|
name: AppDefinition.provider || 'aws',
|
|
548
|
-
...(process.env.AWS_PROFILE && { profile: process.env.AWS_PROFILE }),
|
|
549
508
|
runtime: 'nodejs20.x',
|
|
550
509
|
timeout: 30,
|
|
551
510
|
region,
|
|
@@ -612,7 +571,6 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
|
|
|
612
571
|
functions: {
|
|
613
572
|
auth: {
|
|
614
573
|
handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
615
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
616
574
|
events: [
|
|
617
575
|
{ httpApi: { path: '/api/integrations', method: 'ANY' } },
|
|
618
576
|
{ httpApi: { path: '/api/integrations/{proxy+}', method: 'ANY' } },
|
|
@@ -621,56 +579,15 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
|
|
|
621
579
|
},
|
|
622
580
|
user: {
|
|
623
581
|
handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
|
|
624
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
625
582
|
events: [{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } }],
|
|
626
583
|
},
|
|
627
584
|
health: {
|
|
628
585
|
handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
|
|
629
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
630
586
|
events: [
|
|
631
587
|
{ httpApi: { path: '/health', method: 'GET' } },
|
|
632
588
|
{ httpApi: { path: '/health/{proxy+}', method: 'GET' } },
|
|
633
589
|
],
|
|
634
590
|
},
|
|
635
|
-
dbMigrate: {
|
|
636
|
-
handler: 'node_modules/@friggframework/core/handlers/workers/db-migration.handler',
|
|
637
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
638
|
-
timeout: 300, // 5 minutes for long-running migrations
|
|
639
|
-
memorySize: 512, // Extra memory for Prisma CLI operations
|
|
640
|
-
reservedConcurrency: 1, // Prevent concurrent migrations
|
|
641
|
-
description: 'Runs database migrations via Prisma (invoke manually from CI/CD)',
|
|
642
|
-
// No events - this function is invoked manually via AWS CLI
|
|
643
|
-
maximumEventAge: 60, // Don't retry old migration requests (60 seconds)
|
|
644
|
-
maximumRetryAttempts: 0, // Don't auto-retry failed migrations
|
|
645
|
-
tags: {
|
|
646
|
-
Purpose: 'DatabaseMigration',
|
|
647
|
-
ManagedBy: 'Frigg',
|
|
648
|
-
},
|
|
649
|
-
// Environment variables for non-interactive Prisma CLI operation
|
|
650
|
-
environment: {
|
|
651
|
-
CI: '1', // Forces Prisma to non-interactive mode
|
|
652
|
-
PRISMA_HIDE_UPDATE_MESSAGE: '1', // Suppress update messages
|
|
653
|
-
PRISMA_MIGRATE_SKIP_SEED: '1', // Skip seeding during migrations
|
|
654
|
-
},
|
|
655
|
-
// Function-specific packaging: Include Prisma schemas (CLI from layer)
|
|
656
|
-
package: {
|
|
657
|
-
patterns: [
|
|
658
|
-
// Include Prisma schemas from @friggframework/core
|
|
659
|
-
// Note: Prisma CLI and clients come from Lambda Layer
|
|
660
|
-
'node_modules/@friggframework/core/prisma-mongodb/**',
|
|
661
|
-
'node_modules/@friggframework/core/prisma-postgresql/**',
|
|
662
|
-
],
|
|
663
|
-
},
|
|
664
|
-
},
|
|
665
|
-
},
|
|
666
|
-
layers: {
|
|
667
|
-
prisma: {
|
|
668
|
-
path: 'layers/prisma',
|
|
669
|
-
name: '${self:service}-prisma-${sls:stage}',
|
|
670
|
-
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.',
|
|
671
|
-
compatibleRuntimes: ['nodejs18.x', 'nodejs20.x'],
|
|
672
|
-
retain: false, // Don't retain old layer versions
|
|
673
|
-
},
|
|
674
591
|
},
|
|
675
592
|
resources: {
|
|
676
593
|
Resources: {
|
|
@@ -749,21 +666,14 @@ const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) =
|
|
|
749
666
|
|
|
750
667
|
if (discoveredResources.defaultKmsKeyId) {
|
|
751
668
|
console.log(`Using existing KMS key: ${discoveredResources.defaultKmsKeyId}`);
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
|
|
761
|
-
TargetKeyId: discoveredResources.defaultKmsKeyId,
|
|
762
|
-
},
|
|
763
|
-
};
|
|
764
|
-
} else {
|
|
765
|
-
console.log('KMS alias already exists, skipping alias creation');
|
|
766
|
-
}
|
|
669
|
+
definition.resources.Resources.FriggKMSKeyAlias = {
|
|
670
|
+
Type: 'AWS::KMS::Alias',
|
|
671
|
+
DeletionPolicy: 'Retain',
|
|
672
|
+
Properties: {
|
|
673
|
+
AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
|
|
674
|
+
TargetKeyId: discoveredResources.defaultKmsKeyId,
|
|
675
|
+
},
|
|
676
|
+
};
|
|
767
677
|
|
|
768
678
|
definition.provider.iamRoleStatements.push({
|
|
769
679
|
Effect: 'Allow',
|
|
@@ -1600,9 +1510,7 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1600
1510
|
},
|
|
1601
1511
|
};
|
|
1602
1512
|
|
|
1603
|
-
|
|
1604
|
-
// (Aurora requires Secrets Manager access for credential retrieval)
|
|
1605
|
-
if (AppDefinition.secretsManager?.enable === true || AppDefinition.database?.postgres?.enable === true) {
|
|
1513
|
+
if (AppDefinition.secretsManager?.enable === true) {
|
|
1606
1514
|
definition.resources.Resources.VPCEndpointSecretsManager = {
|
|
1607
1515
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
1608
1516
|
Properties: {
|
|
@@ -1619,283 +1527,6 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1619
1527
|
}
|
|
1620
1528
|
};
|
|
1621
1529
|
|
|
1622
|
-
const createAuroraInfrastructure = (definition, AppDefinition, discoveredResources) => {
|
|
1623
|
-
const dbConfig = AppDefinition.database.postgres;
|
|
1624
|
-
|
|
1625
|
-
console.log('🔧 Creating Aurora Serverless v2 infrastructure...');
|
|
1626
|
-
|
|
1627
|
-
// 1. DB Subnet Group (using Lambda private subnets)
|
|
1628
|
-
definition.resources.Resources.FriggDBSubnetGroup = {
|
|
1629
|
-
Type: 'AWS::RDS::DBSubnetGroup',
|
|
1630
|
-
Properties: {
|
|
1631
|
-
DBSubnetGroupDescription: 'Subnet group for Frigg Aurora cluster',
|
|
1632
|
-
SubnetIds: [
|
|
1633
|
-
discoveredResources.privateSubnetId1,
|
|
1634
|
-
discoveredResources.privateSubnetId2
|
|
1635
|
-
],
|
|
1636
|
-
Tags: [
|
|
1637
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-db-subnet-group' },
|
|
1638
|
-
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1639
|
-
{ Key: 'Service', Value: '${self:service}' },
|
|
1640
|
-
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
1641
|
-
]
|
|
1642
|
-
}
|
|
1643
|
-
};
|
|
1644
|
-
|
|
1645
|
-
// 2. Security Group (allow Lambda SG to access 5432)
|
|
1646
|
-
// In create-new VPC mode, Lambda uses FriggLambdaSecurityGroup
|
|
1647
|
-
// In other modes, use discovered default security group
|
|
1648
|
-
const lambdaSecurityGroupId = AppDefinition.vpc?.management === 'create-new'
|
|
1649
|
-
? { Ref: 'FriggLambdaSecurityGroup' }
|
|
1650
|
-
: discoveredResources.defaultSecurityGroupId;
|
|
1651
|
-
|
|
1652
|
-
definition.resources.Resources.FriggAuroraSecurityGroup = {
|
|
1653
|
-
Type: 'AWS::EC2::SecurityGroup',
|
|
1654
|
-
Properties: {
|
|
1655
|
-
GroupDescription: 'Security group for Frigg Aurora PostgreSQL',
|
|
1656
|
-
VpcId: discoveredResources.defaultVpcId,
|
|
1657
|
-
SecurityGroupIngress: [
|
|
1658
|
-
{
|
|
1659
|
-
IpProtocol: 'tcp',
|
|
1660
|
-
FromPort: 5432,
|
|
1661
|
-
ToPort: 5432,
|
|
1662
|
-
SourceSecurityGroupId: lambdaSecurityGroupId,
|
|
1663
|
-
Description: 'PostgreSQL access from Lambda functions'
|
|
1664
|
-
}
|
|
1665
|
-
],
|
|
1666
|
-
Tags: [
|
|
1667
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-sg' },
|
|
1668
|
-
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1669
|
-
{ Key: 'Service', Value: '${self:service}' },
|
|
1670
|
-
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
1671
|
-
]
|
|
1672
|
-
}
|
|
1673
|
-
};
|
|
1674
|
-
|
|
1675
|
-
// 3. Secrets Manager Secret (database credentials)
|
|
1676
|
-
definition.resources.Resources.FriggDatabaseSecret = {
|
|
1677
|
-
Type: 'AWS::SecretsManager::Secret',
|
|
1678
|
-
Properties: {
|
|
1679
|
-
Name: '${self:service}-${self:provider.stage}-aurora-credentials',
|
|
1680
|
-
Description: 'Aurora PostgreSQL credentials for Frigg application',
|
|
1681
|
-
GenerateSecretString: {
|
|
1682
|
-
SecretStringTemplate: JSON.stringify({
|
|
1683
|
-
username: dbConfig.masterUsername || 'frigg_admin'
|
|
1684
|
-
}),
|
|
1685
|
-
GenerateStringKey: 'password',
|
|
1686
|
-
PasswordLength: 32,
|
|
1687
|
-
ExcludeCharacters: '"@/\\'
|
|
1688
|
-
},
|
|
1689
|
-
Tags: [
|
|
1690
|
-
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1691
|
-
{ Key: 'Service', Value: '${self:service}' },
|
|
1692
|
-
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
1693
|
-
]
|
|
1694
|
-
}
|
|
1695
|
-
};
|
|
1696
|
-
|
|
1697
|
-
// 4. Aurora Serverless v2 Cluster
|
|
1698
|
-
definition.resources.Resources.FriggAuroraCluster = {
|
|
1699
|
-
Type: 'AWS::RDS::DBCluster',
|
|
1700
|
-
DeletionPolicy: 'Snapshot',
|
|
1701
|
-
UpdateReplacePolicy: 'Snapshot',
|
|
1702
|
-
Properties: {
|
|
1703
|
-
Engine: 'aurora-postgresql',
|
|
1704
|
-
EngineVersion: dbConfig.engineVersion || '15.3',
|
|
1705
|
-
EngineMode: 'provisioned', // Required for Serverless v2
|
|
1706
|
-
DatabaseName: dbConfig.databaseName || 'frigg_db',
|
|
1707
|
-
MasterUsername: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:username}}' },
|
|
1708
|
-
MasterUserPassword: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:password}}' },
|
|
1709
|
-
DBSubnetGroupName: { Ref: 'FriggDBSubnetGroup' },
|
|
1710
|
-
VpcSecurityGroupIds: [{ Ref: 'FriggAuroraSecurityGroup' }],
|
|
1711
|
-
ServerlessV2ScalingConfiguration: {
|
|
1712
|
-
MinCapacity: dbConfig.scaling?.minCapacity || 0.5,
|
|
1713
|
-
MaxCapacity: dbConfig.scaling?.maxCapacity || 1.0
|
|
1714
|
-
},
|
|
1715
|
-
BackupRetentionPeriod: dbConfig.backupRetentionDays || 7,
|
|
1716
|
-
PreferredBackupWindow: dbConfig.preferredBackupWindow || '03:00-04:00',
|
|
1717
|
-
DeletionProtection: dbConfig.deletionProtection !== false,
|
|
1718
|
-
EnableCloudwatchLogsExports: ['postgresql'],
|
|
1719
|
-
Tags: [
|
|
1720
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-cluster' },
|
|
1721
|
-
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1722
|
-
{ Key: 'Service', Value: '${self:service}' },
|
|
1723
|
-
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
1724
|
-
]
|
|
1725
|
-
}
|
|
1726
|
-
};
|
|
1727
|
-
|
|
1728
|
-
// 5. Aurora Serverless v2 Instance
|
|
1729
|
-
definition.resources.Resources.FriggAuroraInstance = {
|
|
1730
|
-
Type: 'AWS::RDS::DBInstance',
|
|
1731
|
-
Properties: {
|
|
1732
|
-
Engine: 'aurora-postgresql',
|
|
1733
|
-
DBInstanceClass: 'db.serverless',
|
|
1734
|
-
DBClusterIdentifier: { Ref: 'FriggAuroraCluster' },
|
|
1735
|
-
PubliclyAccessible: false,
|
|
1736
|
-
EnablePerformanceInsights: dbConfig.enablePerformanceInsights || false,
|
|
1737
|
-
Tags: [
|
|
1738
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-instance' },
|
|
1739
|
-
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1740
|
-
{ Key: 'Service', Value: '${self:service}' },
|
|
1741
|
-
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
1742
|
-
]
|
|
1743
|
-
}
|
|
1744
|
-
};
|
|
1745
|
-
|
|
1746
|
-
// 6. Secret Attachment (links cluster to secret)
|
|
1747
|
-
definition.resources.Resources.FriggSecretAttachment = {
|
|
1748
|
-
Type: 'AWS::SecretsManager::SecretTargetAttachment',
|
|
1749
|
-
Properties: {
|
|
1750
|
-
SecretId: { Ref: 'FriggDatabaseSecret' },
|
|
1751
|
-
TargetId: { Ref: 'FriggAuroraCluster' },
|
|
1752
|
-
TargetType: 'AWS::RDS::DBCluster'
|
|
1753
|
-
}
|
|
1754
|
-
};
|
|
1755
|
-
|
|
1756
|
-
// 7. Add IAM permissions for Secrets Manager
|
|
1757
|
-
definition.provider.iamRoleStatements.push({
|
|
1758
|
-
Effect: 'Allow',
|
|
1759
|
-
Action: [
|
|
1760
|
-
'secretsmanager:GetSecretValue',
|
|
1761
|
-
'secretsmanager:DescribeSecret'
|
|
1762
|
-
],
|
|
1763
|
-
Resource: { Ref: 'FriggDatabaseSecret' }
|
|
1764
|
-
});
|
|
1765
|
-
|
|
1766
|
-
// 8. Set DATABASE_URL environment variable
|
|
1767
|
-
definition.provider.environment.DATABASE_URL = {
|
|
1768
|
-
'Fn::Sub': [
|
|
1769
|
-
'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
|
|
1770
|
-
{
|
|
1771
|
-
Username: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:username}}' },
|
|
1772
|
-
Password: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:password}}' },
|
|
1773
|
-
Endpoint: { 'Fn::GetAtt': ['FriggAuroraCluster', 'Endpoint'] },
|
|
1774
|
-
Port: { 'Fn::GetAtt': ['FriggAuroraCluster', 'Port'] },
|
|
1775
|
-
DatabaseName: dbConfig.databaseName || 'frigg_db'
|
|
1776
|
-
}
|
|
1777
|
-
]
|
|
1778
|
-
};
|
|
1779
|
-
|
|
1780
|
-
// 9. Set DB_TYPE for Prisma client selection
|
|
1781
|
-
definition.provider.environment.DB_TYPE = 'postgresql';
|
|
1782
|
-
|
|
1783
|
-
console.log('✅ Aurora infrastructure resources created');
|
|
1784
|
-
};
|
|
1785
|
-
|
|
1786
|
-
const useExistingAurora = (definition, AppDefinition, discoveredResources) => {
|
|
1787
|
-
const dbConfig = AppDefinition.database.postgres;
|
|
1788
|
-
|
|
1789
|
-
console.log(`🔗 Using existing Aurora cluster: ${discoveredResources.aurora.clusterIdentifier}`);
|
|
1790
|
-
|
|
1791
|
-
// Add IAM permissions for Secrets Manager if secret exists
|
|
1792
|
-
if (discoveredResources.aurora.secretArn) {
|
|
1793
|
-
definition.provider.iamRoleStatements.push({
|
|
1794
|
-
Effect: 'Allow',
|
|
1795
|
-
Action: [
|
|
1796
|
-
'secretsmanager:GetSecretValue',
|
|
1797
|
-
'secretsmanager:DescribeSecret'
|
|
1798
|
-
],
|
|
1799
|
-
Resource: discoveredResources.aurora.secretArn
|
|
1800
|
-
});
|
|
1801
|
-
|
|
1802
|
-
// Set DATABASE_URL from discovered secret
|
|
1803
|
-
definition.provider.environment.DATABASE_URL = {
|
|
1804
|
-
'Fn::Sub': [
|
|
1805
|
-
'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
|
|
1806
|
-
{
|
|
1807
|
-
Username: { 'Fn::Sub': `{{resolve:secretsmanager:${discoveredResources.aurora.secretArn}:SecretString:username}}` },
|
|
1808
|
-
Password: { 'Fn::Sub': `{{resolve:secretsmanager:${discoveredResources.aurora.secretArn}:SecretString:password}}` },
|
|
1809
|
-
Endpoint: discoveredResources.aurora.endpoint,
|
|
1810
|
-
Port: discoveredResources.aurora.port,
|
|
1811
|
-
DatabaseName: dbConfig.databaseName || 'frigg_db'
|
|
1812
|
-
}
|
|
1813
|
-
]
|
|
1814
|
-
};
|
|
1815
|
-
} else if (dbConfig.secretArn) {
|
|
1816
|
-
// Use user-provided secret ARN
|
|
1817
|
-
definition.provider.iamRoleStatements.push({
|
|
1818
|
-
Effect: 'Allow',
|
|
1819
|
-
Action: [
|
|
1820
|
-
'secretsmanager:GetSecretValue',
|
|
1821
|
-
'secretsmanager:DescribeSecret'
|
|
1822
|
-
],
|
|
1823
|
-
Resource: dbConfig.secretArn
|
|
1824
|
-
});
|
|
1825
|
-
|
|
1826
|
-
definition.provider.environment.DATABASE_URL = {
|
|
1827
|
-
'Fn::Sub': [
|
|
1828
|
-
'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
|
|
1829
|
-
{
|
|
1830
|
-
Username: { 'Fn::Sub': `{{resolve:secretsmanager:${dbConfig.secretArn}:SecretString:username}}` },
|
|
1831
|
-
Password: { 'Fn::Sub': `{{resolve:secretsmanager:${dbConfig.secretArn}:SecretString:password}}` },
|
|
1832
|
-
Endpoint: discoveredResources.aurora.endpoint,
|
|
1833
|
-
Port: discoveredResources.aurora.port,
|
|
1834
|
-
DatabaseName: dbConfig.databaseName || 'frigg_db'
|
|
1835
|
-
}
|
|
1836
|
-
]
|
|
1837
|
-
};
|
|
1838
|
-
} else {
|
|
1839
|
-
throw new Error('No database secret found. Provide secretArn in database.postgres configuration or ensure Secrets Manager secret exists.');
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
// Set DB_TYPE for Prisma client selection
|
|
1843
|
-
definition.provider.environment.DB_TYPE = 'postgresql';
|
|
1844
|
-
|
|
1845
|
-
console.log('✅ Existing Aurora cluster configured');
|
|
1846
|
-
};
|
|
1847
|
-
|
|
1848
|
-
const useDiscoveredAurora = (definition, AppDefinition, discoveredResources) => {
|
|
1849
|
-
console.log(`🔍 Using discovered Aurora cluster: ${discoveredResources.aurora.clusterIdentifier}`);
|
|
1850
|
-
useExistingAurora(definition, AppDefinition, discoveredResources);
|
|
1851
|
-
};
|
|
1852
|
-
|
|
1853
|
-
const configurePostgres = (definition, AppDefinition, discoveredResources) => {
|
|
1854
|
-
if (!AppDefinition.database?.postgres?.enable) {
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
// Validate VPC is enabled (required for Aurora deployment)
|
|
1859
|
-
if (!AppDefinition.vpc?.enable) {
|
|
1860
|
-
throw new Error(
|
|
1861
|
-
'Aurora PostgreSQL requires VPC deployment. ' +
|
|
1862
|
-
'Set vpc.enable to true in your app definition.'
|
|
1863
|
-
);
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
// Validate private subnets exist (Aurora requires at least 2 subnets in different AZs)
|
|
1867
|
-
// Skip validation if VPC management is 'create-new' (subnets will be created)
|
|
1868
|
-
const vpcManagement = AppDefinition.vpc?.management || 'discover';
|
|
1869
|
-
if (vpcManagement !== 'create-new' && (!discoveredResources.privateSubnetId1 || !discoveredResources.privateSubnetId2)) {
|
|
1870
|
-
throw new Error(
|
|
1871
|
-
'Aurora PostgreSQL requires at least 2 private subnets in different availability zones. ' +
|
|
1872
|
-
'No private subnets were discovered in your VPC. ' +
|
|
1873
|
-
'Please create private subnets or use VPC management mode "create-new".'
|
|
1874
|
-
);
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
const dbConfig = AppDefinition.database.postgres;
|
|
1878
|
-
const management = dbConfig.management || 'discover';
|
|
1879
|
-
|
|
1880
|
-
console.log(`\n🐘 PostgreSQL Management Mode: ${management}`);
|
|
1881
|
-
|
|
1882
|
-
if (management === 'create-new' || discoveredResources.aurora?.needsCreation) {
|
|
1883
|
-
createAuroraInfrastructure(definition, AppDefinition, discoveredResources);
|
|
1884
|
-
} else if (management === 'use-existing') {
|
|
1885
|
-
if (!discoveredResources.aurora?.clusterIdentifier && !dbConfig.clusterIdentifier) {
|
|
1886
|
-
throw new Error('PostgreSQL management is set to "use-existing" but no clusterIdentifier was found or provided');
|
|
1887
|
-
}
|
|
1888
|
-
useExistingAurora(definition, AppDefinition, discoveredResources);
|
|
1889
|
-
} else {
|
|
1890
|
-
// discover mode
|
|
1891
|
-
if (discoveredResources.aurora?.clusterIdentifier) {
|
|
1892
|
-
useDiscoveredAurora(definition, AppDefinition, discoveredResources);
|
|
1893
|
-
} else {
|
|
1894
|
-
throw new Error('No Aurora cluster found in discovery mode. Set management to "create-new" or provide clusterIdentifier with "use-existing".');
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
};
|
|
1898
|
-
|
|
1899
1530
|
const configureSsm = (definition, AppDefinition) => {
|
|
1900
1531
|
if (AppDefinition.ssm?.enable !== true) {
|
|
1901
1532
|
return;
|
|
@@ -1994,47 +1625,15 @@ const configureWebsockets = (definition, AppDefinition) => {
|
|
|
1994
1625
|
};
|
|
1995
1626
|
};
|
|
1996
1627
|
|
|
1997
|
-
/**
|
|
1998
|
-
* Ensure Prisma Lambda Layer exists
|
|
1999
|
-
* Automatically builds the layer if it doesn't exist in the project root
|
|
2000
|
-
*/
|
|
2001
|
-
async function ensurePrismaLayerExists() {
|
|
2002
|
-
const projectRoot = process.cwd();
|
|
2003
|
-
const layerPath = path.join(projectRoot, 'layers/prisma');
|
|
2004
|
-
|
|
2005
|
-
// Check if layer already exists
|
|
2006
|
-
if (fs.existsSync(layerPath)) {
|
|
2007
|
-
console.log('✓ Prisma Lambda Layer already exists at', layerPath);
|
|
2008
|
-
return;
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
// Layer doesn't exist - build it automatically
|
|
2012
|
-
console.log('📦 Prisma Lambda Layer not found - building automatically...');
|
|
2013
|
-
console.log(' This may take a minute on first deployment.\n');
|
|
2014
|
-
|
|
2015
|
-
try {
|
|
2016
|
-
await buildPrismaLayer();
|
|
2017
|
-
console.log('✓ Prisma Lambda Layer built successfully\n');
|
|
2018
|
-
} catch (error) {
|
|
2019
|
-
console.error('✗ Failed to build Prisma Lambda Layer:', error.message);
|
|
2020
|
-
console.error(' You may need to run: npm install @friggframework/core\n');
|
|
2021
|
-
throw error;
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
1628
|
const composeServerlessDefinition = async (AppDefinition) => {
|
|
2026
1629
|
console.log('composeServerlessDefinition', AppDefinition);
|
|
2027
1630
|
|
|
2028
|
-
// Ensure Prisma layer exists before generating serverless config
|
|
2029
|
-
await ensurePrismaLayerExists();
|
|
2030
|
-
|
|
2031
1631
|
const discoveredResources = await gatherDiscoveredResources(AppDefinition);
|
|
2032
1632
|
const appEnvironmentVars = getAppEnvironmentVars(AppDefinition);
|
|
2033
1633
|
const definition = createBaseDefinition(AppDefinition, appEnvironmentVars, discoveredResources);
|
|
2034
1634
|
|
|
2035
1635
|
applyKmsConfiguration(definition, AppDefinition, discoveredResources);
|
|
2036
1636
|
configureVpc(definition, AppDefinition, discoveredResources);
|
|
2037
|
-
configurePostgres(definition, AppDefinition, discoveredResources);
|
|
2038
1637
|
configureSsm(definition, AppDefinition);
|
|
2039
1638
|
attachIntegrations(definition, AppDefinition);
|
|
2040
1639
|
configureWebsockets(definition, AppDefinition);
|
|
@@ -1762,97 +1762,6 @@ describe('composeServerlessDefinition', () => {
|
|
|
1762
1762
|
});
|
|
1763
1763
|
});
|
|
1764
1764
|
|
|
1765
|
-
describe('Database Migration Lambda', () => {
|
|
1766
|
-
it('should include dbMigrate function in all deployments', async () => {
|
|
1767
|
-
const appDefinition = {
|
|
1768
|
-
name: 'test-app',
|
|
1769
|
-
integrations: []
|
|
1770
|
-
};
|
|
1771
|
-
|
|
1772
|
-
const result = await composeServerlessDefinition(appDefinition);
|
|
1773
|
-
|
|
1774
|
-
// Check dbMigrate function exists
|
|
1775
|
-
expect(result.functions.dbMigrate).toBeDefined();
|
|
1776
|
-
expect(result.functions.dbMigrate.handler).toBe(
|
|
1777
|
-
'node_modules/@friggframework/core/handlers/workers/db-migration.handler'
|
|
1778
|
-
);
|
|
1779
|
-
});
|
|
1780
|
-
|
|
1781
|
-
it('should configure dbMigrate with correct settings', async () => {
|
|
1782
|
-
const appDefinition = {
|
|
1783
|
-
integrations: []
|
|
1784
|
-
};
|
|
1785
|
-
|
|
1786
|
-
const result = await composeServerlessDefinition(appDefinition);
|
|
1787
|
-
|
|
1788
|
-
const dbMigrate = result.functions.dbMigrate;
|
|
1789
|
-
|
|
1790
|
-
// Check timeout (5 minutes for long migrations)
|
|
1791
|
-
expect(dbMigrate.timeout).toBe(300);
|
|
1792
|
-
|
|
1793
|
-
// Check memory allocation (extra for Prisma CLI)
|
|
1794
|
-
expect(dbMigrate.memorySize).toBe(512);
|
|
1795
|
-
|
|
1796
|
-
// Check description
|
|
1797
|
-
expect(dbMigrate.description).toContain('database migrations');
|
|
1798
|
-
expect(dbMigrate.description).toContain('Prisma');
|
|
1799
|
-
});
|
|
1800
|
-
|
|
1801
|
-
it('should not have HTTP events (manual invocation only)', async () => {
|
|
1802
|
-
const appDefinition = {
|
|
1803
|
-
integrations: []
|
|
1804
|
-
};
|
|
1805
|
-
|
|
1806
|
-
const result = await composeServerlessDefinition(appDefinition);
|
|
1807
|
-
|
|
1808
|
-
const dbMigrate = result.functions.dbMigrate;
|
|
1809
|
-
|
|
1810
|
-
// Should have no events (manually invoked via AWS CLI)
|
|
1811
|
-
expect(dbMigrate.events).toBeUndefined();
|
|
1812
|
-
});
|
|
1813
|
-
|
|
1814
|
-
it('should include dbMigrate even with VPC enabled', async () => {
|
|
1815
|
-
const appDefinition = {
|
|
1816
|
-
vpc: {
|
|
1817
|
-
enable: true,
|
|
1818
|
-
management: 'discover'
|
|
1819
|
-
},
|
|
1820
|
-
integrations: []
|
|
1821
|
-
};
|
|
1822
|
-
|
|
1823
|
-
const result = await composeServerlessDefinition(appDefinition);
|
|
1824
|
-
|
|
1825
|
-
// dbMigrate should exist with VPC configuration
|
|
1826
|
-
expect(result.functions.dbMigrate).toBeDefined();
|
|
1827
|
-
|
|
1828
|
-
// Should have same VPC access as other functions
|
|
1829
|
-
expect(result.provider.vpc).toBeDefined();
|
|
1830
|
-
});
|
|
1831
|
-
|
|
1832
|
-
it('should include dbMigrate with database configuration', async () => {
|
|
1833
|
-
const appDefinition = {
|
|
1834
|
-
vpc: {
|
|
1835
|
-
enable: true,
|
|
1836
|
-
management: 'discover'
|
|
1837
|
-
},
|
|
1838
|
-
database: {
|
|
1839
|
-
postgres: {
|
|
1840
|
-
enable: true,
|
|
1841
|
-
management: 'discover'
|
|
1842
|
-
}
|
|
1843
|
-
},
|
|
1844
|
-
integrations: []
|
|
1845
|
-
};
|
|
1846
|
-
|
|
1847
|
-
const result = await composeServerlessDefinition(appDefinition);
|
|
1848
|
-
|
|
1849
|
-
// dbMigrate should exist alongside database configuration
|
|
1850
|
-
expect(result.functions.dbMigrate).toBeDefined();
|
|
1851
|
-
expect(result.provider.environment.DB_TYPE).toBe('postgresql');
|
|
1852
|
-
expect(result.provider.environment.DATABASE_URL).toBeDefined();
|
|
1853
|
-
});
|
|
1854
|
-
});
|
|
1855
|
-
|
|
1856
1765
|
describe('Edge Cases', () => {
|
|
1857
1766
|
it('should handle empty app definition', async () => {
|
|
1858
1767
|
const appDefinition = {};
|