@friggframework/devtools 2.0.0--canary.461.e679ea5.0 → 2.0.0--canary.461.d885809.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
|
|
@@ -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
|
});
|
|
@@ -856,11 +946,11 @@ describe('VpcBuilder', () => {
|
|
|
856
946
|
expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
|
|
857
947
|
expect(result.resources.FriggVPC).toBeDefined();
|
|
858
948
|
expect(result.resources.FriggVPC.Properties.CidrBlock).toBe('10.0.0.0/16');
|
|
859
|
-
|
|
949
|
+
|
|
860
950
|
// Should create stage-specific subnets with Fn::Cidr (dynamic from VPC CIDR)
|
|
861
951
|
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
862
952
|
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
863
|
-
|
|
953
|
+
|
|
864
954
|
// Subnets should use CloudFormation Fn::Cidr, NOT hardcoded 172.31.x.x
|
|
865
955
|
expect(result.resources.FriggPrivateSubnet1.Properties.CidrBlock).toEqual({
|
|
866
956
|
'Fn::Select': [0, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
@@ -868,7 +958,7 @@ describe('VpcBuilder', () => {
|
|
|
868
958
|
expect(result.resources.FriggPrivateSubnet2.Properties.CidrBlock).toEqual({
|
|
869
959
|
'Fn::Select': [1, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
870
960
|
});
|
|
871
|
-
|
|
961
|
+
|
|
872
962
|
// Should create new NAT Gateway
|
|
873
963
|
expect(result.resources.FriggNATGateway).toBeDefined();
|
|
874
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.d885809.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.d885809.0",
|
|
15
|
+
"@friggframework/test": "2.0.0--canary.461.d885809.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.d885809.0",
|
|
38
|
+
"@friggframework/prettier-config": "2.0.0--canary.461.d885809.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": "d8858090c719e05e3bd548c092844430f728ff05"
|
|
74
74
|
}
|