commands/deploy.js

/**
 * Deploys the waiting room infrastructure.
 * @module deploy
 */

const path = require("path");
const logger = require('../utils/logger')('dev');
const { readFile, validateInitRan, validateProfileName } = require("../utils/utilities");
const chalk = require("chalk");
const ora = require("ora");

const createRole = require("../aws/deploy/createRole");
const deployS3 = require('../aws/deploy/deployS3');
const deployDlq = require('../aws/deploy/deployDlq');
const deploySqs = require("../aws/deploy/deploySqs");
const deployDynamo = require("../aws/deploy/deployDynamo");
const deployApiGateway = require("../aws/deploy/deployApiGateway");
const deployPostLambda = require("../aws/deploy/deployPostLambda");
const deployPreLambda = require('../aws/deploy/deployPreLambda');
const deployCloudwatchEvent = require('../aws/deploy/deployCloudwatchEvent');
const deployPollingRoute = require("../aws/deploy/deployPollingRoute");
const deployClientCheckRoute = require("../aws/deploy/deployClientCheckRoute");
const deployPollingS3Object = require("../aws/deploy/deployPollingS3Object");
const addPostLambdaEventPermission = require("../aws/deploy/addPostLambdaEventPermission");

const ANSWERS_FILE_PATH = path.join(__dirname, "..", "config", "user-answers.json");
const S3_ASSET_PATH = path.join(__dirname, "..", "..", "assets", "s3");
const POST_LAMBDA_ASSET = path.join(__dirname, "..", "..", "assets", "postlambda", 'index.js.zip');
const PRE_LAMBDA_ASSET = path.join(__dirname, "..", "..", "assets", "prelambda", 'index.js.zip');
const POLL_FILE_PATH = path.join(__dirname, "..", "..", "assets", "s3", 'polling.js');

/**
 * The deploy command combines all of the deploy modules and invokes them with the required parameters. 
 * @param {string} profileName The profile to deploy
 * @returns {undefined}
 */

module.exports = async (profileName) => {
  const initRan = await validateInitRan(ANSWERS_FILE_PATH);
  if (!initRan) return;
  
  const profiles = JSON.parse(await readFile(ANSWERS_FILE_PATH));
  const validProfileName = validateProfileName(profileName, profiles, "deploy");
  if (!validProfileName) return;

  const {[profileName] : { PROFILE_NAME, WAITING_ROOM_NAME, REGION, PROTECT_URL, RATE }} = profiles;
  const S3_NAME = `beekeeper-${PROFILE_NAME}-s3`
  const DLQ_NAME = `beekeeper-${PROFILE_NAME}-dlq`
  const SQS_NAME = `beekeeper-${PROFILE_NAME}-sqs`
  const DYNAMO_NAME = `beekeeper-${PROFILE_NAME}-ddb`
  const API_GATEWAY_NAME = `beekeeper-${PROFILE_NAME}-apigateway`
  const POST_LAMBDA_NAME = `beekeeper-${PROFILE_NAME}-postlambda`
  const PRE_LAMBDA_NAME = `beekeeper-${PROFILE_NAME}-prelambda`
  const ROLE_NAME = `beekeeper-${PROFILE_NAME}-master-role`
  const CRON_JOB_NAME = `beekeeper-${PROFILE_NAME}-cloudwatcheventcron`
  const STAGE_NAME = `prod`;
  const spinner = ora();

  logger.highlight('🐝  Deploying waiting room infrastructure');
  console.log("")

  // Create Role
  let roleArn;
  try {
    spinner.start("Creating IAM role")
    roleArn = await createRole(REGION, ROLE_NAME);
    spinner.succeed("Successfully created IAM role")
  } catch (err) {
    spinner.fail("Failed to create IAM role")
    logger.failDeploy();
    return;
  }

  // Deploy S3 Bucket + S3 Objects
  let s3ObjectRootDomain;
  try {
    spinner.start("Deploying S3 bucket")
    s3ObjectRootDomain = await deployS3(REGION, S3_NAME, S3_ASSET_PATH);
    spinner.succeed("Successfully deployed S3 bucket")
  } catch (err) {
    spinner.fail("Failed to deploy S3 bucket")
    logger.failDeploy();
    return;
  }

  // Deploy DLQ
  let dlqArn
  try {
    spinner.start("Deploying DLQ")
    dlqArn = await deployDlq(REGION, DLQ_NAME);
    spinner.succeed("Successfully deployed DLQ")
  } catch (err) {
    spinner.fail("Failed to deployed DLQ")
    logger.failDeploy();
    return;
  }

  // Deploy SQS
  let sqsUrl;
  try {
    spinner.start("Deploying SQS")
    sqsUrl = await deploySqs(REGION, SQS_NAME, dlqArn);
    spinner.succeed("Successfully deployed SQS")
  } catch (err) {
    spinner.fail("Failed to deployed SQS")
    logger.failDeploy();
    return;
  }

  // Deploy DynamoDB
  let dbArn;
  try {
    spinner.start("Deploying DynamoDB")
    dbArn = await deployDynamo(REGION, DYNAMO_NAME);
    spinner.succeed("Successfully deployed DynamoDB")
  } catch (err) {
    spinner.fail("Failed to deployed DynamoDB")
    logger.failDeploy();
    return;
  }

  // Deploy Post Lambda
  let postLambdaArn;
  let eventArn;
  try {
    spinner.start("Deploying post-lambda")
    postLambdaArn = await deployPostLambda(REGION, POST_LAMBDA_NAME, sqsUrl, POST_LAMBDA_ASSET, roleArn, DYNAMO_NAME, RATE);

    // Deploy Cloudwatch Event Rules for Post Lambda (CRON)
    eventArn = await deployCloudwatchEvent(REGION, postLambdaArn, CRON_JOB_NAME);
    
    // Add event permission
    await addPostLambdaEventPermission(REGION, POST_LAMBDA_NAME, eventArn, CRON_JOB_NAME, postLambdaArn);

    spinner.succeed("Successfully deployed post-lambda")
  } catch (err) {
    spinner.fail("Failed to deployed post-lambda")
    logger.failDeploy();
    return;
  }

  // Deploy Pre Lambda
  let preLambdaArn;
  try {
    spinner.start("Deploying pre-lambda")
    preLambdaArn = await deployPreLambda(REGION, PRE_LAMBDA_NAME, sqsUrl, PRE_LAMBDA_ASSET, roleArn, s3ObjectRootDomain, PROTECT_URL);

    spinner.succeed("Successfully deployed pre-lambda")
  } catch (err) {
    spinner.fail("Failed to deployed pre-lambda")
    logger.failDeploy();
    return;
  }

  // Deploy API Gateway + Waiting Room Route
  let restApiId, stageBeekeeperUrl;
  let stagePollingUrl;
  let clientCheckUrl;
  try {
    spinner.start("Deploying API Gateway")
    let result = await deployApiGateway(REGION, API_GATEWAY_NAME, preLambdaArn, STAGE_NAME);
    restApiId = result.restApiId;
    stageBeekeeperUrl = result.stageBeekeeperUrl;

    // Deploy Waiting Room Polling Route on API Gateway
    stagePollingUrl = await deployPollingRoute(restApiId, REGION, API_GATEWAY_NAME, DYNAMO_NAME, roleArn, s3ObjectRootDomain, STAGE_NAME, PROTECT_URL);

    const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
    await delay(5000);

    // Deploy Client Check Route on API Gateway
    clientCheckUrl = await deployClientCheckRoute(restApiId, REGION, API_GATEWAY_NAME, DYNAMO_NAME, roleArn, STAGE_NAME, PROTECT_URL);
    
    // Create and upload poll.js to S3 bucket
    await deployPollingS3Object(REGION, S3_NAME, stagePollingUrl, POLL_FILE_PATH, WAITING_ROOM_NAME, RATE);
    
    spinner.succeed("Successfully deployed API Gateway")
    console.log("");
    logger.highlight(`${chalk.green.bold("✔")} Successfully deployed waiting room infrastructure`);
    console.log("");
    console.log(`Here's your waiting room URL: ${chalk.yellow.bold(stageBeekeeperUrl)}`);
    console.log(`Here's the client check endpoint: ${chalk.yellow.bold(clientCheckUrl)}`);
  } catch (err) {
    spinner.fail("Failed to deployed API Gateway")
    logger.failDeploy();
    return;
  }
}