in infrastructure/lib/orthanc-stack.ts [19:192]
constructor(scope: cdk.Construct, id: string, props: OrthancStackProps) {
super(scope, id, props);
// ********************************
// Orthanc Credentials (Secrets Manager)
// ********************************
const orthancCredentials = new secretsmanager.Secret(this, 'Orthanc-Credentials',{
generateSecretString: {
secretStringTemplate: JSON.stringify({}),
generateStringKey: 'admin',
excludeCharacters: "\'\"@/\\",
passwordLength: 16
}});
// ********************************
// ECS Fargate Cluster & ALB & Task definition
// ********************************
const cluster = new ecs.Cluster(this, "OrthancCluster", {
vpc: props.vpc,
});
// create a task definition with CloudWatch Logs
const logging = new ecs.AwsLogDriver({
streamPrefix: "orthanc",
});
const taskDef = new ecs.FargateTaskDefinition(this, "OrthancTaskDefinition", {
memoryLimitMiB: 4096,
cpu: 2048,
});
let orthancConfig = {
AwsS3Storage: {
BucketName: props.orthancBucket?.bucketName,
Region: Aws.REGION,
ConnectionTimeout: 30,
RequestTimeout: 1200,
RootPath: "",
StorageStructure: "flat",
MigrationFromFileSystemEnabled: false }
};
let container = {
image: ecs.ContainerImage.fromAsset('./lib/local-image-official-s3/'),
logging,
taskDefinition: taskDef,
environment: {
ORTHANC__POSTGRESQL__HOST: props.rdsInstance.dbInstanceEndpointAddress,
ORTHANC__POSTGRESQL__PORT: props.rdsInstance.dbInstanceEndpointPort,
LOCALDOMAIN: Aws.REGION + ".compute.internal-orthanconaws.local",
DICOM_WEB_PLUGIN_ENABLED: "true",
ORTHANC__POSTGRESQL__USERNAME: "postgres",
//VERBOSE_STARTUP: "true", // uncomment to enable verbose logging in container
//VERBOSE_ENABLED: "true", // uncomment to enable verbose logging in container
//TRACE_ENABLED: "true", // uncomment to enable trace level logging in container
STONE_WEB_VIEWER_PLUGIN_ENABLED: "true",
STORAGE_BUNDLE_DEFAULTS: "false",
LD_LIBRARY_PATH: "/usr/local/lib",
WSI_PLUGIN_ENABLED: "true",
ORTHANC_JSON: props.enable_dicom_s3_storage ? JSON.stringify(orthancConfig) : "{}",
// If we disabled S3, remove the plugin so it won't cause issues at startup
BEFORE_ORTHANC_STARTUP_SCRIPT: props.enable_dicom_s3_storage ? "" : "/tmp/custom-script.sh"
},
secrets: {
ORTHANC__REGISTERED_USERS: ecs.Secret.fromSecretsManager(orthancCredentials),
ORTHANC__POSTGRESQL__PASSWORD: ecs.Secret.fromSecretsManager(props.secret)
},
linuxParameters: new LinuxParameters(this, "OrthancLinuxParams", { initProcessEnabled: true}),
containerName: "orthanc-container",
portMappings: [
{
containerPort: 8042,
hostPort: 8042,
protocol: Protocol.TCP
},
{
containerPort: 4242,
hostPort: 4242,
protocol: Protocol.TCP
},
],
SecurityGroup: props.ecsSecurityGroup
};
const orthancContainerDefinition: ContainerDefinition = taskDef.addContainer("OrthancContainer", container);
orthancCredentials.grantRead(orthancContainerDefinition.taskDefinition.taskRole);
props.secret.grantRead(orthancContainerDefinition.taskDefinition.taskRole);
if(props.enable_dicom_s3_storage) { // If S3 DICOM storage is enabled, add neccessary permissions to bucket
props.orthancBucket?.grantReadWrite(taskDef.taskRole);
}
else { // If S3 DICOM storage is disabled, fall back to EFS - add volume and mount points
const volume: ecs.Volume = {
name: "orthanc-efs",
efsVolumeConfiguration: {
fileSystemId: props.orthancFileSystem?.fileSystemId ? props.orthancFileSystem?.fileSystemId : "",
transitEncryption: "ENABLED",
authorizationConfig: {
accessPointId: props.efsAccessPoint?.accessPointId,
iam: "ENABLED"
}
}
};
orthancContainerDefinition.addMountPoints(
{
containerPath: "/var/lib/orthanc/db",
sourceVolume: volume.name,
readOnly: false,
}
);
taskDef.addVolume(volume);
}
const loadBalancer = new elbv2.ApplicationLoadBalancer(this, "OrthancLoadBalancer", {
vpc: props.vpc,
securityGroup: props.loadBalancerSecurityGroup,
internetFacing: true,
});
if(props.access_logs_bucket_arn != "") {
loadBalancer.logAccessLogs(s3.Bucket.fromBucketArn(this, "MyAccessLogBucket", props.access_logs_bucket_arn));
}
const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, "OrthancService", {
cluster,
loadBalancer: loadBalancer,
taskDefinition: taskDef,
desiredCount: props.enable_multi_az ? 2 : 1,
platformVersion: FargatePlatformVersion.VERSION1_4,
securityGroups: [props.ecsSecurityGroup]
});
fargateService.targetGroup.configureHealthCheck({
path: "/",
interval: cdk.Duration.seconds(60),
healthyHttpCodes:"200-499", // We have to check for 401 as the default state of "/" is unauthenticated
});
// ********************************
// Cloudfront Distribution
// ********************************
const myOriginRequestPolicy = new cloudfront.OriginRequestPolicy(this, 'OriginRequestPolicy', {
originRequestPolicyName: 'OrthancPolicy',
comment: 'Policy optimised for Orthanc',
cookieBehavior: cloudfront.OriginRequestCookieBehavior.all(),
headerBehavior: cloudfront.OriginRequestHeaderBehavior.all(),
queryStringBehavior: cloudfront.OriginRequestQueryStringBehavior.all(),
});
const orthancDistribution = new cloudfront.Distribution(this, 'OrthancDistribution', {
defaultBehavior: {
origin: new origins.LoadBalancerV2Origin(loadBalancer, { protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY }),
originRequestPolicy: myOriginRequestPolicy,
cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
},
minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2019
});
// ********************************
// Stack Outputs
// ********************************
new cdk.CfnOutput(this, 'OrthancCredentialsName', {
value: orthancCredentials.secretName,
description: 'The name of the OrthancCredentials secret',
exportName: 'orthancCredentialsName',
});
new cdk.CfnOutput(this, 'OrthancURL', {
value: orthancDistribution.distributionDomainName,
description: 'Orthanc Distribution URL',
exportName: 'orthancDistributionURL',
});
};