├── .gitignore ├── python ├── sns │ ├── pythonInvoke.sh │ ├── subscribeViaEmail.py │ └── publishToSNS.py ├── lambda │ └── checkServers.py └── sns-python.cfm ├── iamTestApp ├── application.cfc └── index.cfm ├── iamPolicies ├── snsSendMessage.txt └── awsPlayboxPrivateReadWrite.txt ├── nodejs └── lambda │ ├── generateRandomNumber.js │ ├── transcribeTranslateExample │ ├── prepTranslatedTextForSpeech.js │ ├── checkTranscribeJobStatus.js │ ├── startTranscribeJob.js │ ├── getTranscriptionFile.js │ ├── translateText.js │ └── convertTextToSpeech.js │ ├── returnDataToCaller.js │ ├── detectLabelsForImage.js │ └── checkForLargeFileUploads.js ├── assets └── styles.css ├── model ├── awsCredentials.cfc ├── dynamoItemMaker.cfc ├── awsServiceFactory.cfc └── rekognitionLib.cfc ├── application.cfc ├── stateMachines ├── choiceDemoStateMachine.json └── transcribeTranslateSpeakWorkflow.json ├── lambda.cfm ├── index.cfm ├── sns.cfm ├── dynamodb.cfm ├── showSourceCode.cfm ├── README.md ├── translate.cfm ├── rekognition.cfm ├── stepFunctions.cfm ├── transcribe.cfm ├── s3.cfm └── iam.cfm /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .DS_Store -------------------------------------------------------------------------------- /python/sns/pythonInvoke.sh: -------------------------------------------------------------------------------- 1 | ################## 2 | ## First arg = Python file to execute 3 | ################## 4 | 5 | #! /bin/bash 6 | 7 | python $1 -------------------------------------------------------------------------------- /iamTestApp/application.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | this.name = 'awsPlayboxIAMTest'; 4 | this.applicationTimeout = CreateTimeSpan(0, 0, 15, 0); 5 | this.sessionManagement = false; 6 | 7 | } -------------------------------------------------------------------------------- /iamPolicies/snsSendMessage.txt: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "AllowsPublishToOneTopic", 6 | "Effect": "Allow", 7 | "Action": "sns:Publish", 8 | "Resource": "%CURRENT_TOPIC_ARN%" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /nodejs/lambda/generateRandomNumber.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.handler = (event, context, callback) => { 4 | //console.log('Received event:', JSON.stringify(event, null, 2)); 5 | var min = event.min ? event.min : 1; 6 | var max = event.max ? event.max : 100; 7 | var result = Math.floor(Math.random() * (max - min)) + min; 8 | callback(null, result); 9 | }; 10 | -------------------------------------------------------------------------------- /python/sns/subscribeViaEmail.py: -------------------------------------------------------------------------------- 1 | import boto.sns 2 | import logging 3 | 4 | logging.basicConfig(filename="subscribeViaEmail.log", level=logging.DEBUG) 5 | 6 | snsConnection = boto.sns.connect_to_region("us-east-1") 7 | 8 | topicARN = "YOUR TOPIC ARN GOES HERE" 9 | emailAddress = "YOUR EMAIL ADDRESS GOES HERE" 10 | 11 | subscription = snsConnection.subscribe(topicARN, "email", emailAddress) 12 | 13 | print subscription -------------------------------------------------------------------------------- /iamPolicies/awsPlayboxPrivateReadWrite.txt: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "AllowAccessToSpecificBucket", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "s3:PutObject", 9 | "s3:GetObjectAcl", 10 | "s3:GetObject", 11 | "s3:ListBucket", 12 | "s3:GetBucketAcl", 13 | "s3:DeleteObject" 14 | ], 15 | "Resource": [ 16 | "arn:aws:s3:::awsplayboxprivatebucket", 17 | "arn:aws:s3:::awsplayboxprivatebucket/*" 18 | ] 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /python/sns/publishToSNS.py: -------------------------------------------------------------------------------- 1 | import boto.sns 2 | from datetime import datetime 3 | import logging 4 | 5 | logging.basicConfig(filename="publishToSNS.log", level=logging.DEBUG) 6 | 7 | now = datetime.now() 8 | 9 | snsConnection = boto.sns.connect_to_region("us-east-1") 10 | 11 | topicARN = "YOUR TOPIC ARN GOES HERE" 12 | message = "Did you know that today is " + now.strftime("%A %B %d, %Y") + "?" 13 | # Note that in SMS messages, all you send is the subject, which must be less than 100 characters 14 | message_subject = "Hello from Python at " + now.strftime("%-I:%M%p") + "!" 15 | 16 | result = snsConnection.publish(topicARN, message, subject=message_subject) 17 | 18 | print result -------------------------------------------------------------------------------- /nodejs/lambda/transcribeTranslateExample/prepTranslatedTextForSpeech.js: -------------------------------------------------------------------------------- 1 | // This function simply translates variable names between the cfdemoTranslateText and cfDemoConvertTextToSpeech functions because Step Functions states language can't do that. 2 | 3 | exports.handler = (event, context, callback) => { 4 | 5 | var textToSpeak = event.translatedText; 6 | var languageOfText = event.languageOfText; 7 | var transcriptFileName = event.sourceTranscriptFileName; 8 | 9 | var returnData = { 10 | textToSpeak: textToSpeak, 11 | languageOfText: languageOfText, 12 | transcriptFileName: transcriptFileName 13 | } 14 | 15 | callback(null, returnData); 16 | }; -------------------------------------------------------------------------------- /nodejs/lambda/returnDataToCaller.js: -------------------------------------------------------------------------------- 1 | exports.handler = (event, context, callback) => { 2 | var resultString = ""; 3 | var timestamp = new Date(); 4 | 5 | console.log('firstName =', event.firstName); 6 | console.log('lastName =', event.lastName); 7 | console.log('email =', event.email); 8 | 9 | for (var i=0; i < event.classes.length; i++) { 10 | console.log("In " + event.classes[i].courseNumber + ", your role is " + event.classes[i].role); 11 | } 12 | 13 | resultString = "Hello " + event.firstName + " " + event.lastName + 14 | ". As of " + timestamp + ", you are currently enrolled in " + 15 | event.classes.length + " courses."; 16 | callback(null, resultString); 17 | }; -------------------------------------------------------------------------------- /nodejs/lambda/detectLabelsForImage.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const AWS = require('aws-sdk'); 3 | const rekognition = new AWS.Rekognition(); 4 | 5 | exports.handler = (event, context, callback) => { 6 | console.log("Reading input from event:\n", util.inspect(event, {depth: 5})); 7 | 8 | const srcBucket = event.s3Bucket; 9 | // Object key may have spaces or unicode non-ASCII characters. 10 | const srcKey = decodeURIComponent(event.s3Key.replace(/\+/g, " ")); 11 | 12 | var params = { 13 | Image: { 14 | S3Object: { 15 | Bucket: srcBucket, 16 | Name: srcKey 17 | } 18 | }, 19 | MaxLabels: 10, 20 | MinConfidence: 70 21 | }; 22 | 23 | rekognition.detectLabels(params).promise().then(function (data) { 24 | data.s3Bucket = srcBucket; 25 | data.s3Key = srcKey; 26 | callback(null, data); 27 | }).catch(function (err) { 28 | callback(err); 29 | }); 30 | 31 | }; -------------------------------------------------------------------------------- /assets/styles.css: -------------------------------------------------------------------------------- 1 | body {font-family: 'Open Sans', verdana, tahoma, arial, sans-serif; font-size: 18px;} 2 | 3 | a:visited { 4 | color: blue; 5 | } 6 | 7 | #mainBox { 8 | text-align:left; 9 | width: 80%; 10 | padding:6px; 11 | border:4px dotted #ffa500; 12 | border-radius:10px; 13 | } 14 | 15 | .smallerText { 16 | font-size: 70%; 17 | } 18 | 19 | .homeButton { 20 | font-size: 10px; 21 | background: #ffa500; 22 | padding: 4px; 23 | border-radius:4px; 24 | text-decoration: none; 25 | } 26 | 27 | .spacer { 28 | padding: 12px; 29 | } 30 | 31 | .paddedList li:not(:last-child) { 32 | margin-bottom: 10px; 33 | } 34 | 35 | .doItButton { 36 | font-size: 10px; 37 | background: #00ff00; 38 | padding: 4px; 39 | border-radius:4px; 40 | text-decoration: none; 41 | } 42 | 43 | .successBox { 44 | color: white; 45 | background: #006d00; 46 | padding: 8px; 47 | border-radius:4px; 48 | } 49 | 50 | .errorBox { 51 | color: white; 52 | background: #a00000; 53 | padding: 8px; 54 | border-radius:4px; 55 | } -------------------------------------------------------------------------------- /model/awsCredentials.cfc: -------------------------------------------------------------------------------- 1 | /* 2 | AWS Credentials Config 3 | 4 | This component contains your AWS accessKey and secretKey credentials. 5 | You must enter your own accessKey and secretKey here for any of these examples to work. 6 | Additionally, the IAM account with which these credentials are assoicated must have permissions to work with SNS, Lambda, and DynamoDB. 7 | For more information on IAM accounts and permissions, see http://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html 8 | 9 | Author: Brian Klaas (brian.klaas@gmail.com) 10 | (c) 2019, Brian Klaas 11 | 12 | */ 13 | 14 | component accessors="true" output="false" hint="Simpler holder for AWS credentials." { 15 | 16 | property name="accessKey" type="string"; 17 | property name="secretKey" type="string"; 18 | 19 | this.accessKey = 'YOUR IAM ACCESS KEY GOES HERE'; 20 | this.secretKey = 'YOUR IAM SECRET KEY GOES HERE'; 21 | 22 | /** 23 | * @description Component initialization 24 | */ 25 | public any function init() { 26 | return this; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nodejs/lambda/transcribeTranslateExample/checkTranscribeJobStatus.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const AWS = require('aws-sdk'); 3 | const transcribe = new AWS.TranscribeService; 4 | 5 | exports.handler = (event, context, callback) => { 6 | console.log("Reading input from event:\n", util.inspect(event, {depth: 5})); 7 | 8 | var jobName = event.jobName; 9 | 10 | var params = { 11 | TranscriptionJobName: jobName 12 | } 13 | 14 | var request = transcribe.getTranscriptionJob(params, function(err, data) { 15 | if (err) { // an error occurred 16 | console.log(err, err.stack); 17 | callback(err, null); 18 | } else { 19 | console.log(data); // successful response, return job status 20 | var returnData = { 21 | jobName: jobName, 22 | jobStatus: data.TranscriptionJob.TranscriptionJobStatus, 23 | transcriptFileUri: data.TranscriptionJob.Transcript.TranscriptFileUri, 24 | transcriptFileName: jobName 25 | }; 26 | callback(null, returnData); 27 | } 28 | }); 29 | 30 | }; -------------------------------------------------------------------------------- /nodejs/lambda/transcribeTranslateExample/startTranscribeJob.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const AWS = require('aws-sdk'); 3 | const transcribe = new AWS.TranscribeService; 4 | 5 | exports.handler = (event, context, callback) => { 6 | console.log("Reading input from event:\n", util.inspect(event, {depth: 5})); 7 | 8 | // The job name must be unique to your account 9 | var jobName = 'confDemo-' + Date.now(); 10 | var srcFile = event.urlOfFileOnS3; 11 | var mediaType = event.mediaType; 12 | var returnData = { 13 | jobName: jobName 14 | } 15 | 16 | var params = { 17 | LanguageCode: 'en-US', 18 | Media: { 19 | MediaFileUri: srcFile 20 | }, 21 | MediaFormat: mediaType, 22 | TranscriptionJobName: jobName 23 | } 24 | 25 | transcribe.startTranscriptionJob(params, function(err, data) { 26 | if (err) { 27 | console.log(err, err.stack); 28 | callback(err, null) 29 | } else { 30 | console.log(data); // successful response 31 | callback(null, returnData); 32 | } 33 | }); 34 | 35 | }; -------------------------------------------------------------------------------- /application.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | this.name = 'awsPlaybox'; 4 | this.applicationTimeout = CreateTimeSpan(0, 0, 15, 0); 5 | this.sessionManagement = false; 6 | 7 | function onApplicationStart(){ 8 | application.awsServiceFactory = createObject("component", "model.awsServiceFactory").init(); 9 | 10 | application.currentStepFunctionExecutions = arrayNew(1); 11 | application.currentTranscribeJobs = arrayNew(1); 12 | 13 | // Put your ARNs for Lambda, and your DynamoDB table name here 14 | application.awsResources = structNew(); 15 | application.awsResources.lambdaFunctionARN = "ARN OF THE LAMBDA FUNCTION IN lambda.cfm GOES HERE"; 16 | application.awsResources.stepFunctionRandomImageARN = "ARN OF THE RANDOM IMAGE STEP FUNCTION STATE MACHINE GOES HERE"; 17 | application.awsResources.stepFunctionTranscribeTranslateARN = "ARN OF THE TRANSCRIBE, TRANSLATE, SPEAK STEP FUNCTION STATE MACHINE GOES HERE"; 18 | application.awsResources.dynamoDBTableName = "TABLE NAME OF THE TABLE IN DYNAMODB IN dynamodb.cfm GOES HERE"; 19 | application.awsResources.currentSNSTopicARN = ""; 20 | application.awsResources.iam = {}; 21 | 22 | return true; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /nodejs/lambda/checkForLargeFileUploads.js: -------------------------------------------------------------------------------- 1 | console.log('Loading function'); 2 | 3 | var aws = require('aws-sdk'); 4 | var s3 = new aws.S3({ apiVersion: '2006-03-01' }); 5 | var sns = new aws.SNS(); 6 | var topicARN = 'YOUR TOPIC ARN GOES HERE'; 7 | 8 | exports.handler = function(event, context) { 9 | 10 | // Get the object from the event and its file size 11 | var bucket = event.Records[0].s3.bucket.name; 12 | var fileName = event.Records[0].s3.object.key; 13 | var fileSize = event.Records[0].s3.object.size; 14 | 15 | // Notify SNS topic if the file is larger than 20MB 16 | if ( fileSize > 20480000) { 17 | console.log("Notifying SNS of large upload. File: " + fileName); 18 | var messageBody = 'File: ' + fileName + '\n\nSize: ' + fileSize; 19 | var params = { 20 | TopicArn: topicARN, 21 | Message: messageBody, 22 | Subject: 'File Exceeding Size Limits Put in Bucket ' + bucket 23 | }; 24 | sns.publish(params, function(err, data) { 25 | if (err) { 26 | console.log(err, err.stack); 27 | context.fail('Error on SNS publish'); 28 | } else { 29 | console.log('Successfully sent a message to SNS. Result:'); 30 | console.log(data); 31 | context.succeed('Published to SNS about large file.'); 32 | } 33 | }); 34 | } else { 35 | context.succeed('No alert needed.'); 36 | } 37 | }; -------------------------------------------------------------------------------- /python/lambda/checkServers.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | from datetime import datetime 3 | from urllib2 import urlopen 4 | 5 | snsClient = boto3.client('sns') 6 | # The ARN of the SNS topic you want to notify in case of failure goes here. You need to set up this SNS topic. 7 | topicARN = 'YOUR_ARN_GOES_HERE' 8 | 9 | # URLs of the sites to check 10 | SITES = ['http://yoursite.com/heartbeat.txt'] 11 | # String expected to be returned from the request 12 | EXPECTED = 'This server is healthy' 13 | 14 | def validate(res): 15 | '''Return False to trigger a SNS message. 16 | Could modify this to perform any number of arbitrary checks on the contents of SITE. 17 | ''' 18 | return EXPECTED in res 19 | 20 | 21 | def lambda_handler(event, context): 22 | for site in SITES: 23 | print('Checking {} at {}...'.format(site, event['time'])) 24 | try: 25 | if not validate(urlopen(site).read()): 26 | raise Exception('Validation failed') 27 | except: 28 | print('Check failed!') 29 | messageSubject = 'Site Check Scheduled Lambda Function Failed to Reach ' + site 30 | message = 'Failure reported at ' + event['time'] 31 | snsClient.publish(TopicArn=topicARN, Message=message, Subject=messageSubject) 32 | else: 33 | print('Check passed!') 34 | 35 | return event['time'] 36 | -------------------------------------------------------------------------------- /model/dynamoItemMaker.cfc: -------------------------------------------------------------------------------- 1 | /* 2 | DynamoDB Item Maker 3 | 4 | This component creates DynamoDB Item objects with randomized attributes. 5 | 6 | Author: Brian Klaas (brian.klaas@gmail.com) 7 | (c) 2019, Brian Klaas 8 | 9 | */ 10 | 11 | component output="false" hint="A utility for creating random DynamoDB items." { 12 | 13 | /** 14 | * @description Component initialization 15 | */ 16 | public any function init() { 17 | variables.possibleEvents = ['quiz.setTempAnswer','quiz.updateTempAnswer','quiz.deleteTempAnswer']; 18 | return this; 19 | } 20 | 21 | /** 22 | * @description Creates a DynamoDB Item object with randomized attributes 23 | * @requiredArguments 24 | * - serviceName = Name of the service we want to use 25 | */ 26 | public any function makeItem() { 27 | var itemObject = CreateObject('java', 'com.amazonaws.services.dynamodbv2.document.Item').init(); 28 | itemObject.withPrimaryKey('userID', JavaCast('string',RandRange(100,1000)), 'epochTime', JavaCast('long',Now().getTime())); 29 | itemObject.withNumber('courseOfferingID', JavaCast('int',RandRange(50,250))); 30 | itemObject.withString('event', variables.possibleEvents[RandRange(1,ArrayLen(variables.possibleEvents))]); 31 | if(RandRange(1,10) LTE 5) { 32 | var params = {'quizID':RandRange(25,75), 'questionID':RandRange(5000,9000), 'answerID':RandRange(10000,15000)}; 33 | itemObject.withMap('params',params); 34 | } 35 | if(RandRange(1,10) LTE 2) { 36 | itemObject.withBoolean('leavePage', JavaCast('boolean', true)); 37 | } 38 | return itemObject; 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /stateMachines/choiceDemoStateMachine.json: -------------------------------------------------------------------------------- 1 | { 2 | "Comment": "A simple example of making choices in Step Functions.", 3 | "StartAt": "generateRandomNumber", 4 | "States": { 5 | "generateRandomNumber": { 6 | "Type": "Task", 7 | "Resource": "ARN OF THE RANDOM NUMBER LAMBDA FUNCTION (generateRandomNumber.js) GOES HERE", 8 | "ResultPath": "$.randomNumber", 9 | "Next": "ChoiceState" 10 | }, 11 | "ChoiceState": { 12 | "Type" : "Choice", 13 | "Choices": [ 14 | { 15 | "Variable": "$.randomNumber", 16 | "NumericLessThanEquals": 50, 17 | "Next": "setDataForImageOne" 18 | }, 19 | { 20 | "Variable": "$.randomNumber", 21 | "NumericGreaterThan": 50, 22 | "Next": "setDataForImageTwo" 23 | } 24 | ], 25 | "Default": "DefaultState" 26 | }, 27 | 28 | "setDataForImageOne": { 29 | "Type" : "Pass", 30 | "Result": { "s3Bucket": "YOUR S3 BUCKET NAME", "s3Key": "PATH TO IMAGE" }, 31 | "Next": "getImageLabels" 32 | }, 33 | 34 | "setDataForImageTwo": { 35 | "Type" : "Pass", 36 | "Result": { "s3Bucket": "YOUR S3 BUCKET NAME", "s3Key": "PATH TO IMAGE" }, 37 | "Next": "getImageLabels" 38 | }, 39 | 40 | "getImageLabels": { 41 | "Type" : "Task", 42 | "Resource": "ARN OF THE DETECT LABELS LAMBDA FUNCTION (detectLabelsForImage.js) GOES HERE", 43 | "Next": "Finish" 44 | }, 45 | 46 | "DefaultState": { 47 | "Type": "Fail", 48 | "Cause": "We really should not have ended up here from a numeric value decision." 49 | }, 50 | 51 | "Finish": { 52 | "Type": "Succeed" 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /lambda.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | payload = { 6 | "firstName": "Brian", 7 | "lastName": "Klaas", 8 | "email": "brian.klaas@gmail.com", 9 | "classes": [ 10 | { 11 | "courseNumber": "260.710.81", 12 | "role": "Faculty" 13 | }, 14 | { 15 | "courseNumber": "120.641.01", 16 | "role": "Student" 17 | } 18 | ] 19 | } 20 | 21 | jsonPayload = serializeJSON(payload); 22 | // You need uncomment the line below if you have the "Prefix serialized JSON with " option turned on in the ColdFusion administrator. 23 | // jsonPayload = replace(jsonPayload,"//",""); 24 | 25 | lambda = application.awsServiceFactory.createServiceObject('lambda'); 26 | invokeRequest = CreateObject('java', 'com.amazonaws.services.lambda.model.InvokeRequest').init(); 27 | invokeRequest.setFunctionName(application.awsResources.lambdaFunctionARN); 28 | invokeRequest.setPayload(jsonPayload); 29 | 30 | result = variables.lambda.invoke(invokeRequest); 31 | 32 | sourcePayload = result.getPayload(); 33 | // The payload returned from a Lambda function invocation in the Java SDK is always a Java binary stream. As such, it needs to be decoded into a string of characters. 34 | charset = CreateObject('java', 'java.nio.charset.Charset').forName("UTF-8"); 35 | charsetDecoder = charset.newDecoder(); 36 | lambdaFunctionResult = charsetDecoder.decode(sourcePayload).toString(); 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | AWS Playbox: AWS Service Demos 47 | 48 | 49 | 50 | 51 | 52 |
53 |
54 |

AWS Service Demos:

55 |

Lambda Function Invocation

56 | 57 | 58 |

Result of function invocation:

59 |

#lambdaFunctionResult#

60 |
61 | 62 |

Invoke Demo Function

63 |

Home

64 |
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /python/sns-python.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | AWS Playbox: AWS Service Demos 46 | 47 | 48 | 49 | 50 | 51 |
52 |
53 |

AWS Service Demos:

54 |

Simple Notification Service (SNS)

55 |

Python Example

56 | 57 | 58 |
59 |

#resultMessage#

60 |
61 |
62 | 63 |

Subscribe to a topic via email     [ Source ]

64 |

Send a test SNS notification     [ Source ]

65 |

Home

66 |
67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /model/awsServiceFactory.cfc: -------------------------------------------------------------------------------- 1 | /* 2 | AWS Service Factory 3 | 4 | This component creates AWS service objects based on the parameter passed in. 5 | 6 | Author: Brian Klaas (brian.klaas@gmail.com) 7 | (c) 2020, Brian Klaas 8 | 9 | */ 10 | 11 | component output="false" hint="A utility for creating AWS Service objects." { 12 | 13 | /** 14 | * @description Component initialization 15 | */ 16 | public any function init() { 17 | var credentialsConfig = CreateObject('component','awsCredentials').init(); 18 | // AWS Docs for Working with Credentials: http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html 19 | var awsCredentials = CreateObject('java','com.amazonaws.auth.BasicAWSCredentials').init(credentialsConfig.accessKey, credentialsConfig.secretKey); 20 | variables.awsStaticCredentialsProvider = CreateObject('java','com.amazonaws.auth.AWSStaticCredentialsProvider').init(awsCredentials); 21 | variables.awsRegion = "us-east-1"; 22 | return this; 23 | } 24 | 25 | /** 26 | * @description Creates a service object based on the service name provided 27 | * @requiredArguments 28 | * - serviceName = Name of the service we want to use. Currently supports SNS, Lambda, and DynamoDB. 29 | */ 30 | public any function createServiceObject(required string serviceName) { 31 | var serviceObject = 0; 32 | var javaObjectName = ""; 33 | switch(lcase(arguments.serviceName)){ 34 | case 'dynamodb': 35 | javaObjectName = "com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder"; 36 | break; 37 | case 'iam': 38 | javaObjectName = "com.amazonaws.services.identitymanagement.AmazonIdentityManagementClientBuilder"; 39 | break; 40 | case 'lambda': 41 | javaObjectName = "com.amazonaws.services.lambda.AWSLambdaClientBuilder"; 42 | break; 43 | case 'rekognition': 44 | javaObjectName = "com.amazonaws.services.rekognition.AmazonRekognitionClientBuilder"; 45 | break; 46 | case 's3': 47 | javaObjectName = "com.amazonaws.services.s3.AmazonS3ClientBuilder"; 48 | break; 49 | case 'sns': 50 | javaObjectName = "com.amazonaws.services.sns.AmazonSNSClientBuilder"; 51 | break; 52 | case 'stepFunctions': 53 | javaObjectName = "com.amazonaws.services.stepfunctions.AWSStepFunctionsClientBuilder"; 54 | break; 55 | case 'transcribe': 56 | javaObjectName = "com.amazonaws.services.transcribe.AmazonTranscribeClientBuilder"; 57 | break; 58 | case 'translate': 59 | javaObjectName = "com.amazonaws.services.translate.AmazonTranslateClientBuilder"; 60 | break; 61 | default: 62 | throw(message="Unsupported service requested", detail="You have requested an AWS service (#arguments.serviceName#) which is not supported at this time."); 63 | break; 64 | } 65 | serviceObject = CreateObject('java', '#javaObjectName#').standard().withCredentials(variables.awsStaticCredentialsProvider).withRegion(#variables.awsRegion#).build(); 66 | return serviceObject; 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /nodejs/lambda/transcribeTranslateExample/getTranscriptionFile.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const AWS = require('aws-sdk'); 3 | const https = require('https'); 4 | const S3 = new AWS.S3(); 5 | 6 | exports.handler = (event, context, callback) => { 7 | console.log("Reading input from event:\n", util.inspect(event, {depth: 5})); 8 | 9 | // Note: The authenticated URL that Transcribe provides to you only stays valid for a couple minutes 10 | var transcriptFileUri = event.transcriptFileUri; 11 | var transcriptFileName = event.transcriptFileName; 12 | 13 | // We have to use promises for both steps in the process because S3 operations are async 14 | getTranscript(transcriptFileUri).then(function(getTranscriptResponse) { 15 | console.log("Retrieved transcript:", getTranscriptResponse); 16 | return writeTranscriptToS3(getTranscriptResponse,transcriptFileName); 17 | }).then(function(filePathOnS3) { 18 | console.log("filePathOnS3 is " + filePathOnS3); 19 | var returnData = { 20 | transcriptFilePathOnS3: filePathOnS3, 21 | transcriptFileName: transcriptFileName 22 | }; 23 | callback(null, returnData); 24 | }).catch(function(err) { 25 | console.error("Failed to write transcript file!", err); 26 | callback(err, null); 27 | }) 28 | }; 29 | 30 | function getTranscript(transcriptFileUri) { 31 | return new Promise(function(resolve, reject) { 32 | https.get(transcriptFileUri, res => { 33 | res.setEncoding("utf8"); 34 | let body = ""; 35 | res.on("data", data => { 36 | body += data; 37 | }); 38 | res.on("end", () => { 39 | body = JSON.parse(body); 40 | let transcript = body.results.transcripts[0].transcript; 41 | console.log("Here's the transcript:\n", transcript); 42 | resolve(transcript); 43 | }); 44 | res.on("error", (err) => { 45 | console.log("Error getting transcript:\n", err); 46 | reject(Error(err)); 47 | }); 48 | }); 49 | }); 50 | } 51 | 52 | function writeTranscriptToS3(transcript,transcriptFileName) { 53 | return new Promise(function(resolve, reject) { 54 | console.log("Writing transcript to S3 with the name" + transcriptFileName); 55 | let filePathOnS3 = 'transcripts/' + transcriptFileName + '.txt'; 56 | var params = { 57 | Bucket: 'NAME OF YOUR BUCKET WHERE YOU WANT OUTPUT TO GO', 58 | Key: filePathOnS3, 59 | Body: transcript 60 | }; 61 | var putObjectPromise = S3.putObject(params).promise(); 62 | putObjectPromise.then(function(data) { 63 | console.log('Successfully put transcript file on S3'); 64 | resolve(filePathOnS3); 65 | }).catch(function(err) { 66 | console.log("Error putting file on S3:\n", err); 67 | reject(Error(err)); 68 | }); 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /index.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AWS Service Playbox 6 | 7 | 8 | 9 | 10 | 11 |
12 | 50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /nodejs/lambda/transcribeTranslateExample/translateText.js: -------------------------------------------------------------------------------- 1 | // As the Translate service doesn't run ansynchronously, and the jobs take longer than 3 seconds (the default Lambda function timeout), set the timeout to 10 seconds to give Translate time to do the work. 2 | 3 | const util = require('util'); 4 | const AWS = require('aws-sdk'); 5 | const S3 = new AWS.S3(); 6 | const Translate = new AWS.Translate(); 7 | 8 | exports.handler = (event, context, callback) => { 9 | console.log("Reading input from event:\n", util.inspect(event, {depth: 5})); 10 | 11 | var transcriptFileOnS3 = event.transcriptFilePathOnS3; 12 | var sourceTranscriptFileName = transcriptFileOnS3.split('/').pop(); 13 | var languageToUse = event.languageToUse; 14 | 15 | // We have to use promises for all steps in the process because S3 operations are async 16 | getTranscriptFile(transcriptFileOnS3).then(function(getTranscriptResponse) { 17 | console.log("Retrieved transcript:", getTranscriptResponse); 18 | return translateText(getTranscriptResponse, languageToUse); 19 | }).then(function(translatedTextObj) { 20 | console.log("Here's the translation:\n", translatedTextObj); 21 | var returnData = { 22 | translatedText: translatedTextObj.TranslatedText, 23 | languageOfText: languageToUse, 24 | sourceTranscriptFileName: sourceTranscriptFileName 25 | } 26 | callback(null, returnData); 27 | }).catch(function(err) { 28 | console.error("Failure during translation!\n", err, err.stack); 29 | callback(err, null); 30 | }) 31 | }; 32 | 33 | function getTranscriptFile(transcriptFileOnS3) { 34 | return new Promise(function(resolve, reject) { 35 | var params = { 36 | Bucket: 'NAME OF YOUR BUCKET WHERE YOU WANT OUTPUT TO GO', 37 | Key: transcriptFileOnS3 38 | }; 39 | var getObjectPromise = S3.getObject(params).promise(); 40 | getObjectPromise.then(function(data) { 41 | console.log('Successfully retrieved transcript file from S3'); 42 | // S3 returns the body of the result as a JS Buffer object, so we have to convert it 43 | let resultText = data.Body.toString('ascii'); 44 | resolve(resultText); 45 | }).catch(function(err) { 46 | console.log("Error getting file from S3:\n", err); 47 | reject(Error(err)); 48 | }); 49 | }); 50 | } 51 | 52 | function translateText(textToTranslate, languageToUse) { 53 | return new Promise(function(resolve, reject) { 54 | // Translate has a current maximum length for translation of 5000 bytes 55 | var maxLength = 4500; 56 | var trimmedString = textToTranslate.substr(0, maxLength); 57 | // We don't want to pass in words that are cut off in the middle 58 | trimmedString = trimmedString.substr(0, Math.min(trimmedString.length, trimmedString.lastIndexOf(" "))); 59 | console.log("We're going to translate:\n", trimmedString); 60 | var params = { 61 | SourceLanguageCode: 'en', 62 | TargetLanguageCode: languageToUse, 63 | Text: trimmedString 64 | }; 65 | Translate.translateText(params, function(err, data) { 66 | if (err) { 67 | console.log("Error calling Translate"); 68 | reject(Error(err)); 69 | } else { 70 | console.log("Successful translation:\n"); 71 | console.log(data); 72 | resolve(data); 73 | } 74 | }); 75 | }); 76 | } -------------------------------------------------------------------------------- /sns.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | topicName = "AWSPlayboxDemoTopic-" & dateTimeFormat(Now(), "yyyy-mm-dd-HH-nn-ss"); 9 | createTopicRequest = CreateObject('java', 'com.amazonaws.services.sns.model.CreateTopicRequest').withName(topicName); 10 | 11 | createTopicResult = sns.createTopic(createTopicRequest); 12 | 13 | application.awsResources.currentSNSTopicARN = createTopicResult.getTopicArn(); 14 | 15 | topicCreated = 1; 16 | 17 | 18 | 19 | 20 | 21 | if (Len(Trim(FORM.emailAddress)) LTE 5) { 22 | throw(message="Invalid email address provided."); 23 | abort; 24 | } 25 | subscribeRequest = CreateObject('java', 'com.amazonaws.services.sns.model.SubscribeRequest').withTopicARN(application.awsResources.currentSNSTopicARN).withProtocol("email").withEndpoint(Trim(FORM.emailAddress)); 26 | sns.subscribe(subscribeRequest); 27 | 28 | subscriptionRequestSent = 1; 29 | 30 | 31 | 32 | 33 | 34 | subject = "AWS Playbox SNS CFML Demo"; 35 | message = "Hello there!" & chr(13) & chr(13) & "The current time is " & DateTimeFormat(Now(), "Full") & "."; 36 | 37 | publishRequest = CreateObject('java', 'com.amazonaws.services.sns.model.PublishRequest').withTopicARN(application.awsResources.currentSNSTopicARN).withSubject(subject).withMessage(message); 38 | sns.publish(publishRequest); 39 | 40 | snsMessageSent = 1; 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | AWS Playbox: AWS Service Demos 51 | 52 | 53 | 54 | 55 | 56 |
57 |
58 |

AWS Service Demos:

59 |

Simple Notification Service (SNS)

60 | 61 | 62 |

The topic was created!

63 |
64 | 65 |

A subscription request was sent for #Trim(FORM.emailAddress)#. You must confirm that you want this subscription by responding to the "AWS Notification - Subscription Confirmation" email sent by AWS before any SNS notifications are sent to this address.

66 |
67 |
68 | 69 |

A SNS notification was sent!

70 |
71 | 72 | 73 |

The topic ARN is: #application.awsResources.currentSNSTopicARN#

74 |
75 |

Subscribe to this topic via this email address:  

76 |
77 |

Send a test SNS notification 78 |
(Note: you must have subscribed to the topic and confirmed your subscription to see any result)

79 | 80 |

Create a new topic

81 |
82 |

Home

83 |
84 |
85 | 86 | 87 | -------------------------------------------------------------------------------- /dynamodb.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dynamoDB = application.awsServiceFactory.createServiceObject('dynamoDB'); 6 | 7 | switch(lcase(trim(URL.goDynamo))){ 8 | case 'listTables': 9 | dynamoResult = dynamoDB.listTables(); 10 | break; 11 | 12 | case 'putItem': 13 | table = CreateObject('java', 'com.amazonaws.services.dynamodbv2.document.Table').init(dynamoDB, application.awsResources.dynamoDBTableName); 14 | 15 | // This is just a convenience method for this demo. You can build your DynamoDB Item objects any way you want. 16 | itemMaker = CreateObject('component','awsPlaybox.model.dynamoItemMaker').init(); 17 | 18 | for (i=1; i LTE 5; i++) { 19 | item = itemMaker.makeItem(); 20 | outcome = table.putItem(item); 21 | } 22 | 23 | dynamoResult = item.toJSONPretty(); 24 | break; 25 | 26 | // This is cfscript's way of dealing with mulitiple matching values for the same block 27 | case 'scanTable': case 'scanTableFilter': 28 | scanRequest = CreateObject('java', 'com.amazonaws.services.dynamodbv2.model.ScanRequest').init(application.awsResources.dynamoDBTableName); 29 | 30 | if (URL.goDynamo IS 'scanTableFilter') { 31 | // Although the value is a number, the dynamoAttributeValue.withN function requires a string to be passed in. 32 | filterBackTo = JavaCast('string',DateAdd('n', -1, Now()).getTime()); 33 | // For more information on table scan filtering, see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ScanJavaDocumentAPI.html 34 | dynamoAttributeValue = CreateObject('java', 'com.amazonaws.services.dynamodbv2.model.AttributeValue').init(); 35 | attributeValuesStruct = {':filterBackTo'=dynamoAttributeValue.withN(filterBackTo)}; 36 | scanRequest.withFilterExpression('epochTime > :filterBackTo'); 37 | scanRequest.withExpressionAttributeValues(attributeValuesStruct); 38 | } 39 | 40 | scanResult = dynamoDB.scan(scanRequest); 41 | 42 | if (arrayLen(scanResult.items) GT 0) { 43 | scanResult.items.each(function(element,index) { 44 | dynamoResult &= element.toString() & '

'; 45 | }); 46 | } else { 47 | dynamoResult = "No records returned from the scan."; 48 | } 49 | break; 50 | 51 | default: 52 | throw(message="Unsupported action requested", detail="You have requested an action (#URL.goDynamo#) which is not supported at this time."); 53 | break; 54 | } 55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | AWS Playbox: AWS Service Demos 65 | 66 | 67 | 68 | 69 | 70 |
71 |
72 |

AWS Service Demos:

73 |

DynamoDB

74 | 75 | 76 |

You said: #URL.goDynamo# 77 |

Your Result:
78 | #dynamoResult#

79 |
80 | 81 | 82 |

List Tables

83 |

Put Items

84 |

Scan Table

85 |

Scan Table for Records in the Last Minute

86 |

Home

87 |
88 |
89 | 90 | -------------------------------------------------------------------------------- /nodejs/lambda/transcribeTranslateExample/convertTextToSpeech.js: -------------------------------------------------------------------------------- 1 | // As speaking jobs can take a few seconds, the default Lambda function timeout will not be enough. Set the timeout to 10 seconds to give Polly time to do the work. 2 | 3 | const util = require('util'); 4 | const AWS = require('aws-sdk'); 5 | const Polly = new AWS.Polly(); 6 | const S3 = new AWS.S3(); 7 | 8 | exports.handler = (event, context, callback) => { 9 | console.log("Reading input from event:\n", util.inspect(event, {depth: 5})); 10 | 11 | var textToSpeak = event.textToSpeak; 12 | var languageOfText = event.languageOfText; 13 | var fileNameForOutput = event.transcriptFileName; 14 | 15 | // We have to use promises for both steps in the process because S3 operations are async 16 | makeMP3FromText(textToSpeak, languageOfText).then(function(makeMP3Result) { 17 | console.log("Result from speaking transcript:", makeMP3Result); 18 | return writeFileToS3(makeMP3Result.AudioStream, languageOfText, fileNameForOutput); 19 | }).then(function(mp3FileNameOnS3) { 20 | console.log("mp3FileNameOnS3:" + mp3FileNameOnS3); 21 | var returnData = {}; 22 | returnData["mp3FileNameOnS3-"+languageOfText] = mp3FileNameOnS3 23 | callback(null, returnData); 24 | }).catch(function(err) { 25 | console.error("Failed to generate MP3!", err); 26 | callback(err, null); 27 | }) 28 | }; 29 | 30 | function makeMP3FromText(textToSpeak, languageOfText) { 31 | return new Promise(function(resolve, reject) { 32 | console.log("Making an MP3 in the language: " + languageOfText); 33 | var voiceToUse = 'Ivy'; 34 | // Polly has a current maximum character length of 3000 characters 35 | var maxLength = 2900; 36 | var trimmedText = textToSpeak.substr(0, maxLength); 37 | switch(languageOfText) { 38 | case 'es': 39 | voiceToUse = (Math.random() >= 0.5) ? "Penelope" : "Miguel"; 40 | break; 41 | case 'fr': 42 | voiceToUse = (Math.random() >= 0.5) ? "Celine" : "Mathieu"; 43 | break; 44 | case 'de': 45 | voiceToUse = (Math.random() >= 0.5) ? "Vicki" : "Hans"; 46 | break; 47 | } 48 | var params = { 49 | OutputFormat: "mp3", 50 | SampleRate: "8000", 51 | Text: trimmedText, 52 | VoiceId: voiceToUse 53 | } 54 | var speakPromise = Polly.synthesizeSpeech(params).promise(); 55 | speakPromise.then(function(data) { 56 | console.log('Successfully generated MP3 file'); 57 | resolve(data); 58 | }).catch(function(err) { 59 | console.log("Error generating MP3 file:\n", err); 60 | reject(Error(err)); 61 | }); 62 | }); 63 | } 64 | 65 | function writeFileToS3(mp3AudioStream, languageOfText, fileNameForOutput) { 66 | return new Promise(function(resolve, reject) { 67 | let audioFileName = fileNameForOutput; 68 | // Remove .txt from the file name, if it exists 69 | if (audioFileName.split('.').pop() == 'txt') { 70 | audioFileName = audioFileName.slice(0, -4); 71 | } 72 | let filePathOnS3 = 'audio/' + audioFileName + '-' + languageOfText + '.mp3'; 73 | var params = { 74 | Bucket: 'NAME OF YOUR BUCKET WHERE YOU WANT OUTPUT TO GO', 75 | Key: filePathOnS3, 76 | Body: mp3AudioStream, 77 | ContentType: 'audio/mpeg3' 78 | }; 79 | var putObjectPromise = S3.putObject(params).promise(); 80 | putObjectPromise.then(function(data) { 81 | console.log('Successfully put audio file on S3'); 82 | resolve(filePathOnS3); 83 | }).catch(function(err) { 84 | console.log("Error putting file on S3:\n", err); 85 | reject(Error(err)); 86 | }); 87 | }); 88 | } -------------------------------------------------------------------------------- /showSourceCode.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | AWS Playbox: AWS Service Demos 76 | 77 | 78 | 79 | 80 | 81 |
82 |
83 |

AWS Service Demos:

84 |

Lambda

85 |

Code Example: #fileTitle#

86 | 87 | 88 | 89 | 90 |
#sourceCode#
91 |
92 | 93 |

Home

94 |
95 |
96 | 97 | 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The AWS Playbox: Demos for Using Amazon Web Services from CFML 2 | 3 | There are three requirements for getting these demos working: 4 | 5 | 1. Add the AWS SDK .jar and related files to your CF install. 6 | 2. Set up your own AWS credentials and add them to awsCredentials.cfc. 7 | 3. Set up your own copies of the required resources in SNS, Lambda, DynamoDB, S3, and Step Functions. 8 | 9 | ### Requirement One: The AWS SDK .jar and Related Files 10 | 11 | If you are running CF2018, the demos in this repo require that you have the following .jar file in your /cfusion/lib/ directory: 12 | 13 | - aws-java-sdk-1.11.311 or later 14 | 15 | If you are running CF2016 or earlier, you also need to add the following .jar files to your /cfusion/lib/ directory: 16 | - jackson-annotations 17 | - jackson-core 18 | - jackson-databind 19 | - joda-time 20 | 21 | All of these files can be downloaded from [https://aws.amazon.com/sdk-for-java/](https://aws.amazon.com/sdk-for-java/) Files other than the actual SDK .jar itself can be found in the /third-party directory within the SDK download. 22 | 23 | ### Requirement Two: Your Own AWS Credentials 24 | 25 | You have to create your own AWS account and provide both the AccessKey and SecretKey in model/awsCredentials.cfc. 26 | 27 | The account for which you are providing credentials must also have permissions for the following services: 28 | 29 | - IAM 30 | - S3 31 | - SNS 32 | - Lambda 33 | - CloudWatch (for Lambda logging) 34 | - DynamoDB 35 | - Step Functions 36 | - Rekognition 37 | - Transcribe 38 | - Translate 39 | - Polly 40 | 41 | For more infomration about IAM accounts, roles, and permissions, please review the [IAM guide](http://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html). 42 | 43 | ### Requirement Three: Your Own AWS Resources 44 | 45 | You need to set up the following resources within AWS for these demos to work: 46 | 47 | 1. SNS - create a topic to which messsages can be sent. The ARN (Amazon Resource Name, like a URL) of this topic must be added to application.cfc. 48 | 2. Lambda - create a Lambda function using the code in nodejs/lambda/lambda-returnDataToCaller.js. The Lambda runtime should be NodeJS 4.3 or later, and you do not need to configure a trigger for the function, as it will be invoked from this application. The ARN of the function must be added to application.cfc. 49 | 3. DynamoDB - create a DynamoDB table with a partition (primary) key of "userID" (String) and a sort key (range key) of "epochTime" (Number). The table name must be added to application.cfc. 50 | 4. Rekognition - add photos for Rekognition to analyze. There's a separate list of photos for matching faces, and a list for generating labels (image analysis). These photos and the name of the S3 bucket in which they can be found need to be added to the top of rekognition.cfm. 51 | 5. Step Functions: 52 | There are two workflows you can set up: 53 | - Describe an Image 54 | 55 | a. Create the two Lambda functions used in this workflow using the code in nodejs/lambda/ -- generateRandomNumber.js and detectLabelsForImage.js. 56 | b. Add the ARNs of those functions to stateMachines/choiceDemoStateMachine.json. 57 | c. Add the name of the S3 bucket and the path to the photos that will be analyzed to stateMachines/choiceDemoStateMachine.json. 58 | d. Once you've added all the required information, use stateMachines/choiceDemoStateMachine.json to create a new Step Function state machine in the AWS Console. 59 | e. Add the ARN of the workflow to application.cfc as the application.awsResources.stepFunctionRandomImageARN value. 60 | 61 | - Transcribe, Translate, and Speak a Video 62 | 63 | a. Create the five Lambda functions used in this workflow using the code in nodejs/lambda/transcribeTranslateExample. You will need to add the name of your S3 bucket where you want the output from the workflow to go to getTranscriptionFile.js, translateText.js, and convertTextToSpeech.js. 64 | b. Add the ARNs of those functions to stateMachines/transcribeTranslateSpeakWorkflow.json. 65 | c. Once you've added all the required information, use stateMachines/transcribeTranslateSpeakWorkflow.json to create a new Step Function state machine in the AWS Console. 66 | d. Add the ARN of the workflow to application.cfc as the application.awsResources.stepFunctionTranscribeTranslateARN value. 67 | e. Modify stepFunctions.cfm (the inputStruct variable) to point to the URL of a MP4 file on S3. 68 | 69 | Remember, the AWS docs are pretty great. Use the [Java API Reference](http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/index.html) often, as it'll tell you almost everything you need to know for working with a particular AWS service. 70 | 71 | Enjoy! 72 | 73 | ### P.S.: Python 74 | 75 | There are a number of Python examples in this repo. You can get them running pretty easily by: 76 | 77 | 1. Installing boto via pip 78 | 2. Using your own ARNs as noted in the Python code in the /python directory of the repo 79 | 3. Setting python/pythonInvoke.sh to run as an executable on your machine 80 | 81 | Boto makes it very easy to use AWS from Python and acts as the AWS-approved and supported SDK for Python. -------------------------------------------------------------------------------- /iamTestApp/index.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | awsCredentials = CreateObject('java','com.amazonaws.auth.BasicAWSCredentials').init(trim(FORM.userAccessKey), trim(FORM.userSecretKey)); 6 | awsStaticCredentialsProvider = CreateObject('java','com.amazonaws.auth.AWSStaticCredentialsProvider').init(awsCredentials); 7 | awsRegion = "us-east-1"; 8 | if ((Len(Trim(FORM.s3BucketName)) GT 1) && (Len(Trim(FORM.fileToPutOnS3)) GT 1)) { 9 | uploadedFile = fileUpload(getTempDirectory(), "form.fileToPutOnS3", " ", "makeunique"); 10 | fileLocation = getTempDirectory() & uploadedFile.serverFile; 11 | fileContent = fileReadBinary(getTempDirectory() & uploadedFile.serverFile); 12 | // We're not using CFML's built-in support for S3 here because it requires a lot more permissions to be set than what we'd normally want. 13 | // It's also good to show how to upload a file to S3 using the SDK. 14 | s3 = CreateObject('java', 'com.amazonaws.services.s3.AmazonS3ClientBuilder').standard().withCredentials(awsStaticCredentialsProvider).withRegion(#awsRegion#).build(); 15 | javaFileObject = CreateObject('java', 'java.io.File').init(fileLocation); 16 | putFileRequest = CreateObject('java', 'com.amazonaws.services.s3.model.PutObjectRequest').init(trim(FORM.s3BucketName), uploadedFile.serverFile, javaFileObject); 17 | s3.putObject(putFileRequest); 18 | successMsg = "The file was uploaded to the S3 bucket. "; 19 | } 20 | if (Len(Trim(FORM.snsTopicARN)) GT 20) { 21 | sns = CreateObject('java', 'com.amazonaws.services.sns.AmazonSNSClientBuilder').standard().withCredentials(awsStaticCredentialsProvider).withRegion(#awsRegion#).build(); 22 | subject = "AWS Playbox IAM Permissions Demo"; 23 | message = "Hello there!" & chr(13) & chr(13) & "The current time is " & DateTimeFormat(Now(), "Full") & "."; 24 | publishRequest = CreateObject('java', 'com.amazonaws.services.sns.model.PublishRequest').withTopicARN(FORM.snsTopicARN).withSubject(subject).withMessage(message); 25 | sns.publish(publishRequest); 26 | successMsg &= "A message was sent to the SNS topic."; 27 | } 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | AWS Playbox: IAM Service Demo 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 |

Identity Access Management (IAM) Demo:

46 |

IAM User Permission Demo

47 |

In order for this demo to work, you must have first created all the resources on the IAM playbox page.

48 |

If you want to test failure scenarios, enter a valid S3 bucket or SNS topic to which this user does not have permission.

49 |

 

50 | 51 |

#successMsg#

52 |
53 |
54 |

User Credentials

55 |

56 | Enter the Access Key for this user:
57 | value="#trim(FORM.userAccessKey)#"> 58 |

59 |

60 | Enter the Secret Key for this user:
61 | value="#trim(FORM.userSecretKey)#"> 62 |

63 |

S3 Test

64 |

65 | Enter the bucket name specified in the /awsPlaybox/iamPolicies/awsPlayboxPrivateReadWrite.txt policy file:
66 | value="#trim(FORM.s3BucketName)#">
67 | Select a file to upload:
68 | 69 |

 

70 |

SNS Test

71 |

72 | Enter the ARN of the SNS topic that you created on the SNS demo page:
73 | value="#trim(FORM.snsTopicARN)#">
74 | 75 |

76 |
77 |

 

78 |

AWSPlaybox Home

79 |
80 |
81 | 82 | -------------------------------------------------------------------------------- /translate.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | if (not(len(trim(FORM.targetLanguage))) or not(len(trim(FORM.textToTranslate)))) { 6 | writeOutput('You need to supply both a target language and text to translate.'); 7 | abort; 8 | } 9 | 10 | // The AWS Service Factory, created at application startup, handles the building of the object that will speak to 11 | // the Translate service, and handles authentication to AWS. 12 | translateService = application.awsServiceFactory.createServiceObject('translate'); 13 | 14 | // We can re-use the same TranslateJobRequest object when looping below, so we'll only create it once. 15 | translateJobRequest = CreateObject('java', 'com.amazonaws.services.translate.model.TranslateTextRequest').init(); 16 | translateJobRequest.setSourceLanguageCode('en'); 17 | translateJobRequest.setTargetLanguageCode(trim(FORM.targetLanguage)); 18 | 19 | // Translate has a service limit of translating 5000 bytes of UTF-8 characters per request. 20 | // We'll only translate 4900 characters at a time just to be safe. 21 | // Additionally, the service has a throttling limit of 10,000 bytes per 10 seconds per language pair (source/target language). 22 | // As such, we have to see how long our text is and then break it apart into chunks that will not go over these limits. 23 | // If you're looking for sample long text, try the full text of Herman Melville's Moby Dick: https://www.gutenberg.org/files/2701/2701-h/2701-h.htm 24 | trimmedSourceText = trim(FORM.textToTranslate); 25 | totalChunks = ceiling(len(trimmedSourceText) / 4900); 26 | totalPauses = ceiling(totalChunks / 2); 27 | currentEndPosition = 1; 28 | currentChunkCounter = 0; 29 | currentPauseCounter = 0; 30 | finalTranslation = ""; 31 | 32 | for (currentChunkCounter = 1; currentChunkCounter <= totalChunks; currentChunkCounter++) { 33 | chunkToTranslate = mid(trimmedSourceText, currentEndPosition, 4900); 34 | currentEndPosition += 4900; 35 | 36 | // We don't want to cut words off in the middle, so let's adjust for that. 37 | if (len(chunkToTranslate) GTE 4900) { 38 | lastWord = ListLast(chunkToTranslate, " "); 39 | chunkToTranslate = left(chunkToTranslate, (len(chunkToTranslate) - len(lastWord))); 40 | currentEndPosition -= len(lastWord); 41 | } 42 | 43 | // We can re-use the translateJobRequest object because the only thing we change from request to request is the text being translated. 44 | translateJobRequest.setText(chunkToTranslate); 45 | translateTextResult = translateService.translateText(translateJobRequest); 46 | // For more on the translateTextResult object see https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/translate/model/TranslateTextResult.html 47 | finalTranslation &= translateTextResult.getTranslatedText(); 48 | 49 | // Check to see if we need to pause as to not exceed the 10,000 bytes per 10 seconds limit. 50 | if ((currentChunkCounter mod 2) eq 0) { 51 | if (currentPauseCounter LTE totalPauses) { 52 | sleep(10000); 53 | currentPauseCounter++; 54 | } 55 | } 56 | } // End totalChunks loop 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | AWS Playbox: AWS Service Demos 67 | 68 | 69 | 70 | 71 | 72 |
73 |
74 |

AWS Service Demos:

75 |

Translate

76 | 77 | 78 |

Translated Output:

79 |

#finalTranslation#

80 |
81 |
82 | 83 |

Translate Some Text:

84 |
85 |

Translating from English to: 86 | 100 |

101 |

Text to translate:
102 | 103 |

104 |

105 |
106 | 107 |

Home

108 |
109 |
110 | 111 | 112 | -------------------------------------------------------------------------------- /rekognition.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | rekognitionLib = CreateObject('component', 'awsPlaybox.model.rekognitionLib').init(); 13 | switch(trim(URL.rekogRequest)){ 14 | case 'compareFaces': 15 | sourceImage = s3images.facesForMatching[randRange(1, arrayLen(s3images.facesForMatching))]; 16 | targetImage = s3images.facesForMatching[randRange(1, arrayLen(s3images.facesForMatching))]; 17 | compareFacesResult = rekognitionLib.compareFaces(s3images.awsBucketName, sourceImage, targetImage); 18 | similarityValue = rekognitionLib.getSimilarityValue(compareFacesResult); 19 | if (similarityValue gte 0) { 20 | rekogFunctionResult = "Rekognition gave a similarity value of " & similarityValue & " to the two images."; 21 | } else { 22 | rekogFunctionResult = "There was no match between the two images!"; 23 | } 24 | break; 25 | 26 | case 'detectLabels': 27 | sourceImage = s3images.imagesForLabels[randRange(1, arrayLen(s3images.imagesForLabels))]; 28 | getImageLabelsResult = rekognitionLib.getImageLabels(s3images.awsBucketName, sourceImage); 29 | break; 30 | 31 | case 'detectSentiment': 32 | sourceImage = s3images.facesForMatching[randRange(1, arrayLen(s3images.facesForMatching))]; 33 | detectSentimentResult = rekognitionLib.detectSentiment(s3images.awsBucketName, sourceImage); 34 | break; 35 | 36 | case 'detectText': 37 | sourceImage = s3images.imagesWithText[randRange(1, arrayLen(s3images.imagesWithText))]; 38 | detectTextResult = rekognitionLib.detectText(s3images.awsBucketName, sourceImage); 39 | break; 40 | 41 | default: 42 | throw(message="Unsupported service requested", detail="You have requested a method (#URL.rekogRequest#) which is not supported at this time."); 43 | break; 44 | } 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | AWS Playbox: AWS Service Demos 55 | 56 | 57 | 58 | 59 | 60 |
61 |
62 |

AWS Service Demos:

63 |

Rekognition

64 | 65 | 66 | 67 | 68 | 69 |

#rekogFunctionResult#

70 |

Here are the two images used:

71 |

72 | 73 | 74 |

75 |
76 |
77 | 78 | 79 |
80 |
81 |

Here is the image used:

82 |

83 | 84 |

85 |
86 |
87 |

Here are the labels:

88 | 89 |
  • #idxThisLabel.label# — #idxThisLabel.confidence#%
  • 90 |
    91 |
    92 |
    93 |
    94 |
    95 |
    96 | 97 | 98 |
    99 |
    100 |

    Here is the image used:

    101 |

    102 | 103 |

    104 |
    105 |
    106 | 107 | 108 | 109 |

    Face #faceCounter#:

    110 | 111 |
    112 |
    113 |
    114 |
    115 |
    116 |
    117 | 118 | 119 |
    120 |
    121 |

    Here is the image used:

    122 |

    123 | 124 |

    125 |
    126 |
    127 |

    Lines of text:

    128 | 129 | #idxThisLine.id# | #idxThisLine.label# | (#idxThisLine.confidence#%)
    130 |
    131 |

    Individual words:

    132 | 133 | Line: #idxThisWord.parentID# — #idxThisWord.label# (#idxThisWord.confidence#%)
    134 |
    135 |
    136 |
    137 |
    138 |
    139 |
    140 |
    141 |
    142 | 143 |

    Compare Two Faces

    144 |

    Label the Properties of an Image

    145 |

    Detect Facial Sentiment of an Image

    146 |

    Detect Text in an Image

    147 | 148 |

    Home

    149 |
    150 |
    151 | 152 | 153 | -------------------------------------------------------------------------------- /stateMachines/transcribeTranslateSpeakWorkflow.json: -------------------------------------------------------------------------------- 1 | { 2 | "Comment": "Manages the workflow of transcribing a MP4, translating into various languages, and making MP3 files of those translations.", 3 | "StartAt": "startTranscribeMP4", 4 | "States": { 5 | "startTranscribeMP4": { 6 | "Type": "Task", 7 | "Resource": "ARN OF THE START TRANSCRIBE JOB LAMBDA FUNCTION (startTranscribeJob.js) GOES HERE", 8 | "Next": "WaitForTranscriptionComplete", 9 | "Retry": [ 10 | { 11 | "ErrorEquals": [ "States.ALL" ], 12 | "IntervalSeconds": 30, 13 | "MaxAttempts": 3, 14 | "BackoffRate": 10 15 | } 16 | ] 17 | }, 18 | "WaitForTranscriptionComplete": { 19 | "Type": "Wait", 20 | "Seconds": 30, 21 | "Next": "Get Job Status" 22 | }, 23 | "Get Job Status": { 24 | "Type": "Task", 25 | "Resource": "ARN OF THE CHECK TRANSCRIBE JOB STATUS LAMBDA FUNCTION (checkTranscribeJobStatus.js) GOES HERE", 26 | "Next": "Transcript Complete?" 27 | }, 28 | "Transcript Complete?": { 29 | "Type": "Choice", 30 | "Choices": [ 31 | { 32 | "Variable": "$.jobStatus", 33 | "StringEquals": "FAILED", 34 | "Next": "Transcript Failed" 35 | }, 36 | { 37 | "Variable": "$.jobStatus", 38 | "StringEquals": "COMPLETED", 39 | "Next": "Get Transcription File" 40 | } 41 | ], 42 | "Default": "WaitForTranscriptionComplete" 43 | }, 44 | "Transcript Failed": { 45 | "Type": "Fail", 46 | "Cause": "AWS Transcribe Job Failed", 47 | "Error": "Get Job Status returned FAILED" 48 | }, 49 | "Get Transcription File": { 50 | "Type": "Task", 51 | "Resource": "ARN OF THE GET TRANSCRIPTION FILE LAMBDA FUNCTION (getTranscriptionFile.js) GOES HERE", 52 | "Next": "Make Versions" 53 | }, 54 | "Make Versions": { 55 | "Type": "Parallel", 56 | "Next": "Workflow Complete", 57 | "Branches": [ 58 | { 59 | "StartAt": "makeSpanishStart", 60 | "States": { 61 | "makeSpanishStart": { 62 | "Type": "Pass", 63 | "Result": "es", 64 | "ResultPath": "$.languageToUse", 65 | "Next": "makeSpanishText" 66 | }, 67 | "makeSpanishText": { 68 | "Type": "Task", 69 | "Resource": "ARN OF THE TRANSLATE TEXT LAMBDA FUNCTION (translateText.js) GOES HERE", 70 | "Next": "prepSpanishVoiceOutput", 71 | "Retry": [ 72 | { 73 | "ErrorEquals": [ "States.ALL" ], 74 | "IntervalSeconds": 60, 75 | "MaxAttempts": 3, 76 | "BackoffRate": 10 77 | } 78 | ] 79 | }, 80 | "prepSpanishVoiceOutput": { 81 | "Type": "Task", 82 | "Resource": "ARN OF THE PREP TRANSLATED TEXT FOR SPEECH LAMBDA FUNCTION (prepTranslatedTextForSpeech.js) GOES HERE", 83 | "Next": "makeSpanishAudioFile" 84 | }, 85 | "makeSpanishAudioFile": { 86 | "Type": "Task", 87 | "Resource": "ARN OF THE CONVERT TEXT TO SPEECH LAMBDA FUNCTION (convertTextToSpeech.js) GOES HERE", 88 | "Retry": [ 89 | { 90 | "ErrorEquals": [ "States.ALL" ], 91 | "IntervalSeconds": 60, 92 | "MaxAttempts": 3, 93 | "BackoffRate": 10 94 | } 95 | ], 96 | "End": true 97 | } 98 | } 99 | }, 100 | { 101 | "StartAt": "makeFrenchStart", 102 | "States": { 103 | "makeFrenchStart": { 104 | "Type": "Pass", 105 | "Result": "fr", 106 | "ResultPath": "$.languageToUse", 107 | "Next": "makeFrenchText" 108 | }, 109 | "makeFrenchText": { 110 | "Type": "Task", 111 | "Resource": "ARN OF THE TRANSLATE TEXT LAMBDA FUNCTION (translateText.js) GOES HERE", 112 | "Next": "prepFrenchVoiceOutput", 113 | "Retry": [ 114 | { 115 | "ErrorEquals": [ "States.ALL" ], 116 | "IntervalSeconds": 60, 117 | "MaxAttempts": 3, 118 | "BackoffRate": 10 119 | } 120 | ] 121 | }, 122 | "prepFrenchVoiceOutput": { 123 | "Type": "Task", 124 | "Resource": "ARN OF THE PREP TRANSLATED TEXT FOR SPEECH LAMBDA FUNCTION (prepTranslatedTextForSpeech.js) GOES HERE", 125 | "Next": "makeFrenchAudioFile" 126 | }, 127 | "makeFrenchAudioFile": { 128 | "Type": "Task", 129 | "Resource": "ARN OF THE CONVERT TEXT TO SPEECH LAMBDA FUNCTION (convertTextToSpeech.js) GOES HERE", 130 | "Retry": [ 131 | { 132 | "ErrorEquals": [ "States.ALL" ], 133 | "IntervalSeconds": 60, 134 | "MaxAttempts": 3, 135 | "BackoffRate": 10 136 | } 137 | ], 138 | "End": true 139 | } 140 | } 141 | }, 142 | { 143 | "StartAt": "makeGermanStart", 144 | "States": { 145 | "makeGermanStart": { 146 | "Type": "Pass", 147 | "Result": "de", 148 | "ResultPath": "$.languageToUse", 149 | "Next": "makeGermanText" 150 | }, 151 | "makeGermanText": { 152 | "Type": "Task", 153 | "Resource": "ARN OF THE TRANSLATE TEXT LAMBDA FUNCTION (translateText.js) GOES HERE", 154 | "Next": "prepGermanVoiceOutput", 155 | "Retry": [ 156 | { 157 | "ErrorEquals": [ "States.ALL" ], 158 | "IntervalSeconds": 60, 159 | "MaxAttempts": 3, 160 | "BackoffRate": 10 161 | } 162 | ] 163 | }, 164 | "prepGermanVoiceOutput": { 165 | "Type": "Task", 166 | "Resource": "ARN OF THE PREP TRANSLATED TEXT FOR SPEECH LAMBDA FUNCTION (prepTranslatedTextForSpeech.js) GOES HERE", 167 | "Next": "makeGermanAudioFile" 168 | }, 169 | "makeGermanAudioFile": { 170 | "Type": "Task", 171 | "Resource": "ARN OF THE CONVERT TEXT TO SPEECH LAMBDA FUNCTION (convertTextToSpeech.js) GOES HERE", 172 | "Retry": [ 173 | { 174 | "ErrorEquals": [ "States.ALL" ], 175 | "IntervalSeconds": 60, 176 | "MaxAttempts": 3, 177 | "BackoffRate": 10 178 | } 179 | ], 180 | "End": true 181 | } 182 | } 183 | } 184 | ] 185 | }, 186 | "Workflow Complete": { 187 | "Type": "Succeed" 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /stepFunctions.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | // The AWS Service Factory, created at application startup, handles the building of the object that will speak to 6 | // the Step Functions service, and handles authentication to AWS. 7 | stepFunctionService = application.awsServiceFactory.createServiceObject('stepFunctions'); 8 | 9 | // Each execution request in step functions must have a unique name 10 | executionRequest = CreateObject('java', 'com.amazonaws.services.stepfunctions.model.StartExecutionRequest').init(); 11 | jobName = "AWSPlayboxExecution" & DateDiff("s", DateConvert("utc2Local", "January 1 1970 00:00"), now()); 12 | executionRequest.setName(jobName); 13 | 14 | if (URL.invokeStepFunction IS "image") { 15 | stepFunctionARN = application.awsResources.stepFunctionRandomImageARN; 16 | executionType = "Image Description"; 17 | } else if (URL.invokeStepFunction IS "videoWorkflow") { 18 | stepFunctionARN = application.awsResources.stepFunctionTranscribeTranslateARN; 19 | executionType = "Video Workflow"; 20 | // The video workflow requires the following input: urlOfFileOnS3, mediaType 21 | // JavaScript (running in the Lambda function) is case-sensitive. As such, we can't use implicit struct notation because Adobe CF will serialize the JSON using keys in all caps. 22 | inputStruct = StructNew(); 23 | inputStruct['urlOfFileOnS3']="HTTPS PATH TO YOUR SOURCE VIDEO FILE ON S3"; 24 | inputStruct['mediaType']="mp4"; 25 | // If your CF server is configured to add a double slash at the start of serialized JSON, and is CF11+, you need to strip that out by setting the third parameter of serializeJSON -- useSecureJSONPrefix -- to false 26 | // If you're running CF10, use: executionRequest.setInput(right(serializeJSON(inputStruct), (Len(serializeJSON(inputStruct))-2))); 27 | executionRequest.setInput(serializeJSON(inputStruct, false, false)); 28 | } 29 | executionRequest.setStateMachineArn(stepFunctionARN); 30 | 31 | // When we start an execution of a Step Function workflow, we get an executionResult object back. 32 | // The execution result will have the unique ARN of the execution in AWS. 33 | // executionResult is of type com.amazonaws.services.stepfunctions.model.StartExecutionResult 34 | executionResult = stepFunctionService.StartExecution(executionRequest); 35 | executionARN = executionResult.getExecutionARN(); 36 | 37 | executionInfo = { 38 | executionType=executionType, 39 | executionARN=executionARN, 40 | timeStarted=Now() 41 | }; 42 | 43 | // Here we add the information about current executions to memory so we can retrieve them as needed. In a real app, you'd want to save this to database. 44 | arrayAppend(application.currentStepFunctionExecutions, executionInfo); 45 | 46 | 47 | 48 | 49 | 50 | stepFunctionService = application.awsServiceFactory.createServiceObject('stepFunctions'); 51 | 52 | // The describe execution request object is what we use to check the status of a current Step Function workflow execution 53 | describeExecutionRequest = CreateObject('java', 'com.amazonaws.services.stepfunctions.model.DescribeExecutionRequest').init(); 54 | describeExecutionRequest.setExecutionArn(URL.checkStepFunctionARN); 55 | 56 | // describeActivityResult is of type com.amazonaws.services.stepfunctions.model.DescribeActivityResult 57 | describeActivityResult = stepFunctionService.describeExecution(describeExecutionRequest); 58 | 59 | stepFunctionResult.status = describeActivityResult.getStatus(); 60 | // We only care about success results here. A production application would would handle error results and schedule in progress tasks for checking again at a later time. 61 | if (stepFunctionResult.status IS "SUCCEEDED") { 62 | stepFunctionResult.finishedOn = describeActivityResult.getStopDate(); 63 | stepFunctionResult.output = DeserializeJSON(describeActivityResult.getOutput()); 64 | application.currentStepFunctionExecutions.each(function(element, index) { 65 | if (element.executionARN IS checkStepFunctionARN) { 66 | stepFunctionResult.invocationType = element.executionType; 67 | arrayDeleteAt(application.currentStepFunctionExecutions, index); 68 | break; 69 | } 70 | }); 71 | } 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | AWS Playbox: AWS Service Demos 82 | 83 | 84 | 85 | 86 | 87 |
    88 |
    89 |

    AWS Service Demos:

    90 |

    Step Function Invocation

    91 | 92 | 93 |

    Result of Step Function invocation:

    94 | 95 | 96 | 97 |
    98 |
    99 |

    Status: #stepFunctionResult.status#

    100 |

    Finished On: #DateTimeFormat(stepFunctionResult.finishedOn, "long")#

    101 |

    102 |
    103 |
    104 |

    Here is the image used:

    105 |

    106 | 107 |

    108 |
    109 |
    110 |
    111 | 112 |

    Status: #stepFunctionResult.status#

    113 |

    Finished On: #DateTimeFormat(stepFunctionResult.finishedOn, "long")#

    114 |

    115 |
    116 | 117 |

    #stepFunctionResult.status#

    118 |
    119 |
    120 |
    121 | 122 |
    123 |

    Launch Step Function Workflow:

    124 |

    Describe a Random Image

    125 |

    Transcribe, Translate, and Speak a Video

    126 | 127 |
    128 |

    Current Step Function Executions:

    129 | 130 | 135 | 136 |
    137 |

    Home

    138 |
    139 |
    140 | 141 | -------------------------------------------------------------------------------- /model/rekognitionLib.cfc: -------------------------------------------------------------------------------- 1 | /* 2 | Rekognition Utility Functions 3 | 4 | This component contains functions to make requests to AWS Rekognition. 5 | 6 | Author: Brian Klaas (brian.klaas@gmail.com) 7 | (c) 2019, Brian Klaas 8 | 9 | */ 10 | 11 | component output="false" hint="A utility for making requests to AWS Rekognition." { 12 | 13 | /** 14 | * @description Component initialization 15 | */ 16 | public any function init() { 17 | variables.rekognitionService = application.awsServiceFactory.createServiceObject('rekognition'); 18 | return this; 19 | } 20 | 21 | /** 22 | * @description Generates a compare faces request and returns a compare faces result 23 | * @requiredArguments 24 | * - awsBucketName = Name of the bucket where the images reside 25 | * - face1Path = Path to the first (source) face image in the provided bucket 26 | * - face2Path = Path to the second (target) face image in the provided bucket 27 | */ 28 | public any function compareFaces(required string awsBucketName, required string face1Path, required string face2Path) { 29 | var compareFacesRequest = CreateObject('java', 'com.amazonaws.services.rekognition.model.CompareFacesRequest').init(); 30 | 31 | var faceImage1 = CreateObject('java', 'com.amazonaws.services.rekognition.model.Image').init(); 32 | var faceImage1S3Object = CreateObject('java', 'com.amazonaws.services.rekognition.model.S3Object').init(); 33 | faceImage1S3Object.setBucket(arguments.awsBucketName); 34 | faceImage1S3Object.setName(arguments.face1Path); 35 | faceImage1.setS3Object(faceImage1S3Object); 36 | 37 | var faceImage2 = CreateObject('java', 'com.amazonaws.services.rekognition.model.Image').init(); 38 | var faceImage2S3Object = CreateObject('java', 'com.amazonaws.services.rekognition.model.S3Object').init(); 39 | faceImage2S3Object.setBucket(arguments.awsBucketName); 40 | faceImage2S3Object.setName(arguments.face2Path); 41 | faceImage2.setS3Object(faceImage2S3Object); 42 | 43 | compareFacesRequest.setSourceImage(faceImage1); 44 | compareFacesRequest.setTargetImage(faceImage2); 45 | 46 | return variables.rekognitionService.compareFaces(compareFacesRequest); 47 | } 48 | 49 | /** 50 | * @description Returns an array of structures of sentiment labels for the faces in the image 51 | * @requiredArguments 52 | * - awsBucketName = Name of the bucket where the images reside 53 | * - pathToImage = Path to the image in the provided bucket 54 | */ 55 | public array function detectSentiment(required string awsBucketName, required string pathToImage) { 56 | var returnArray = arrayNew(1); 57 | var thisFaceObj = 0; 58 | var faceCounter = 0; 59 | var detectFacesRequest = CreateObject('java', 'com.amazonaws.services.rekognition.model.DetectFacesRequest').init(); 60 | var imageToAnalyze = CreateObject('java', 'com.amazonaws.services.rekognition.model.Image').init(); 61 | var imageOnS3 = CreateObject('java', 'com.amazonaws.services.rekognition.model.S3Object').init(); 62 | imageOnS3.setBucket(arguments.awsBucketName); 63 | imageOnS3.setName(arguments.pathToImage); 64 | imageToAnalyze.setS3Object(imageOnS3); 65 | detectFacesRequest.setImage(imageToAnalyze); 66 | var attributesArray = ["ALL"]; 67 | detectFacesRequest.setAttributes(attributesArray); 68 | var detectFacesResult = variables.rekognitionService.detectFaces(detectFacesRequest); 69 | var facesArray = detectFacesResult.getFaceDetails(); 70 | 71 | facesArray.each(function(thisFaceObj, index) { 72 | faceCounter++; 73 | returnArray[faceCounter] = structNew(); 74 | // For all the properties of the FaceDetail object, see https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/rekognition/model/FaceDetail.html 75 | returnArray[faceCounter]['Age Range'] = thisFaceObj.getAgeRange().getLow() & "-" & thisFaceObj.getAgeRange().getHigh(); 76 | returnArray[faceCounter]['Gender'] = thisFaceObj.getGender().getValue(); 77 | returnArray[faceCounter]['Eyeglasses'] = thisFaceObj.getEyeglasses().getValue(); 78 | returnArray[faceCounter]['Sunglasses'] = thisFaceObj.getSunglasses().getValue(); 79 | returnArray[faceCounter]['Smiling'] = thisFaceObj.getSmile().getValue(); 80 | returnArray[faceCounter]['Eyes Open'] = thisFaceObj.getEyesOpen().getValue(); 81 | returnArray[faceCounter]['Mouth Open'] = thisFaceObj.getMouthOpen().getValue(); 82 | returnArray[faceCounter]['Has Beard'] = thisFaceObj.getBeard().getValue(); 83 | var emotionsForFace = thisFaceObj.getEmotions(); 84 | var thisEmotionObj = 0; 85 | var emotionCounter = 0; 86 | emotionsForFace.each(function(thisEmotionObj, index) { 87 | emotionCounter++; 88 | returnArray[faceCounter]['emotions'][emotionCounter] = structNew(); 89 | returnArray[faceCounter]['emotions'][emotionCounter]['Type'] = thisEmotionObj.getType(); 90 | returnArray[faceCounter]['emotions'][emotionCounter]['Confidence'] = Int(thisEmotionObj.getConfidence()); 91 | }); 92 | }); 93 | return returnArray; 94 | } 95 | 96 | /** 97 | * @description Returns an structure of both the lines of text found in the image and the individual words 98 | * @requiredArguments 99 | * - awsBucketName = Name of the bucket where the images reside 100 | * - pathToImage = Path to the image in the provided bucket 101 | */ 102 | public struct function detectText(required string awsBucketName, required string pathToImage) { 103 | var returnStruct = structNew(); 104 | var thisDetectionObj = 0; 105 | var linesCounter = 0; 106 | var wordsCounter = 0; 107 | var detectTextRequest = CreateObject('java', 'com.amazonaws.services.rekognition.model.DetectTextRequest').init(); 108 | var imageToScan = CreateObject('java', 'com.amazonaws.services.rekognition.model.Image').init(); 109 | var imageS3Object = CreateObject('java', 'com.amazonaws.services.rekognition.model.S3Object').init(); 110 | imageS3Object.setBucket(arguments.awsBucketName); 111 | imageS3Object.setName(arguments.pathToImage); 112 | imageToScan.setS3Object(imageS3Object); 113 | detectTextRequest.setImage(imageToScan); 114 | 115 | var detectTextResult = variables.rekognitionService.detectText(detectTextRequest); 116 | var detectionsArray = detectTextResult.getTextDetections(); 117 | 118 | returnStruct.lines = arrayNew(1); 119 | returnStruct.words = arrayNew(1); 120 | 121 | detectionsArray.each(function(thisDetectionObj, index) { 122 | if (thisDetectionObj.getType() is "LINE") { 123 | linesCounter++; 124 | returnStruct.lines[linesCounter] = structNew(); 125 | returnStruct.lines[linesCounter]['label'] = thisDetectionObj.getDetectedText(); 126 | returnStruct.lines[linesCounter]['confidence'] = Int(thisDetectionObj.getConfidence()); 127 | returnStruct.lines[linesCounter]['id'] = thisDetectionObj.getID(); 128 | returnStruct.lines[linesCounter]['geometry'] = thisDetectionObj.getGeometry().toString(); 129 | } else { 130 | wordsCounter++; 131 | returnStruct.words[wordsCounter] = structNew(); 132 | returnStruct.words[wordsCounter]['label'] = thisDetectionObj.getDetectedText(); 133 | returnStruct.words[wordsCounter]['confidence'] = Int(thisDetectionObj.getConfidence()); 134 | returnStruct.words[wordsCounter]['id'] = thisDetectionObj.getID(); 135 | returnStruct.words[wordsCounter]['parentID'] = thisDetectionObj.getParentID(); 136 | returnStruct.words[wordsCounter]['geometry'] = thisDetectionObj.getGeometry().toString(); 137 | } 138 | }); 139 | return returnStruct; 140 | } 141 | 142 | /** 143 | * @description Returns an array of labels for the image as structures, with the label as the key and the confidence level as the value 144 | * @requiredArguments 145 | * - awsBucketName = Name of the bucket where the images reside 146 | * - pathToImage = Path to the image in the provided bucket 147 | */ 148 | public array function getImageLabels(required string awsBucketName, required string pathToImage) { 149 | var returnArray = arrayNew(1); 150 | var thisLabelObj = 0; 151 | var counter = 0; 152 | var labelsRequest = CreateObject('java', 'com.amazonaws.services.rekognition.model.DetectLabelsRequest').init(); 153 | var imageToLabel = CreateObject('java', 'com.amazonaws.services.rekognition.model.Image').init(); 154 | var imageS3Object = CreateObject('java', 'com.amazonaws.services.rekognition.model.S3Object').init(); 155 | imageS3Object.setBucket(arguments.awsBucketName); 156 | imageS3Object.setName(arguments.pathToImage); 157 | imageToLabel.setS3Object(imageS3Object); 158 | labelsRequest.setImage(imageToLabel); 159 | 160 | var labelsRequestResult = variables.rekognitionService.detectLabels(labelsRequest); 161 | var labelsArray = labelsRequestResult.getLabels(); 162 | 163 | labelsArray.each(function(thisLabelObj, index) { 164 | counter++; 165 | returnArray[counter] = structNew(); 166 | returnArray[counter]['label'] = thisLabelObj.getName(); 167 | returnArray[counter]['confidence'] = Int(thisLabelObj.getConfidence()); 168 | }); 169 | return returnArray; 170 | } 171 | 172 | /** 173 | * @description Returns the similarity value for the provided faces, or a -1 if there was no match 174 | * @requiredArguments 175 | * - compareFacesResult = Result from AWS CompareFacesRequest, of type com.amazonaws.services.rekognition.model.CompareFacesResult 176 | */ 177 | public numeric function getSimilarityValue(required any compareFacesResult) { 178 | var similarityValue = -1; 179 | var faceMatchesArray = arguments.compareFacesResult.getFaceMatches(); 180 | if (arrayLen(faceMatchesArray) gt 0) { 181 | similarityValue = faceMatchesArray[1].getSimilarity(); 182 | } 183 | return similarityValue; 184 | } 185 | } -------------------------------------------------------------------------------- /transcribe.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | // Although Transcribe supports FLAC, WAV, MP3 and MP4 files, this demo only handles MP3 or MP4. 6 | fileExtension = listLast(Trim(FORM.pathToFileOnS3), "."); 7 | if (NOT listFindNoCase("MP3,MP4", fileExtension)) { 8 | writeOutput('Unsupported fileExtension: ' & fileExtension); 9 | abort; 10 | } 11 | 12 | // The AWS Service Factory, created at application startup, handles the building of the object that will speak to 13 | // the Transcribe service, and handles authentication to AWS. 14 | transcribeService = application.awsServiceFactory.createServiceObject('transcribe'); 15 | 16 | // Each job in Transcribe for your account must have a unique name 17 | jobName = "AWSPlayboxTranscribeJob" & DateDiff("s", DateConvert("utc2Local", "January 1 1970 00:00"), now()); 18 | 19 | // Unlike most basic properties of a Transcribe job, the path to the file has to be put into its own Media object 20 | s3MediaObject = CreateObject('java', 'com.amazonaws.services.transcribe.model.Media').init(); 21 | s3MediaObject.setMediaFileUri(Trim(FORM.pathToFileOnS3)); 22 | 23 | // Transcribe job settings allow you to specify custom dictionaries, identify multiple speakers, and more. 24 | // https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/transcribe/model/Settings.html 25 | // Here we are enabling settings to identify up to five multiple speakers for all our jobs. 26 | transcribeJobSettings = CreateObject('java', 'com.amazonaws.services.transcribe.model.Settings').init(); 27 | transcribeJobSettings.setShowSpeakerLabels(true); 28 | transcribeJobSettings.setMaxSpeakerLabels(5); 29 | 30 | startTranscriptionJobRequest = CreateObject('java', 'com.amazonaws.services.transcribe.model.StartTranscriptionJobRequest').init(); 31 | startTranscriptionJobRequest.setTranscriptionJobName(jobName); 32 | startTranscriptionJobRequest.setMedia(s3MediaObject); 33 | startTranscriptionJobRequest.setMediaFormat(fileExtension); 34 | startTranscriptionJobRequest.setSettings(transcribeJobSettings); 35 | // This demo assumes that the audio is in English-US (en-US). As of Aug, 2018, Transcribe also supports Spanish-US (es-US). 36 | startTranscriptionJobRequest.setLanguageCode('en-US'); 37 | 38 | // When we start an execution of Transcribe job, we get a start job result object back. 39 | // Job result object is of type com.amazonaws.services.transcribe.model.StartTranscriptionJobResult 40 | startTranscriptionJobResult = transcribeService.startTranscriptionJob(startTranscriptionJobRequest); 41 | transcriptionJob = startTranscriptionJobResult.getTranscriptionJob(); 42 | 43 | jobInfo = { 44 | jobName=jobName, 45 | timeStarted=Now() 46 | }; 47 | 48 | // Here we add the information about current executions to memory so we can retrieve them as needed. 49 | // In a real app, you'd want to save this to database. 50 | arrayAppend(application.currentTranscribeJobs, jobInfo); 51 | 52 | 53 | 54 | 55 | 56 | transcribeService = application.awsServiceFactory.createServiceObject('transcribe'); 57 | deleteJob = 0; 58 | 59 | // In order to get information on any given transcript job, we have to make a getTranscriptionJobRequest 60 | getTranscriptionJobRequest = CreateObject('java', 'com.amazonaws.services.transcribe.model.GetTranscriptionJobRequest').init(); 61 | getTranscriptionJobRequest.setTranscriptionJobName(URL.checkTranscribeJob); 62 | 63 | getTranscriptionJobResult = transcribeService.getTranscriptionJob(getTranscriptionJobRequest); 64 | // The getTranscriptionJobResult object has its own method called getTranscriptionJob. Confusing, no? 65 | transcriptJob = getTranscriptionJobResult.getTranscriptionJob(); 66 | 67 | transcribeJobResult.status = transcriptJob.getTranscriptionJobStatus(); 68 | transcribeJobResult.jobName = transcriptJob.getTranscriptionJobName(); 69 | transcribeJobResult.createdOn = transcriptJob.getCreationTime(); 70 | transcribeJobResult.sourceMediaUri = transcriptJob.getMedia().getMediaFileUri(); 71 | // Possible results for the job status are Completed, Failed, and In_Progress. 72 | // A production application would would handle error results and schedule in progress tasks for checking again at a later time. 73 | if (transcribeJobResult.status IS "COMPLETED") { 74 | transcribeJobResult.finishedOn = transcriptJob.getCompletionTime(); 75 | // The URI location of the transcription output is in a Transcript object returned from the getTranscript method. 76 | transcribeJobResult.transcriptUri = transcriptJob.getTranscript().getTranscriptFileUri(); 77 | deleteJob = 1; 78 | } else if (transcribeJobResult.status IS "FAILED") { 79 | transcribeJobResult.failureReason = transcriptJob.getFailureReason(); 80 | deleteJob = 1; 81 | } 82 | if (deleteJob eq 1) { 83 | application.currentTranscribeJobs.each(function(element, index) { 84 | if (element.jobName IS URL.checkTranscribeJob) { 85 | arrayDeleteAt(application.currentTranscribeJobs, index); 86 | break; 87 | } 88 | }); 89 | } 90 | 91 | 92 | 93 | 94 | 95 | // See the comments for checkTranscribeJob, above, for what this all does. 96 | transcribeService = application.awsServiceFactory.createServiceObject('transcribe'); 97 | getTranscriptionJobRequest = CreateObject('java', 'com.amazonaws.services.transcribe.model.GetTranscriptionJobRequest').init(); 98 | getTranscriptionJobRequest.setTranscriptionJobName(URL.getTranscriptText); 99 | getTranscriptionJobResult = transcribeService.getTranscriptionJob(getTranscriptionJobRequest); 100 | transcriptJob = getTranscriptionJobResult.getTranscriptionJob(); 101 | transcriptUri = transcriptJob.getTranscript().getTranscriptFileUri(); 102 | 103 | // Read in the transcript file and pull out just the transcript text 104 | cfhttp(method="GET", charset="utf-8", url="#transcriptUri#", result="transcriptFile"); 105 | transcriptFileAsJSON = deserializeJSON(transcriptFile.fileContent, false); 106 | // The JSON result file contains four properties: accountID, jobName, results, and status. 107 | transcriptData = transcriptFileAsJSON.results; 108 | // Withn the results property, there are three properties: items, speaker labels, and transcripts. 109 | // Items are the indivdiual words in the transcript and the exact time each word appears. Useful for making captions. 110 | // The transcripts property is an array and, at this time, will only contain a single member, a property labeled: transcript. 111 | transcriptText = transcriptData.transcripts[1].transcript; 112 | cfheader(name="Content-Disposition", value="inline; fileName=#URL.getTranscriptText#.txt"); 113 | writeDump(transcriptText); 114 | abort; 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | AWS Playbox: AWS Service Demos 125 | 126 | 127 | 128 | 129 | 130 |
    131 |
    132 |

    AWS Service Demos:

    133 |

    Transcribe

    134 | 135 | 136 |

    Result of Transcribe job check:

    137 | 138 |

    Job Name: #transcribeJobResult.jobName#

    139 |

    Source Media: #transcribeJobResult.sourceMediaUri#

    140 |

    Created On: #DateTimeFormat(transcribeJobResult.createdOn, "long")#

    141 |

    Status: #transcribeJobResult.status#

    142 | 143 |

    Finished On: #DateTimeFormat(transcribeJobResult.finishedOn, "long")#

    144 |

    Transcript file location: #transcribeJobResult.transcriptUri#

    145 |

    Download the full transcript job output 146 |

    Download just the text transcript 147 | 148 |

    Note: these links are only valid until #DateTimeFormat(DateAdd('n',5,now()), 'short')#

    149 | 150 |

    Reason for failure: #transcribeJobResult.failureReason#

    151 |
    152 |
    153 |
    154 |
    155 | 156 | 157 |

    Current Transcribe Jobs:

    158 | 159 | 164 | 165 |
    166 | 167 |

    Start New Transcribe Job:

    168 |
    169 |

    Path to MP4 or MP3 file on S3:   170 |
    Note: the file in S3 must be in the same region as where you are calling Transcribe. 171 |
    The correct format for the path is: https://s3-<aws-region>.amazonaws.com/<bucket-name>/<keyprefix>/<objectkey> 172 |
    For example: https://s3-us-east-1.amazonaws.com/examplebucket/mediafolder/example.mp4
    173 |

    174 |
    175 | 176 |

    Home

    177 |
    178 |
    179 | 180 | 181 | -------------------------------------------------------------------------------- /s3.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | s3 = application.awsServiceFactory.createServiceObject('s3'); 9 | if ((Len(Trim(FORM.s3BucketName)) GT 1) && (Len(Trim(FORM.fileToPutOnS3)) GT 1)) { 10 | uploadedFile = fileUpload(getTempDirectory(), "form.fileToPutOnS3", " ", "makeunique"); 11 | fileLocation = getTempDirectory() & uploadedFile.serverFile; 12 | // The AWS SDK putFileRequest object requires a Java file object in binary format 13 | fileContent = fileReadBinary(getTempDirectory() & uploadedFile.serverFile); 14 | // The method signature for storing a file with Server-Side Encryption requires a byte stream, not a standard Java file object 15 | if (structKeyExists(FORM, "useSSES3")) { 16 | objectMetadata = CreateObject('java', 'com.amazonaws.services.s3.model.ObjectMetadata').init(); 17 | objectMetadata.setContentLength(ArrayLen(fileContent)); 18 | objectMetadata.setSSEAlgorithm(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION); 19 | fileInputStream = CreateObject('java', 'java.io.ByteArrayInputStream').init(fileContent); 20 | putFileRequest = CreateObject('java', 'com.amazonaws.services.s3.model.PutObjectRequest').init(trim(FORM.s3BucketName), uploadedFile.serverFile, fileInputStream, objectMetadata); 21 | } else { 22 | javaFileObject = CreateObject('java', 'java.io.File').init(fileLocation); 23 | putFileRequest = CreateObject('java', 'com.amazonaws.services.s3.model.PutObjectRequest').init(trim(FORM.s3BucketName), uploadedFile.serverFile, javaFileObject); 24 | } 25 | if ((structKeyExists(FORM, "storageClass")) && (len(trim(FORM.storageClass)) gt 1)) { 26 | storageClassObj = CreateObject('java', 'com.amazonaws.services.s3.model.StorageClass'); 27 | putFileRequest.setStorageClass(storageClassObj.valueOf(FORM.storageClass)); 28 | } 29 | if ((structKeyExists(FORM, "tagKey")) && (structKeyExists(FORM, "tagValue"))) { 30 | tag = CreateObject('java', 'com.amazonaws.services.s3.model.Tag').init(FORM.tagKey, FORM.tagValue); 31 | fileTagging = CreateObject('java', 'com.amazonaws.services.s3.model.ObjectTagging').init([tag]); 32 | putFileRequest.setTagging(fileTagging); 33 | } 34 | s3.putObject(putFileRequest); 35 | successMsg = "The file was uploaded to the S3 bucket. "; 36 | } 37 | 38 | 39 | 40 | 41 | 42 | 43 | credentialsConfig = CreateObject('component','awsPlaybox.model.awsCredentials').init(); 44 | signingUtils = CreateObject("component", "awsPlaybox.model.s3RequestSigningUtils").init(credentialsConfig.accessKey, credentialsConfig.secretKey); 45 | signedURL = signingUtils.createSignedURL(s3BucketName=FORM.s3BucketName,objectKey=FORM.pathToFile); 46 | resultData = "Signed URL to s3://#FORM.s3BucketName#/#FORM.pathToFile# : #signedURL#"; 47 | 48 | 49 | 50 | 53 | 54 | 55 | s3 = application.awsServiceFactory.createServiceObject('s3'); 56 | // Only update configuration if a rule has been selected 57 | if ((structKeyExists(FORM, "moveTo1ZIAAfter30")) || (structKeyExists(FORM, "deleteAfter90"))) { 58 | bucketLifecycleConfig = CreateObject('java', 'com.amazonaws.services.s3.model.BucketLifecycleConfiguration').init(); 59 | // Big thanks to Ben Nadel for documenting how to instantiate Java nested classes! 60 | // https://www.bennadel.com/blog/1370-ask-ben-instantiating-nested-java-classes-in-coldfusion.htm 61 | rule = CreateObject('java', 'com.amazonaws.services.s3.model.BucketLifecycleConfiguration$Rule').init(); 62 | ruleIDString = ""; 63 | if (structKeyExists(FORM, "moveTo1ZIAAfter30")) { 64 | ruleIDString = "Move to One Zone Infrequent Access after 30 days"; 65 | storageClassObj = CreateObject('java', 'com.amazonaws.services.s3.model.StorageClass'); 66 | transition = CreateObject('java', 'com.amazonaws.services.s3.model.BucketLifecycleConfiguration$Transition').withDays(30).withStorageClass(storageClassObj.valueOf('OneZoneInfrequentAccess')); 67 | // setTransitions expects a Java List, which is just a CF array 68 | rule.setTransitions([transition]); 69 | } 70 | if (structKeyExists(FORM, "deleteAfter90")) { 71 | if (len(ruleIDString)) { 72 | ruleIDString &= " and delete file after 90 days"; 73 | } else { 74 | ruleIDString = "Delete file after 90 days"; 75 | } 76 | rule.setExpirationInDays(90); 77 | } 78 | rule.setId(ruleIDString); 79 | rule.setStatus(bucketLifecycleConfig.ENABLED); 80 | bucketLifecycleConfig.setRules([rule]); 81 | s3.setBucketLifecycleConfiguration(FORM.s3BucketNameForLifecycle, bucketLifecycleConfig); 82 | } 83 | if (structKeyExists(FORM, "deleteLifecycleConfig")) { 84 | s3.deleteBucketLifecycleConfiguration(FORM.s3BucketNameForLifecycle); 85 | } 86 | // Get the current bucket lifecycle rules for display 87 | // This information sometimes gets cached by AWS, so that's why we sleep 88 | sleep(500); 89 | lifecycleConfigResult = s3.getBucketLifecycleConfiguration(FORM.s3BucketNameForLifecycle); 90 | if (NOT isNull(lifecycleConfigResult)) { 91 | rulesOnBucket = lifecycleConfigResult.getRules(); 92 | if (arrayLen(rulesOnBucket)) { 93 | ruleListString = "
      "; 94 | rulesOnBucket.each(function(item) { 95 | ruleListString &= "
    • " & item.getId() & "
    • "; 96 | }); 97 | ruleListString &= "
    "; 98 | } 99 | resultData = "Lifecycle rules on the #FORM.s3BucketNameForLifecycle# bucket: " & ruleListString; 100 | } else { 101 | resultData = "There are no lifecycle rules on the #FORM.s3BucketNameForLifecycle# bucket."; 102 | } 103 |
    104 |
    105 | 106 | 107 | 108 | 109 | s3 = application.awsServiceFactory.createServiceObject('s3'); 110 | // Once you enable versioning on a bucket, you can never set it to OFF. It must always be suspended. 111 | status = "Suspended"; 112 | if (structKeyExists(FORM, "enableVersioning")) { 113 | status = "Enabled"; 114 | } 115 | bucketVersioningConfig = CreateObject('java', 'com.amazonaws.services.s3.model.BucketVersioningConfiguration').withStatus(status); 116 | bucketVersioningConfigRequest = CreateObject('java', 'com.amazonaws.services.s3.model.SetBucketVersioningConfigurationRequest').init(FORM.s3BucketNameForVersioning, bucketVersioningConfig); 117 | s3.setBucketVersioningConfiguration(bucketVersioningConfigRequest); 118 | bucketVersioning = s3.getBucketVersioningConfiguration(FORM.s3BucketNameForVersioning); 119 | successMsg = "Versioning on the #FORM.s3BucketNameForVersioning# bucket: " & bucketVersioning.getStatus(); 120 | 121 | 122 | 123 | 124 | 125 | 126 | s3 = application.awsServiceFactory.createServiceObject('s3'); 127 | // We'll only retrieve the 5 most recent versions of the file 128 | listVersionsRequest = CreateObject('java', 'com.amazonaws.services.s3.model.ListVersionsRequest').withBucketName(FORM.s3BucketNameForFileVersions).withPrefix(trim(FORM.pathToFileForVersioning)).withMaxResults(5); 129 | versionsResult = s3.listVersions(listVersionsRequest); 130 | summariesArray = versionsResult.getVersionSummaries(); 131 | summariesArray.each(function(objectSummary) { 132 | resultData &= "Key: " & objectSummary.getKey() & ", versionID: " & objectSummary.getVersionId() & "
    "; 133 | }); 134 |
    135 |
    136 | 137 | 138 | 139 | 140 | s3 = application.awsServiceFactory.createServiceObject('s3'); 141 | taggingRequest = CreateObject('java', 'com.amazonaws.services.s3.model.GetObjectTaggingRequest').init(FORM.s3BucketNameForFileTags, FORM.pathToFileForTags); 142 | tagsResult = s3.getObjectTagging(taggingRequest); 143 | tagsArray = tagsResult.getTagSet(); 144 | tagsArray.each(function(tag) { 145 | resultData &= "Key: " & tag.getKey() & ", Value: " & tag.getValue() & "
    "; 146 | }); 147 |
    148 |
    149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | AWS Playbox: AWS Service Demos 157 | 158 | 159 | 160 | 161 | 162 |
    163 |
    164 |

    AWS Service Demos:

    165 |

    Simple Storage Service (S3)

    166 | 167 |

    #successMsg#

    168 |
    169 | 170 |

    #resultData#

    171 |
    172 | 173 |
    174 |

    Upload a File with the AWS Java SDK

    175 |

    This will bypass CFML tags and functions to upload the file to the specified destination bucket.

    176 |
    177 |

    178 | Enter the destination bucket name: 179 | value="#trim(FORM.s3BucketName)#">
    180 | Select a file to upload:
    181 | Use Server-Side Encryption with Amazon S3-Managed Keys:
    182 | Storage class:
    190 | With tags: Key: Value:
    191 | 192 |

    193 |
    194 |
    195 |

    Create a Time-Expiring, Signed URL for a File on S3

    196 |
    197 |

    198 | Enter the destination bucket name: 199 | value="#trim(FORM.s3BucketName)#">
    200 | Enter the path and file name in the bucket:
    201 | 202 |

    203 |
    204 |
    205 |

    Bucket Lifecycle Rules

    206 |
    207 |

    208 | Enter the target bucket name: 209 | value="#trim(FORM.s3BucketNameForLifecycle)#">
    210 | Move to S3 One Zone-Infrequent Access after 30 days
    211 | Delete file after 90 days
    212 | OR:
    213 | Delete lifecycle configuration on bucket
    214 | 215 |

    216 |
    217 |
    218 |

    Bucket Versioning

    219 |
    220 |

    221 | Enter the bucket name: 222 | value="#trim(FORM.s3BucketNameForVersioning)#">
    223 | Enable versioning
    224 | Disable versioning
    225 | 226 |

    227 |
    228 |
    229 |

    List Versions of a File

    230 |
    231 |

    232 | Enter the destination bucket name: 233 | value="#trim(FORM.s3BucketNameForFileVersions)#">
    234 | Enter the path and file name in the bucket:
    235 | 236 |

    237 |
    238 |
    239 |

    List Tags on a File

    240 |
    241 |

    242 | Enter the destination bucket name: 243 | value="#trim(FORM.s3BucketNameForFileTags)#">
    244 | Enter the path and file name in the bucket:
    245 | 246 |

    247 |
    248 |
    249 |

    Home

    250 |
    251 |
    252 | 253 | 254 | -------------------------------------------------------------------------------- /iam.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | if (structKeyExists(application.awsResources.iam, "S3PolicyARN")) { 8 | errorMsg = "You have already created a policy with read/write permission to the 'awsPlayboxPrivate' S3 bucket."; 9 | } else { 10 | policyName = 'awsPlayboxDemoPolicy-ReadWriteAWSPlayboxPrivateBucket-' & dateTimeFormat(Now(), "yyyy-mm-dd-HH-nn-ss"); 11 | createPolicyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.CreatePolicyRequest') 12 | .withPolicyName(policyName) 13 | .withDescription('Allows read/write permission to the awsPlayboxPrivate S3 bucket.'); 14 | policyJSON = fileRead(expandPath("./iamPolicies/awsPlayboxPrivateReadWrite.txt")); 15 | createPolicyRequest.setPolicyDocument(policyJSON); 16 | createPolicyResult = iam.createPolicy(createPolicyRequest); 17 | policyDetails = createPolicyResult.getPolicy(); 18 | application.awsResources.iam.S3PolicyName = policyDetails.getPolicyName(); 19 | application.awsResources.iam.S3PolicyARN = policyDetails.getARN(); 20 | } 21 | 22 | 23 | 24 | 25 | 26 | if (structKeyExists(application.awsResources.iam, "SNSPolicyARN")) { 27 | errorMsg = "You have already created a policy with permission to send messages to the SNS topic: " & application.awsResources.currentSNSTopicARN; 28 | } else { 29 | policyName = 'awsPlayboxDemoPolicy-SendToSNS-' & dateTimeFormat(Now(), "yyyy-mm-dd-HH-nn-ss"); 30 | createPolicyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.CreatePolicyRequest') 31 | .withPolicyName(policyName) 32 | .withDescription('Allows user to send message to the SNS topic:' & application.awsResources.currentSNSTopicARN); 33 | policyJSON = fileRead(expandPath("./iamPolicies/snsSendMessage.txt")); 34 | // The policy text file has a placeholder for the current SNS topic for the application 35 | policyDetails = replace(policyDetails, "%CURRENT_TOPIC_ARN%", application.awsResources.currentSNSTopicARN); 36 | createPolicyRequest.setPolicyDocument(policyJSON); 37 | createPolicyResult = iam.createPolicy(createPolicyRequest); 38 | policyDetails = createPolicyResult.getPolicy(); 39 | application.awsResources.iam.SNSPolicyName = policyDetails.getPolicyName(); 40 | application.awsResources.iam.SNSPolicyARN = policyDetails.getARN(); 41 | } 42 | 43 | 44 | 45 | 46 | 47 | if (structKeyExists(application.awsResources.iam, "S3GroupARN")) { 48 | errorMsg = "You have already created a group with the S3 bucket policy."; 49 | } else if (NOT structKeyExists(application.awsResources.iam, "S3PolicyARN")) { 50 | errorMsg = "You first need to create a policy with read/write permission to the 'awsPlayboxPrivate' S3 bucket."; 51 | } else if (NOT structKeyExists(application.awsResources.iam, "SNSPolicyARN")) { 52 | errorMsg = "You first need to create a policy with permission to publish to the current SNS topic."; 53 | } else { 54 | // This is a two-step process: 55 | // 1. Create the group 56 | // 2. Add the policy/policies to the group 57 | groupName = 'awsPlayboxDemoGroup-' & dateTimeFormat(Now(), "yyyy-mm-dd-HH-nn-ss"); 58 | createGroupRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.CreateGroupRequest') 59 | .withGroupName(groupName); 60 | createGroupResult = iam.createGroup(createGroupRequest); 61 | groupDetails = createGroupResult.getGroup(); 62 | // Add the S3 policy to the group 63 | attachGroupPolicyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.AttachGroupPolicyRequest') 64 | .withGroupName(groupName) 65 | .withPolicyArn(application.awsResources.iam.S3PolicyARN); 66 | // attachGroupPolicy doesn't really return anyting useful in the AttachGroupPolicyResult object. It either succeeds or throws an error. 67 | // See https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/identitymanagement/model/AttachGroupPolicyResult.html 68 | attachGroupPolicyRequestResult = iam.attachGroupPolicy(attachGroupPolicyRequest); 69 | // Attach the SNS policy to the group 70 | attachGroupPolicyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.AttachGroupPolicyRequest') 71 | .withGroupName(groupName) 72 | .withPolicyArn(application.awsResources.iam.SNSPolicyARN); 73 | attachGroupPolicyRequestResult = iam.attachGroupPolicy(attachGroupPolicyRequest); 74 | // Only add the information to the application structure if everything has gone through thus far 75 | application.awsResources.iam.PlayboxGroupName = groupDetails.getGroupName(); 76 | application.awsResources.iam.PlayboxGroupARN = groupDetails.getARN(); 77 | } 78 | 79 | 80 | 81 | 82 | 83 | if (structKeyExists(application.awsResources.iam, "PlayboxUserARN")) { 84 | errorMsg = "You have already created a user for this demonstration."; 85 | } else if (NOT structKeyExists(application.awsResources.iam, "PlayboxGroupName")) { 86 | errorMsg = "You first need to create a group to access the S3 bucket and SNS topic."; 87 | } else { 88 | // This is a three step process: 89 | // 1. Create the user 90 | // 2. Create an Access Key for the user and get a Secret Key back 91 | // 3. Add the user to the group 92 | // 93 | // STEP ONE: Create the User 94 | userName = 'awsPlayboxDemo-' & dateTimeFormat(Now(), "yyyy-mm-dd-HH-nn-ss"); 95 | createUserRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.CreateUserRequest') 96 | .withUserName(userName); 97 | // Tags help you identify user types for management and billing purposes. 98 | // They're very helpful as the complexity of your AWS service usage grows. 99 | // The setTags() method takes a Java . A CFML array works just fine. 100 | userTag = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.Tag') 101 | .withKey('userType') 102 | .withValue('Demonstration'); 103 | tagArray = [ userTag ]; 104 | createUserRequest.setTags(tagArray); 105 | createUserResult = iam.createUser(createUserRequest); 106 | userDetails = createUserResult.getUser(); 107 | // 108 | // STEP TWO: Create an Access Key (and get the corresponding Secret Key in the result) 109 | // By default, when you create a new user, that user has no permissions, and no way to authenticate to AWS. 110 | // You have to create an Access Key/Secret Key pair for basic authentication. 111 | createAccessKeyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.CreateAccessKeyRequest') 112 | .withUserName(userName); 113 | createAccessKeyResult = iam.createAccessKey(createAccessKeyRequest); 114 | accesKeyInfo = createAccessKeyResult.getAccessKey(); 115 | // Note that Secret Keys are only ever delivered one time. There's no way to retrieve them from AWS after creation. 116 | // 117 | // STEP THREE: Add the user to the group 118 | addUserToGroupRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.AddUserToGroupRequest') 119 | .withGroupName(application.awsResources.iam.PlayboxGroupName) 120 | .withUserName(userName); 121 | // addUserToGroup doesn't really return anyting useful in the AddUserToGroupResult object. It either succeeds or throws an error. 122 | // See https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/identitymanagement/model/AddUserToGroupResult.html 123 | addUserToGroupResult = iam.addUserToGroup(addUserToGroupRequest); 124 | // 125 | // Only add the information to the application structure if everything has gone through thus far 126 | application.awsResources.iam.PlayboxUserName = userDetails.getUserName(); 127 | application.awsResources.iam.PlayboxUserARN = userDetails.getARN(); 128 | // We're storing the Secret Key in memory here. 129 | // For the love of all that is good, if you store this information in your own apps, you better do it as securely as possible. 130 | application.awsResources.iam.PlayboxUserAccessKeyID = accesKeyInfo.getAccessKeyID(); 131 | application.awsResources.iam.PlayboxUserSecretKey = accesKeyInfo.getSecretAccessKey(); 132 | application.awsResources.iam.PlayboxUserAccessKeyStatus = accesKeyInfo.getStatus(); 133 | // The createdOn value for the Access Key is useful for determining which Access/Secret keys are old and need to be rotated. 134 | application.awsResources.iam.PlayboxUserAccessKeyCreatedOn = accesKeyInfo.getCreateDate(); 135 | } 136 | 137 | 138 | 139 | 140 | 141 | if (NOT structKeyExists(application.awsResources.iam, "PlayboxUserARN")) { 142 | errorMsg = "You first need to create a user for this demonstration."; 143 | } else { 144 | // A user can have more than one set of credentials in AWS. 145 | // It's common practice to set the current set of credentials as "inactive" 146 | // and then assign a new set when you rotate access keys. 147 | // You would do that via an UpdateAccessKeyRequest. 148 | // Here, we're simply deleting the old and creating a new one. 149 | // You should use the createdOn value on your access keys to determine 150 | // which ones are old and should be rotated. 151 | deleteAccessKeyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.DeleteAccessKeyRequest') 152 | .withUserName(application.awsResources.iam.PlayboxUserName) 153 | .withAccessKeyID(application.awsResources.iam.PlayboxUserAccessKeyID); 154 | // The deleteAccessKey method doesn't really return anything useful in the result object. It either succeeds or fails. 155 | deleteAccessKey = iam.deleteAccessKey(deleteAccessKeyRequest); 156 | // Now make a new access key for this user 157 | createAccessKeyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.CreateAccessKeyRequest') 158 | .withUserName(application.awsResources.iam.PlayboxUserName); 159 | createAccessKeyResult = iam.createAccessKey(createAccessKeyRequest); 160 | accesKeyInfo = createAccessKeyResult.getAccessKey(); 161 | application.awsResources.iam.PlayboxUserAccessKeyID = accesKeyInfo.getAccessKeyID(); 162 | application.awsResources.iam.PlayboxUserSecretKey = accesKeyInfo.getSecretAccessKey(); 163 | application.awsResources.iam.PlayboxUserAccessKeyStatus = accesKeyInfo.getStatus(); 164 | application.awsResources.iam.PlayboxUserAccessKeyCreatedOn = accesKeyInfo.getCreateDate(); 165 | accessKeysRotated = 1; 166 | } 167 | 168 | 169 | 170 | 171 | 172 | if (structKeyExists(application.awsResources.iam, "PlayboxUserARN")) { 173 | // Before you can delete the user, you have to: 174 | // 1. Delete all access keys 175 | // a. To do this with multiple access keys on an account, you have to first request all keys via 176 | // a ListAccessKeysRequest, then loop through the result, deleting each key as you go. 177 | // 2. Remove a user from all groups 178 | deleteAccessKeyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.DeleteAccessKeyRequest') 179 | .withUserName(application.awsResources.iam.PlayboxUserName) 180 | .withAccessKeyID(application.awsResources.iam.PlayboxUserAccessKeyID); 181 | deleteAccessKey = iam.deleteAccessKey(deleteAccessKeyRequest); 182 | removeUserFromGroupRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.RemoveUserFromGroupRequest') 183 | .withGroupName(application.awsResources.iam.PlayboxGroupName) 184 | .withUserName(application.awsResources.iam.PlayboxUserName); 185 | removeUserFromGroup = iam.removeUserFromGroup(removeUserFromGroupRequest); 186 | deleteUserRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.DeleteUserRequest') 187 | .withUserName(application.awsResources.iam.PlayboxUserName); 188 | deleteUserResult = iam.deleteUser(deleteUserRequest); 189 | } 190 | if (structKeyExists(application.awsResources.iam, "PlayboxGroupARN")) { 191 | // Before you can delete a group, you have to detach all policies associated with that group 192 | detachGroupPolicyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.DetachGroupPolicyRequest') 193 | .withPolicyArn(application.awsResources.iam.S3PolicyARN) 194 | .withGroupName(application.awsResources.iam.PlayboxGroupName); 195 | detachGroupPolicyResult = iam.detachGroupPolicy(detachGroupPolicyRequest); 196 | detachGroupPolicyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.DetachGroupPolicyRequest') 197 | .withPolicyArn(application.awsResources.iam.SNSPolicyARN) 198 | .withGroupName(application.awsResources.iam.PlayboxGroupName); 199 | detachGroupPolicyResult = iam.detachGroupPolicy(detachGroupPolicyRequest); 200 | deleteGroupRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.DeleteGroupRequest') 201 | .withGroupName(application.awsResources.iam.PlayboxGroupName); 202 | deleteGroupResult = iam.deleteGroup(deleteGroupRequest); 203 | } 204 | if (structKeyExists(application.awsResources.iam, "S3PolicyARN")) { 205 | deleteS3PolicyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.DeletePolicyRequest') 206 | .withPolicyArn(application.awsResources.iam.S3PolicyARN); 207 | deleteS3PolicyResult = iam.deletePolicy(deleteS3PolicyRequest); 208 | } 209 | if (structKeyExists(application.awsResources.iam, "SNSPolicyARN")) { 210 | deleteSNSPolicyRequest = CreateObject('java', 'com.amazonaws.services.identitymanagement.model.DeletePolicyRequest') 211 | .withPolicyArn(application.awsResources.iam.SNSPolicyARN); 212 | deleteSNSPolicyResult = iam.deletePolicy(deleteSNSPolicyRequest); 213 | } 214 | application.awsResources.iam = {}; 215 | errorMsg = "All current IAM resources have been deleted."; 216 | 217 | 218 | 219 | 220 | stepsDone = {}; 221 | stepsDone.createS3Policy = (structKeyExists(application.awsResources.iam, "S3PolicyARN")) ? 1 : 0; 222 | stepsDone.createSNSPolicy = (structKeyExists(application.awsResources.iam, "SNSPolicyARN")) ? 1 : 0; 223 | stepsDone.createPlayboxGroup = (structKeyExists(application.awsResources.iam, "PlayboxGroupName")) ? 1 : 0; 224 | stepsDone.createPlayboxUser = (structKeyExists(application.awsResources.iam, "PlayboxUserARN")) ? 1 : 0; 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | AWS Playbox: AWS Service Demos 234 | 235 | 236 | 237 | 238 | 239 |
    240 |
    241 |

    AWS Service Demos:

    242 |

    Identity Access Management (IAM)

    243 |

    Basic IAM Workflow:

    244 |
      245 |
    • Create policies
    • 246 |
    • Make groups to follow those policies
    • 247 |
    • Create users and add them to those groups to acccess resources in AWS
    • 248 |
    • Rotate user access keys on a regular basis
    • 249 |
    250 | 251 | 252 |

    #errorMsg#

    253 |
    254 | 255 |

    Policies

    256 |
      257 |
    1. 258 | Create policy with read/write permission to "awsPlayboxPrivate" S3 bucket. 259 | 260 |   Do It 261 | 262 |
        263 | 264 |
      • Policy Name: #application.awsResources.iam.S3PolicyName#
      • 265 |
      • Policy ARN: #application.awsResources.iam.S3PolicyARN#
      • 266 |
        267 |
      268 |
      269 |
    2. 270 |
    3. 271 | Create policy to allow sending of messages to the SNS topic created on the SNS page. 272 | 273 |

      Go to the SNS page and create a topic first. 274 | 275 | 276 |   Do It 277 |
        278 | 279 |
      • SNS Topic ARN: #application.awsResources.currentSNSTopicARN#
      • 280 |
        281 |
      282 | 283 |
        284 | 285 |
      • Policy Name: #application.awsResources.iam.SNSPolicyName#
      • 286 |
      • Policy ARN: #application.awsResources.iam.SNSPolicyARN#
      • 287 |
        288 |
      289 |
      290 |
      291 |
    4. 292 |
    293 |

    Groups

    294 |
      295 |
    1. 296 | Create group to utilize the S3 and SNS policies, above. 297 | 298 |   Do It 299 | 300 |
        301 | 302 |
      • Group Name: #application.awsResources.iam.PlayboxGroupName#
      • 303 |
      • Group ARN: #application.awsResources.iam.PlayboxGroupARN#
      • 304 |
        305 |
      306 |
      307 |
    2. 308 |
    309 |

    Users

    310 |
      311 |
    1. 312 | Create user to access resources using the AWS Playbox group, above. 313 | 314 |   Do It 315 | 316 |
        317 | 318 |
      • User Name: #application.awsResources.iam.PlayboxUserName#
      • 319 |
      • User ARN: #application.awsResources.iam.PlayboxUserARN#
      • 320 |
      • User Access Key: #application.awsResources.iam.PlayboxUserAccessKeyID#
      • 321 |
      • User Secret Key: #application.awsResources.iam.PlayboxUserSecretKey#
      • 322 |
      • User Access Key Status: #application.awsResources.iam.PlayboxUserAccessKeyStatus#
      • 323 |
      • User Access Key Created On: #DateTimeFormat(application.awsResources.iam.PlayboxUserAccessKeyCreatedOn,"yyyy-mm-dd HH:nn:ss")#
      • 324 |
      • Test out the permissions for this user
      • 325 |
        326 |
      327 |
      328 |
    2. 329 |
    3. 330 | Rotate access keys of AWS Playbox user, above. 331 | 332 |   Do It 333 | 334 | 335 |
        336 | 337 |
      • Access keys successfully rotated, and are listed above.
      • 338 |
        339 |
      340 |
      341 |
    4. 342 |
    343 |

     

    344 |

    Delete all of these IAM resources.

    345 |

    Home

    346 |
    347 |
    348 | 349 | --------------------------------------------------------------------------------