@friggframework/devtools 2.0.0--canary.461.35d292b.0 → 2.0.0--canary.461.fa25c10.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.
|
@@ -83,7 +83,36 @@ class AuroraBuilder extends InfrastructureBuilder {
|
|
|
83
83
|
console.log(`\n[${this.name}] Configuring Aurora PostgreSQL...`);
|
|
84
84
|
|
|
85
85
|
const dbConfig = appDefinition.database.postgres;
|
|
86
|
-
|
|
86
|
+
|
|
87
|
+
// Normalize top-level managementMode
|
|
88
|
+
const globalMode = appDefinition.managementMode || 'discover';
|
|
89
|
+
const vpcIsolation = appDefinition.vpcIsolation || 'shared';
|
|
90
|
+
|
|
91
|
+
let management = dbConfig.management;
|
|
92
|
+
|
|
93
|
+
if (globalMode === 'managed') {
|
|
94
|
+
// Warn about ignored granular options
|
|
95
|
+
if (dbConfig.management) {
|
|
96
|
+
console.log(` ⚠️ managementMode='managed' ignoring: database.postgres.management`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Clear granular option to prevent conflicts
|
|
100
|
+
delete appDefinition.database.postgres.management;
|
|
101
|
+
|
|
102
|
+
// Set management based on isolation strategy
|
|
103
|
+
if (vpcIsolation === 'isolated') {
|
|
104
|
+
management = 'managed'; // New VPC = new Aurora
|
|
105
|
+
console.log(` managementMode='managed' + vpcIsolation='isolated' → creating new Aurora`);
|
|
106
|
+
} else {
|
|
107
|
+
management = 'discover'; // Shared VPC = reuse Aurora
|
|
108
|
+
appDefinition.database.postgres.autoCreateCredentials = true;
|
|
109
|
+
console.log(` managementMode='managed' + vpcIsolation='shared' → discovering Aurora`);
|
|
110
|
+
}
|
|
111
|
+
} else if (globalMode === 'existing') {
|
|
112
|
+
management = 'existing';
|
|
113
|
+
} else {
|
|
114
|
+
management = management || 'discover';
|
|
115
|
+
}
|
|
87
116
|
|
|
88
117
|
console.log(` PostgreSQL Management Mode: ${management}`);
|
|
89
118
|
|
|
@@ -749,6 +749,102 @@ describe('AuroraBuilder', () => {
|
|
|
749
749
|
});
|
|
750
750
|
});
|
|
751
751
|
|
|
752
|
+
describe('Top-Level Management Mode', () => {
|
|
753
|
+
it('should use managementMode=managed with vpcIsolation=isolated to create new Aurora', async () => {
|
|
754
|
+
const appDefinition = {
|
|
755
|
+
managementMode: 'managed',
|
|
756
|
+
vpcIsolation: 'isolated',
|
|
757
|
+
database: {
|
|
758
|
+
postgres: {
|
|
759
|
+
enable: true,
|
|
760
|
+
management: 'discover', // Should be IGNORED
|
|
761
|
+
minCapacity: 0.5,
|
|
762
|
+
maxCapacity: 1,
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
const discoveredResources = {
|
|
768
|
+
auroraClusterEndpoint: 'existing-cluster.us-east-1.rds.amazonaws.com',
|
|
769
|
+
privateSubnetId1: 'subnet-1',
|
|
770
|
+
privateSubnetId2: 'subnet-2',
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
774
|
+
|
|
775
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
776
|
+
|
|
777
|
+
// Should warn about ignored options
|
|
778
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
779
|
+
expect.stringContaining("managementMode='managed' ignoring")
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
// Should create new Aurora cluster (isolated mode)
|
|
783
|
+
expect(result.resources.FriggAuroraCluster).toBeDefined();
|
|
784
|
+
expect(result.environment.DATABASE_URL).toBeDefined();
|
|
785
|
+
|
|
786
|
+
consoleLogSpy.mockRestore();
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it('should use managementMode=managed with vpcIsolation=shared to discover Aurora', async () => {
|
|
790
|
+
const appDefinition = {
|
|
791
|
+
managementMode: 'managed',
|
|
792
|
+
vpcIsolation: 'shared',
|
|
793
|
+
database: {
|
|
794
|
+
postgres: {
|
|
795
|
+
enable: true,
|
|
796
|
+
management: 'managed', // Should be IGNORED
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
const discoveredResources = {
|
|
802
|
+
auroraClusterEndpoint: 'existing-cluster.us-east-1.rds.amazonaws.com',
|
|
803
|
+
auroraClusterPort: 5432,
|
|
804
|
+
auroraClusterIdentifier: 'existing-cluster',
|
|
805
|
+
privateSubnetId1: 'subnet-1',
|
|
806
|
+
privateSubnetId2: 'subnet-2',
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
810
|
+
|
|
811
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
812
|
+
|
|
813
|
+
// Should warn about ignored options
|
|
814
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
815
|
+
expect.stringContaining("ignoring")
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
// Should discover existing Aurora
|
|
819
|
+
expect(result.resources.FriggAuroraCluster).toBeUndefined();
|
|
820
|
+
expect(result.environment.DATABASE_URL).toBeDefined();
|
|
821
|
+
|
|
822
|
+
consoleLogSpy.mockRestore();
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
it('should respect granular management when no managementMode specified', async () => {
|
|
826
|
+
const appDefinition = {
|
|
827
|
+
// No managementMode
|
|
828
|
+
database: {
|
|
829
|
+
postgres: {
|
|
830
|
+
enable: true,
|
|
831
|
+
management: 'managed', // Should be RESPECTED
|
|
832
|
+
},
|
|
833
|
+
},
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
const discoveredResources = {
|
|
837
|
+
privateSubnetId1: 'subnet-1',
|
|
838
|
+
privateSubnetId2: 'subnet-2',
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
842
|
+
|
|
843
|
+
// Should create Aurora cluster
|
|
844
|
+
expect(result.resources.FriggAuroraCluster).toBeDefined();
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
|
|
752
848
|
describe('build() - use-existing mode', () => {
|
|
753
849
|
it('should use provided database endpoint', async () => {
|
|
754
850
|
const appDefinition = {
|
|
@@ -81,6 +81,21 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
81
81
|
return result;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Warn about ignored options when managementMode='managed'
|
|
86
|
+
*/
|
|
87
|
+
warnIgnoredOptions(appDefinition) {
|
|
88
|
+
const ignoredOptions = [];
|
|
89
|
+
if (appDefinition.vpc?.management) ignoredOptions.push('vpc.management');
|
|
90
|
+
if (appDefinition.vpc?.subnets?.management) ignoredOptions.push('vpc.subnets.management');
|
|
91
|
+
if (appDefinition.vpc?.natGateway?.management) ignoredOptions.push('vpc.natGateway.management');
|
|
92
|
+
if (appDefinition.vpc?.shareAcrossStages !== undefined) ignoredOptions.push('vpc.shareAcrossStages');
|
|
93
|
+
|
|
94
|
+
if (ignoredOptions.length > 0) {
|
|
95
|
+
console.log(` ⚠️ managementMode='managed' ignoring: ${ignoredOptions.join(', ')}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
84
99
|
/**
|
|
85
100
|
* Build complete VPC infrastructure based on management mode
|
|
86
101
|
*/
|
|
@@ -110,23 +125,49 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
110
125
|
Resource: '*',
|
|
111
126
|
});
|
|
112
127
|
|
|
113
|
-
// Normalize
|
|
114
|
-
|
|
128
|
+
// Normalize top-level managementMode (simplified API)
|
|
129
|
+
const globalMode = appDefinition.managementMode || 'discover';
|
|
130
|
+
const vpcIsolation = appDefinition.vpcIsolation || 'shared';
|
|
131
|
+
|
|
115
132
|
let management = appDefinition.vpc.management;
|
|
116
|
-
|
|
117
|
-
|
|
133
|
+
|
|
134
|
+
if (globalMode === 'managed') {
|
|
135
|
+
// Warn about ignored granular options
|
|
136
|
+
this.warnIgnoredOptions(appDefinition);
|
|
137
|
+
|
|
138
|
+
// Clear granular options to prevent conflicts
|
|
139
|
+
delete appDefinition.vpc.management;
|
|
140
|
+
if (appDefinition.vpc.subnets) delete appDefinition.vpc.subnets.management;
|
|
141
|
+
if (appDefinition.vpc.natGateway) delete appDefinition.vpc.natGateway.management;
|
|
142
|
+
delete appDefinition.vpc.shareAcrossStages;
|
|
143
|
+
|
|
144
|
+
// Set management based on isolation strategy
|
|
145
|
+
if (vpcIsolation === 'isolated') {
|
|
146
|
+
management = 'create-new';
|
|
147
|
+
appDefinition.vpc.natGateway = appDefinition.vpc.natGateway || {};
|
|
148
|
+
appDefinition.vpc.natGateway.management = 'createAndManage';
|
|
149
|
+
console.log(` managementMode='managed' + vpcIsolation='isolated' → creating new VPC`);
|
|
150
|
+
} else {
|
|
151
|
+
management = 'discover';
|
|
152
|
+
appDefinition.vpc.selfHeal = true;
|
|
153
|
+
console.log(` managementMode='managed' + vpcIsolation='shared' → discovering VPC`);
|
|
154
|
+
}
|
|
155
|
+
} else if (globalMode === 'existing') {
|
|
156
|
+
management = 'use-existing';
|
|
157
|
+
} else if (!management && appDefinition.vpc.shareAcrossStages !== undefined) {
|
|
158
|
+
// Legacy shareAcrossStages support (backwards compatibility)
|
|
118
159
|
management = appDefinition.vpc.shareAcrossStages ? 'discover' : 'create-new';
|
|
119
160
|
console.log(` VPC Sharing: ${appDefinition.vpc.shareAcrossStages ? 'shared' : 'isolated'} (translated to ${management})`);
|
|
120
161
|
|
|
121
|
-
// When creating isolated VPC, also create isolated NAT Gateway
|
|
122
162
|
if (!appDefinition.vpc.shareAcrossStages && !appDefinition.vpc.natGateway?.management) {
|
|
123
163
|
appDefinition.vpc.natGateway = appDefinition.vpc.natGateway || {};
|
|
124
164
|
appDefinition.vpc.natGateway.management = 'createAndManage';
|
|
125
165
|
console.log(` NAT Gateway: creating isolated NAT (shareAcrossStages=false)`);
|
|
126
166
|
}
|
|
127
167
|
} else {
|
|
128
|
-
management = management || 'discover';
|
|
168
|
+
management = management || 'discover';
|
|
129
169
|
}
|
|
170
|
+
|
|
130
171
|
console.log(` VPC Management Mode: ${management}`);
|
|
131
172
|
|
|
132
173
|
// Handle self-healing if enabled
|
|
@@ -148,8 +189,8 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
148
189
|
break;
|
|
149
190
|
}
|
|
150
191
|
|
|
151
|
-
// Build subnets
|
|
152
|
-
await this.buildSubnets(appDefinition, discoveredResources, result);
|
|
192
|
+
// Build subnets - pass normalized management mode for correct CIDR generation
|
|
193
|
+
await this.buildSubnets(appDefinition, discoveredResources, result, management);
|
|
153
194
|
|
|
154
195
|
// Build NAT Gateway if needed
|
|
155
196
|
await this.buildNatGateway(appDefinition, discoveredResources, result);
|
|
@@ -385,9 +426,9 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
385
426
|
|
|
386
427
|
/**
|
|
387
428
|
* Build subnet infrastructure
|
|
429
|
+
* @param {Object} vpcManagement - Normalized VPC management mode (passed from build() to ensure consistency)
|
|
388
430
|
*/
|
|
389
|
-
async buildSubnets(appDefinition, discoveredResources, result) {
|
|
390
|
-
const vpcManagement = appDefinition.vpc.management || 'discover';
|
|
431
|
+
async buildSubnets(appDefinition, discoveredResources, result, vpcManagement) {
|
|
391
432
|
// Default subnet management depends on context:
|
|
392
433
|
// - use-existing mode with subnet IDs provided: use-existing
|
|
393
434
|
// - create-new mode: create
|
|
@@ -809,6 +809,96 @@ describe('VpcBuilder', () => {
|
|
|
809
809
|
});
|
|
810
810
|
});
|
|
811
811
|
|
|
812
|
+
describe('Management Mode (Simplified API)', () => {
|
|
813
|
+
it('should use managementMode=managed with vpcIsolation=isolated to create new VPC', async () => {
|
|
814
|
+
const appDefinition = {
|
|
815
|
+
managementMode: 'managed',
|
|
816
|
+
vpcIsolation: 'isolated',
|
|
817
|
+
vpc: {
|
|
818
|
+
enable: true,
|
|
819
|
+
management: 'discover', // Should be IGNORED
|
|
820
|
+
},
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
const discoveredResources = {
|
|
824
|
+
defaultVpcId: 'vpc-existing',
|
|
825
|
+
natGatewayId: 'nat-existing',
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
829
|
+
|
|
830
|
+
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
831
|
+
|
|
832
|
+
// Should warn about ignored options
|
|
833
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
834
|
+
expect.stringContaining("managementMode='managed' ignoring")
|
|
835
|
+
);
|
|
836
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
837
|
+
expect.stringContaining("vpc.management")
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
// Should create new isolated VPC
|
|
841
|
+
expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
|
|
842
|
+
expect(result.resources.FriggVPC).toBeDefined();
|
|
843
|
+
|
|
844
|
+
// Subnets should use CloudFormation Fn::Cidr
|
|
845
|
+
expect(result.resources.FriggPrivateSubnet1.Properties.CidrBlock).toEqual({
|
|
846
|
+
'Fn::Select': [0, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
consoleLogSpy.mockRestore();
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
it('should use managementMode=managed with vpcIsolation=shared to discover VPC', async () => {
|
|
853
|
+
const appDefinition = {
|
|
854
|
+
managementMode: 'managed',
|
|
855
|
+
vpcIsolation: 'shared',
|
|
856
|
+
vpc: {
|
|
857
|
+
enable: true,
|
|
858
|
+
subnets: { management: 'use-existing' }, // Should be IGNORED
|
|
859
|
+
},
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
const discoveredResources = {
|
|
863
|
+
defaultVpcId: 'vpc-existing',
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
867
|
+
|
|
868
|
+
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
869
|
+
|
|
870
|
+
// Should warn about ignored options
|
|
871
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
872
|
+
expect.stringContaining("ignoring")
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
// Should discover existing VPC
|
|
876
|
+
expect(result.vpcId).toBe('vpc-existing');
|
|
877
|
+
expect(result.resources.FriggVPC).toBeUndefined();
|
|
878
|
+
|
|
879
|
+
// Should create new stage-specific subnets
|
|
880
|
+
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
881
|
+
|
|
882
|
+
consoleLogSpy.mockRestore();
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
it('should default to discover mode for backwards compatibility', async () => {
|
|
886
|
+
const appDefinition = {
|
|
887
|
+
// No managementMode specified
|
|
888
|
+
vpc: {
|
|
889
|
+
enable: true,
|
|
890
|
+
management: 'create-new', // Should be RESPECTED
|
|
891
|
+
},
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
const result = await vpcBuilder.build(appDefinition, {});
|
|
895
|
+
|
|
896
|
+
// Should respect legacy vpc.management
|
|
897
|
+
expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
|
|
898
|
+
expect(result.resources.FriggVPC).toBeDefined();
|
|
899
|
+
});
|
|
900
|
+
});
|
|
901
|
+
|
|
812
902
|
describe('VPC Sharing Control', () => {
|
|
813
903
|
it('should share VPC across stages when shareAcrossStages is true (default)', async () => {
|
|
814
904
|
const appDefinition = {
|
|
@@ -828,11 +918,11 @@ describe('VpcBuilder', () => {
|
|
|
828
918
|
// Should use discovered VPC (not create new one)
|
|
829
919
|
expect(result.vpcId).toBe('vpc-shared');
|
|
830
920
|
expect(result.resources.FriggVPC).toBeUndefined();
|
|
831
|
-
|
|
921
|
+
|
|
832
922
|
// Should create stage-specific subnets for isolation
|
|
833
923
|
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
834
924
|
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
835
|
-
|
|
925
|
+
|
|
836
926
|
// Should reuse discovered NAT Gateway
|
|
837
927
|
expect(result.resources.FriggNATGateway).toBeUndefined();
|
|
838
928
|
});
|
|
@@ -855,11 +945,20 @@ describe('VpcBuilder', () => {
|
|
|
855
945
|
// Should create new VPC (ignore discovered resources)
|
|
856
946
|
expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
|
|
857
947
|
expect(result.resources.FriggVPC).toBeDefined();
|
|
858
|
-
|
|
859
|
-
|
|
948
|
+
expect(result.resources.FriggVPC.Properties.CidrBlock).toBe('10.0.0.0/16');
|
|
949
|
+
|
|
950
|
+
// Should create stage-specific subnets with Fn::Cidr (dynamic from VPC CIDR)
|
|
860
951
|
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
861
952
|
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
862
|
-
|
|
953
|
+
|
|
954
|
+
// Subnets should use CloudFormation Fn::Cidr, NOT hardcoded 172.31.x.x
|
|
955
|
+
expect(result.resources.FriggPrivateSubnet1.Properties.CidrBlock).toEqual({
|
|
956
|
+
'Fn::Select': [0, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
957
|
+
});
|
|
958
|
+
expect(result.resources.FriggPrivateSubnet2.Properties.CidrBlock).toEqual({
|
|
959
|
+
'Fn::Select': [1, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
960
|
+
});
|
|
961
|
+
|
|
863
962
|
// Should create new NAT Gateway
|
|
864
963
|
expect(result.resources.FriggNATGateway).toBeDefined();
|
|
865
964
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.461.
|
|
4
|
+
"version": "2.0.0--canary.461.fa25c10.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-ec2": "^3.835.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.835.0",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"@babel/eslint-parser": "^7.18.9",
|
|
12
12
|
"@babel/parser": "^7.25.3",
|
|
13
13
|
"@babel/traverse": "^7.25.3",
|
|
14
|
-
"@friggframework/schemas": "2.0.0--canary.461.
|
|
15
|
-
"@friggframework/test": "2.0.0--canary.461.
|
|
14
|
+
"@friggframework/schemas": "2.0.0--canary.461.fa25c10.0",
|
|
15
|
+
"@friggframework/test": "2.0.0--canary.461.fa25c10.0",
|
|
16
16
|
"@hapi/boom": "^10.0.1",
|
|
17
17
|
"@inquirer/prompts": "^5.3.8",
|
|
18
18
|
"axios": "^1.7.2",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"serverless-http": "^2.7.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@friggframework/eslint-config": "2.0.0--canary.461.
|
|
38
|
-
"@friggframework/prettier-config": "2.0.0--canary.461.
|
|
37
|
+
"@friggframework/eslint-config": "2.0.0--canary.461.fa25c10.0",
|
|
38
|
+
"@friggframework/prettier-config": "2.0.0--canary.461.fa25c10.0",
|
|
39
39
|
"aws-sdk-client-mock": "^4.1.0",
|
|
40
40
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
41
41
|
"jest": "^30.1.3",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "fa25c1040b8fa112874e8ab729844b5ccfd8ae3d"
|
|
74
74
|
}
|