@medplum/cdk 2.0.7
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/README.md +83 -0
- package/babel.config.json +3 -0
- package/cdk.json +3 -0
- package/dist/cjs/index.cjs +767 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/init.cjs +443 -0
- package/dist/cjs/init.cjs.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/jest.config.json +14 -0
- package/package.json +33 -0
- package/rollup.config.mjs +56 -0
- package/src/__mocks__/@aws-sdk/client-acm.ts +45 -0
- package/src/__mocks__/@aws-sdk/client-ssm.ts +13 -0
- package/src/__mocks__/@aws-sdk/client-sts.ts +18 -0
- package/src/backend.ts +416 -0
- package/src/config.ts +31 -0
- package/src/frontend.ts +168 -0
- package/src/index.test.ts +232 -0
- package/src/index.ts +68 -0
- package/src/init.test.ts +378 -0
- package/src/init.ts +505 -0
- package/src/storage.ts +134 -0
- package/src/waf.ts +122 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { unlinkSync, writeFileSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { main } from './index';
|
|
4
|
+
|
|
5
|
+
describe('Infra', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
console.log = jest.fn();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('Missing config', () => {
|
|
11
|
+
expect(() => main()).not.toThrow();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('Synth stack', () => {
|
|
15
|
+
// Create a temp config file
|
|
16
|
+
const filename = resolve('./medplum.test.config.json');
|
|
17
|
+
writeFileSync(
|
|
18
|
+
filename,
|
|
19
|
+
JSON.stringify({
|
|
20
|
+
name: 'unittest',
|
|
21
|
+
stackName: 'MedplumUnitTestStack',
|
|
22
|
+
accountNumber: '647991932601',
|
|
23
|
+
region: 'us-east-1',
|
|
24
|
+
domainName: 'medplum.com',
|
|
25
|
+
apiPort: 8103,
|
|
26
|
+
apiDomainName: 'api.medplum.com',
|
|
27
|
+
apiSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/08bf1daf-3a2b-4cbe-91a0-739b4364a1ec',
|
|
28
|
+
appDomainName: 'app.medplum.com',
|
|
29
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/fd21b628-b2c0-4a5d-b4f5-b5c9a6d63b1a',
|
|
30
|
+
storageBucketName: 'medplum-storage',
|
|
31
|
+
storageDomainName: 'storage.medplum.com',
|
|
32
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/19d85245-0a1d-4bf5-9789-23082b1a15fc',
|
|
33
|
+
storagePublicKey: '-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----',
|
|
34
|
+
maxAzs: 2,
|
|
35
|
+
rdsInstances: 1,
|
|
36
|
+
desiredServerCount: 1,
|
|
37
|
+
serverImage: 'medplum/medplum-server:staging',
|
|
38
|
+
serverMemory: 512,
|
|
39
|
+
serverCpu: 256,
|
|
40
|
+
loadBalancerLoggingEnabled: true,
|
|
41
|
+
loadBalancerLoggingBucket: 'medplum-logs-us-east-1',
|
|
42
|
+
loadBalancerLoggingPrefix: 'elb',
|
|
43
|
+
clamscanEnabled: true,
|
|
44
|
+
clamscanLoggingBucket: 'medplum-logs-us-east-1',
|
|
45
|
+
clamscanLoggingPrefix: 'clamscan',
|
|
46
|
+
}),
|
|
47
|
+
{ encoding: 'utf-8' }
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(() => main({ config: filename })).not.toThrow();
|
|
51
|
+
unlinkSync(filename);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('Multi region stack', () => {
|
|
55
|
+
const filename = resolve('./medplum.multiregion.config.json');
|
|
56
|
+
writeFileSync(
|
|
57
|
+
filename,
|
|
58
|
+
JSON.stringify({
|
|
59
|
+
name: 'multiregion',
|
|
60
|
+
stackName: 'MedplumMultiRegionStack',
|
|
61
|
+
accountNumber: '647991932601',
|
|
62
|
+
region: 'ap-southeast-1',
|
|
63
|
+
domainName: 'ap-southeast-1.medplum.com',
|
|
64
|
+
apiPort: 8103,
|
|
65
|
+
apiDomainName: 'api.medplum.com',
|
|
66
|
+
apiSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/08bf1daf-3a2b-4cbe-91a0-739b4364a1ec',
|
|
67
|
+
appDomainName: 'app.medplum.com',
|
|
68
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/fd21b628-b2c0-4a5d-b4f5-b5c9a6d63b1a',
|
|
69
|
+
storageBucketName: 'medplum-storage',
|
|
70
|
+
storageDomainName: 'storage.medplum.com',
|
|
71
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/19d85245-0a1d-4bf5-9789-23082b1a15fc',
|
|
72
|
+
storagePublicKey: '-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----',
|
|
73
|
+
maxAzs: 2,
|
|
74
|
+
rdsInstances: 1,
|
|
75
|
+
desiredServerCount: 1,
|
|
76
|
+
serverImage: 'medplum/medplum-server:staging',
|
|
77
|
+
serverMemory: 512,
|
|
78
|
+
serverCpu: 256,
|
|
79
|
+
loadBalancerLoggingEnabled: true,
|
|
80
|
+
loadBalancerLoggingBucket: 'medplum-logs-us-east-1',
|
|
81
|
+
loadBalancerLoggingPrefix: 'elb',
|
|
82
|
+
clamscanEnabled: true,
|
|
83
|
+
clamscanLoggingBucket: 'medplum-logs-us-east-1',
|
|
84
|
+
clamscanLoggingPrefix: 'clamscan',
|
|
85
|
+
}),
|
|
86
|
+
{ encoding: 'utf-8' }
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(() => main({ config: filename })).not.toThrow();
|
|
90
|
+
unlinkSync(filename);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('ECR image', () => {
|
|
94
|
+
// Create a temp config file
|
|
95
|
+
const filename = resolve('./medplum.customvpc.config.json');
|
|
96
|
+
writeFileSync(
|
|
97
|
+
filename,
|
|
98
|
+
JSON.stringify({
|
|
99
|
+
name: 'customvpc',
|
|
100
|
+
stackName: 'MedplumCustomVpcStack',
|
|
101
|
+
accountNumber: '647991932601',
|
|
102
|
+
region: 'us-east-1',
|
|
103
|
+
domainName: 'medplum.com',
|
|
104
|
+
apiPort: 8103,
|
|
105
|
+
apiDomainName: 'api.medplum.com',
|
|
106
|
+
apiSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/08bf1daf-3a2b-4cbe-91a0-739b4364a1ec',
|
|
107
|
+
appDomainName: 'app.medplum.com',
|
|
108
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/fd21b628-b2c0-4a5d-b4f5-b5c9a6d63b1a',
|
|
109
|
+
storageBucketName: 'medplum-storage',
|
|
110
|
+
storageDomainName: 'storage.medplum.com',
|
|
111
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/19d85245-0a1d-4bf5-9789-23082b1a15fc',
|
|
112
|
+
storagePublicKey: '-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----',
|
|
113
|
+
maxAzs: 2,
|
|
114
|
+
rdsInstances: 1,
|
|
115
|
+
rdsInstanceType: 't3.micro',
|
|
116
|
+
desiredServerCount: 1,
|
|
117
|
+
serverImage: '647991932601.dkr.ecr.us-east-1.amazonaws.com/medplum-server:staging',
|
|
118
|
+
serverMemory: 512,
|
|
119
|
+
serverCpu: 256,
|
|
120
|
+
}),
|
|
121
|
+
{ encoding: 'utf-8' }
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(() => main({ config: filename })).not.toThrow();
|
|
125
|
+
unlinkSync(filename);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('Custom VPC', () => {
|
|
129
|
+
// Create a temp config file
|
|
130
|
+
const filename = resolve('./medplum.customvpc.config.json');
|
|
131
|
+
writeFileSync(
|
|
132
|
+
filename,
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
name: 'customvpc',
|
|
135
|
+
stackName: 'MedplumCustomVpcStack',
|
|
136
|
+
accountNumber: '647991932601',
|
|
137
|
+
region: 'us-east-1',
|
|
138
|
+
domainName: 'medplum.com',
|
|
139
|
+
apiPort: 8103,
|
|
140
|
+
vpcId: 'vpc-0fc3a4d0600000000',
|
|
141
|
+
apiDomainName: 'api.medplum.com',
|
|
142
|
+
apiSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/08bf1daf-3a2b-4cbe-91a0-739b4364a1ec',
|
|
143
|
+
appDomainName: 'app.medplum.com',
|
|
144
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/fd21b628-b2c0-4a5d-b4f5-b5c9a6d63b1a',
|
|
145
|
+
storageBucketName: 'medplum-storage',
|
|
146
|
+
storageDomainName: 'storage.medplum.com',
|
|
147
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/19d85245-0a1d-4bf5-9789-23082b1a15fc',
|
|
148
|
+
storagePublicKey: '-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----',
|
|
149
|
+
maxAzs: 2,
|
|
150
|
+
rdsInstances: 1,
|
|
151
|
+
desiredServerCount: 1,
|
|
152
|
+
serverImage: 'medplum/medplum-server:latest',
|
|
153
|
+
serverMemory: 512,
|
|
154
|
+
serverCpu: 256,
|
|
155
|
+
}),
|
|
156
|
+
{ encoding: 'utf-8' }
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
expect(() => main({ config: filename })).not.toThrow();
|
|
160
|
+
unlinkSync(filename);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('Custom RDS instance type', () => {
|
|
164
|
+
// Create a temp config file
|
|
165
|
+
const filename = resolve('./medplum.customRdsInstanceType.config.json');
|
|
166
|
+
writeFileSync(
|
|
167
|
+
filename,
|
|
168
|
+
JSON.stringify({
|
|
169
|
+
name: 'customRdsInstanceType',
|
|
170
|
+
stackName: 'MedplumCustomRdsInstanceTypeStack',
|
|
171
|
+
accountNumber: '647991932601',
|
|
172
|
+
region: 'us-east-1',
|
|
173
|
+
domainName: 'medplum.com',
|
|
174
|
+
apiPort: 8103,
|
|
175
|
+
apiDomainName: 'api.medplum.com',
|
|
176
|
+
apiSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/08bf1daf-3a2b-4cbe-91a0-739b4364a1ec',
|
|
177
|
+
appDomainName: 'app.medplum.com',
|
|
178
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/fd21b628-b2c0-4a5d-b4f5-b5c9a6d63b1a',
|
|
179
|
+
storageBucketName: 'medplum-storage',
|
|
180
|
+
storageDomainName: 'storage.medplum.com',
|
|
181
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/19d85245-0a1d-4bf5-9789-23082b1a15fc',
|
|
182
|
+
storagePublicKey: '-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----',
|
|
183
|
+
maxAzs: 2,
|
|
184
|
+
rdsInstances: 1,
|
|
185
|
+
rdsInstanceType: 't3.micro',
|
|
186
|
+
desiredServerCount: 1,
|
|
187
|
+
serverImage: 'medplum/medplum-server:latest',
|
|
188
|
+
serverMemory: 512,
|
|
189
|
+
serverCpu: 256,
|
|
190
|
+
}),
|
|
191
|
+
{ encoding: 'utf-8' }
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(() => main({ config: filename })).not.toThrow();
|
|
195
|
+
unlinkSync(filename);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('Custom RDS secrets', () => {
|
|
199
|
+
// Create a temp config file
|
|
200
|
+
const filename = resolve('./medplum.customRdsSecrets.config.json');
|
|
201
|
+
const rdsSecretsArn = 'arn:aws:secretsmanager:s-east-1:647991932601:secret:SecretName-6RandomCharacters';
|
|
202
|
+
writeFileSync(
|
|
203
|
+
filename,
|
|
204
|
+
JSON.stringify({
|
|
205
|
+
name: 'customRdsSecrets',
|
|
206
|
+
stackName: 'MedplumCustomRdsSecretsStack',
|
|
207
|
+
accountNumber: '647991932601',
|
|
208
|
+
region: 'us-east-1',
|
|
209
|
+
domainName: 'medplum.com',
|
|
210
|
+
apiPort: 8103,
|
|
211
|
+
apiDomainName: 'api.medplum.com',
|
|
212
|
+
apiSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/08bf1daf-3a2b-4cbe-91a0-739b4364a1ec',
|
|
213
|
+
appDomainName: 'app.medplum.com',
|
|
214
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/fd21b628-b2c0-4a5d-b4f5-b5c9a6d63b1a',
|
|
215
|
+
storageBucketName: 'medplum-storage',
|
|
216
|
+
storageDomainName: 'storage.medplum.com',
|
|
217
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/19d85245-0a1d-4bf5-9789-23082b1a15fc',
|
|
218
|
+
storagePublicKey: '-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----',
|
|
219
|
+
maxAzs: 2,
|
|
220
|
+
rdsSecretsArn,
|
|
221
|
+
desiredServerCount: 1,
|
|
222
|
+
serverImage: 'medplum/medplum-server:latest',
|
|
223
|
+
serverMemory: 512,
|
|
224
|
+
serverCpu: 256,
|
|
225
|
+
}),
|
|
226
|
+
{ encoding: 'utf-8' }
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
expect(() => main({ config: filename })).not.toThrow();
|
|
230
|
+
unlinkSync(filename);
|
|
231
|
+
});
|
|
232
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { App, Stack } from 'aws-cdk-lib';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { BackEnd } from './backend';
|
|
5
|
+
import { MedplumInfraConfig } from './config';
|
|
6
|
+
import { FrontEnd } from './frontend';
|
|
7
|
+
import { Storage } from './storage';
|
|
8
|
+
|
|
9
|
+
class MedplumStack {
|
|
10
|
+
primaryStack: Stack;
|
|
11
|
+
backEnd: BackEnd;
|
|
12
|
+
frontEnd: FrontEnd;
|
|
13
|
+
storage: Storage;
|
|
14
|
+
|
|
15
|
+
constructor(scope: App, config: MedplumInfraConfig) {
|
|
16
|
+
this.primaryStack = new Stack(scope, config.stackName, {
|
|
17
|
+
env: {
|
|
18
|
+
region: config.region,
|
|
19
|
+
account: config.accountNumber,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
this.backEnd = new BackEnd(this.primaryStack, config);
|
|
24
|
+
this.frontEnd = new FrontEnd(this.primaryStack, config, config.region);
|
|
25
|
+
this.storage = new Storage(this.primaryStack, config, config.region);
|
|
26
|
+
|
|
27
|
+
if (config.region !== 'us-east-1') {
|
|
28
|
+
// Some resources must be created in us-east-1
|
|
29
|
+
// For example, CloudFront distributions and ACM certificates
|
|
30
|
+
// If the primary region is not us-east-1, create these resources in us-east-1
|
|
31
|
+
const usEast1Stack = new Stack(scope, config.stackName + '-us-east-1', {
|
|
32
|
+
env: {
|
|
33
|
+
region: 'us-east-1',
|
|
34
|
+
account: config.accountNumber,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
this.frontEnd = new FrontEnd(usEast1Stack, config, 'us-east-1');
|
|
39
|
+
this.storage = new Storage(usEast1Stack, config, 'us-east-1');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function main(context?: Record<string, string>): void {
|
|
45
|
+
const app = new App({ context });
|
|
46
|
+
|
|
47
|
+
const configFileName = app.node.tryGetContext('config');
|
|
48
|
+
if (!configFileName) {
|
|
49
|
+
console.log('Missing "config" context variable');
|
|
50
|
+
console.log('Usage: cdk deploy -c config=my-config.json');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const config = JSON.parse(readFileSync(resolve(configFileName), 'utf-8')) as MedplumInfraConfig;
|
|
55
|
+
|
|
56
|
+
const stack = new MedplumStack(app, config);
|
|
57
|
+
|
|
58
|
+
console.log('Stack', stack.primaryStack.stackId);
|
|
59
|
+
console.log('BackEnd', stack.backEnd.node.id);
|
|
60
|
+
console.log('FrontEnd', stack.frontEnd.node.id);
|
|
61
|
+
console.log('Storage', stack.storage.node.id);
|
|
62
|
+
|
|
63
|
+
app.synth();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (require.main === module) {
|
|
67
|
+
main();
|
|
68
|
+
}
|
package/src/init.test.ts
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
import { main } from './init';
|
|
5
|
+
|
|
6
|
+
jest.mock('@aws-sdk/client-acm');
|
|
7
|
+
jest.mock('@aws-sdk/client-ssm');
|
|
8
|
+
jest.mock('@aws-sdk/client-sts');
|
|
9
|
+
|
|
10
|
+
test('Init tool success', async () => {
|
|
11
|
+
const filename = `test-${randomUUID()}.json`;
|
|
12
|
+
|
|
13
|
+
await main(
|
|
14
|
+
mockReadline(
|
|
15
|
+
'foo',
|
|
16
|
+
filename,
|
|
17
|
+
'us-east-1',
|
|
18
|
+
'account-123',
|
|
19
|
+
'TestStack',
|
|
20
|
+
'test.example.com',
|
|
21
|
+
'support@example.com',
|
|
22
|
+
'', // default API domain
|
|
23
|
+
'', // default app domain
|
|
24
|
+
'', // default storage domain
|
|
25
|
+
'', // default storage bucket
|
|
26
|
+
'', // default availability zones
|
|
27
|
+
'y', // Yes, create a database
|
|
28
|
+
'', // default database instances
|
|
29
|
+
'', // default server instances
|
|
30
|
+
'', // default server memory
|
|
31
|
+
'', // default server cpu
|
|
32
|
+
'', // default server image
|
|
33
|
+
'y', // Yes, request api certificate
|
|
34
|
+
'', // default DNS validation
|
|
35
|
+
'y', // Yes, request app certificate
|
|
36
|
+
'', // default DNS validation
|
|
37
|
+
'y', // Yes, request storage certificate
|
|
38
|
+
'', // default DNS validation
|
|
39
|
+
'y' // Yes, write to Parameter Store
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const config = JSON.parse(readFileSync(filename, 'utf8'));
|
|
44
|
+
expect(config).toMatchObject({
|
|
45
|
+
apiPort: 8103,
|
|
46
|
+
name: 'foo',
|
|
47
|
+
region: 'us-east-1',
|
|
48
|
+
accountNumber: 'account-123',
|
|
49
|
+
stackName: 'TestStack',
|
|
50
|
+
domainName: 'test.example.com',
|
|
51
|
+
apiDomainName: 'api.test.example.com',
|
|
52
|
+
appDomainName: 'app.test.example.com',
|
|
53
|
+
storageDomainName: 'storage.test.example.com',
|
|
54
|
+
storageBucketName: 'medplum-foo-storage',
|
|
55
|
+
maxAzs: 2,
|
|
56
|
+
rdsInstances: 1,
|
|
57
|
+
desiredServerCount: 1,
|
|
58
|
+
serverMemory: 512,
|
|
59
|
+
serverCpu: 256,
|
|
60
|
+
serverImage: 'medplum/medplum-server:latest',
|
|
61
|
+
storagePublicKey: expect.stringContaining('-----BEGIN PUBLIC KEY-----'),
|
|
62
|
+
apiSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
63
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
64
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
65
|
+
});
|
|
66
|
+
unlinkSync(filename);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('Overwrite existing file', async () => {
|
|
70
|
+
const filename = `test-${randomUUID()}.json`;
|
|
71
|
+
writeFileSync(filename, '{}', 'utf8');
|
|
72
|
+
|
|
73
|
+
await main(
|
|
74
|
+
mockReadline(
|
|
75
|
+
'foo',
|
|
76
|
+
filename,
|
|
77
|
+
'y', // Yes, overwrite
|
|
78
|
+
'us-east-1',
|
|
79
|
+
'account-123',
|
|
80
|
+
'TestStack',
|
|
81
|
+
'test.example.com',
|
|
82
|
+
'support@example.com',
|
|
83
|
+
'', // default API domain
|
|
84
|
+
'', // default app domain
|
|
85
|
+
'', // default storage domain
|
|
86
|
+
'', // default storage bucket
|
|
87
|
+
'', // default availability zones
|
|
88
|
+
'y', // Yes, create a database
|
|
89
|
+
'', // default database instances
|
|
90
|
+
'', // default server instances
|
|
91
|
+
'', // default server memory
|
|
92
|
+
'', // default server cpu
|
|
93
|
+
'', // default server image
|
|
94
|
+
'y', // Yes, request api certificate
|
|
95
|
+
'', // default DNS validation
|
|
96
|
+
'y', // Yes, request app certificate
|
|
97
|
+
'', // default DNS validation
|
|
98
|
+
'y', // Yes, request storage certificate
|
|
99
|
+
'', // default DNS validation
|
|
100
|
+
'y' // Yes, write to Parameter Store
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const config = JSON.parse(readFileSync(filename, 'utf8'));
|
|
105
|
+
expect(config).toMatchObject({
|
|
106
|
+
apiPort: 8103,
|
|
107
|
+
name: 'foo',
|
|
108
|
+
region: 'us-east-1',
|
|
109
|
+
accountNumber: 'account-123',
|
|
110
|
+
stackName: 'TestStack',
|
|
111
|
+
domainName: 'test.example.com',
|
|
112
|
+
apiDomainName: 'api.test.example.com',
|
|
113
|
+
appDomainName: 'app.test.example.com',
|
|
114
|
+
storageDomainName: 'storage.test.example.com',
|
|
115
|
+
storageBucketName: 'medplum-foo-storage',
|
|
116
|
+
maxAzs: 2,
|
|
117
|
+
rdsInstances: 1,
|
|
118
|
+
desiredServerCount: 1,
|
|
119
|
+
serverMemory: 512,
|
|
120
|
+
serverCpu: 256,
|
|
121
|
+
serverImage: 'medplum/medplum-server:latest',
|
|
122
|
+
storagePublicKey: expect.stringContaining('-----BEGIN PUBLIC KEY-----'),
|
|
123
|
+
apiSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
124
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
125
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
126
|
+
});
|
|
127
|
+
unlinkSync(filename);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('Invalid AWS credentials', async () => {
|
|
131
|
+
const filename = `test-${randomUUID()}.json`;
|
|
132
|
+
|
|
133
|
+
console.log = jest.fn();
|
|
134
|
+
|
|
135
|
+
await main(
|
|
136
|
+
mockReadline(
|
|
137
|
+
'foo',
|
|
138
|
+
filename,
|
|
139
|
+
'us-bad-1', // Special fake region for mock clients
|
|
140
|
+
'account-123',
|
|
141
|
+
'TestStack',
|
|
142
|
+
'test.example.com',
|
|
143
|
+
'support@example.com',
|
|
144
|
+
'', // default API domain
|
|
145
|
+
'', // default app domain
|
|
146
|
+
'', // default storage domain
|
|
147
|
+
'', // default storage bucket
|
|
148
|
+
'', // default availability zones
|
|
149
|
+
'y', // Yes, create a database
|
|
150
|
+
'', // default database instances
|
|
151
|
+
'', // default server instances
|
|
152
|
+
'', // default server memory
|
|
153
|
+
'', // default server cpu
|
|
154
|
+
'', // default server image
|
|
155
|
+
'y', // Yes, request api certificate
|
|
156
|
+
'', // default DNS validation
|
|
157
|
+
'y', // Yes, request app certificate
|
|
158
|
+
'', // default DNS validation
|
|
159
|
+
'y', // Yes, request storage certificate
|
|
160
|
+
'', // default DNS validation
|
|
161
|
+
'y' // Yes, write to Parameter Store
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
expect(console.log).toHaveBeenCalledWith('Warning: Unable to get AWS account ID', 'Invalid region');
|
|
166
|
+
|
|
167
|
+
const config = JSON.parse(readFileSync(filename, 'utf8'));
|
|
168
|
+
expect(config).toMatchObject({
|
|
169
|
+
apiPort: 8103,
|
|
170
|
+
name: 'foo',
|
|
171
|
+
region: 'us-bad-1',
|
|
172
|
+
accountNumber: 'account-123',
|
|
173
|
+
stackName: 'TestStack',
|
|
174
|
+
domainName: 'test.example.com',
|
|
175
|
+
apiDomainName: 'api.test.example.com',
|
|
176
|
+
appDomainName: 'app.test.example.com',
|
|
177
|
+
storageDomainName: 'storage.test.example.com',
|
|
178
|
+
storageBucketName: 'medplum-foo-storage',
|
|
179
|
+
maxAzs: 2,
|
|
180
|
+
rdsInstances: 1,
|
|
181
|
+
desiredServerCount: 1,
|
|
182
|
+
serverMemory: 512,
|
|
183
|
+
serverCpu: 256,
|
|
184
|
+
serverImage: 'medplum/medplum-server:latest',
|
|
185
|
+
storagePublicKey: expect.stringContaining('-----BEGIN PUBLIC KEY-----'),
|
|
186
|
+
apiSslCertArn: 'TODO',
|
|
187
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
188
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
189
|
+
});
|
|
190
|
+
unlinkSync(filename);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('Bring your own database', async () => {
|
|
194
|
+
const filename = `test-${randomUUID()}.json`;
|
|
195
|
+
|
|
196
|
+
await main(
|
|
197
|
+
mockReadline(
|
|
198
|
+
'foo',
|
|
199
|
+
filename,
|
|
200
|
+
'us-east-1',
|
|
201
|
+
'account-123',
|
|
202
|
+
'TestStack',
|
|
203
|
+
'test.example.com',
|
|
204
|
+
'support@example.com',
|
|
205
|
+
'', // default API domain
|
|
206
|
+
'', // default app domain
|
|
207
|
+
'', // default storage domain
|
|
208
|
+
'', // default storage bucket
|
|
209
|
+
'', // default availability zones
|
|
210
|
+
'n', // No, do not create a database
|
|
211
|
+
'', // default server instances
|
|
212
|
+
'', // default server memory
|
|
213
|
+
'', // default server cpu
|
|
214
|
+
'', // default server image
|
|
215
|
+
'y', // Yes, request api certificate
|
|
216
|
+
'', // default DNS validation
|
|
217
|
+
'y', // Yes, request app certificate
|
|
218
|
+
'', // default DNS validation
|
|
219
|
+
'y', // Yes, request storage certificate
|
|
220
|
+
'', // default DNS validation
|
|
221
|
+
'y' // Yes, write to Parameter Store
|
|
222
|
+
)
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const config = JSON.parse(readFileSync(filename, 'utf8'));
|
|
226
|
+
expect(config).toMatchObject({
|
|
227
|
+
apiPort: 8103,
|
|
228
|
+
name: 'foo',
|
|
229
|
+
region: 'us-east-1',
|
|
230
|
+
accountNumber: 'account-123',
|
|
231
|
+
stackName: 'TestStack',
|
|
232
|
+
domainName: 'test.example.com',
|
|
233
|
+
apiDomainName: 'api.test.example.com',
|
|
234
|
+
appDomainName: 'app.test.example.com',
|
|
235
|
+
storageDomainName: 'storage.test.example.com',
|
|
236
|
+
storageBucketName: 'medplum-foo-storage',
|
|
237
|
+
maxAzs: 2,
|
|
238
|
+
rdsSecretsArn: 'TODO',
|
|
239
|
+
desiredServerCount: 1,
|
|
240
|
+
serverMemory: 512,
|
|
241
|
+
serverCpu: 256,
|
|
242
|
+
serverImage: 'medplum/medplum-server:latest',
|
|
243
|
+
storagePublicKey: expect.stringContaining('-----BEGIN PUBLIC KEY-----'),
|
|
244
|
+
apiSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
245
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
246
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
247
|
+
});
|
|
248
|
+
unlinkSync(filename);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('Do not request SSL certs', async () => {
|
|
252
|
+
const filename = `test-${randomUUID()}.json`;
|
|
253
|
+
|
|
254
|
+
await main(
|
|
255
|
+
mockReadline(
|
|
256
|
+
'foo',
|
|
257
|
+
filename,
|
|
258
|
+
'us-east-1',
|
|
259
|
+
'account-123',
|
|
260
|
+
'TestStack',
|
|
261
|
+
'test.example.com',
|
|
262
|
+
'support@example.com',
|
|
263
|
+
'', // default API domain
|
|
264
|
+
'', // default app domain
|
|
265
|
+
'', // default storage domain
|
|
266
|
+
'', // default storage bucket
|
|
267
|
+
'', // default availability zones
|
|
268
|
+
'y', // Yes, create a database
|
|
269
|
+
'', // default database instances
|
|
270
|
+
'', // default server instances
|
|
271
|
+
'', // default server memory
|
|
272
|
+
'', // default server cpu
|
|
273
|
+
'', // default server image
|
|
274
|
+
'n', // No api certificate
|
|
275
|
+
'n', // No app certificate
|
|
276
|
+
'n', // No storage certificate
|
|
277
|
+
'y' // Yes, write to Parameter Store
|
|
278
|
+
)
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const config = JSON.parse(readFileSync(filename, 'utf8'));
|
|
282
|
+
expect(config).toMatchObject({
|
|
283
|
+
apiPort: 8103,
|
|
284
|
+
name: 'foo',
|
|
285
|
+
region: 'us-east-1',
|
|
286
|
+
accountNumber: 'account-123',
|
|
287
|
+
stackName: 'TestStack',
|
|
288
|
+
domainName: 'test.example.com',
|
|
289
|
+
apiDomainName: 'api.test.example.com',
|
|
290
|
+
appDomainName: 'app.test.example.com',
|
|
291
|
+
storageDomainName: 'storage.test.example.com',
|
|
292
|
+
storageBucketName: 'medplum-foo-storage',
|
|
293
|
+
maxAzs: 2,
|
|
294
|
+
rdsInstances: 1,
|
|
295
|
+
desiredServerCount: 1,
|
|
296
|
+
serverMemory: 512,
|
|
297
|
+
serverCpu: 256,
|
|
298
|
+
serverImage: 'medplum/medplum-server:latest',
|
|
299
|
+
storagePublicKey: expect.stringContaining('-----BEGIN PUBLIC KEY-----'),
|
|
300
|
+
apiSslCertArn: 'TODO',
|
|
301
|
+
appSslCertArn: 'TODO',
|
|
302
|
+
storageSslCertArn: 'TODO',
|
|
303
|
+
});
|
|
304
|
+
unlinkSync(filename);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('Existing SSL certificates', async () => {
|
|
308
|
+
const filename = `test-${randomUUID()}.json`;
|
|
309
|
+
|
|
310
|
+
await main(
|
|
311
|
+
mockReadline(
|
|
312
|
+
'foo',
|
|
313
|
+
filename,
|
|
314
|
+
'us-east-1',
|
|
315
|
+
'account-123',
|
|
316
|
+
'TestStack',
|
|
317
|
+
'existing.example.com',
|
|
318
|
+
'support@example.com',
|
|
319
|
+
'', // default API domain
|
|
320
|
+
'', // default app domain
|
|
321
|
+
'', // default storage domain
|
|
322
|
+
'', // default storage bucket
|
|
323
|
+
'', // default availability zones
|
|
324
|
+
'y', // Yes, create a database
|
|
325
|
+
'', // default database instances
|
|
326
|
+
'', // default server instances
|
|
327
|
+
'', // default server memory
|
|
328
|
+
'', // default server cpu
|
|
329
|
+
'', // default server image
|
|
330
|
+
'y', // Yes, request api certificate
|
|
331
|
+
'', // default DNS validation
|
|
332
|
+
'y', // Yes, request app certificate
|
|
333
|
+
'', // default DNS validation
|
|
334
|
+
'y', // Yes, request storage certificate
|
|
335
|
+
'', // default DNS validation
|
|
336
|
+
'y' // Yes, write to Parameter Store
|
|
337
|
+
)
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const config = JSON.parse(readFileSync(filename, 'utf8'));
|
|
341
|
+
expect(config).toMatchObject({
|
|
342
|
+
apiPort: 8103,
|
|
343
|
+
name: 'foo',
|
|
344
|
+
region: 'us-east-1',
|
|
345
|
+
accountNumber: 'account-123',
|
|
346
|
+
stackName: 'TestStack',
|
|
347
|
+
domainName: 'existing.example.com',
|
|
348
|
+
apiDomainName: 'api.existing.example.com',
|
|
349
|
+
appDomainName: 'app.existing.example.com',
|
|
350
|
+
storageDomainName: 'storage.existing.example.com',
|
|
351
|
+
storageBucketName: 'medplum-foo-storage',
|
|
352
|
+
maxAzs: 2,
|
|
353
|
+
rdsInstances: 1,
|
|
354
|
+
desiredServerCount: 1,
|
|
355
|
+
serverMemory: 512,
|
|
356
|
+
serverCpu: 256,
|
|
357
|
+
serverImage: 'medplum/medplum-server:latest',
|
|
358
|
+
storagePublicKey: expect.stringContaining('-----BEGIN PUBLIC KEY-----'),
|
|
359
|
+
apiSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789013',
|
|
360
|
+
appSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
361
|
+
storageSslCertArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
362
|
+
});
|
|
363
|
+
unlinkSync(filename);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
function mockReadline(...answers: string[]): readline.Interface {
|
|
367
|
+
const result = { write: jest.fn(), question: jest.fn() };
|
|
368
|
+
const debug = false;
|
|
369
|
+
for (const answer of answers) {
|
|
370
|
+
result.question.mockImplementationOnce((q: string, cb: (answer: string) => void) => {
|
|
371
|
+
if (debug) {
|
|
372
|
+
console.log(q, answer);
|
|
373
|
+
}
|
|
374
|
+
cb(answer);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return result as unknown as readline.Interface;
|
|
378
|
+
}
|