Skip to content

Commit 2fee454

Browse files
authored
Merge pull request #7 from Frameio/ks/DEVREL-589
Adds Backup to S3. Setup repo for many examples.
2 parents aa0c7ee + 09a7dea commit 2fee454

File tree

11 files changed

+268
-3
lines changed

11 files changed

+268
-3
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2018 Frame.io
3+
Copyright (c) 2020 Frame.io
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# frameio-custom-action-offload-to-s3
2+
3+
A Custom Actions workflow for backing up Frame.io assets to your own S3 bucket.
4+
5+
## Instructions for use
6+
7+
This repo contains sample code demonstrating a deployment of Custom Actions in the context of a serverless workflow.
8+
9+
The code is pre-formatted for insertion into two Lambda functions. An HTTP endpoint should be created in AWS API Gateway and configured as the Trigger for the initial Lambda.
10+
11+
Once the initial Lambda runs, it will gather data from the Frame.io API and invoke a second Lambda function which completes the upload to S3 using a buffered file write.
12+
13+
The benefit of this workflow is that a Frame.io customer does not have to run a persistent server to download assets and re-upload them to a backup system. This is accomplished automatically and cheaply with serverless technology.
14+
15+
## Requirements
16+
- Frame.io developer token
17+
- Node JS 12.x runtime
18+
- AWS account with the ability to set IAM permissions
19+
20+
This function requires two environment variables:
21+
- `FRAMEIO_TOKEN`: an API token with appropriate permissions to get assets from Frame.io
22+
- `BUCKET_NAME`: an AWS S3 bucket where files will be uploaded.
23+
24+
## Components
25+
- Frame.io Custom Action
26+
- AWS API Gateway
27+
- Two AWS Lambda functions
28+
- S3 (data is streamed to S3 using the [aws-sdk](https://aws.amazon.com/sdk-for-node-js/) [S3.putObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html) method)
29+
- First Lambda function (`lambda.js`) receive the Custom Actions event and parses the Frame.io asset.
30+
31+
## Optional enhancements
32+
33+
As written, this lambda function simply triggers an upload of a single asset. There are many ways it could be enhanced or optimized:
34+
35+
- Lambda functions are restricted to a maximum runtime of 15 minutes which limits the size of a file you can reliably upload. To upload files larger than 2-3 GBs, additional Lambdas could be spawned and tasked with performing one part of a distributed multi-part file upload.
36+
- AWS Step Functions provide an alternative and arguably better soluting to fan-out Lambdas for a large upload job.
37+
38+
The integration to Frame.io could be expanded to cover many other workflows:
39+
- The `fetchDataFromAPI()` function can be modified to target [proxy media files](https://support.frame.io/en/articles/13321-what-is-my-video-converted-to-when-it-s-uploaded), allowing you to incorporate Frame.io's media conversion pipeline into your backup workflow.
40+
- Instead of using Custom Actions, you could write your own integration to our API to back up many Projects or Folders worth of files. See the [Frame.io API Guides]](https://docs.frame.io/docs) for more details.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { fetchAsset, invokeLambda } = require('./modules/api');
2+
3+
exports.handler = async function (event, context) {
4+
// Save the X-ray Trace ID and and this Lambda's function name.
5+
// We'll pass them to our second 'file handler' Lambda.
6+
const firstLambdaTraceID = process.env._X_AMZN_TRACE_ID;
7+
const caller = context.functionName;
8+
9+
let id = JSON.parse(event.body).resource.id;
10+
let { url, name} = await fetchAsset(id);
11+
12+
try {
13+
await invokeLambda(caller, firstLambdaTraceID, url, name);
14+
let returnPayload = {
15+
statusCode: 202,
16+
body: JSON.stringify({
17+
'title': 'Job received',
18+
'description': `Your backup job for '${name}' has been triggered.`,
19+
'traceID': `${firstLambdaTraceID}`
20+
})
21+
};
22+
return returnPayload;
23+
} catch(err) {
24+
return (`Hit a problem: ${err.message}`);
25+
}
26+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const AWS = require('aws-sdk');
2+
const fetch = require('node-fetch');
3+
4+
async function fetchAsset (id) {
5+
const token = process.env.FRAMEIO_TOKEN;
6+
let url = `http://api.frame.io/v2/assets/${id}`;
7+
let requestOptions = {
8+
method: 'GET',
9+
headers: {
10+
'Content-Type': 'application/json',
11+
'Authorization': `Bearer ${token}`
12+
}
13+
};
14+
try {
15+
let response = await fetch(url, requestOptions);
16+
let result = await response.json();
17+
return { url: result.original, name: result.name };
18+
} catch(err) {
19+
return (`error: ${err}`);
20+
}
21+
}
22+
23+
function invokeLambda (caller, firstLambdaTraceID, url, name) {
24+
25+
const lambda = new AWS.Lambda();
26+
27+
let req = {
28+
FunctionName: 'kstone-custom-action-offload-to-s3-second-lambda',
29+
InvocationType: 'Event', // returns statusCode 202 on success. See invoke() SDK for info
30+
Payload: JSON.stringify({
31+
caller: caller,
32+
firstLambdaTraceID: firstLambdaTraceID,
33+
url: url,
34+
name: name })
35+
};
36+
37+
return lambda.invoke(req).promise();
38+
}
39+
40+
async function s3Uploader(url, name) {
41+
42+
const s3 = new AWS.S3();
43+
44+
let response = await fetch(url);
45+
let stream = response.body;
46+
47+
const params = {
48+
Bucket: process.env.BUCKET_NAME,
49+
Key: name,
50+
Body: stream
51+
};
52+
53+
return (s3.upload(params).promise());
54+
}
55+
56+
module.exports = { fetchAsset, invokeLambda, s3Uploader };
Binary file not shown.

examples/custom-action-offload-to-s3/node-fetch-layer/package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "node-fetch-layer",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"build": "npm install && mkdir -p nodejs && cp -r node_modules nodejs/ && zip -r node-fetch-layer.zip nodejs"
8+
},
9+
"keywords": [],
10+
"author": "",
11+
"license": "ISC",
12+
"dependencies": {
13+
"node-fetch": "^2.6.0"
14+
}
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const { s3Uploader } = require('./modules/api');
2+
3+
exports.handler = async (event) => {
4+
let { caller, firstLambdaTraceID, url, name } = event;
5+
6+
// Logs for convenient searching in X-Ray and CloudWatch
7+
console.log(`Second Lambda trace ID: ${process.env._X_AMZN_TRACE_ID}`);
8+
console.log(`Called by ${caller} with trace ID: ${firstLambdaTraceID}. Begin uploading ${name}...`);
9+
10+
try {
11+
await s3Uploader(url, name);
12+
} catch(err) {
13+
return (`error: ${err}`);
14+
}
15+
16+
return (console.log(`Done uploading ${name}!`));
17+
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{
2+
"resource": "/kstone-custom-action-offload-to-s3",
3+
"path": "/kstone-custom-action-offload-to-s3",
4+
"httpMethod": "POST",
5+
"headers": {
6+
"content-type": "application/json",
7+
"Host": "188hrg8gxb.execute-api.us-east-2.amazonaws.com",
8+
"User-Agent": "Frame.io API",
9+
"X-Amzn-Trace-Id": "Root=1-5f4ad89b-b52895fe5e608f091fb6b7d8",
10+
"X-Forwarded-For": "52.44.243.35",
11+
"X-Forwarded-Port": "443",
12+
"X-Forwarded-Proto": "https",
13+
"x-frameio-request-timestamp": "1598740635",
14+
"x-frameio-signature": "v0=fdd82f426536c5a927f29eeab7e01e177c4c0ca0a33f2827817c32e530fddaa2"
15+
},
16+
"multiValueHeaders": {
17+
"content-type": [
18+
"application/json"
19+
],
20+
"Host": [
21+
"188hrg8gxb.execute-api.us-east-2.amazonaws.com"
22+
],
23+
"User-Agent": [
24+
"Frame.io API"
25+
],
26+
"X-Amzn-Trace-Id": [
27+
"Root=1-5f4ad89b-b52895fe5e608f091fb6b7d8"
28+
],
29+
"X-Forwarded-For": [
30+
"52.44.243.35"
31+
],
32+
"X-Forwarded-Port": [
33+
"443"
34+
],
35+
"X-Forwarded-Proto": [
36+
"https"
37+
],
38+
"x-frameio-request-timestamp": [
39+
"1598740635"
40+
],
41+
"x-frameio-signature": [
42+
"v0=fdd82f426536c5a927f29eeab7e01e177c4c0ca0a33f2827817c32e530fddaa2"
43+
]
44+
},
45+
"queryStringParameters": null,
46+
"multiValueQueryStringParameters": null,
47+
"pathParameters": null,
48+
"stageVariables": null,
49+
"requestContext": {
50+
"resourceId": "51f4b5",
51+
"resourcePath": "/kstone-custom-action-offload-to-s3",
52+
"httpMethod": "POST",
53+
"extendedRequestId": "SDbIUGgdCYcF33A=",
54+
"requestTime": "29/Aug/2020:22:37:15 +0000",
55+
"path": "/default/kstone-custom-action-offload-to-s3",
56+
"accountId": "720872555379",
57+
"protocol": "HTTP/1.1",
58+
"stage": "default",
59+
"domainPrefix": "188hrg8gxb",
60+
"requestTimeEpoch": 1598740635596,
61+
"requestId": "eeb69611-0501-4d2f-89fd-be9b8fce3e64",
62+
"identity": {
63+
"cognitoIdentityPoolId": null,
64+
"accountId": null,
65+
"cognitoIdentityId": null,
66+
"caller": null,
67+
"sourceIp": "52.44.243.35",
68+
"principalOrgId": null,
69+
"accessKey": null,
70+
"cognitoAuthenticationType": null,
71+
"cognitoAuthenticationProvider": null,
72+
"userArn": null,
73+
"userAgent": "Frame.io API",
74+
"user": null
75+
},
76+
"domainName": "188hrg8gxb.execute-api.us-east-2.amazonaws.com",
77+
"apiId": "188hrg8gxb"
78+
},
79+
"body": {
80+
"action_id": "44f40b4a-65e9-479a-b2bf-40ec57111c88",
81+
"interaction_id": "706e2345-14d7-4784-9e76-81a42e206915",
82+
"project": {
83+
"id": "5065bd0f-7d61-48f2-beec-7bc4313d0b23"
84+
},
85+
"resource": {
86+
"id": "ce611de8-79b8-4bf5-bdf3-0b5a72f7dbb6",
87+
"type": "asset"
88+
},
89+
"team": {
90+
"id": "aa891687-4b1e-4150-9b6d-9e4911c5b436"
91+
},
92+
"type": "custom.action.demo",
93+
"user": {
94+
"id": "59c9ade1-311b-4c3b-8231-b9d88e9a1a85"
95+
}
96+
},
97+
"isBase64Encoded": false
98+
}
File renamed without changes.

0 commit comments

Comments
 (0)