From 839021bbdb149fd577d25461af255afc2160c6d2 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 15 Jul 2024 13:09:58 +0200 Subject: [PATCH 1/6] s3-trigger-fargate-task pattern --- s3-eventbridge-fargate-cdk/.gitignore | 10 ++ s3-eventbridge-fargate-cdk/.npmignore | 6 + s3-eventbridge-fargate-cdk/README.md | 83 +++++++++ s3-eventbridge-fargate-cdk/cdk.json | 65 +++++++ s3-eventbridge-fargate-cdk/doc/archi.drawio | 1 + s3-eventbridge-fargate-cdk/doc/archi.png | Bin 0 -> 24937 bytes .../docker/doc-ingestion/Dockerfile | 12 ++ .../docker/doc-ingestion/app.py | 18 ++ .../docker/doc-ingestion/requirements.txt | 2 + s3-eventbridge-fargate-cdk/jest.config.js | 8 + s3-eventbridge-fargate-cdk/package.json | 27 +++ .../src/s3-trigger-fargate-task-stack.ts | 167 ++++++++++++++++++ .../src/s3-trigger-fargate-task.ts | 8 + s3-eventbridge-fargate-cdk/tsconfig.json | 31 ++++ 14 files changed, 438 insertions(+) create mode 100644 s3-eventbridge-fargate-cdk/.gitignore create mode 100644 s3-eventbridge-fargate-cdk/.npmignore create mode 100644 s3-eventbridge-fargate-cdk/README.md create mode 100644 s3-eventbridge-fargate-cdk/cdk.json create mode 100644 s3-eventbridge-fargate-cdk/doc/archi.drawio create mode 100644 s3-eventbridge-fargate-cdk/doc/archi.png create mode 100644 s3-eventbridge-fargate-cdk/docker/doc-ingestion/Dockerfile create mode 100644 s3-eventbridge-fargate-cdk/docker/doc-ingestion/app.py create mode 100644 s3-eventbridge-fargate-cdk/docker/doc-ingestion/requirements.txt create mode 100644 s3-eventbridge-fargate-cdk/jest.config.js create mode 100644 s3-eventbridge-fargate-cdk/package.json create mode 100644 s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task-stack.ts create mode 100644 s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task.ts create mode 100644 s3-eventbridge-fargate-cdk/tsconfig.json diff --git a/s3-eventbridge-fargate-cdk/.gitignore b/s3-eventbridge-fargate-cdk/.gitignore new file mode 100644 index 000000000..a05e3ebb3 --- /dev/null +++ b/s3-eventbridge-fargate-cdk/.gitignore @@ -0,0 +1,10 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out + +.idea \ No newline at end of file diff --git a/s3-eventbridge-fargate-cdk/.npmignore b/s3-eventbridge-fargate-cdk/.npmignore new file mode 100644 index 000000000..c1d6d45dc --- /dev/null +++ b/s3-eventbridge-fargate-cdk/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/s3-eventbridge-fargate-cdk/README.md b/s3-eventbridge-fargate-cdk/README.md new file mode 100644 index 000000000..94d48a0b3 --- /dev/null +++ b/s3-eventbridge-fargate-cdk/README.md @@ -0,0 +1,83 @@ +# Amazon S3 to AWS Fargate + +This pattern demonstrates how to trigger an AWS Fargate task when an object is uploaded to Amazon S3. +This pattern is commonly implemented with an AWS Lambda function, but this is not always possible: +- Processing > 15 min +- Docker image > 10G +- GPU required + +When you need more (more time, more memory, more power), but still want to use serverless service, you can look at Fargate. + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [AWS Cloud Development Kit](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) (AWS CDK) installed + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + ``` + git clone https://github.com/aws-samples/serverless-patterns + ``` +2. Change directory to the pattern directory: + ``` + cd serverless-patterns/s3-teventbridge-fargate-cdk + ``` +3. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file: + ``` + npm install && npx cdk deploy + ``` +4. During the prompts: + * Allow CDK to create resources and IAM roles with the required permissions. + +5. Note the outputs from the CDK deployment process. These contain the resource names and/or ARNs which are used for testing. + +## How it works + +![Architecture](doc/archi.png) + +1. When a file is uploaded to S3, in the specified bucket (eventually with a specific prefix), an event is triggered +2. EventBridge catch this event and triggers the ECS Fargate task +3. Fargate bootstrap a container to run the task + +## Testing + +1. Once CDK / CloudFormation deployed the stack, upload a file to S3 using the following command. + (replace `your-bucket-name` with the CDK output `S3TriggerFargateTaskStack.IngestionBucket`) : + +```shell + aws s3api put-object --bucket your-bucket-name --key file-name --body file-name +``` + +2. The S3 file upload will trigger the EventBridge Rule which will run the ECS task. The ECS task will execute and print the content of the document in CloudWatch. + +3. Check the CloudWatch log group to see the ECS task execution details. Logs can be found in `/ecs/doc-ingestion` log stream. +Use the following commands to get the logs: + +```shell +# get log streams +aws logs describe-log-streams --log-group-name /ecs/doc-ingestion +# use the latest log stream name in the next command to get logs +aws logs get-log-events --log-group-name /ecs/doc-ingestion --log-stream-name doc-ingestion-logs/DocIngestion/... +``` + +4. You should see the content of your file in the logs. + +## Cleanup + +1. Delete the stack + ```bash + npx cdk destroy + ``` +2. Confirm the stack has been deleted + ```bash + aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'S3TriggerFargateTaskStack')].StackStatus" + ``` +---- +Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/s3-eventbridge-fargate-cdk/cdk.json b/s3-eventbridge-fargate-cdk/cdk.json new file mode 100644 index 000000000..259b4b6ab --- /dev/null +++ b/s3-eventbridge-fargate-cdk/cdk.json @@ -0,0 +1,65 @@ +{ + "app": "npx ts-node --prefer-ts-exts src/s3-trigger-fargate-task.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true + } +} diff --git a/s3-eventbridge-fargate-cdk/doc/archi.drawio b/s3-eventbridge-fargate-cdk/doc/archi.drawio new file mode 100644 index 000000000..4a20a6c9e --- /dev/null +++ b/s3-eventbridge-fargate-cdk/doc/archi.drawio @@ -0,0 +1 @@ +7Vptd5owFP41fpwHCAh+LKjd69nO3DnbPu2kEiErEBei1f763UBAMNTVM/qyWauWPHnhJs997g1pByhIt5ccr+IPLCTJwDLC7QBNBpY1tkfwLYFdCYxcqwQiTsMSMvfAnN4SBRoKXdOQ5K2GgrFE0FUbXLAsIwvRwjDn7KbdbMmS9l1XOCIaMF/gREe/0lDEJepZ7h5/TWgUV3c2R+OyJsVVYzWTPMYhu2lAaDpAAWdMlFfpNiCJXLtqXcp+sztqa8M4ycR9OsxFaH3z32a/bj8a32eX7y5fkY+vbGWb2FUTJiHMXxUZFzGLWIaT6R71OVtnIZGjGlDat3nP2ApAE8CfRIidIhOvBQMoFmmiasFgvvum+heF77IwdKriZNusnOyapU+E05QIwhVYTkBafee6KChna74gRxaj8i/MIyKOtEM1e+D1hIE1fAf9OEmwoJu2HVj5X1S321MEF4qlExhT425wslZ30ijMr4lYxGp9VoxmojDC8eENPhKUHweaBhIZWk4H2IW5OmjqzeCX2XWHQ7ALc3XQ1JvJUmV1G+zCXEe3+LC32dHbPOgNb3D3tUhoRoI63Mg1XrJMBCxhvFh/BD8zyagfcRxS0qobGRcIuY26CeUwEGUZ1GdSTHI8miTN8VwX4grgueDsmjRqlsULakKcx7UkN4QLCiHsPb4iySeWUzX8FROCpY0GFwmNZIWQwvWxKi3AKimvpmTlDJWeTasqK4+Tt8T5qlyOJd1KO3yIditZmW4jmReG+Ca3h5yUGnyzkPb4UCyv2q1yVMtaGkq2x4WtC7HqMFKRd3dQvtkHctcrobgRwyusd+lamnQvUnwLC2AZc6SpGCYu2iRoBB3ymNIwLEM0yektvqrpaQcB6ccyKueKUM2zMpaRAzdUUB+0OOMWLRbyNFo8Q6elyqO90zJ6ihzYY9pC90xbzlOmLfSStv7ltDWb2TPPPy1t+YGJnPNJW2QDBlzBQ0zUU6BEzy1/OS8a/qc17HpTwz5NwxPDCUz3bDS8hByKRU/6dZ6bft0T9ftXDtexhyzyyHhsGHdtOJ+dKxURjPCpDO15tVXuci+yyH8InF/34zkj58+eg4yhh3TnsR9ql+zd/fBSLI9fZr5zeYpByP7jU4yJHvMxpjoxfVp5q23ifyZvvk7Ij56OJWz3QNyO7jm2M/Q6MsODids89VTxxXfunxo2hWP04Tr1ucgx1+mIOQ/nN0eOtKbBHL5nakd1LnnBOcwLtp66Hzkv6EcvhdOeDSWW67RV00GJ86iM2BojnyHBnA0hWgbsIKQrjD0cIfrJxhe5mT8XQkaw37Ato36ZbXo6trY90QPF/R/Ai7rGfxGg6W8= \ No newline at end of file diff --git a/s3-eventbridge-fargate-cdk/doc/archi.png b/s3-eventbridge-fargate-cdk/doc/archi.png new file mode 100644 index 0000000000000000000000000000000000000000..c54f47b829b402d00e3eb71985e812b81ad6f8b3 GIT binary patch literal 24937 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/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; diff --git a/s3-eventbridge-fargate-cdk/package.json b/s3-eventbridge-fargate-cdk/package.json new file mode 100644 index 000000000..63550ef57 --- /dev/null +++ b/s3-eventbridge-fargate-cdk/package.json @@ -0,0 +1,27 @@ +{ + "name": "s3-trigger-fargate-task", + "version": "0.1.0", + "bin": { + "s3-trigger-fargate-task": "src/s3-trigger-fargate-task.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.11", + "@types/node": "20.11.14", + "jest": "^29.7.0", + "ts-jest": "^29.1.2", + "aws-cdk": "2.126.0", + "ts-node": "^10.9.2", + "typescript": "~5.3.3" + }, + "dependencies": { + "aws-cdk-lib": "2.126.0", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + } +} \ No newline at end of file diff --git a/s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task-stack.ts b/s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task-stack.ts new file mode 100644 index 000000000..59c220c1d --- /dev/null +++ b/s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task-stack.ts @@ -0,0 +1,167 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import { RetentionDays } from 'aws-cdk-lib/aws-logs'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from "aws-cdk-lib/aws-events-targets"; +import * as path from "node:path"; +import { DockerImageAsset } from "aws-cdk-lib/aws-ecr-assets"; +import { CfnOutput, Duration } from "aws-cdk-lib"; + +export class S3TriggerFargateTaskStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Ingestion bucket + const bucket = new s3.Bucket(this, 'IngestionBucket', { + eventBridgeEnabled: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + enforceSSL: true, + publicReadAccess: false + }); + + // VPC for the ECS Cluster + const vpc = new ec2.Vpc(this, "VPC", { + maxAzs: 2, + natGateways: 1, + subnetConfiguration: [ + { + cidrMask: 24, + name: "public", + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: "private", + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // S3 Gateway Endpoint to avoid accessing S3 from Internet + const s3GatewayEndpoint = vpc.addGatewayEndpoint('S3GatewayEndpoint', { + service: ec2.GatewayVpcEndpointAwsService.S3 + }); + + // IAM policy to only enable access to S3 through vpc endpoint (OPTIONAL) + const bucketDenyPolicy = new iam.PolicyStatement({ + effect: iam.Effect.DENY, + principals: [new iam.AnyPrincipal()], + actions: ['s3:GetObject'], // adapt to your security requirements + resources: [ + `arn:aws:s3:::${bucket.bucketName}`, + `arn:aws:s3:::${bucket.bucketName}/*`, + ], + conditions: { + 'StringNotEquals': { + 'aws:SourceVpce': s3GatewayEndpoint.vpcEndpointId, + }, + }, + }); + bucket.addToResourcePolicy(bucketDenyPolicy); + + const cluster = new ecs.Cluster(this, "Cluster", { + vpc: vpc, + }); + + // ECS Task definition + const taskDefinition = new ecs.FargateTaskDefinition(this, "TaskDefinition", { + cpu: 512, + memoryLimitMiB: 2048, + runtimePlatform: { + cpuArchitecture: ecs.CpuArchitecture.X86_64, + operatingSystemFamily: ecs.OperatingSystemFamily.LINUX + }, + }); + + // Container logs + const ecsLogGroup = new logs.LogGroup(this, 'ContainerLogGroup', { + logGroupName: '/ecs/doc-ingestion', + removalPolicy: cdk.RemovalPolicy.DESTROY, + retention: RetentionDays.FIVE_DAYS + }); + + // Container image + const container = taskDefinition.addContainer('DocIngestion', { + image: ecs.ContainerImage.fromDockerImageAsset(new DockerImageAsset(this, 'DocIngestionImage', { + directory: path.join(__dirname, '../docker/doc-ingestion'), + })), + logging: new ecs.AwsLogDriver({ streamPrefix: 'doc-ingestion-logs', logGroup: ecsLogGroup}), + }); + + // Define the ECS Task as a target for the EventBridge rule + // We extract information from the event and store them in environment variables + const ecsTask = new targets.EcsTask({ + taskDefinition, + cluster, + containerOverrides: [ + { + containerName: container.containerName, + environment: [ + { + name: 'S3_BUCKET', + value: events.EventField.fromPath("$.detail.bucket.name"), + }, + { + name: 'S3_OBJECT_KEY', + value:events. EventField.fromPath("$.detail.object.key"), + } + ] + } + ], + maxEventAge: Duration.hours(2), + retryAttempts: 3 + }); + + // EventBridge rule definition + const rule = new events.Rule(this, 'rule', { + eventPattern: { + source: ['aws.s3'], + detailType: [ + 'Object Created' + ], + detail: { + bucket: { + name: [ + bucket.bucketName + ] + }, + // OPTIONAL: if there is a specific prefix to watch for + // object: { + // key: [{ + // prefix: "prefix" + // }] + // } + } + }, + targets: [ + ecsTask, + // just for debugging purpose, log events in cloudwatch + // new targets.CloudWatchLogGroup(new logs.LogGroup(this, 'RuleLogs', { + // logGroupName: '/aws/events/doc-ingestion', + // removalPolicy: cdk.RemovalPolicy.DESTROY, + // retention: RetentionDays.FIVE_DAYS + // })) + ] + }); + + // Allow ECS task to access the bucket + bucket.grantRead(taskDefinition.taskRole); + + new CfnOutput(this, 'IngestionBucketOut', { + key: 'IngestionBucket', + value: bucket.bucketName + }); + + new CfnOutput(this, 'ECSLogsOut', { + key: 'ECSLogs', + value: ecsLogGroup.logGroupName + }); + } +} diff --git a/s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task.ts b/s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task.ts new file mode 100644 index 000000000..ac0360242 --- /dev/null +++ b/s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task.ts @@ -0,0 +1,8 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { S3TriggerFargateTaskStack } from './s3-trigger-fargate-task-stack'; + +const app = new cdk.App(); +new S3TriggerFargateTaskStack(app, 'S3TriggerFargateTaskStack', { +}); \ No newline at end of file diff --git a/s3-eventbridge-fargate-cdk/tsconfig.json b/s3-eventbridge-fargate-cdk/tsconfig.json new file mode 100644 index 000000000..aaa7dc510 --- /dev/null +++ b/s3-eventbridge-fargate-cdk/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} From d287c7b58d96ddc5525cd995ce336ef0ee0287f6 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 15 Jul 2024 13:27:06 +0200 Subject: [PATCH 2/6] pattern.json file --- s3-eventbridge-fargate-cdk/pattern.json | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 s3-eventbridge-fargate-cdk/pattern.json diff --git a/s3-eventbridge-fargate-cdk/pattern.json b/s3-eventbridge-fargate-cdk/pattern.json new file mode 100644 index 000000000..fda556685 --- /dev/null +++ b/s3-eventbridge-fargate-cdk/pattern.json @@ -0,0 +1,62 @@ +{ + "title": "Amazon S3 to AWS Fargate", + "description": "Trigger an AWS Fargate Task when a file is uploaded to S3", + "language": "Typescript", + "level": "200", + "framework": "CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern demonstrates how to trigger an AWS Fargate task when an object is uploaded to Amazon S3.", + "This pattern is commonly implemented with an AWS Lambda function, but this is not always possible:", + " - Processing > 15 min", + " - Docker image > 10G", + " - GPU required", + "Thanks to EventBridge, S3 events can be used to trigger an ECS/Fargate task." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/s3-eventbridge-fargate-cdk", + "templateURL": "serverless-patterns/s3-eventbridge-fargate-cdk", + "projectFolder": "s3-eventbridge-fargate-cdk", + "templateFile": "src/s3-trigger-fargate-task-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Using EventBridge to handle S3 events", + "link": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventBridge.html" + }, + { + "text": "ECS Task as a target for EventBridge", + "link": "https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-targets.html#targets-specifics-ecs-task" + } + ] + }, + "deploy": { + "text": [ + "npm install && cdk deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: cdk destroy." + ] + }, + "authors": [ + { + "name": "Jerome Van Der Linden", + "image": "https://serverlessland.com/assets/images/resources/contributors/jerome-van-der-linden.jpg", + "bio": "Jerome is a Solutions Architect Builder at AWS. Passionate about building stuff using the AWS services, and especially the serverless ones.", + "linkedin": "jeromevdl", + "twitter": "jeromevdl" + } + ] +} \ No newline at end of file From ec032adca6a4c8e55086899de500fb09ed24f6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Van=20Der=20Linden?= <117538+jeromevdl@users.noreply.github.com> Date: Tue, 22 Oct 2024 08:30:56 +0200 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: ellisms <114107920+ellisms@users.noreply.github.com> --- s3-eventbridge-fargate-cdk/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/s3-eventbridge-fargate-cdk/README.md b/s3-eventbridge-fargate-cdk/README.md index 94d48a0b3..37f2c568f 100644 --- a/s3-eventbridge-fargate-cdk/README.md +++ b/s3-eventbridge-fargate-cdk/README.md @@ -1,6 +1,6 @@ # Amazon S3 to AWS Fargate -This pattern demonstrates how to trigger an AWS Fargate task when an object is uploaded to Amazon S3. +This pattern demonstrates how to invoke an AWS Fargate task when an object is uploaded to Amazon S3. This pattern is commonly implemented with an AWS Lambda function, but this is not always possible: - Processing > 15 min - Docker image > 10G @@ -65,7 +65,7 @@ aws logs describe-log-streams --log-group-name /ecs/doc-ingestion aws logs get-log-events --log-group-name /ecs/doc-ingestion --log-stream-name doc-ingestion-logs/DocIngestion/... ``` -4. You should see the content of your file in the logs. +4. The file contents are displayed in the log. ## Cleanup From 03e9403235e70d055d1728a8bffde0ab6c29f20e Mon Sep 17 00:00:00 2001 From: ellisms <114107920+ellisms@users.noreply.github.com> Date: Tue, 22 Oct 2024 08:25:02 -0400 Subject: [PATCH 4/6] fix typo --- s3-eventbridge-fargate-cdk/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/s3-eventbridge-fargate-cdk/README.md b/s3-eventbridge-fargate-cdk/README.md index 37f2c568f..f2ac220db 100644 --- a/s3-eventbridge-fargate-cdk/README.md +++ b/s3-eventbridge-fargate-cdk/README.md @@ -25,7 +25,7 @@ Important: this application uses various AWS services and there are costs associ ``` 2. Change directory to the pattern directory: ``` - cd serverless-patterns/s3-teventbridge-fargate-cdk + cd serverless-patterns/s3-eventbridge-fargate-cdk ``` 3. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file: ``` From 60acace95ee0f07d3ad7d01710dbb2f315c8801b Mon Sep 17 00:00:00 2001 From: ellisms <114107920+ellisms@users.noreply.github.com> Date: Tue, 22 Oct 2024 08:25:56 -0400 Subject: [PATCH 5/6] tagging --- s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task.ts b/s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task.ts index ac0360242..108cbde7e 100644 --- a/s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task.ts +++ b/s3-eventbridge-fargate-cdk/src/s3-trigger-fargate-task.ts @@ -2,7 +2,7 @@ import 'source-map-support/register'; import * as cdk from 'aws-cdk-lib'; import { S3TriggerFargateTaskStack } from './s3-trigger-fargate-task-stack'; - +const description = "S3-Fargate Pattern (uksb-1tthgi812) (tag:s3-eventbridge-fargate-cdk)"; const app = new cdk.App(); -new S3TriggerFargateTaskStack(app, 'S3TriggerFargateTaskStack', { -}); \ No newline at end of file +new S3TriggerFargateTaskStack(app, 'S3TriggerFargateTaskStack', {description:description +}); From 98d1b6638512e02c2bf5c00ed570f51b88ece205 Mon Sep 17 00:00:00 2001 From: ellisms <114107920+ellisms@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:04:28 -0400 Subject: [PATCH 6/6] publishing file --- .../s3-eventbirdge-fargate-cdk.json | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 s3-eventbridge-fargate-cdk/s3-eventbirdge-fargate-cdk.json diff --git a/s3-eventbridge-fargate-cdk/s3-eventbirdge-fargate-cdk.json b/s3-eventbridge-fargate-cdk/s3-eventbirdge-fargate-cdk.json new file mode 100644 index 000000000..38376b7ad --- /dev/null +++ b/s3-eventbridge-fargate-cdk/s3-eventbirdge-fargate-cdk.json @@ -0,0 +1,92 @@ +{ + "title": "Amazon S3 to AWS Fargate", + "description": "Start an AWS Fargate Task when a file is uploaded to S3", + "language": "TypeScript", + "level": "200", + "framework": "CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern demonstrates how to start an AWS Fargate task when an object is uploaded to Amazon S3.", + "This pattern is commonly implemented with an AWS Lambda function, but this is not always possible:", + " - Processing > 15 min", + " - Docker image > 10G", + " - GPU required", + "Thanks to Amazon EventBridge, S3 events can start an ECS/Fargate task." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/s3-eventbridge-fargate-cdk", + "templateURL": "serverless-patterns/s3-eventbridge-fargate-cdk/src", + "projectFolder": "s3-eventbridge-fargate-cdk", + "templateFile": "src/s3-trigger-fargate-task-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Using EventBridge to handle S3 events", + "link": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventBridge.html" + }, + { + "text": "ECS Task as a target for EventBridge", + "link": "https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-targets.html#targets-specifics-ecs-task" + } + ] + }, + "deploy": { + "text": [ + "npm install && cdk deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: cdk destroy." + ] + }, + "authors": [ + { + "name": "Jerome Van Der Linden", + "image": "https://serverlessland.com/assets/images/resources/contributors/jerome-van-der-linden.jpg", + "bio": "Jerome is a Solutions Architect Builder at AWS. Passionate about building stuff using the AWS services, and especially the serverless ones.", + "linkedin": "jeromevdl", + "twitter": "jeromevdl" + } + ], + "patternArch": { + "icon1": { + "x": 20, + "y": 50, + "service": "s3", + "label": "Amazon S3" + }, + "icon2": { + "x": 50, + "y": 50, + "service": "eventbridge", + "label": "Amazon EventBridge" + }, + "icon3": { + "x": 80, + "y": 50, + "service": "fargate", + "label": "AWS Fargate" + }, + "line1": { + "from": "icon1", + "to": "icon2", + "label": "Event" + }, + "line2": { + "from": "icon2", + "to": "icon3", + "label": "Rule" + } + } +}