@medplum/cdk 2.1.17 → 2.1.19

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/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@medplum/cdk",
3
- "version": "2.1.17",
3
+ "version": "2.1.19",
4
4
  "description": "Medplum CDK Infra as Code",
5
5
  "homepage": "https://www.medplum.com/",
6
+ "bugs": {
7
+ "url": "https://github.com/medplum/medplum/issues"
8
+ },
6
9
  "repository": {
7
10
  "type": "git",
8
11
  "url": "git+https://github.com/medplum/medplum.git",
@@ -10,6 +13,9 @@
10
13
  },
11
14
  "license": "Apache-2.0",
12
15
  "author": "Medplum <hello@medplum.com>",
16
+ "files": [
17
+ "dist"
18
+ ],
13
19
  "scripts": {
14
20
  "build": "npm run clean && tsc --project tsconfig.build.json && node esbuild.mjs",
15
21
  "cdk": "cdk",
package/babel.config.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "presets": [["@babel/preset-env", { "targets": { "node": "current" } }], "@babel/preset-typescript"]
3
- }
package/cdk.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "app": "ts-node src/index.ts"
3
- }
package/esbuild.mjs DELETED
@@ -1,47 +0,0 @@
1
- /* global console */
2
- /* eslint no-console: "off" */
3
-
4
- import esbuild from 'esbuild';
5
- import { writeFileSync } from 'fs';
6
-
7
- const options = {
8
- entryPoints: ['./src/index.ts'],
9
- bundle: true,
10
- platform: 'node',
11
- loader: { '.ts': 'ts' },
12
- resolveExtensions: ['.ts'],
13
- target: 'es2021',
14
- tsconfig: 'tsconfig.json',
15
- minify: true,
16
- sourcemap: true,
17
- external: [
18
- '@aws-sdk/client-acm',
19
- '@aws-sdk/client-ssm',
20
- '@aws-sdk/client-sts',
21
- 'aws-cdk-lib',
22
- 'aws-cdk-lib/aws-ecr',
23
- 'aws-cdk-lib/aws-rds',
24
- 'cdk',
25
- 'cdk-nag',
26
- 'cdk-serverless-clamscan',
27
- 'constructs',
28
- ],
29
- };
30
-
31
- esbuild
32
- .build({
33
- ...options,
34
- format: 'cjs',
35
- outfile: './dist/cjs/index.cjs',
36
- })
37
- .then(() => writeFileSync('./dist/cjs/package.json', '{"type": "commonjs"}'))
38
- .catch(console.error);
39
-
40
- esbuild
41
- .build({
42
- ...options,
43
- format: 'esm',
44
- outfile: './dist/esm/index.mjs',
45
- })
46
- .then(() => writeFileSync('./dist/esm/package.json', '{"type": "module"}'))
47
- .catch(console.error);
package/jest.config.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "testEnvironment": "node",
3
- "transform": {
4
- "^.+\\.(js|jsx|ts|tsx)$": "babel-jest"
5
- },
6
- "moduleFileExtensions": ["ts", "js", "json", "node"],
7
- "testMatch": ["**/src/**/*.test.ts"],
8
- "coverageDirectory": "coverage",
9
- "coverageReporters": ["json", "text"],
10
- "collectCoverageFrom": ["**/src/**/*", "!**/src/__mocks__/**/*.ts"]
11
- }
package/src/backend.ts DELETED
@@ -1,496 +0,0 @@
1
- import { MedplumInfraConfig } from '@medplum/core';
2
- import {
3
- Duration,
4
- RemovalPolicy,
5
- aws_ec2 as ec2,
6
- aws_ecs as ecs,
7
- aws_elasticache as elasticache,
8
- aws_elasticloadbalancingv2 as elbv2,
9
- aws_iam as iam,
10
- aws_logs as logs,
11
- aws_rds as rds,
12
- aws_route53 as route53,
13
- aws_s3 as s3,
14
- aws_secretsmanager as secretsmanager,
15
- aws_ssm as ssm,
16
- aws_route53_targets as targets,
17
- aws_wafv2 as wafv2,
18
- } from 'aws-cdk-lib';
19
- import { Repository } from 'aws-cdk-lib/aws-ecr';
20
- import { ClusterInstance } from 'aws-cdk-lib/aws-rds';
21
- import { Construct } from 'constructs';
22
- import { awsManagedRules } from './waf';
23
-
24
- /**
25
- * Based on: https://github.com/aws-samples/http-api-aws-fargate-cdk/blob/master/cdk/singleAccount/lib/fargate-vpclink-stack.ts
26
- *
27
- * RDS config: https://docs.aws.amazon.com/cdk/api/latest/docs/aws-rds-readme.html
28
- */
29
- export class BackEnd extends Construct {
30
- vpc: ec2.IVpc;
31
- botLambdaRole: iam.IRole;
32
- rdsSecretsArn?: string;
33
- rdsCluster?: rds.DatabaseCluster;
34
- redisSubnetGroup: elasticache.CfnSubnetGroup;
35
- redisSecurityGroup: ec2.SecurityGroup;
36
- redisPassword: secretsmanager.ISecret;
37
- redisCluster: elasticache.CfnReplicationGroup;
38
- redisSecrets: secretsmanager.ISecret;
39
- ecsCluster: ecs.Cluster;
40
- taskRolePolicies: iam.PolicyDocument;
41
- taskRole: iam.Role;
42
- taskDefinition: ecs.FargateTaskDefinition;
43
- logGroup: logs.ILogGroup;
44
- logDriver: ecs.AwsLogDriver;
45
- serviceContainer: ecs.ContainerDefinition;
46
- fargateSecurityGroup: ec2.SecurityGroup;
47
- fargateService: ecs.FargateService;
48
- targetGroup: elbv2.ApplicationTargetGroup;
49
- loadBalancer: elbv2.ApplicationLoadBalancer;
50
- waf: wafv2.CfnWebACL;
51
- wafAssociation: wafv2.CfnWebACLAssociation;
52
- dnsRecord?: route53.ARecord;
53
- regionParameter: ssm.StringParameter;
54
- databaseSecretsParameter: ssm.StringParameter;
55
- redisSecretsParameter: ssm.StringParameter;
56
- botLambdaRoleParameter: ssm.StringParameter;
57
-
58
- constructor(scope: Construct, config: MedplumInfraConfig) {
59
- super(scope, 'BackEnd');
60
-
61
- const name = config.name;
62
-
63
- // VPC
64
- if (config.vpcId) {
65
- // Lookup VPC by ARN
66
- this.vpc = ec2.Vpc.fromLookup(this, 'VPC', { vpcId: config.vpcId });
67
- } else {
68
- // VPC Flow Logs
69
- const vpcFlowLogs = new logs.LogGroup(this, 'VpcFlowLogs', {
70
- logGroupName: '/medplum/flowlogs/' + name,
71
- removalPolicy: RemovalPolicy.DESTROY,
72
- });
73
-
74
- // Create VPC
75
- this.vpc = new ec2.Vpc(this, 'VPC', {
76
- maxAzs: config.maxAzs,
77
- flowLogs: {
78
- cloudwatch: {
79
- destination: ec2.FlowLogDestination.toCloudWatchLogs(vpcFlowLogs),
80
- trafficType: ec2.FlowLogTrafficType.ALL,
81
- },
82
- },
83
- });
84
- }
85
-
86
- // Bot Lambda Role
87
- this.botLambdaRole = new iam.Role(this, 'BotLambdaRole', {
88
- assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
89
- });
90
-
91
- // RDS
92
- this.rdsSecretsArn = config.rdsSecretsArn;
93
- if (!this.rdsSecretsArn) {
94
- // See: https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds-readme.html#migrating-from-instanceprops
95
- const instanceProps: rds.ProvisionedClusterInstanceProps = {
96
- instanceType: config.rdsInstanceType ? new ec2.InstanceType(config.rdsInstanceType) : undefined,
97
- enablePerformanceInsights: true,
98
- isFromLegacyInstanceProps: true,
99
- };
100
-
101
- let readers = undefined;
102
- if (config.rdsInstances > 1) {
103
- readers = [];
104
- for (let i = 0; i < config.rdsInstances - 1; i++) {
105
- readers.push(
106
- ClusterInstance.provisioned('Instance' + (i + 2), {
107
- ...instanceProps,
108
- })
109
- );
110
- }
111
- }
112
-
113
- this.rdsCluster = new rds.DatabaseCluster(this, 'DatabaseCluster', {
114
- engine: rds.DatabaseClusterEngine.auroraPostgres({
115
- version: config.rdsInstanceVersion
116
- ? rds.AuroraPostgresEngineVersion.of(
117
- config.rdsInstanceVersion,
118
- config.rdsInstanceVersion.slice(0, config.rdsInstanceVersion.indexOf('.')),
119
- { s3Import: true, s3Export: true }
120
- )
121
- : rds.AuroraPostgresEngineVersion.VER_12_9,
122
- }),
123
- credentials: rds.Credentials.fromGeneratedSecret('clusteradmin'),
124
- defaultDatabaseName: 'medplum',
125
- storageEncrypted: true,
126
- vpc: this.vpc,
127
- vpcSubnets: {
128
- subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
129
- },
130
- writer: ClusterInstance.provisioned('Instance1', {
131
- ...instanceProps,
132
- }),
133
- readers,
134
- backup: {
135
- retention: Duration.days(7),
136
- },
137
- cloudwatchLogsExports: ['postgresql'],
138
- instanceUpdateBehaviour: rds.InstanceUpdateBehaviour.ROLLING,
139
- });
140
-
141
- this.rdsSecretsArn = (this.rdsCluster.secret as secretsmanager.ISecret).secretArn;
142
- }
143
-
144
- // Redis
145
- // Important: For HIPAA compliance, you must specify TransitEncryptionEnabled as true, an AuthToken, and a CacheSubnetGroup.
146
- this.redisSubnetGroup = new elasticache.CfnSubnetGroup(this, 'RedisSubnetGroup', {
147
- description: 'Redis Subnet Group',
148
- subnetIds: this.vpc.privateSubnets.map((subnet) => subnet.subnetId),
149
- });
150
-
151
- this.redisSecurityGroup = new ec2.SecurityGroup(this, 'RedisSecurityGroup', {
152
- vpc: this.vpc,
153
- description: 'Redis Security Group',
154
- allowAllOutbound: false,
155
- });
156
-
157
- this.redisPassword = new secretsmanager.Secret(this, 'RedisPassword', {
158
- generateSecretString: {
159
- secretStringTemplate: '{}',
160
- generateStringKey: 'password',
161
- excludeCharacters: '@%*()_+=`~{}|[]\\:";\'?,./',
162
- },
163
- });
164
-
165
- this.redisCluster = new elasticache.CfnReplicationGroup(this, 'RedisCluster', {
166
- engine: 'Redis',
167
- engineVersion: '6.x',
168
- cacheNodeType: config.cacheNodeType ?? 'cache.t2.medium',
169
- replicationGroupDescription: 'RedisReplicationGroup',
170
- authToken: this.redisPassword.secretValueFromJson('password').toString(),
171
- transitEncryptionEnabled: true,
172
- atRestEncryptionEnabled: true,
173
- multiAzEnabled: true,
174
- cacheSubnetGroupName: this.redisSubnetGroup.ref,
175
- numNodeGroups: 1,
176
- replicasPerNodeGroup: 1,
177
- securityGroupIds: [this.redisSecurityGroup.securityGroupId],
178
- });
179
- this.redisCluster.node.addDependency(this.redisPassword);
180
-
181
- this.redisSecrets = new secretsmanager.Secret(this, 'RedisSecrets', {
182
- generateSecretString: {
183
- secretStringTemplate: JSON.stringify({
184
- host: this.redisCluster.attrPrimaryEndPointAddress,
185
- port: this.redisCluster.attrPrimaryEndPointPort,
186
- password: this.redisPassword.secretValueFromJson('password').toString(),
187
- tls: {},
188
- }),
189
- generateStringKey: 'unused',
190
- },
191
- });
192
- this.redisSecrets.node.addDependency(this.redisPassword);
193
- this.redisSecrets.node.addDependency(this.redisCluster);
194
-
195
- // ECS Cluster
196
- this.ecsCluster = new ecs.Cluster(this, 'Cluster', {
197
- vpc: this.vpc,
198
- });
199
-
200
- // Task Policies
201
- this.taskRolePolicies = new iam.PolicyDocument({
202
- statements: [
203
- // CloudWatch Logs: Create streams and put events
204
- new iam.PolicyStatement({
205
- effect: iam.Effect.ALLOW,
206
- actions: ['logs:CreateLogStream', 'logs:PutLogEvents'],
207
- resources: ['arn:aws:logs:*'],
208
- }),
209
-
210
- // Secrets Manager: Read only access to secrets
211
- // https://docs.aws.amazon.com/mediaconnect/latest/ug/iam-policy-examples-asm-secrets.html
212
- new iam.PolicyStatement({
213
- effect: iam.Effect.ALLOW,
214
- actions: [
215
- 'secretsmanager:GetResourcePolicy',
216
- 'secretsmanager:GetSecretValue',
217
- 'secretsmanager:DescribeSecret',
218
- 'secretsmanager:ListSecrets',
219
- 'secretsmanager:ListSecretVersionIds',
220
- ],
221
- resources: ['arn:aws:secretsmanager:*'],
222
- }),
223
-
224
- // Parameter Store: Read only access
225
- // https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-access.html
226
- new iam.PolicyStatement({
227
- effect: iam.Effect.ALLOW,
228
- actions: ['ssm:GetParametersByPath', 'ssm:GetParameters', 'ssm:GetParameter', 'ssm:DescribeParameters'],
229
- resources: ['arn:aws:ssm:*'],
230
- }),
231
-
232
- // SES: Send emails
233
- // https://docs.aws.amazon.com/ses/latest/dg/sending-authorization-policy-examples.html
234
- new iam.PolicyStatement({
235
- effect: iam.Effect.ALLOW,
236
- actions: ['ses:SendEmail', 'ses:SendRawEmail'],
237
- resources: ['arn:aws:ses:*'],
238
- }),
239
-
240
- // S3: Read and write access to buckets
241
- // https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html
242
- new iam.PolicyStatement({
243
- effect: iam.Effect.ALLOW,
244
- actions: ['s3:ListBucket', 's3:GetObject', 's3:PutObject', 's3:DeleteObject'],
245
- resources: ['arn:aws:s3:::*'],
246
- }),
247
-
248
- // IAM: Pass role to innvoke lambda functions
249
- // https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_passrole.html
250
- new iam.PolicyStatement({
251
- effect: iam.Effect.ALLOW,
252
- actions: ['iam:ListRoles', 'iam:GetRole', 'iam:PassRole'],
253
- resources: [this.botLambdaRole.roleArn],
254
- }),
255
-
256
- // Lambda: Create, read, update, delete, and invoke functions
257
- // https://docs.aws.amazon.com/lambda/latest/dg/access-control-identity-based.html
258
- new iam.PolicyStatement({
259
- effect: iam.Effect.ALLOW,
260
- actions: [
261
- 'lambda:CreateFunction',
262
- 'lambda:GetFunction',
263
- 'lambda:GetFunctionConfiguration',
264
- 'lambda:UpdateFunctionCode',
265
- 'lambda:UpdateFunctionConfiguration',
266
- 'lambda:ListLayerVersions',
267
- 'lambda:GetLayerVersion',
268
- 'lambda:InvokeFunction',
269
- ],
270
- resources: ['arn:aws:lambda:*'],
271
- }),
272
- ],
273
- });
274
-
275
- // Task Role
276
- this.taskRole = new iam.Role(this, 'TaskExecutionRole', {
277
- assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
278
- description: 'Medplum Server Task Execution Role',
279
- inlinePolicies: {
280
- TaskExecutionPolicies: this.taskRolePolicies,
281
- },
282
- });
283
-
284
- // Task Definitions
285
- this.taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition', {
286
- memoryLimitMiB: config.serverMemory,
287
- cpu: config.serverCpu,
288
- taskRole: this.taskRole,
289
- });
290
-
291
- // Log Groups
292
- this.logGroup = new logs.LogGroup(this, 'LogGroup', {
293
- logGroupName: '/ecs/medplum/' + name,
294
- removalPolicy: RemovalPolicy.DESTROY,
295
- });
296
-
297
- this.logDriver = new ecs.AwsLogDriver({
298
- logGroup: this.logGroup,
299
- streamPrefix: 'Medplum',
300
- });
301
-
302
- // Task Containers
303
- this.serviceContainer = this.taskDefinition.addContainer('MedplumTaskDefinition', {
304
- image: this.getContainerImage(config, config.serverImage),
305
- command: [config.region === 'us-east-1' ? `aws:/medplum/${name}/` : `aws:${config.region}:/medplum/${name}/`],
306
- logging: this.logDriver,
307
- });
308
-
309
- this.serviceContainer.addPortMappings({
310
- containerPort: config.apiPort,
311
- hostPort: config.apiPort,
312
- });
313
-
314
- if (config.additionalContainers) {
315
- for (const container of config.additionalContainers) {
316
- this.taskDefinition.addContainer('AdditionalContainer-' + container.name, {
317
- containerName: container.name,
318
- image: this.getContainerImage(config, container.image),
319
- command: container.command,
320
- environment: container.environment,
321
- logging: this.logDriver,
322
- });
323
- }
324
- }
325
-
326
- // Security Groups
327
- this.fargateSecurityGroup = new ec2.SecurityGroup(this, 'ServiceSecurityGroup', {
328
- allowAllOutbound: true,
329
- securityGroupName: 'MedplumSecurityGroup',
330
- vpc: this.vpc,
331
- });
332
-
333
- // Fargate Services
334
- this.fargateService = new ecs.FargateService(this, 'FargateService', {
335
- cluster: this.ecsCluster,
336
- taskDefinition: this.taskDefinition,
337
- assignPublicIp: false,
338
- vpcSubnets: {
339
- subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
340
- },
341
- desiredCount: config.desiredServerCount,
342
- securityGroups: [this.fargateSecurityGroup],
343
- healthCheckGracePeriod: Duration.minutes(5),
344
- });
345
-
346
- // Add dependencies - make sure Fargate service is created after RDS and Redis
347
- if (this.rdsCluster) {
348
- this.fargateService.node.addDependency(this.rdsCluster);
349
- }
350
- this.fargateService.node.addDependency(this.redisCluster);
351
-
352
- // Load Balancer Target Group
353
- this.targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {
354
- vpc: this.vpc,
355
- port: config.apiPort,
356
- protocol: elbv2.ApplicationProtocol.HTTP,
357
- healthCheck: {
358
- path: '/healthcheck',
359
- interval: Duration.seconds(30),
360
- timeout: Duration.seconds(3),
361
- healthyThresholdCount: 2,
362
- unhealthyThresholdCount: 5,
363
- },
364
- targets: [this.fargateService],
365
- });
366
-
367
- // Load Balancer
368
- this.loadBalancer = new elbv2.ApplicationLoadBalancer(this, 'LoadBalancer', {
369
- vpc: this.vpc,
370
- internetFacing: config.apiInternetFacing !== false, // default true
371
- http2Enabled: true,
372
- });
373
-
374
- if (config.loadBalancerLoggingBucket) {
375
- // Load Balancer logging
376
- this.loadBalancer.logAccessLogs(
377
- s3.Bucket.fromBucketName(this, 'LoggingBucket', config.loadBalancerLoggingBucket),
378
- config.loadBalancerLoggingPrefix
379
- );
380
- }
381
-
382
- // HTTPS Listener
383
- // Forward to the target group
384
- this.loadBalancer.addListener('HttpsListener', {
385
- port: 443,
386
- certificates: [
387
- {
388
- certificateArn: config.apiSslCertArn,
389
- },
390
- ],
391
- sslPolicy: elbv2.SslPolicy.FORWARD_SECRECY_TLS12_RES_GCM,
392
- defaultAction: elbv2.ListenerAction.forward([this.targetGroup]),
393
- });
394
-
395
- // WAF
396
- this.waf = new wafv2.CfnWebACL(this, 'BackEndWAF', {
397
- defaultAction: { allow: {} },
398
- scope: 'REGIONAL',
399
- name: `${config.stackName}-BackEndWAF`,
400
- rules: awsManagedRules,
401
- visibilityConfig: {
402
- cloudWatchMetricsEnabled: true,
403
- metricName: `${config.stackName}-BackEndWAF-Metric`,
404
- sampledRequestsEnabled: false,
405
- },
406
- });
407
-
408
- // Create an association between the load balancer and the WAF
409
- this.wafAssociation = new wafv2.CfnWebACLAssociation(this, 'LoadBalancerAssociation', {
410
- resourceArn: this.loadBalancer.loadBalancerArn,
411
- webAclArn: this.waf.attrArn,
412
- });
413
-
414
- // Grant RDS access to the fargate group
415
- if (this.rdsCluster) {
416
- this.rdsCluster.connections.allowDefaultPortFrom(this.fargateSecurityGroup);
417
- }
418
-
419
- // Grant Redis access to the fargate group
420
- this.redisSecurityGroup.addIngressRule(this.fargateSecurityGroup, ec2.Port.tcp(6379));
421
-
422
- // DNS
423
- if (!config.skipDns) {
424
- // Route 53
425
- const zone = route53.HostedZone.fromLookup(this, 'Zone', {
426
- domainName: config.domainName.split('.').slice(-2).join('.'),
427
- });
428
-
429
- // Route53 alias record for the load balancer
430
- this.dnsRecord = new route53.ARecord(this, 'LoadBalancerAliasRecord', {
431
- recordName: config.apiDomainName,
432
- target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(this.loadBalancer)),
433
- zone: zone,
434
- });
435
- }
436
-
437
- // SSM Parameters
438
- this.regionParameter = new ssm.StringParameter(this, 'RegionParameter', {
439
- tier: ssm.ParameterTier.STANDARD,
440
- parameterName: `/medplum/${name}/awsRegion`,
441
- description: 'AWS region',
442
- stringValue: config.region,
443
- });
444
-
445
- this.databaseSecretsParameter = new ssm.StringParameter(this, 'DatabaseSecretsParameter', {
446
- tier: ssm.ParameterTier.STANDARD,
447
- parameterName: `/medplum/${name}/DatabaseSecrets`,
448
- description: 'Database secrets ARN',
449
- stringValue: this.rdsSecretsArn,
450
- });
451
-
452
- this.redisSecretsParameter = new ssm.StringParameter(this, 'RedisSecretsParameter', {
453
- tier: ssm.ParameterTier.STANDARD,
454
- parameterName: `/medplum/${name}/RedisSecrets`,
455
- description: 'Redis secrets ARN',
456
- stringValue: this.redisSecrets.secretArn,
457
- });
458
-
459
- this.botLambdaRoleParameter = new ssm.StringParameter(this, 'BotLambdaRoleParameter', {
460
- tier: ssm.ParameterTier.STANDARD,
461
- parameterName: `/medplum/${name}/botLambdaRoleArn`,
462
- description: 'Bot lambda execution role ARN',
463
- stringValue: this.botLambdaRole.roleArn,
464
- });
465
- }
466
-
467
- /**
468
- * Returns a container image for the given image name.
469
- * If the image name is an ECR image, then the image will be pulled from ECR.
470
- * Otherwise, the image name is assumed to be a Docker Hub image.
471
- * @param config - The config settings (account number and region).
472
- * @param imageName - The image name.
473
- * @returns The container image.
474
- */
475
- private getContainerImage(config: MedplumInfraConfig, imageName: string): ecs.ContainerImage {
476
- // Pull out the image name and tag from the image URI if it's an ECR image
477
- const ecrImageUriRegex = new RegExp(
478
- `^${config.accountNumber}\\.dkr\\.ecr\\.${config.region}\\.amazonaws\\.com/(.*)[:@](.*)$`
479
- );
480
- const nameTagMatches = ecrImageUriRegex.exec(imageName);
481
- const serverImageName = nameTagMatches?.[1];
482
- const serverImageTag = nameTagMatches?.[2];
483
- if (serverImageName && serverImageTag) {
484
- // Creating an ecr repository image will automatically grant fine-grained permissions to ecs to access the image
485
- const ecrRepo = Repository.fromRepositoryArn(
486
- this,
487
- 'ServerImageRepo',
488
- `arn:aws:ecr:${config.region}:${config.accountNumber}:repository/${serverImageName}`
489
- );
490
- return ecs.ContainerImage.fromEcrRepository(ecrRepo, serverImageTag);
491
- }
492
-
493
- // Otherwise, use the standard container image
494
- return ecs.ContainerImage.fromRegistry(imageName);
495
- }
496
- }