Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple backends & minor fixes #1

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
50 changes: 48 additions & 2 deletions commands/video/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ module.exports = {
name: subcommand,
run: async (context) => {
const { amplify } = context;
const targetDir = amplify.pathManager.getBackendDirPath();
const amplifyMeta = amplify.getProjectMeta();
const targetDir = amplify.pathManager.getBackendDirPath();
const projectDetails = context.amplify.getProjectDetails();
const newEnvName = projectDetails.localEnvInfo.envName;
const resourceFilesBaseDir = `${targetDir}/video/${shared.resourceName}/`;
const resourceFilesList = fs.readdirSync(resourceFilesBaseDir);

if (!(category in amplifyMeta) || Object.keys(amplifyMeta[category]).length === 0) {
context.print.error(`You have no ${category} projects.`);
Expand All @@ -35,7 +39,49 @@ module.exports = {
return;
}

const props = JSON.parse(fs.readFileSync(`${targetDir}/video/${shared.resourceName}/props.json`));
// Check if env-specific props file already exists
const hasOwnEnvProps = resourceFilesList.includes(`${newEnvName}-props.json`);

// Check if ANY props file exist for a different env in this project || returns array
const hasAnyEnvProps = resourceFilesList.find(fileName => fileName.includes('-props.json'));

// If this env doesn't have its own props AND there is an existing amplify-video resource
if (!hasOwnEnvProps && hasAnyEnvProps) {
// take the first props file you find and copy that!
const propsFilenameToCopy = resourceFilesList.filter(propsFileName => propsFileName.includes('-props.json'))[0];

// extract substring for the existing env's name we're going to copy over
const envNameToReplace = propsFilenameToCopy.substr(0, propsFilenameToCopy.indexOf('-'));

// read JSON from the existing env's props file
const existingPropsToMutate = JSON.parse(fs.readFileSync(`${resourceFilesBaseDir}/${propsFilenameToCopy}`));

const searchAndReplaceProps = () => {
const newPropsObj = {};
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(existingPropsToMutate.contentDeliveryNetwork)) {
// look for any string values that contain existing env's name
if (typeof value === 'string' && value.includes(`${envNameToReplace}`)) {
// replace with new env name
const newValue = value.replace(new RegExp(envNameToReplace, 'g'), `${newEnvName}`);
newPropsObj[key] = newValue;
} else {
// copy existing values that do not match replacement conditions aka "generic props"
newPropsObj[key] = value;
}
}
return newPropsObj;
};

// merge new props and existing generic props
const newPropsToSave = Object.assign(
existingPropsToMutate, { contentDeliveryNetwork: searchAndReplaceProps() },
);

fs.writeFileSync(`${resourceFilesBaseDir}/${newEnvName}-props.json`, JSON.stringify(newPropsToSave, null, 4));
}

const props = JSON.parse(fs.readFileSync(`${targetDir}/video/${shared.resourceName}/${projectDetails.localEnvInfo.envName}-props.json`));

return buildTemplates(context, props);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ Resources:
ServiceToken: !Ref pFunctionArn
name: !Ref pProjectName
type: !Ref pQuality
latencyMode: !Ref pLatencyMode
latencyMode: !Ref pLatencyMode
tags: {"amplify-video" : "amplify-video"}
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@
"rDistribution" : {
"Type": "AWS::CloudFront::Distribution",
"Properties": {
"Tags" : [{
"Key" : "amplify-video",
"Value" : "amplify-video"
}],
"DistributionConfig": {
"Origins": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ Resources:
rCloudFrontDist:
Type: AWS::CloudFront::Distribution
Properties:
Tags:
- Key: amplify-video
Value: amplify-video
DistributionConfig:
DefaultCacheBehavior:
ForwardedValues:
Expand All @@ -40,8 +43,8 @@ Resources:
TrustedKeyGroups:
- !Ref rCloudFrontKeyGroup
<% } %>
Origins:
-
Origins:
-
DomainName: !Ref pBucketUrl
Id: vodS3Origin
S3OriginConfig:
Expand Down Expand Up @@ -72,4 +75,4 @@ Outputs:
<% } %>
oCFDomain:
Value: !GetAtt rCloudFrontDist.DomainName
Description: Domain for our videos
Description: Domain for our videos
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ const aws = require('aws-sdk');
var globalPem;
/* eslint-enable */

exports.handler = async (event) => {
async function handler(event) {
const response = await signPath(event.source.id);
return response;
};
}

async function sign(pathURL) {
const epoch = Math.floor(new Date(new Date().getTime() + (3600 * 1000)).getTime() / 1000);
Expand Down Expand Up @@ -45,4 +45,5 @@ async function signPath(id) {

module.exports = {
signPath,
handler,
};
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ async function createJob(eventObject) {
UserMetadata: {},
Role: process.env.MC_ROLE,
Settings: jobSettings,
Tags: { 'amplify-video': 'amplify-video' },
};
await mcClient.createJob(jobParams).promise();
}
6 changes: 4 additions & 2 deletions provider-utils/awscloudformation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ let serviceMetadata;
async function addResource(context, service, options) {
serviceMetadata = context.amplify.readJsonFile(`${__dirname}/../supported-services.json`)[service];
const targetDir = context.amplify.pathManager.getBackendDirPath();
const projectDetails = context.amplify.getProjectDetails();
const { serviceWalkthroughFilename, defaultValuesFilename } = serviceMetadata;
const serviceWalkthroughSrc = `${__dirname}/service-walkthroughs/${serviceWalkthroughFilename}`;
const { serviceQuestions } = require(serviceWalkthroughSrc);
Expand All @@ -23,21 +24,22 @@ async function addResource(context, service, options) {
if (result.parameters !== undefined) {
await fs.writeFileSync(`${targetDir}/video/${result.shared.resourceName}/parameters.json`, JSON.stringify(result.parameters, null, 4));
}
await fs.writeFileSync(`${targetDir}/video/${result.shared.resourceName}/props.json`, JSON.stringify(result, null, 4));
await fs.writeFileSync(`${targetDir}/video/${result.shared.resourceName}/${projectDetails.localEnvInfo.envName}-props.json`, JSON.stringify(result, null, 4));
await buildTemplates(context, result);
}

async function updateResource(context, service, options, resourceName) {
serviceMetadata = context.amplify.readJsonFile(`${__dirname}/../supported-services.json`)[service];
const targetDir = context.amplify.pathManager.getBackendDirPath();
const projectDetails = context.amplify.getProjectDetails();
const { serviceWalkthroughFilename, defaultValuesFilename } = serviceMetadata;
const serviceWalkthroughSrc = `${__dirname}/service-walkthroughs/${serviceWalkthroughFilename}`;
const { serviceQuestions } = require(serviceWalkthroughSrc);
const result = await serviceQuestions(context, options, defaultValuesFilename, resourceName);
if (result.parameters !== undefined) {
await fs.writeFileSync(`${targetDir}/video/${result.shared.resourceName}/parameters.json`, JSON.stringify(result.parameters, null, 4));
}
await fs.writeFileSync(`${targetDir}/video/${result.shared.resourceName}/props.json`, JSON.stringify(result, null, 4));
await fs.writeFileSync(`${targetDir}/video/${result.shared.resourceName}/${projectDetails.localEnvInfo.envName}-props.json`, JSON.stringify(result, null, 4));
await buildTemplates(context, result);
context.print.success(`Successfully updated ${result.shared.resourceName}`);
}
Expand Down
14 changes: 7 additions & 7 deletions provider-utils/awscloudformation/schemas/schema.graphql.ejs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@


type vodAsset @model (subscriptions: {level: public})
type VodAsset @model (subscriptions: {level: public})
@auth(
rules: [
<% if (locals.permissions && locals.permissions.permissionSchema.includes("any")) { -%>
Expand All @@ -21,11 +21,11 @@ type vodAsset @model (subscriptions: {level: public})
description:String!

#DO NOT EDIT
video:videoObject @connection
}
video:VideoObject @connection
}

#DO NOT EDIT
type videoObject @model
type VideoObject @model
@auth(
rules: [
<% if (locals.permissions && locals.permissions.permissionSchema.includes("any")) { -%>
Expand All @@ -42,7 +42,7 @@ type videoObject @model
)
{
id:ID!
<% if (contentDeliveryNetwork.signedKey) { %>
token: String @function(name: "<%= contentDeliveryNetwork.functionName %>")
<% if (contentDeliveryNetwork.signedKey) { -%>
token: String @function(name: "<%= contentDeliveryNetwork.functionNameSchema %>")
<% } -%>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ async function serviceQuestions(context, options, defaultValuesFilename, resourc
const defaultLocation = path.resolve(`${__dirname}/../default-values/${defaultValuesFilename}`);
let resource = {};
const targetDir = amplify.pathManager.getBackendDirPath();
const projectDetails = context.amplify.getProjectDetails();
let advancedAnswers = {};
let mediaLiveAnswers = {};
let mediaPackageAnswers;
Expand All @@ -22,7 +23,7 @@ async function serviceQuestions(context, options, defaultValuesFilename, resourc
defaults = JSON.parse(fs.readFileSync(`${defaultLocation}`));
defaults.resourceName = 'mylivestream';
try {
const oldValues = JSON.parse(fs.readFileSync(`${targetDir}/video/${resourceName}/props.json`));
const oldValues = JSON.parse(fs.readFileSync(`${targetDir}/video/${resourceName}/${projectDetails.localEnvInfo.envName}-props.json`));
Object.assign(defaults, oldValues);
} catch (err) {
// Do nothing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async function serviceQuestions(context, options, defaultValuesFilename, resourc
nameDict.resourceName = resourceName;
props.shared = nameDict;
try {
oldValues = JSON.parse(fs.readFileSync(`${targetDir}/video/${resourceName}/props.json`));
oldValues = JSON.parse(fs.readFileSync(`${targetDir}/video/${resourceName}/${projectDetails.localEnvInfo.envName}-props.json`));
Object.assign(defaults, oldValues);
} catch (err) {
// Do nothing
Expand Down Expand Up @@ -307,7 +307,7 @@ async function createCDN(context, props, options, aws, oldValues) {
const uuid = Math.random().toString(36).substring(2, 6)
+ Math.random().toString(36).substring(2, 6);
const secretName = `${props.shared.resourceName}-${projectDetails.localEnvInfo.envName}-pem-${uuid}`.slice(0, 63);
const rPublicName = `rCloudFrontPublicKey${uuid}`.slice(0, 63);
const rPublicName = `rCloudFrontPublicKey${projectDetails.localEnvInfo.envName}${uuid}`.slice(0, 63);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armenr

If I'm looking at the right output file (dev-props.json), projectDetails.localEnvInfo.envName seems to be empty.

"contentDeliveryNetwork": {
        "signedKey": true,
        "publicKey": "****",
        "rPublicName": "rCloudFrontPublicKeyvv37ttp5",
        "publicKeyName": "vod-dev-publickey-vv37ttp5",
        "secretPem": "vod-dev-pem-vv37ttp5",
        "secretPemArn": "***",
        "functionName": "vod-dev-tokenGen",
        "functionNameSchema": "vod-${env}-tokenGen",
        "enableDistribution": true
    },

(*) Notice rCloudFrontPublicKeyvv37ttp5 doesn't contain the environment name ("dev")

Copy link
Owner Author

@armenr armenr May 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arturocanalda - Interesting. I'm comparing my results to yours, and there is a difference here. I'm wondering - what version of the amplify libs are you using?

I have a backend called ingrid, that was "auto-created" when I pushed a new branch to my codebase, and it produces this ingrid-props.json:

    "contentDeliveryNetwork": {
        "signedKey": true,
        "publicKey": "-----BEGIN PUBLIC KEY-----\\nREDACTED_KEY_MATERIALS\\n-----END PUBLIC KEY-----\\n",
        "rPublicName": "rCloudFrontPublicKeyingrid6b2201ks",
        "publicKeyName": "komitasv1-ingrid-publickey-6b2201ks",
        "secretPem": "komitasv1-ingrid-pem-6b2201ks",
        "secretPemArn": "******---komitasv1-ingrid-pem-6b2201ks-LoZRxw",
        "functionName": "komitasv1-ingrid-tokenGen",
        "functionNameSchema": "komitasv1-${env}-tokenGen",
        "enableDistribution": true
    },

This was "inherited" or "generated" from a staging-props.json which the plugin correctly created, that has the following in it:

  "contentDeliveryNetwork": {
    "signedKey": true,
    "publicKey": "-----BEGIN PUBLIC KEY-----\\n REDACTED_KEY_MATERIAL\\n-----END PUBLIC KEY-----\\n",
    "rPublicName": "rCloudFrontPublicKeystaging6b2201ks",
    "publicKeyName": "komitasv1-staging-publickey-6b2201ks",
    "secretPem": "komitasv1-staging-pem-6b2201ks",
    "secretPemArn": "*********---- komitasv1-staging-pem-6b2201ks-LoZRxw",
    "functionName": "komitasv1-staging-tokenGen",
    "functionNameSchema": "komitasv1-${env}-tokenGen",
    "enableDistribution": true
  },

As you can see, it appears to be working for me. I am running the following in my environment:

╰─ node -v
v14.16.1

╰─ amplify -v
4.49.0

Which version of node are you running, and which version of the amplify library?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armenr

Indeed, the versions we're using might be the reason why I'm experiencing this issue:

Node: 12.16.1  (maybe time for me to upgrade :)
Amplify: 4.50.2

I will upgrade nodeJS to 14.16.1 and test again later this afternoon. I'll keep you posted.

const publicKeyName = `${props.shared.resourceName}-${projectDetails.localEnvInfo.envName}-publickey-${uuid}`.slice(0, 63);
const smClient = new aws.SecretsManager({ apiVersion: '2017-10-17' });
const createSecretParams = {
Expand All @@ -326,6 +326,7 @@ async function createCDN(context, props, options, aws, oldValues) {
cdnConfigDetails.secretPemArn = secretCreate.ARN;
cdnConfigDetails.functionName = (projectDetails.localEnvInfo.envName)
? `${props.shared.resourceName}-${projectDetails.localEnvInfo.envName}-tokenGen` : `${props.shared.resourceName}-tokenGen`;
cdnConfigDetails.functionNameSchema = `${props.shared.resourceName}-\${env}-tokenGen`;
}
return cdnConfigDetails;
}
Expand Down
3 changes: 2 additions & 1 deletion provider-utils/awscloudformation/utils/livestream-obs.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ async function setupOBS(context, resourceName) {
async function createConfig(context, projectConfig, projectName) {
// check for obs installation!
let profileDir = '';
const projectDetails = context.amplify.getProjectDetails();
if (process.platform === 'darwin') {
profileDir = `${process.env.HOME}/Library/Application Support/obs-studio/basic/profiles/`;
} else if (process.platform === 'win32') {
Expand All @@ -45,7 +46,7 @@ async function createConfig(context, projectConfig, projectName) {
generateServiceLive(profileDir, projectConfig.output.oMediaLivePrimaryIngestUrl);
} else if (projectConfig.serviceType === 'ivs') {
const targetDir = context.amplify.pathManager.getBackendDirPath();
const props = JSON.parse(fs.readFileSync(`${targetDir}/video/${projectName}/props.json`));
const props = JSON.parse(fs.readFileSync(`${targetDir}/video/${projectName}/${projectDetails.localEnvInfo.envName}-props.json`));
generateINIIVS(projectName, profileDir, props);
generateServiceIVS(profileDir, projectConfig.output);
}
Expand Down
53 changes: 51 additions & 2 deletions provider-utils/awscloudformation/utils/video-staging.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,57 @@ async function pushTemplates(context) {
async function build(context, resourceName, projectType, props) {
const { amplify } = context;
const targetDir = amplify.pathManager.getBackendDirPath();
const projectDetails = context.amplify.getProjectDetails();

const newEnvName = projectDetails.localEnvInfo.envName;
const resourceFilesBaseDir = `${targetDir}/video/${resourceName}/`;
const resourceFilesList = fs.readdirSync(resourceFilesBaseDir);


// Check if env-specific props file already exists
const hasOwnEnvProps = resourceFilesList.includes(`${newEnvName}-props.json`);

// Check if ANY props file exist for a different env in this project || returns array
const hasAnyEnvProps = resourceFilesList.find(fileName => fileName.includes('-props.json'));

// If this env doesn't have its own props AND there is an existing amplify-video resource
if (!hasOwnEnvProps && hasAnyEnvProps) {
// take the first props file you find and copy that!
const propsFilenameToCopy = resourceFilesList.filter(propsFileName => propsFileName.includes('-props.json'))[0];

// extract substring for the existing env's name we're going to copy over
const envNameToReplace = propsFilenameToCopy.substr(0, propsFilenameToCopy.indexOf('-'));

// read JSON from the existing env's props file
const existingPropsToMutate = JSON.parse(fs.readFileSync(`${resourceFilesBaseDir}/${propsFilenameToCopy}`));

const searchAndReplaceProps = () => {
const newPropsObj = {};
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(existingPropsToMutate.contentDeliveryNetwork)) {
// look for any string values that contain existing env's name
if (typeof value === 'string' && value.includes(`${envNameToReplace}`)) {
// replace with new env name
const newValue = value.replace(new RegExp(envNameToReplace, 'g'), `${newEnvName}`);
newPropsObj[key] = newValue;
} else {
// copy existing values that do not match replacement conditions aka "generic props"
newPropsObj[key] = value;
}
}
return newPropsObj;
};

// merge new props and existing generic props
const newPropsToSave = Object.assign(
existingPropsToMutate, { contentDeliveryNetwork: searchAndReplaceProps() },
);

fs.writeFileSync(`${resourceFilesBaseDir}/${newEnvName}-props.json`, JSON.stringify(newPropsToSave, null, 4));
}

if (!props) {
props = JSON.parse(fs.readFileSync(`${targetDir}/video/${resourceName}/props.json`));
props = JSON.parse(fs.readFileSync(`${targetDir}/video/${resourceName}/${projectDetails.localEnvInfo.envName}-props.json`));
}
if (projectType === 'video-on-demand') {
props = getVODEnvVars(context, props, resourceName);
Expand Down Expand Up @@ -95,7 +144,7 @@ function getVODEnvVars(context, props, resourceName) {
delete props.shared.bucket;
delete props.shared.bucketInput;
delete props.shared.bucketOutput;
fs.writeFileSync(`${targetDir}/video/${resourceName}/props.json`, JSON.stringify(props, null, 4));
fs.writeFileSync(`${targetDir}/video/${resourceName}/${amplifyProjectDetails.localEnvInfo.envName}-props.json`, JSON.stringify(props, null, 4));
}
const envVars = amplifyProjectDetails.teamProviderInfo[currentEnvInfo]
.categories.video[resourceName];
Expand Down
47 changes: 47 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
const fs = require('fs');

const newEnvName = 'tonia';
const resourceDirectoryFiles = fs.readdirSync(
`${__dirname}/`,
);

// Check if env-specific props file already exists
const hasOwnEnvProps = resourceDirectoryFiles.includes(`${newEnvName}-props.json`);

// Check if ANY env props exist
const hasAnyEnvProps = resourceDirectoryFiles.find(item => item.includes('-props.json'));

// console.log('hasOwnEnv', hasOwnEnvProps);
// console.log('hasAnyEnvProps', hasAnyEnvProps);

if (!hasOwnEnvProps) {
if (hasAnyEnvProps) {
// take the first props file you find and copy that!
const propsFilenameToCopy = resourceDirectoryFiles.filter(propsFileName => propsFileName.includes('-props.json'))[0];
const envNameToReplace = propsFilenameToCopy.substr(0, propsFilenameToCopy.indexOf('-'));
const propsToMutate = JSON.parse(fs.readFileSync(`${__dirname}/${propsFilenameToCopy}`));

const searchAndReplaceProps = () => {
const newPropsObj = {};
for (const [key, value] of Object.entries(propsToMutate.contentDeliveryNetwork)) {
if (typeof value === 'string' && value.includes(`${envNameToReplace}`)) {
const newValue = value.replace(new RegExp(envNameToReplace, 'g'), `${newEnvName}`);
newPropsObj[key] = newValue;
} else {
newPropsObj[key] = value;
}
}
return newPropsObj;
};

const newPropsToSave = Object.assign(
propsToMutate, { contentDeliveryNetwork: searchAndReplaceProps() },
);

console.log('IM GONNA SAVE THIS, ', newPropsToSave);

fs.writeFileSync(`${__dirname}/${newEnvName}-props.json`, JSON.stringify(newPropsToSave, null, 4));
}
}