Skip to content

Commit

Permalink
fix main.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
drernie committed Dec 6, 2023
1 parent 5455e26 commit 0c24e46
Showing 1 changed file with 9 additions and 247 deletions.
256 changes: 9 additions & 247 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,250 +1,12 @@
import { Duration, RemovalPolicy, Stack, type StackProps } from 'aws-cdk-lib';
import { Rule } from 'aws-cdk-lib/aws-events';
import { SnsTopic } from 'aws-cdk-lib/aws-events-targets';
import {
AccountPrincipal,
ManagedPolicy,
PolicyStatement,
Role,
ServicePrincipal,
} from 'aws-cdk-lib/aws-iam';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { S3EventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import {
Bucket,
BlockPublicAccess,
EventType,
BucketEncryption,
} from 'aws-cdk-lib/aws-s3';
import { Topic } from 'aws-cdk-lib/aws-sns';
import { type Construct } from 'constructs';
import {
APP_NAME,
AWS_ACCOUNT_ID,
AWS_REGION,
INPUT_BUCKET,
OUTPUT_BUCKET,
MANIFEST_PREFIX,
MANIFEST_SUFFIX,
READY2RUN_WORKFLOW_ID,
} from './constants';
import { App } from 'aws-cdk-lib';
import { OmicsQuiltStack } from './omics-quilt';

export class OmicsQuiltStack extends Stack {
public readonly inputBucket: Bucket;
public readonly outputBucket: Bucket;
public readonly statusTopic: Topic;
function main() {
const app = new App();

public readonly manifest_prefix: string;
public readonly manifest_suffix: string;

readonly lambdaRole: Role;
readonly omicsRole: Role;
readonly principal: AccountPrincipal;

constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
this.principal = new AccountPrincipal(AWS_ACCOUNT_ID);
this.manifest_prefix = MANIFEST_PREFIX;
this.manifest_suffix = MANIFEST_SUFFIX;

// Create Input/Output S3 buckets
this.inputBucket = this.makeBucket(INPUT_BUCKET);
this.outputBucket = this.makeBucket(OUTPUT_BUCKET);

// SNS Topic for failure notifications
const topicName = `${APP_NAME}_workflow_status_topic`;
this.statusTopic = new Topic(this, topicName, {
displayName: topicName,
topicName: topicName,
});

// Create an EventBridge rule that sends SNS notification on failure
const ruleWorkflowStatusTopic = new Rule(
this,
`${APP_NAME}_rule_workflow_status_topic`,
{
eventPattern: {
source: ['aws.omics'],
detailType: ['Run Status Change'],
detail: {
status: ['FAILED', 'COMPLETED', 'CREATED'],
},
},
},
);

ruleWorkflowStatusTopic.addTarget(new SnsTopic(this.statusTopic));

const servicePrincipal = new ServicePrincipal('events.amazonaws.com');
this.statusTopic.grantPublish(servicePrincipal);
this.statusTopic.grantPublish(this.principal); // for debugging purposes

// Create an IAM service role for HealthOmics workflows
this.omicsRole = this.makeOmicsRole();

// Create an IAM role for the Lambda functions
this.lambdaRole = this.makeLambdaRole();

// Create Lambda function to submit initial HealthOmics workflow
const fastqWorkflowLambda = this.makeLambda('wf1_fastq', {});
// Add S3 event source to Lambda
fastqWorkflowLambda.addEventSource(
new S3EventSource(this.inputBucket, {
events: [EventType.OBJECT_CREATED],
filters: [
{ prefix: this.manifest_prefix, suffix: this.manifest_suffix },
],
}),
);
}

private makeBucket(name: string) {
const bucketOptions = {
autoDeleteObjects: true,
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
encryption: BucketEncryption.S3_MANAGED,
enforceSSL: true,
removalPolicy: RemovalPolicy.DESTROY,
versioned: true,
};
const bucket = new Bucket(this, name, bucketOptions);
bucket.grantDelete(this.principal);
bucket.grantReadWrite(this.principal);
return bucket;
}

private makeLambda(name: string, env: object) {
const default_env = {
OMICS_ROLE: this.omicsRole.roleArn,
OUTPUT_S3_LOCATION: 's3://' + this.outputBucket.bucketName + '/outputs',
WORKFLOW_ID: READY2RUN_WORKFLOW_ID,
ECR_REGISTRY:
AWS_ACCOUNT_ID + '.dkr.ecr.' + AWS_REGION + '.amazonaws.com',
LOG_LEVEL: 'ALL',
};
// create merged env
const final_env = Object.assign(default_env, env);
return new NodejsFunction(this, name, {
runtime: Runtime.NODEJS_18_X,
role: this.lambdaRole,
timeout: Duration.seconds(60),
retryAttempts: 1,
environment: final_env,
});
}

private makeLambdaRole() {
const lambdaRole = new Role(this, `${APP_NAME}-lambda-role`, {
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName(
'service-role/AWSLambdaBasicExecutionRole',
),
],
});

// Allow the Lambda functions to pass Omics service role to the Omics service
const lambdaIamPassrolePolicy = new PolicyStatement({
actions: ['iam:PassRole'],
resources: [this.omicsRole.roleArn],
});
lambdaRole.addToPolicy(lambdaIamPassrolePolicy);

const lambdaS3Policy = new PolicyStatement({
actions: ['s3:ListBucket', 's3:GetObject', 's3:PutObject'],
resources: [
this.inputBucket.bucketArn,
this.outputBucket.bucketArn,
this.inputBucket.bucketArn + '/*',
this.outputBucket.bucketArn + '/*',
],
});
lambdaRole.addToPolicy(lambdaS3Policy);

const lambdaOmicsPolicy = new PolicyStatement({
actions: ['omics:StartRun', 'omics:TagResource', 'omics:GetRun'],
resources: ['*'],
});
lambdaRole.addToPolicy(lambdaOmicsPolicy);
return lambdaRole;
}

private makeOmicsRole() {
const omicsRole = new Role(this, `${APP_NAME}-omics-service-role`, {
assumedBy: new ServicePrincipal('omics.amazonaws.com'),
});

// Limit to buckets from where inputs need to be read
const omicsS3ReadPolicy = new PolicyStatement({
actions: ['s3:ListBucket', 's3:GetObject'],
resources: [
this.inputBucket.bucketArn,
this.outputBucket.bucketArn,
this.inputBucket.bucketArn + '/*',
this.outputBucket.bucketArn + '/*',
],
});
omicsRole.addToPolicy(omicsS3ReadPolicy);

// Limit to buckets where outputs need to be written
const omicsS3WritePolicy = new PolicyStatement({
actions: ['s3:ListBucket', 's3:PutObject'],
resources: [
this.outputBucket.bucketArn,
this.outputBucket.bucketArn + '/*',
],
});
omicsRole.addToPolicy(omicsS3WritePolicy);

// ECR image access
const omicsEcrPolicy = new PolicyStatement({
actions: [
'ecr:BatchGetImage',
'ecr:GetDownloadUrlForLayer',
'ecr:BatchCheckLayerAvailability',
],
resources: [`arn:aws:ecr:${AWS_REGION}:${AWS_ACCOUNT_ID}:repository/*`],
});
omicsRole.addToPolicy(omicsEcrPolicy);

// CloudWatch logging access
const omicsLoggingPolicy = new PolicyStatement({
actions: [
'logs:CreateLogGroup',
'logs:DescribeLogStreams',
'logs:CreateLogStream',
'logs:PutLogEvents',
],
resources: [
`arn:aws:logs:${AWS_REGION}:${AWS_ACCOUNT_ID}:log-group:/aws/omics/WorkflowLog:log-stream:*`,
`arn:aws:logs:${AWS_REGION}:${AWS_ACCOUNT_ID}:log-group:/aws/omics/WorkflowLog:*`,
],
});
omicsRole.addToPolicy(omicsLoggingPolicy);

// KMS access
const omicsKmsPolicy = new PolicyStatement({
actions: ['kms:Decrypt', 'kms:GenerateDataKey'],
resources: ['*'],
});
omicsRole.addToPolicy(omicsKmsPolicy);

// Allow Omics service role to access some common public AWS S3 buckets with test data
const omicsRoleAdditionalPolicy = new PolicyStatement({
actions: ['s3:Get*', 's3:List*'],
resources: [
'arn:aws:s3:::broad-references',
'arn:aws:s3:::broad-references/*',
'arn:aws:s3:::giab',
'arn:aws:s3:::giab/*',
`arn:aws:s3:::aws-genomics-static-${AWS_REGION}`,
`arn:aws:s3:::aws-genomics-static-${AWS_REGION}/*`,
`arn:aws:s3:::omics-${AWS_REGION}`,
`arn:aws:s3:::omics-${AWS_REGION}/*`,
],
});
omicsRole.addToPolicy(omicsRoleAdditionalPolicy);
return omicsRole;
}
new OmicsQuiltStack(app, 'omics-quilt', { env: { region: 'us-west-2' } });
// new DiaStack(app, 'vivos-prod', { env: prodEnv });
app.synth();
}

main();

0 comments on commit 0c24e46

Please sign in to comment.