@friggframework/devtools 2.0.0--canary.463.62579dd.0 → 2.0.0--canary.461.ec909cf.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 +549 -21
- package/infrastructure/aws-discovery.test.js +447 -1
- package/infrastructure/domains/database/aurora-builder.js +307 -0
- package/infrastructure/domains/database/aurora-builder.test.js +482 -0
- package/infrastructure/domains/networking/vpc-builder.js +718 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +772 -0
- package/infrastructure/domains/networking/vpc-discovery.js +159 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +445 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +385 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +129 -0
- package/infrastructure/infrastructure-composer.test.js +1895 -0
- package/infrastructure/scripts/build-prisma-layer.js +534 -0
- package/infrastructure/serverless-template.js +790 -84
- package/infrastructure/serverless-template.test.js +94 -1
- 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
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aurora PostgreSQL Builder
|
|
3
|
+
*
|
|
4
|
+
* Domain Layer - Hexagonal Architecture
|
|
5
|
+
*
|
|
6
|
+
* Responsible for:
|
|
7
|
+
* - Aurora Serverless v2 cluster creation or discovery
|
|
8
|
+
* - Database subnet groups
|
|
9
|
+
* - Database security groups
|
|
10
|
+
* - Secrets Manager integration for credentials
|
|
11
|
+
* - Database connection environment variables
|
|
12
|
+
*
|
|
13
|
+
* Supports three management modes:
|
|
14
|
+
* 1. create-new: Creates new Aurora cluster
|
|
15
|
+
* 2. use-existing: Uses explicitly provided cluster
|
|
16
|
+
* 3. discover (default): Discovers existing cluster
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { InfrastructureBuilder, ValidationResult } = require('../shared/base-builder');
|
|
20
|
+
|
|
21
|
+
class AuroraBuilder extends InfrastructureBuilder {
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.name = 'AuroraBuilder';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
shouldExecute(appDefinition) {
|
|
28
|
+
// Skip Aurora in local mode (when FRIGG_SKIP_AWS_DISCOVERY is set)
|
|
29
|
+
// Aurora is an AWS-specific service that should only be created in production
|
|
30
|
+
if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return appDefinition.database?.postgres?.enable === true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getDependencies() {
|
|
38
|
+
return ['VpcBuilder']; // Aurora requires VPC to be configured first
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
validate(appDefinition) {
|
|
42
|
+
const result = new ValidationResult();
|
|
43
|
+
|
|
44
|
+
if (!appDefinition.database?.postgres) {
|
|
45
|
+
result.addError('PostgreSQL database configuration is missing');
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const dbConfig = appDefinition.database.postgres;
|
|
50
|
+
|
|
51
|
+
// Validate management mode
|
|
52
|
+
const validModes = ['discover', 'create-new', 'use-existing'];
|
|
53
|
+
const management = dbConfig.management || 'discover';
|
|
54
|
+
if (!validModes.includes(management)) {
|
|
55
|
+
result.addError(`Invalid database.postgres.management: "${management}"`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Validate use-existing requirements
|
|
59
|
+
if (management === 'use-existing' && !dbConfig.endpoint) {
|
|
60
|
+
result.addError('database.postgres.endpoint is required when management="use-existing"');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validate capacity settings
|
|
64
|
+
if (dbConfig.minCapacity !== undefined && (dbConfig.minCapacity < 0.5 || dbConfig.minCapacity > 128)) {
|
|
65
|
+
result.addError('database.postgres.minCapacity must be between 0.5 and 128');
|
|
66
|
+
}
|
|
67
|
+
if (dbConfig.maxCapacity !== undefined && (dbConfig.maxCapacity < 0.5 || dbConfig.maxCapacity > 128)) {
|
|
68
|
+
result.addError('database.postgres.maxCapacity must be between 0.5 and 128');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Warn about public accessibility in production
|
|
72
|
+
if (dbConfig.publiclyAccessible === true) {
|
|
73
|
+
result.addWarning('database.postgres.publiclyAccessible=true is not recommended for production');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build Aurora infrastructure
|
|
81
|
+
*/
|
|
82
|
+
async build(appDefinition, discoveredResources) {
|
|
83
|
+
console.log(`\n[${this.name}] Configuring Aurora PostgreSQL...`);
|
|
84
|
+
|
|
85
|
+
const dbConfig = appDefinition.database.postgres;
|
|
86
|
+
const management = dbConfig.management || 'discover';
|
|
87
|
+
|
|
88
|
+
console.log(` PostgreSQL Management Mode: ${management}`);
|
|
89
|
+
|
|
90
|
+
const result = {
|
|
91
|
+
resources: {},
|
|
92
|
+
iamStatements: [],
|
|
93
|
+
environment: {},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Handle different management modes
|
|
97
|
+
switch (management) {
|
|
98
|
+
case 'create-new':
|
|
99
|
+
await this.createNewAurora(appDefinition, discoveredResources, result);
|
|
100
|
+
break;
|
|
101
|
+
case 'use-existing':
|
|
102
|
+
await this.useExistingAurora(appDefinition, discoveredResources, result);
|
|
103
|
+
break;
|
|
104
|
+
case 'discover':
|
|
105
|
+
default:
|
|
106
|
+
await this.discoverAurora(appDefinition, discoveredResources, result);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(`[${this.name}] ✅ Aurora PostgreSQL configuration completed`);
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create new Aurora cluster
|
|
116
|
+
*/
|
|
117
|
+
async createNewAurora(appDefinition, discoveredResources, result) {
|
|
118
|
+
console.log(' Creating new Aurora Serverless v2 cluster...');
|
|
119
|
+
|
|
120
|
+
const dbConfig = appDefinition.database.postgres;
|
|
121
|
+
const publiclyAccessible = dbConfig.publiclyAccessible === true;
|
|
122
|
+
|
|
123
|
+
// Get subnet IDs for DB Subnet Group
|
|
124
|
+
const subnetIds = publiclyAccessible
|
|
125
|
+
? [discoveredResources.publicSubnetId1, discoveredResources.publicSubnetId2]
|
|
126
|
+
: [discoveredResources.privateSubnetId1, discoveredResources.privateSubnetId2];
|
|
127
|
+
|
|
128
|
+
if (!subnetIds[0] || !subnetIds[1]) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Aurora requires 2 ${publiclyAccessible ? 'public' : 'private'} subnets in different AZs. ` +
|
|
131
|
+
'Ensure VPC is configured correctly.'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Database Subnet Group
|
|
136
|
+
result.resources.FriggDBSubnetGroup = {
|
|
137
|
+
Type: 'AWS::RDS::DBSubnetGroup',
|
|
138
|
+
Properties: {
|
|
139
|
+
DBSubnetGroupName: '${self:service}-${self:provider.stage}-db-subnet-group',
|
|
140
|
+
DBSubnetGroupDescription: 'Subnet group for Frigg Aurora cluster',
|
|
141
|
+
SubnetIds: subnetIds,
|
|
142
|
+
Tags: [
|
|
143
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-db-subnet' },
|
|
144
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Database Credentials Secret
|
|
150
|
+
result.resources.FriggDBSecret = {
|
|
151
|
+
Type: 'AWS::SecretsManager::Secret',
|
|
152
|
+
Properties: {
|
|
153
|
+
Name: '${self:service}-${self:provider.stage}-db-credentials',
|
|
154
|
+
Description: 'Aurora database credentials',
|
|
155
|
+
GenerateSecretString: {
|
|
156
|
+
SecretStringTemplate: JSON.stringify({ username: dbConfig.username || 'postgres' }),
|
|
157
|
+
GenerateStringKey: 'password',
|
|
158
|
+
PasswordLength: 32,
|
|
159
|
+
ExcludeCharacters: '"@/\\',
|
|
160
|
+
},
|
|
161
|
+
Tags: [
|
|
162
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-db-secret' },
|
|
163
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Aurora Cluster
|
|
169
|
+
result.resources.FriggAuroraCluster = {
|
|
170
|
+
Type: 'AWS::RDS::DBCluster',
|
|
171
|
+
DeletionPolicy: 'Snapshot',
|
|
172
|
+
Properties: {
|
|
173
|
+
Engine: 'aurora-postgresql',
|
|
174
|
+
EngineMode: 'provisioned',
|
|
175
|
+
EngineVersion: '15.5',
|
|
176
|
+
DatabaseName: dbConfig.database || 'frigg',
|
|
177
|
+
MasterUsername: {
|
|
178
|
+
'Fn::Sub': '{{resolve:secretsmanager:${FriggDBSecret}:SecretString:username}}',
|
|
179
|
+
},
|
|
180
|
+
MasterUserPassword: {
|
|
181
|
+
'Fn::Sub': '{{resolve:secretsmanager:${FriggDBSecret}:SecretString:password}}',
|
|
182
|
+
},
|
|
183
|
+
DBSubnetGroupName: { Ref: 'FriggDBSubnetGroup' },
|
|
184
|
+
VpcSecurityGroupIds: discoveredResources.vpcSecurityGroupIds || [
|
|
185
|
+
{ Ref: 'FriggLambdaSecurityGroup' },
|
|
186
|
+
],
|
|
187
|
+
PubliclyAccessible: publiclyAccessible,
|
|
188
|
+
ServerlessV2ScalingConfiguration: {
|
|
189
|
+
MinCapacity: dbConfig.minCapacity || 0.5,
|
|
190
|
+
MaxCapacity: dbConfig.maxCapacity || 1,
|
|
191
|
+
},
|
|
192
|
+
EnableHttpEndpoint: false,
|
|
193
|
+
BackupRetentionPeriod: 7,
|
|
194
|
+
PreferredBackupWindow: '03:00-04:00',
|
|
195
|
+
PreferredMaintenanceWindow: 'sun:04:00-sun:05:00',
|
|
196
|
+
Tags: [
|
|
197
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora' },
|
|
198
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Aurora Instance
|
|
204
|
+
result.resources.FriggAuroraInstance = {
|
|
205
|
+
Type: 'AWS::RDS::DBInstance',
|
|
206
|
+
Properties: {
|
|
207
|
+
Engine: 'aurora-postgresql',
|
|
208
|
+
DBInstanceClass: 'db.serverless',
|
|
209
|
+
DBClusterIdentifier: { Ref: 'FriggAuroraCluster' },
|
|
210
|
+
PubliclyAccessible: publiclyAccessible,
|
|
211
|
+
Tags: [
|
|
212
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-instance' },
|
|
213
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Environment variables
|
|
219
|
+
result.environment.DATABASE_URL = this.buildDatabaseUrl(
|
|
220
|
+
{ 'Fn::GetAtt': ['FriggAuroraCluster', 'Endpoint.Address'] },
|
|
221
|
+
{ 'Fn::GetAtt': ['FriggAuroraCluster', 'Endpoint.Port'] },
|
|
222
|
+
dbConfig.database || 'frigg',
|
|
223
|
+
{ Ref: 'FriggDBSecret' }
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// IAM permissions for Secrets Manager
|
|
227
|
+
result.iamStatements.push({
|
|
228
|
+
Effect: 'Allow',
|
|
229
|
+
Action: ['secretsmanager:GetSecretValue'],
|
|
230
|
+
Resource: { Ref: 'FriggDBSecret' },
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
console.log(' ✅ Aurora Serverless v2 cluster resources created');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Use existing Aurora cluster
|
|
238
|
+
*/
|
|
239
|
+
async useExistingAurora(appDefinition, discoveredResources, result) {
|
|
240
|
+
console.log(' Using existing Aurora cluster...');
|
|
241
|
+
|
|
242
|
+
const dbConfig = appDefinition.database.postgres;
|
|
243
|
+
|
|
244
|
+
if (!dbConfig.endpoint) {
|
|
245
|
+
throw new Error('database.postgres.endpoint is required when management="use-existing"');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Set environment variables for existing cluster
|
|
249
|
+
result.environment.DATABASE_HOST = dbConfig.endpoint;
|
|
250
|
+
result.environment.DATABASE_PORT = String(dbConfig.port || 5432);
|
|
251
|
+
result.environment.DATABASE_NAME = dbConfig.database || 'frigg';
|
|
252
|
+
result.environment.DATABASE_USER = dbConfig.username || 'postgres';
|
|
253
|
+
|
|
254
|
+
console.log(` ✅ Using existing cluster: ${dbConfig.endpoint}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Discover existing Aurora cluster
|
|
259
|
+
*/
|
|
260
|
+
async discoverAurora(appDefinition, discoveredResources, result) {
|
|
261
|
+
console.log(' Discovering Aurora cluster...');
|
|
262
|
+
|
|
263
|
+
if (!discoveredResources.auroraClusterEndpoint) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
'No Aurora cluster found in discovery mode. Set management to "create-new" or provide endpoint with "use-existing".'
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(` ✅ Using discovered Aurora cluster: ${discoveredResources.auroraClusterEndpoint}`);
|
|
270
|
+
|
|
271
|
+
// Use discovered cluster details
|
|
272
|
+
result.environment.DATABASE_HOST = discoveredResources.auroraClusterEndpoint;
|
|
273
|
+
result.environment.DATABASE_PORT = String(discoveredResources.auroraPort || 5432);
|
|
274
|
+
|
|
275
|
+
if (discoveredResources.databaseSecretArn) {
|
|
276
|
+
result.environment.DATABASE_SECRET_ARN = discoveredResources.databaseSecretArn;
|
|
277
|
+
result.iamStatements.push({
|
|
278
|
+
Effect: 'Allow',
|
|
279
|
+
Action: ['secretsmanager:GetSecretValue'],
|
|
280
|
+
Resource: discoveredResources.databaseSecretArn,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
console.log(` ✅ Discovered cluster configuration complete`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Build DATABASE_URL connection string
|
|
289
|
+
*/
|
|
290
|
+
buildDatabaseUrl(host, port, database, secretRef) {
|
|
291
|
+
return {
|
|
292
|
+
'Fn::Sub': [
|
|
293
|
+
`postgresql://\${Username}:\${Password}@\${Host}:\${Port}/\${Database}`,
|
|
294
|
+
{
|
|
295
|
+
Username: `{{resolve:secretsmanager:${secretRef}:SecretString:username}}`,
|
|
296
|
+
Password: `{{resolve:secretsmanager:${secretRef}:SecretString:password}}`,
|
|
297
|
+
Host: host,
|
|
298
|
+
Port: port,
|
|
299
|
+
Database: database,
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = { AuroraBuilder };
|
|
307
|
+
|