@friggframework/devtools 2.0.0--canary.461.849e166.0 → 2.0.0--canary.474.aa465e4.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/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/domains/database/aurora-builder.js +234 -57
- package/infrastructure/domains/database/aurora-builder.test.js +7 -2
- package/infrastructure/domains/database/aurora-resolver.js +210 -0
- package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
- package/infrastructure/domains/database/migration-builder.js +256 -215
- package/infrastructure/domains/database/migration-builder.test.js +5 -111
- package/infrastructure/domains/database/migration-resolver.js +163 -0
- package/infrastructure/domains/database/migration-resolver.test.js +337 -0
- package/infrastructure/domains/integration/integration-builder.js +258 -84
- package/infrastructure/domains/integration/integration-resolver.js +170 -0
- package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
- package/infrastructure/domains/networking/vpc-builder.js +856 -135
- package/infrastructure/domains/networking/vpc-builder.test.js +10 -6
- package/infrastructure/domains/networking/vpc-resolver.js +324 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
- package/infrastructure/domains/security/kms-builder.js +179 -22
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/shared/base-resolver.js +186 -0
- package/infrastructure/domains/shared/base-resolver.test.js +305 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +26 -1
- package/infrastructure/domains/shared/types/app-definition.js +205 -0
- package/infrastructure/domains/shared/types/discovery-result.js +106 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
- package/infrastructure/domains/shared/types/index.js +46 -0
- package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
- package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
- package/package.json +6 -6
- package/infrastructure/REFACTOR.md +0 -532
- package/infrastructure/TRANSFORMATION-VISUAL.md +0 -239
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Aurora PostgreSQL Builder
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Domain Layer - Hexagonal Architecture
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* Responsible for:
|
|
7
7
|
* - Aurora Serverless v2 cluster creation or discovery
|
|
8
8
|
* - Database subnet groups
|
|
9
9
|
* - Database security groups
|
|
10
10
|
* - Secrets Manager integration for credentials
|
|
11
11
|
* - Database connection environment variables
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
*
|
|
13
|
+
* Uses ownership-based architecture:
|
|
14
|
+
* - STACK: Resources in our CloudFormation template (definitions + Refs)
|
|
15
|
+
* - EXTERNAL: Resources outside our stack (reference by physical ID)
|
|
16
|
+
* - AUTO: System decides based on discovery
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
const { InfrastructureBuilder, ValidationResult } = require('../shared/base-builder');
|
|
20
|
+
const AuroraResourceResolver = require('./aurora-resolver');
|
|
21
|
+
const { createEmptyDiscoveryResult } = require('../shared/types/discovery-result');
|
|
22
|
+
const { ResourceOwnership } = require('../shared/types/resource-ownership');
|
|
20
23
|
|
|
21
24
|
class AuroraBuilder extends InfrastructureBuilder {
|
|
22
25
|
constructor() {
|
|
@@ -77,86 +80,260 @@ class AuroraBuilder extends InfrastructureBuilder {
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/**
|
|
80
|
-
* Build Aurora infrastructure
|
|
83
|
+
* Build Aurora infrastructure using ownership-based architecture
|
|
81
84
|
*/
|
|
82
85
|
async build(appDefinition, discoveredResources) {
|
|
83
86
|
console.log(`\n[${this.name}] Configuring Aurora PostgreSQL...`);
|
|
84
87
|
|
|
85
|
-
|
|
88
|
+
// Backwards compatibility: Translate old schema to new ownership schema
|
|
89
|
+
appDefinition = this.translateLegacyConfig(appDefinition, discoveredResources);
|
|
86
90
|
|
|
87
|
-
//
|
|
88
|
-
const
|
|
89
|
-
|
|
91
|
+
// Initialize result
|
|
92
|
+
const result = {
|
|
93
|
+
resources: {},
|
|
94
|
+
iamStatements: [],
|
|
95
|
+
environment: {},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Special case: use-existing with endpoint (bypass resolver)
|
|
99
|
+
if (appDefinition.database?.postgres?._useExistingEndpoint) {
|
|
100
|
+
console.log(' Using provided database endpoint (use-existing mode)');
|
|
101
|
+
await this.useExistingAurora(appDefinition, discoveredResources, result);
|
|
102
|
+
console.log(`\n[${this.name}] ✅ Aurora PostgreSQL configuration completed`);
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Get structured discovery result
|
|
107
|
+
const discovery = discoveredResources._structured || this.convertFlatDiscoveryToStructured(discoveredResources, appDefinition);
|
|
108
|
+
|
|
109
|
+
// Use AuroraResourceResolver to make ownership decisions
|
|
110
|
+
const resolver = new AuroraResourceResolver();
|
|
111
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
112
|
+
|
|
113
|
+
console.log('\n 📋 Resource Ownership Decisions:');
|
|
114
|
+
console.log(` Cluster: ${decisions.cluster.ownership} - ${decisions.cluster.reason}`);
|
|
115
|
+
console.log(` Instance: ${decisions.instance.ownership} - ${decisions.instance.reason}`);
|
|
116
|
+
console.log(` Subnet Group: ${decisions.subnetGroup.ownership} - ${decisions.subnetGroup.reason}`);
|
|
117
|
+
console.log(` Secret: ${decisions.secret.ownership} - ${decisions.secret.reason}`);
|
|
118
|
+
|
|
119
|
+
// Build resources based on ownership decisions
|
|
120
|
+
await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result);
|
|
121
|
+
|
|
122
|
+
console.log(`\n[${this.name}] ✅ Aurora PostgreSQL configuration completed`);
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Convert flat discovery to structured discovery
|
|
128
|
+
* Provides backwards compatibility for tests
|
|
129
|
+
*/
|
|
130
|
+
convertFlatDiscoveryToStructured(flatDiscovery, appDefinition = {}) {
|
|
131
|
+
const discovery = createEmptyDiscoveryResult();
|
|
132
|
+
|
|
133
|
+
if (!flatDiscovery) {
|
|
134
|
+
return discovery;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check if resources are from CloudFormation stack
|
|
138
|
+
const isManagedIsolated = appDefinition.managementMode === 'managed' &&
|
|
139
|
+
(appDefinition.vpcIsolation === 'isolated' || !appDefinition.vpcIsolation);
|
|
140
|
+
const hasExistingStackResources = isManagedIsolated && flatDiscovery.auroraClusterId &&
|
|
141
|
+
typeof flatDiscovery.auroraClusterId === 'string';
|
|
142
|
+
|
|
143
|
+
if (flatDiscovery.fromCloudFormationStack || hasExistingStackResources) {
|
|
144
|
+
discovery.fromCloudFormation = true;
|
|
145
|
+
discovery.stackName = flatDiscovery.stackName || 'assumed-stack';
|
|
146
|
+
|
|
147
|
+
// Add stack-managed resources
|
|
148
|
+
let existingLogicalIds = flatDiscovery.existingLogicalIds || [];
|
|
149
|
+
|
|
150
|
+
// Infer logical IDs from physical IDs if needed
|
|
151
|
+
if (hasExistingStackResources && existingLogicalIds.length === 0) {
|
|
152
|
+
if (flatDiscovery.auroraClusterId) existingLogicalIds.push('FriggAuroraCluster');
|
|
153
|
+
if (flatDiscovery.auroraInstanceId) existingLogicalIds.push('FriggAuroraInstance');
|
|
154
|
+
if (flatDiscovery.dbSubnetGroupName) existingLogicalIds.push('FriggDBSubnetGroup');
|
|
155
|
+
if (flatDiscovery.dbSecretArn) existingLogicalIds.push('FriggDBSecret');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
existingLogicalIds.forEach(logicalId => {
|
|
159
|
+
let resourceType = '';
|
|
160
|
+
let physicalId = '';
|
|
161
|
+
|
|
162
|
+
if (logicalId === 'FriggAuroraCluster') {
|
|
163
|
+
resourceType = 'AWS::RDS::DBCluster';
|
|
164
|
+
physicalId = flatDiscovery.auroraClusterId;
|
|
165
|
+
} else if (logicalId === 'FriggAuroraInstance') {
|
|
166
|
+
resourceType = 'AWS::RDS::DBInstance';
|
|
167
|
+
physicalId = flatDiscovery.auroraInstanceId;
|
|
168
|
+
} else if (logicalId === 'FriggDBSubnetGroup') {
|
|
169
|
+
resourceType = 'AWS::RDS::DBSubnetGroup';
|
|
170
|
+
physicalId = flatDiscovery.dbSubnetGroupName;
|
|
171
|
+
} else if (logicalId === 'FriggDBSecret') {
|
|
172
|
+
resourceType = 'AWS::SecretsManager::Secret';
|
|
173
|
+
physicalId = flatDiscovery.dbSecretArn;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (physicalId && typeof physicalId === 'string') {
|
|
177
|
+
discovery.stackManaged.push({
|
|
178
|
+
logicalId,
|
|
179
|
+
physicalId,
|
|
180
|
+
resourceType
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
} else {
|
|
185
|
+
// Resources discovered from AWS API (external)
|
|
186
|
+
// Handle both cluster ID and endpoint
|
|
187
|
+
if (flatDiscovery.auroraClusterId && typeof flatDiscovery.auroraClusterId === 'string') {
|
|
188
|
+
discovery.external.push({
|
|
189
|
+
physicalId: flatDiscovery.auroraClusterId,
|
|
190
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
191
|
+
source: 'aws-discovery'
|
|
192
|
+
});
|
|
193
|
+
} else if (flatDiscovery.auroraClusterEndpoint && typeof flatDiscovery.auroraClusterEndpoint === 'string') {
|
|
194
|
+
// Endpoint provided (discover mode) - treat as external
|
|
195
|
+
discovery.external.push({
|
|
196
|
+
physicalId: flatDiscovery.auroraClusterEndpoint,
|
|
197
|
+
resourceType: 'AWS::RDS::DBCluster',
|
|
198
|
+
source: 'aws-discovery',
|
|
199
|
+
properties: { Endpoint: flatDiscovery.auroraClusterEndpoint }
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (flatDiscovery.auroraInstanceId && typeof flatDiscovery.auroraInstanceId === 'string') {
|
|
204
|
+
discovery.external.push({
|
|
205
|
+
physicalId: flatDiscovery.auroraInstanceId,
|
|
206
|
+
resourceType: 'AWS::RDS::DBInstance',
|
|
207
|
+
source: 'aws-discovery'
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
90
211
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
console.log(` 🔍 DEBUG: Aurora discoveredResources.auroraClusterId = ${discoveredResources?.auroraClusterId}`);
|
|
212
|
+
return discovery;
|
|
213
|
+
}
|
|
94
214
|
|
|
95
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Translate legacy configuration to ownership-based configuration
|
|
217
|
+
* Provides backwards compatibility
|
|
218
|
+
*/
|
|
219
|
+
translateLegacyConfig(appDefinition, discoveredResources) {
|
|
220
|
+
// If already using ownership schema, return as-is
|
|
221
|
+
if (appDefinition.database?.postgres?.ownership) {
|
|
222
|
+
return appDefinition;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const translated = JSON.parse(JSON.stringify(appDefinition));
|
|
226
|
+
|
|
227
|
+
// Initialize ownership sections
|
|
228
|
+
if (!translated.database) translated.database = {};
|
|
229
|
+
if (!translated.database.postgres) translated.database.postgres = {};
|
|
230
|
+
if (!translated.database.postgres.ownership) {
|
|
231
|
+
translated.database.postgres.ownership = {};
|
|
232
|
+
}
|
|
233
|
+
if (!translated.database.postgres.external) {
|
|
234
|
+
translated.database.postgres.external = {};
|
|
235
|
+
}
|
|
236
|
+
if (!translated.database.postgres.config) {
|
|
237
|
+
translated.database.postgres.config = {};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Handle top-level managementMode
|
|
241
|
+
const globalMode = appDefinition.managementMode || 'discover';
|
|
242
|
+
const vpcIsolation = appDefinition.vpcIsolation || 'shared';
|
|
96
243
|
|
|
97
244
|
if (globalMode === 'managed') {
|
|
98
|
-
|
|
99
|
-
if (dbConfig.management) {
|
|
245
|
+
if (appDefinition.database?.postgres?.management) {
|
|
100
246
|
console.log(` ⚠️ managementMode='managed' ignoring: database.postgres.management`);
|
|
101
247
|
}
|
|
102
248
|
|
|
103
|
-
// Clear granular option to prevent conflicts
|
|
104
|
-
delete appDefinition.database.postgres.management;
|
|
105
|
-
|
|
106
|
-
// Set management based on isolation strategy AND existing stack resources
|
|
107
249
|
if (vpcIsolation === 'isolated') {
|
|
108
|
-
// Check if CloudFormation stack already has Aurora (stage-specific)
|
|
109
|
-
// CloudFormation discovery sets 'auroraClusterId' (string) when found in stack
|
|
110
250
|
const hasStackAurora = discoveredResources?.auroraClusterId &&
|
|
111
251
|
typeof discoveredResources.auroraClusterId === 'string';
|
|
112
252
|
|
|
113
|
-
console.log(` 🔍 DEBUG: Aurora hasStackAurora = ${hasStackAurora}`);
|
|
114
|
-
|
|
115
253
|
if (hasStackAurora) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
254
|
+
translated.database.postgres.ownership.cluster = 'auto';
|
|
255
|
+
translated.database.postgres.ownership.instance = 'auto';
|
|
256
|
+
translated.database.postgres.ownership.subnetGroup = 'auto';
|
|
257
|
+
translated.database.postgres.ownership.secret = 'auto';
|
|
119
258
|
console.log(` managementMode='managed' + vpcIsolation='isolated' → stack has Aurora, reusing`);
|
|
120
259
|
} else {
|
|
121
|
-
|
|
122
|
-
|
|
260
|
+
translated.database.postgres.ownership.cluster = 'stack';
|
|
261
|
+
translated.database.postgres.ownership.instance = 'stack';
|
|
262
|
+
translated.database.postgres.ownership.subnetGroup = 'stack';
|
|
263
|
+
translated.database.postgres.ownership.secret = 'stack';
|
|
123
264
|
console.log(` managementMode='managed' + vpcIsolation='isolated' → no stack Aurora, creating new`);
|
|
124
265
|
}
|
|
125
266
|
} else {
|
|
126
|
-
|
|
127
|
-
|
|
267
|
+
translated.database.postgres.ownership.cluster = 'auto';
|
|
268
|
+
translated.database.postgres.ownership.instance = 'auto';
|
|
269
|
+
translated.database.postgres.ownership.subnetGroup = 'auto';
|
|
270
|
+
translated.database.postgres.ownership.secret = 'auto';
|
|
128
271
|
console.log(` managementMode='managed' + vpcIsolation='shared' → discovering Aurora`);
|
|
129
272
|
}
|
|
130
273
|
} else if (globalMode === 'existing') {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
management = management || 'discover';
|
|
274
|
+
translated.database.postgres.ownership.cluster = 'external';
|
|
275
|
+
translated.database.postgres.ownership.instance = 'external';
|
|
134
276
|
}
|
|
135
277
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
278
|
+
// Handle legacy database.postgres.management
|
|
279
|
+
// BUT: if managementMode (top-level) is set, it takes precedence
|
|
280
|
+
const dbManagement = appDefinition.database?.postgres?.management;
|
|
281
|
+
if (dbManagement && globalMode !== 'managed' && globalMode !== 'existing') {
|
|
282
|
+
if (dbManagement === 'managed') {
|
|
283
|
+
translated.database.postgres.ownership.cluster = 'stack';
|
|
284
|
+
translated.database.postgres.ownership.instance = 'stack';
|
|
285
|
+
translated.database.postgres.ownership.subnetGroup = 'stack';
|
|
286
|
+
translated.database.postgres.ownership.secret = 'stack';
|
|
287
|
+
} else if (dbManagement === 'use-existing') {
|
|
288
|
+
// For use-existing with endpoint, we bypass resolver entirely
|
|
289
|
+
// Mark this with a special flag
|
|
290
|
+
translated.database.postgres._useExistingEndpoint = true;
|
|
291
|
+
if (appDefinition.database.postgres.endpoint) {
|
|
292
|
+
translated.database.postgres.external.endpoint = appDefinition.database.postgres.endpoint;
|
|
293
|
+
}
|
|
294
|
+
} else if (dbManagement === 'discover') {
|
|
295
|
+
translated.database.postgres.ownership.cluster = 'auto';
|
|
296
|
+
translated.database.postgres.ownership.instance = 'auto';
|
|
297
|
+
}
|
|
298
|
+
}
|
|
143
299
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
default:
|
|
154
|
-
await this.discoverAurora(appDefinition, discoveredResources, result);
|
|
155
|
-
break;
|
|
300
|
+
// Preserve other database config
|
|
301
|
+
if (appDefinition.database?.postgres?.minCapacity) {
|
|
302
|
+
translated.database.postgres.config.minCapacity = appDefinition.database.postgres.minCapacity;
|
|
303
|
+
}
|
|
304
|
+
if (appDefinition.database?.postgres?.maxCapacity) {
|
|
305
|
+
translated.database.postgres.config.maxCapacity = appDefinition.database.postgres.maxCapacity;
|
|
306
|
+
}
|
|
307
|
+
if (appDefinition.database?.postgres?.publiclyAccessible !== undefined) {
|
|
308
|
+
translated.database.postgres.config.publiclyAccessible = appDefinition.database.postgres.publiclyAccessible;
|
|
156
309
|
}
|
|
157
310
|
|
|
158
|
-
|
|
159
|
-
|
|
311
|
+
return translated;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Build all Aurora resources based on ownership decisions
|
|
316
|
+
*/
|
|
317
|
+
async buildFromDecisions(decisions, appDefinition, discoveredResources, result) {
|
|
318
|
+
// Determine build strategy from ownership decisions
|
|
319
|
+
|
|
320
|
+
if (decisions.cluster.ownership === ResourceOwnership.EXTERNAL) {
|
|
321
|
+
// External cluster discovered - reference it without creating infrastructure
|
|
322
|
+
console.log(' → Discovering and referencing external Aurora cluster');
|
|
323
|
+
await this.discoverAurora(appDefinition, discoveredResources, result);
|
|
324
|
+
} else if (decisions.cluster.ownership === ResourceOwnership.STACK && decisions.cluster.physicalId) {
|
|
325
|
+
// Cluster exists in stack - add definitions (CloudFormation idempotency)
|
|
326
|
+
console.log(' → Adding Aurora definitions to template (existing in stack)');
|
|
327
|
+
await this.createNewAurora(appDefinition, discoveredResources, result);
|
|
328
|
+
} else if (decisions.cluster.ownership === ResourceOwnership.STACK && !decisions.cluster.physicalId) {
|
|
329
|
+
// Create new cluster (stack, no existing)
|
|
330
|
+
console.log(' → Creating new Aurora cluster in stack');
|
|
331
|
+
await this.createNewAurora(appDefinition, discoveredResources, result);
|
|
332
|
+
} else {
|
|
333
|
+
// Fallback: discover mode
|
|
334
|
+
console.log(' → Discovering Aurora resources');
|
|
335
|
+
await this.discoverAurora(appDefinition, discoveredResources, result);
|
|
336
|
+
}
|
|
160
337
|
}
|
|
161
338
|
|
|
162
339
|
/**
|
|
@@ -788,8 +788,12 @@ describe('AuroraBuilder', () => {
|
|
|
788
788
|
expect.stringContaining("stack has Aurora, reusing")
|
|
789
789
|
);
|
|
790
790
|
|
|
791
|
-
// Should
|
|
792
|
-
|
|
791
|
+
// Should keep Aurora definitions in template (CloudFormation idempotency)
|
|
792
|
+
// Even though Aurora exists in stack, we include definitions - CF won't recreate
|
|
793
|
+
expect(result.resources.FriggAuroraCluster).toBeDefined();
|
|
794
|
+
expect(result.resources.FriggAuroraCluster.Type).toBe('AWS::RDS::DBCluster');
|
|
795
|
+
expect(result.resources.FriggAuroraInstance).toBeDefined();
|
|
796
|
+
expect(result.resources.FriggAuroraInstance.Type).toBe('AWS::RDS::DBInstance');
|
|
793
797
|
expect(result.environment.DATABASE_URL).toBeDefined();
|
|
794
798
|
|
|
795
799
|
consoleLogSpy.mockRestore();
|
|
@@ -853,6 +857,7 @@ describe('AuroraBuilder', () => {
|
|
|
853
857
|
auroraClusterEndpoint: 'existing-cluster.us-east-1.rds.amazonaws.com',
|
|
854
858
|
auroraClusterPort: 5432,
|
|
855
859
|
auroraClusterIdentifier: 'existing-cluster',
|
|
860
|
+
databaseSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:shared-db-secret',
|
|
856
861
|
privateSubnetId1: 'subnet-1',
|
|
857
862
|
privateSubnetId2: 'subnet-2',
|
|
858
863
|
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aurora Resource Resolver
|
|
3
|
+
*
|
|
4
|
+
* Resolves ownership for Aurora PostgreSQL resources following the ownership-based architecture.
|
|
5
|
+
*
|
|
6
|
+
* Resources managed:
|
|
7
|
+
* - Aurora Cluster (RDS::DBCluster)
|
|
8
|
+
* - Aurora Instance (RDS::DBInstance)
|
|
9
|
+
* - DB Subnet Group (RDS::DBSubnetGroup)
|
|
10
|
+
* - DB Secret (SecretsManager::Secret)
|
|
11
|
+
*
|
|
12
|
+
* Ownership types:
|
|
13
|
+
* - STACK: Resource defined in our CloudFormation template (use Refs)
|
|
14
|
+
* - EXTERNAL: Resource outside our stack (use physical IDs)
|
|
15
|
+
* - AUTO: System decides based on discovery
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const BaseResourceResolver = require('../shared/base-resolver');
|
|
19
|
+
const { ResourceOwnership } = require('../shared/types/resource-ownership');
|
|
20
|
+
|
|
21
|
+
class AuroraResourceResolver extends BaseResourceResolver {
|
|
22
|
+
/**
|
|
23
|
+
* Resolve Aurora Cluster ownership
|
|
24
|
+
* @param {Object} appDefinition - App definition
|
|
25
|
+
* @param {Object} discovery - Discovery result
|
|
26
|
+
* @returns {Object} Resource decision
|
|
27
|
+
*/
|
|
28
|
+
resolveCluster(appDefinition, discovery) {
|
|
29
|
+
const userIntent = appDefinition.database?.postgres?.ownership?.cluster || 'auto';
|
|
30
|
+
|
|
31
|
+
// Explicit external - use provided cluster identifier
|
|
32
|
+
if (userIntent === 'external') {
|
|
33
|
+
this.requireExternalIds(
|
|
34
|
+
appDefinition.database?.postgres?.external?.clusterIdentifier,
|
|
35
|
+
'clusterIdentifier'
|
|
36
|
+
);
|
|
37
|
+
return this.createExternalDecision(
|
|
38
|
+
appDefinition.database.postgres.external.clusterIdentifier,
|
|
39
|
+
'User specified ownership=external for Aurora cluster'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// For stack or auto: check if cluster exists in stack
|
|
44
|
+
const inStack = this.findInStack('FriggAuroraCluster', discovery);
|
|
45
|
+
|
|
46
|
+
if (inStack) {
|
|
47
|
+
return this.createStackDecision(
|
|
48
|
+
inStack.physicalId,
|
|
49
|
+
'Found FriggAuroraCluster in CloudFormation stack'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check for external cluster
|
|
54
|
+
const external = this.findExternal('AWS::RDS::DBCluster', discovery);
|
|
55
|
+
if (external && userIntent === 'auto') {
|
|
56
|
+
return this.createExternalDecision(
|
|
57
|
+
external.physicalId,
|
|
58
|
+
'Found external Aurora cluster via discovery'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Create new cluster in stack
|
|
63
|
+
return this.createStackDecision(
|
|
64
|
+
null,
|
|
65
|
+
'No existing Aurora cluster - will create in stack'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve Aurora Instance ownership
|
|
71
|
+
* @param {Object} appDefinition - App definition
|
|
72
|
+
* @param {Object} discovery - Discovery result
|
|
73
|
+
* @returns {Object} Resource decision
|
|
74
|
+
*/
|
|
75
|
+
resolveInstance(appDefinition, discovery) {
|
|
76
|
+
const userIntent = appDefinition.database?.postgres?.ownership?.instance || 'auto';
|
|
77
|
+
|
|
78
|
+
// Explicit external
|
|
79
|
+
if (userIntent === 'external') {
|
|
80
|
+
this.requireExternalIds(
|
|
81
|
+
appDefinition.database?.postgres?.external?.instanceIdentifier,
|
|
82
|
+
'instanceIdentifier'
|
|
83
|
+
);
|
|
84
|
+
return this.createExternalDecision(
|
|
85
|
+
appDefinition.database.postgres.external.instanceIdentifier,
|
|
86
|
+
'User specified ownership=external for Aurora instance'
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check if instance exists in stack
|
|
91
|
+
const inStack = this.findInStack('FriggAuroraInstance', discovery);
|
|
92
|
+
|
|
93
|
+
if (inStack) {
|
|
94
|
+
return this.createStackDecision(
|
|
95
|
+
inStack.physicalId,
|
|
96
|
+
'Found FriggAuroraInstance in CloudFormation stack'
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check for external instance
|
|
101
|
+
const external = this.findExternal('AWS::RDS::DBInstance', discovery);
|
|
102
|
+
if (external && userIntent === 'auto') {
|
|
103
|
+
return this.createExternalDecision(
|
|
104
|
+
external.physicalId,
|
|
105
|
+
'Found external Aurora instance via discovery'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Create new instance in stack
|
|
110
|
+
return this.createStackDecision(
|
|
111
|
+
null,
|
|
112
|
+
'No existing Aurora instance - will create in stack'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolve DB Subnet Group ownership
|
|
118
|
+
* @param {Object} appDefinition - App definition
|
|
119
|
+
* @param {Object} discovery - Discovery result
|
|
120
|
+
* @returns {Object} Resource decision
|
|
121
|
+
*/
|
|
122
|
+
resolveSubnetGroup(appDefinition, discovery) {
|
|
123
|
+
const userIntent = appDefinition.database?.postgres?.ownership?.subnetGroup || 'auto';
|
|
124
|
+
|
|
125
|
+
// Explicit external
|
|
126
|
+
if (userIntent === 'external') {
|
|
127
|
+
this.requireExternalIds(
|
|
128
|
+
appDefinition.database?.postgres?.external?.subnetGroupName,
|
|
129
|
+
'subnetGroupName'
|
|
130
|
+
);
|
|
131
|
+
return this.createExternalDecision(
|
|
132
|
+
appDefinition.database.postgres.external.subnetGroupName,
|
|
133
|
+
'User specified ownership=external for DB subnet group'
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check if subnet group exists in stack
|
|
138
|
+
const inStack = this.findInStack('FriggDBSubnetGroup', discovery);
|
|
139
|
+
|
|
140
|
+
if (inStack) {
|
|
141
|
+
return this.createStackDecision(
|
|
142
|
+
inStack.physicalId,
|
|
143
|
+
'Found FriggDBSubnetGroup in CloudFormation stack'
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// For subnet groups, always create in stack (they're cheap and cluster-specific)
|
|
148
|
+
return this.createStackDecision(
|
|
149
|
+
null,
|
|
150
|
+
'No existing DB subnet group - will create in stack'
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Resolve DB Secret ownership
|
|
156
|
+
* @param {Object} appDefinition - App definition
|
|
157
|
+
* @param {Object} discovery - Discovery result
|
|
158
|
+
* @returns {Object} Resource decision
|
|
159
|
+
*/
|
|
160
|
+
resolveSecret(appDefinition, discovery) {
|
|
161
|
+
const userIntent = appDefinition.database?.postgres?.ownership?.secret || 'auto';
|
|
162
|
+
|
|
163
|
+
// Explicit external - use provided secret ARN
|
|
164
|
+
if (userIntent === 'external') {
|
|
165
|
+
this.requireExternalIds(
|
|
166
|
+
appDefinition.database?.postgres?.external?.secretArn,
|
|
167
|
+
'secretArn'
|
|
168
|
+
);
|
|
169
|
+
return this.createExternalDecision(
|
|
170
|
+
appDefinition.database.postgres.external.secretArn,
|
|
171
|
+
'User specified ownership=external for DB secret'
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check if secret exists in stack
|
|
176
|
+
const inStack = this.findInStack('FriggDBSecret', discovery);
|
|
177
|
+
|
|
178
|
+
if (inStack) {
|
|
179
|
+
return this.createStackDecision(
|
|
180
|
+
inStack.physicalId,
|
|
181
|
+
'Found FriggDBSecret in CloudFormation stack'
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// For secrets tied to the cluster, always create in stack
|
|
186
|
+
return this.createStackDecision(
|
|
187
|
+
null,
|
|
188
|
+
'No existing DB secret - will create in stack'
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Resolve all Aurora resources at once
|
|
194
|
+
* Convenience method that returns decisions for all Aurora resources
|
|
195
|
+
*
|
|
196
|
+
* @param {Object} appDefinition - App definition
|
|
197
|
+
* @param {Object} discovery - Discovery result
|
|
198
|
+
* @returns {Object} Decisions for all Aurora resources
|
|
199
|
+
*/
|
|
200
|
+
resolveAll(appDefinition, discovery) {
|
|
201
|
+
return {
|
|
202
|
+
cluster: this.resolveCluster(appDefinition, discovery),
|
|
203
|
+
instance: this.resolveInstance(appDefinition, discovery),
|
|
204
|
+
subnetGroup: this.resolveSubnetGroup(appDefinition, discovery),
|
|
205
|
+
secret: this.resolveSecret(appDefinition, discovery)
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = AuroraResourceResolver;
|