Thumbnailer with AWS Lambda on containers
MODIFY
README.md
README.md
@@ -76,6 +76,7 @@ Example | Description |
[EKS - Hello World](aws-ts-eks-hello-world) | Deploy an EKS Kubernetes cluster with an EBS-backed StorageClass, then a Kubernetes namespace and nginx deployment into the cluster.
[EKS - Migrate Node Groups](aws-ts-migrate-nodegroups) | Create an EKS cluster and node group to use for workload migration with zero downtime.
[Fargate](aws-ts-hello-fargate) | Build, deploy, and run a Dockerized app using ECS, ECR, and Fargate.
+[Lambda Thumbnailer](aws-ts-lambda-thumbnailer) | Create a video thumbnail extractor using serverless functions.
[Miniflux](aws-ts-pulumi-miniflux) | Stand up an RSS Service using Fargate and RDS.
[Pulumi Webhooks](aws-ts-pulumi-webhooks) | Create a Pulumi `cloud.HttpEndpoint` that receives webhook events delivered by the Pulumi Service, then echos the event to Slack.
[RDS and Airflow](aws-ts-airflow) | Deploy a RDS Postgres instance and containerized Airflow.

MODIFY
aws-ts-eks-distro/index.ts
aws-ts-eks-distro/index.ts
-import * as pulumi from "@pulumi/pulumi";
+// Copyright 2016-2020, Pulumi Corporation. All rights reserved.
+
import * as aws from "@pulumi/aws";
import * as k8s from "@pulumi/kubernetes";
+import * as pulumi from "@pulumi/pulumi";
import * as eksdistro from "./eksdistro";
const store = new aws.s3.Bucket("kops-state-store");
@@ -15,7 +17,7 @@ const k8sProvider = new k8s.Provider("provider", { kubeconfig: cluster.kubeconfi
const pod = new k8s.core.v1.Pod("mypod", {
spec: {
containers: [{ name: "echo", image: "k8s.gcr.io/echoserver:1.4" }],
- }
+ },
}, { provider: k8sProvider });
export const kubeconfig = cluster.kubeconfig;

ADD
aws-ts-lambda-thumbnailer/Pulumi.yaml
+name: video-thumbnailer-lambda
+runtime: nodejs
+description: A video thumbnail extractor using serverless functions packaged with containers
+template:
+ config:
+ aws:region:
+ description: The AWS region to deploy into
+ default: us-west-2

ADD
aws-ts-lambda-thumbnailer/docker-ffmpeg-thumb/Dockerfile
+FROM amazon/aws-lambda-nodejs:12
+ARG FUNCTION_DIR="/var/task"
+
+# Install tar and xz
+RUN yum install tar xz unzip -y
+
+# Install awscli
+RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" -s
+RUN unzip -q awscliv2.zip
+RUN ./aws/install
+
+# Install ffmpeg
+RUN curl https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg.tar.xz -s
+RUN tar -xf ffmpeg.tar.xz
+RUN mv ffmpeg-4.3.1-amd64-static/ffmpeg /usr/bin
+
+# Create function directory
+RUN mkdir -p ${FUNCTION_DIR}
+
+# Copy handler function and package.json
+COPY index.js ${FUNCTION_DIR}
+
+# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
+CMD [ "index.handler" ]

ADD
aws-ts-lambda-thumbnailer/docker-ffmpeg-thumb/index.js
+'use strict';
+const { execSync } = require('child_process');
+
+function run(command) {
+ console.log(command);
+ const result = execSync(command, {stdio: 'inherit'});
+ if (result != null) {
+ console.log(result.toString());
+ }
+}
+
+exports.handler = async (event) => {
+ console.log("Video handler called");
+
+ if (!event.Records) {
+ return;
+ }
+
+ for (const record of event.Records) {
+ const fileName = record.s3.object.key;
+ const bucketName = record.s3.bucket.name;
+ const thumbnailFile = fileName.substring(0, fileName.indexOf("_")) + ".jpg";
+ const framePos = fileName.substring(fileName.indexOf("_")+1, fileName.indexOf(".")).replace("-", ":");
+
+ run(`aws s3 cp s3://${bucketName}/${fileName} /tmp/${fileName}`);
+ run(`ffmpeg -v error -i /tmp/${fileName} -ss ${framePos} -vframes 1 -f image2 -an -y /tmp/${thumbnailFile}`);
+ run(`aws s3 cp /tmp/${thumbnailFile} s3://${bucketName}/${thumbnailFile}`);
+
+ console.log(`*** New thumbnail: file ${fileName} was saved at ${record.eventTime}.`);
+ }
+};

ADD
aws-ts-lambda-thumbnailer/index.ts
+// Copyright 2016-2020, Pulumi Corporation. All rights reserved.
+
+import * as aws from "@pulumi/aws";
+import * as awsx from "@pulumi/awsx";
+
+// A bucket to store videos and thumbnails.
+const bucket = new aws.s3.Bucket("bucket");
+
+const image = awsx.ecr.buildAndPushImage("sampleapp", {
+ context: "./docker-ffmpeg-thumb",
+});
+const role = new aws.iam.Role("thumbnailerRole", {
+ assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ Service: "lambda.amazonaws.com" }),
+});
+new aws.iam.RolePolicyAttachment("lambdaFullAccess", {
+ role: role.name,
+ policyArn: aws.iam.ManagedPolicies.AWSLambdaFullAccess
+});
+
+const thumbnailer = new aws.lambda.Function("thumbnailer", {
+ packageType: "Image",
+ imageUri: image.imageValue,
+ role: role.arn,
+ timeout: 900,
+});
+
+// When a new video is uploaded, run the FFMPEG task on the video file.
+// Use the time index specified in the filename (e.g. cat_00-01.mp4 uses timestamp 00:01)
+bucket.onObjectCreated("onNewVideo", thumbnailer, { filterSuffix: ".mp4" });
+
+// Export the bucket name.
+export const bucketName = bucket.id;
+
+// When a new thumbnail is created, log a message.
+bucket.onObjectCreated("onNewThumbnail", new aws.lambda.CallbackFunction<aws.s3.BucketEvent, void>("onNewThumbnail", {
+ callback: async bucketArgs => {
+ console.log("onNewThumbnail called");
+ if (!bucketArgs.Records) {
+ return;
+ }
+
+ for (const record of bucketArgs.Records) {
+ console.log(`*** New thumbnail: file ${record.s3.object.key} was saved at ${record.eventTime}.`);
+ }
+ },
+ policies: [
+ aws.iam.ManagedPolicies.AWSLambdaFullAccess, // Provides wide access to "serverless" services (Dynamo, S3, etc.)
+ ],
+}), { filterSuffix: ".jpg" });

ADD
aws-ts-lambda-thumbnailer/package.json
+{
+ "name": "video-thumbnailer-lambda",
+ "version": "0.1.0",
+ "main": "index.js",
+ "dependencies": {
+ "@pulumi/pulumi": "^2.0.0",
+ "@pulumi/aws": "^3.17.0",
+ "@pulumi/awsx": "^0.20.0"
+ }
+}

ADD
aws-ts-lambda-thumbnailer/sample/cat.mp4
aws-ts-lambda-thumbnailer/sample/cat.mp4
Binary files /dev/null and b/aws-ts-lambda-thumbnailer/sample/cat.mp4 differ

Thumbnailer with AWS Lambda on containers (#850)