Skip to content
This repository has been archived by the owner on Apr 19, 2023. It is now read-only.

Commit

Permalink
Adding support to new AWS Service (#127)
Browse files Browse the repository at this point in the history
* IVS Support
  • Loading branch information
wizage authored Jul 15, 2020
1 parent ec49847 commit 8f086ca
Show file tree
Hide file tree
Showing 13 changed files with 850 additions and 41 deletions.
11 changes: 9 additions & 2 deletions commands/video/setup-obs.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ module.exports = {
return;
}

const filteredProjects = Object.keys(amplifyMeta[category]).filter(project => (
amplifyMeta[category][project].serviceType === 'livestream' || amplifyMeta[category][project].serviceType === 'ivs'));
if (filteredProjects.length === 0) {
context.print.error('You have no livestreaming projects.');
return;
}

const chooseProject = [
{
type: 'list',
name: 'resourceName',
message: 'Choose what project you want to set up OBS for?',
choices: Object.keys(amplifyMeta[category]),
default: Object.keys(amplifyMeta[category])[0],
choices: filteredProjects,
default: filteredProjects[0],
},
];
const props = await inquirer.prompt(chooseProject);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Description: S3 Workflow

Parameters:
pProjectName:
Type: String
Description: ProjectName
AllowedPattern: "[a-zA-Z][a-zA-Z0-9-_]*"
Default: DefaultName
pFunctionArn:
Type: String
Description: Name of function
Default: arn-default
Outputs:
oVideoChannelArn:
Value: !GetAtt rIVSChannel.arn
oVideoOutput:
Value: !GetAtt rIVSChannel.playbackUrl
oVideoInputURL:
Value: !GetAtt rIVSChannel.ingestURL
oVideoInputKey:
Value: !GetAtt rIVSChannel.streamKeyValue

Resources:
rIVSChannel:
Type: "Custom::StarfruitChannel"
Properties:
ServiceToken: !Ref pFunctionArn
name: !Ref pProjectName
API: VideoChannel
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Description: IVS Shim Template

Parameters:
pS3:
Type: String
Description: Store template and lambda package
AllowedPattern: "[a-zA-Z][a-zA-Z0-9-_]*"
Default: amazonbooth
pFunctionName:
Type: String
Description: Name of function
Default: default
pFunctionHash:
Type: String
Description: FunctionHash
Default: default

Outputs:
oIVSLambda:
Value: !GetAtt rIVSChannelLambda.Arn
Description: Arn for Lambda function

Resources:
rIVSChannelLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Ref pFunctionName
Description: A shim for IVS support. Can do anything that the API can do
Handler: index.handler
Role: !GetAtt rIVSRole.Arn
Runtime: nodejs12.x
Timeout: 30
Code:
S3Bucket: !Ref pS3
S3Key: !Sub
- ivs-helpers/IVSShim-${hash}.zip
- { hash: !Ref pFunctionHash }

rIVSRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
-
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
-
PolicyName: !Sub "${AWS::StackName}-internal-trigger-role"
PolicyDocument:
Statement:
-
Effect: Allow
Action:
- ivs:*
Resource: "*"
-
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:DescribeLogStreams
- logs:PutLogEvents
Resource:
- arn:aws:logs:*:*:*
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/* eslint-disable strict */
/* eslint-disable global-require */
/* eslint-disable */
const crypto = require('crypto');
const rp = require('request-promise-native');

const SigV4Utils = {
sign(key, msg) {
return crypto.createHmac('sha256', key).update(msg).digest().toString('hex');
},
sha256(msg) {
return crypto.createHash('sha256').update(msg, 'utf8').digest().toString('hex');
},
getSignatureKey(key, dateStamp, regionName, serviceName) {
const kDate = crypto.createHmac('sha256', `AWS4${key}`).update(dateStamp).digest();
const kRegion = crypto.createHmac('sha256', kDate).update(regionName).digest();
const kService = crypto.createHmac('sha256', kRegion).update(serviceName).digest();
const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest();
return kSigning;
},
};

exports.handler = async (event, context) => {
const config = event.ResourceProperties;
let responseData = {};
switch (event.RequestType) {
case 'Create':
responseData = await createIVS(config);
break;
case 'Delete':
responseData = await deleteIVS(event, config);
break;
case 'Update':
responseData = await updateIVS(event, config);
break;
default:
console.log(`${event.RequestType} is not supported. No changes are applied`);
}
const response = await sendResponse(event, context, 'SUCCESS', responseData);
console.log('CFN STATUS:: ', response, responseData);
};

async function createIVS(config) {
if (config.API === 'VideoChannel') {
const flattenResults = {};
const results = await signAndRequest('POST', '/CreateChannel', config);
flattenResults.arn = results.channel.arn;
flattenResults.playbackUrl = results.channel.playbackUrl;
flattenResults.streamKeyValue = results.streamKey.value;
flattenResults.ingestURL = results.channel.ingestEndpoint;
return flattenResults;
}
return { error: "API doesn't exists" };
}

async function updateIVS(config) {
console.log(config);
return {};
}

async function deleteIVS(event, config) {
if (config.API === 'VideoChannel') {
const deleteRequest = {
arn: event.PhysicalResourceId,
};
const results = await signAndRequest('POST', '/DeleteChannel', deleteRequest);
if (results) {
results.arn = event.PhysicalResourceId;
results.message = 'Delete was successful';
return results;
}
deleteRequest.message = 'Delete was successful';
return deleteRequest;
}
}

async function signAndRequest(method, uriRaw, config) {
const service = 'ivs';
const region = process.env.AWS_REGION;
const accessKey = process.env.AWS_ACCESS_KEY_ID;
const secretKey = process.env.AWS_SECRET_ACCESS_KEY;
const token = encodeURIComponent(process.env.AWS_SESSION_TOKEN);
const algorithm = 'AWS4-HMAC-SHA256';
const host = 'ivs.us-west-2.amazonaws.com';
const canonicalUri = uriRaw;

const now = new Date();
const amzdate = `${now.toISOString().replace(/[-:]/g, '').split('.')[0]}Z`;
const dateStamp = amzdate.split('T')[0];
const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
let canonicalQuerystring = 'X-Amz-Algorithm=AWS4-HMAC-SHA256';
canonicalQuerystring += `&X-Amz-Credential=${encodeURIComponent(`${accessKey}/${credentialScope}`)}`;
canonicalQuerystring += `&X-Amz-Date=${amzdate}`;
canonicalQuerystring += '&X-Amz-Expires=86400';
canonicalQuerystring += `&X-Amz-Security-Token=${token}`;
canonicalQuerystring += '&X-Amz-SignedHeaders=host';
delete config.ServiceToken;
delete config.API;

const canonicalHeaders = `host:${host}\n`;
const payloadHash = SigV4Utils.sha256(JSON.stringify(config));
const canonicalRequest = `${method}\n${canonicalUri}\n${canonicalQuerystring}\n${canonicalHeaders}\nhost\n${payloadHash}`;

const stringToSign = `${algorithm}\n${amzdate}\n${credentialScope}\n${SigV4Utils.sha256(canonicalRequest)}`;
const signingKey = SigV4Utils.getSignatureKey(secretKey, dateStamp, region, service);
const signature = SigV4Utils.sign(signingKey, stringToSign);

canonicalQuerystring += `&X-Amz-Signature=${signature}`;
const requestURL = `${host}${canonicalUri}?${canonicalQuerystring}`;
const options = {
method,
uri: `https://${requestURL}`,
body: config,
json: true,
};
const urlReturn = await rp(options);
console.log(urlReturn);

return urlReturn;
}

async function sendResponse(event, context, responseStatus, responseData) {
const responseBodyDictionary = {
Status: responseStatus,
Reason: `See the details in CloudWatch Log Stream: ${context.logStreamName}`,
StackId: event.StackId,
RequestId: event.RequestId,
LogicalResourceId: event.LogicalResourceId,
Data: responseData,
};
if (responseData !== undefined) {
responseBodyDictionary.PhysicalResourceId = responseData.arn;
responseBodyDictionary.Data = responseData;
}

const responseBody = JSON.stringify(responseBodyDictionary);

console.log('RESPONSE BODY:\n', responseBody);

const options = {
method: 'PUT',
uri: event.ResponseURL,
body: responseBody,
headers: {
'content-type': '',
'content-length': responseBody.length,
},
};
const urlReturn = await rp(options);
console.log('urlReturn: ', urlReturn);

console.log('options: ', options);

console.log('SENDING RESPONSE...\n');
return urlReturn;
}
Loading

0 comments on commit 8f086ca

Please sign in to comment.