Cimpress-MCP/LambdaWrap

View on GitHub
lib/lambda_wrap/api_manager.rb

Summary

Maintainability
C
1 day
Test Coverage
module LambdaWrap
  # Top level class that manages the Serverless Microservice API deployment.
  #
  # @!attribute [r] lambdas
  #   @return [Array<LambdaWrap::Lambda>] The List of Lambdas to be deployed with the API.
  #
  # @!attribute [r] dynamo_tables
  #   @return [Array<LambdaWrap::DynamoTables>] The List of DynamoTables to be deployed with the API.
  #
  # @!attribute [r] api_gateways
  #   @return [Array<LambdaWrap::ApiGateway>] The List of API Gateways to be deployed with the API.
  #
  # @!attribute [r] region
  #   @return [String] The AWS Region to deploy the API.
  #
  # @since 1.0
  class API
    attr_reader :lambdas
    attr_reader :dynamo_tables
    attr_reader :api_gateways
    attr_reader :region

    # Constructor for the high level API Manager class.
    #
    # @param [Hash] options The Options to configure the API.
    # @option options [String] :access_key_id The AWS Access Key Id to communicate with AWS. Will also check the
    #   environment variables for this value.
    # @option options [String] :secret_access_key The AWS Secret Access Key to communicate with AWS. Also checks
    #   environment variables for this value.
    # @option options [String] :region The AWS Region to deploy API to. Also checks environment variables for this
    #   value.
    #
    # @todo Allow clients to pass in a YAML file for all construction.
    def initialize(options = {})
      unless options[:lambda_client] && options[:dynamo_client] && options[:api_gateway_client]
        access_key_id = options[:access_key_id] || ENV['AWS_ACCESS_KEY_ID'] || ENV['ACCESS_KEY'] ||
                        raise(ArgumentError, 'Cannot find AWS Access Key ID.')

        secret_access_key = options[:secret_access_key] || ENV['AWS_SECRET_ACCESS_KEY'] || ENV['SECRET_KEY'] ||
                            raise(ArgumentError, 'Cannot find AWS Secret Key.')

        credentials = Aws::Credentials.new(access_key_id, secret_access_key)
      end

      region = options[:region] || ENV['AWS_REGION'] || ENV['AMAZON_REGION'] || ENV['AWS_DEFAULT_REGION'] ||
               raise(ArgumentError, 'Cannot find AWS Region.')

      @lambdas = []
      @dynamo_tables = []
      @api_gateways = []

      @region = region
      @lambda_client = options[:lambda_client] ||
                       Aws::Lambda::Client.new(credentials: credentials, region: region)
      @dynamo_client = options[:dynamo_client] ||
                       Aws::DynamoDB::Client.new(credentials: credentials, region: region)
      @api_gateway_client = options[:api_gateway_client] ||
                            Aws::APIGateway::Client.new(credentials: credentials, region: region)
    end

    # Add Lambda Object(s) to the API.
    #
    # @param [LambdaWrap::Lambda Array<LambdaWrap::Lambda>] new_lambda Splat of LambdaWrap Lambda
    #  objects to add to the API. Overloaded as:
    #  add_lambda(lambda1) OR  add_lambda([lambda1, lambda2]) OR add_lambda(lambda1, lambda2)
    def add_lambda(*new_lambda)
      flattened_lambdas = new_lambda.flatten
      flattened_lambdas.each { |lambda| parameter_guard(lambda, LambdaWrap::Lambda, 'LambdaWrap::Lambda') }
      lambdas.concat(flattened_lambdas)
    end

    # Add Dynamo Table Object(s) to the API.
    #
    # @param [LambdaWrap::DynamoTable, Array<LambdaWrap::DynamoTable>] new_table Splat of LambdaWrap DynamoTable
    #  objects to add to the API. Overloaded as:
    #  add_dynamo_table(table1) OR  add_dynamo_table([table1, table2]) OR add_dynamo_table(table1, table2)
    def add_dynamo_table(*new_table)
      flattened_tables = new_table.flatten
      flattened_tables.each { |table| parameter_guard(table, LambdaWrap::DynamoTable, 'LambdaWrap::DynamoTable') }
      dynamo_tables.concat(flattened_tables)
    end

    # Add API Gateway Object(s) to the API.
    #
    # @param [LambdaWrap::ApiGateway, Array<LambdaWrap::ApiGateway>] new_api_gateway Splat of LambdaWrap API Gateway
    #  objects to add to the API. Overloaded as:
    #  add_api_gateway(apig1) OR  add_api_gateway([apig1, apig2]) OR add_api_gateway(apig1, apig2)
    def add_api_gateway(*new_api_gateway)
      flattened_api_gateways = new_api_gateway.flatten
      flattened_api_gateways.each { |apig| parameter_guard(apig, LambdaWrap::ApiGateway, 'LambdaWrap::ApiGateway') }
      api_gateways.concat(flattened_api_gateways)
    end

    # Deploys all services to the specified environment.
    #
    # @param [LambdaWrap::Environment] environment_options the Environment to deploy
    def deploy(environment_options)
      environment_parameter_guard(environment_options)
      if no_op?
        puts 'Nothing to deploy.'
        return
      end

      deployment_start_message = 'Deploying '
      deployment_start_message += "#{dynamo_tables.length} Dynamo Tables, " unless dynamo_tables.empty?
      deployment_start_message += "#{lambdas.length} Lambdas, " unless lambdas.empty?
      deployment_start_message += "#{api_gateways.length} API Gateways " unless api_gateways.empty?
      deployment_start_message += "to Environment: #{environment_options.name}"
      puts deployment_start_message

      total_time_start = Time.now

      services_time_start = total_time_start
      dynamo_tables.each { |table| table.deploy(environment_options, @dynamo_client, @region) }
      services_time_end = Time.now

      unless dynamo_tables.empty?
        puts "Deploying #{dynamo_tables.length} Table(s) took: \
        #{Time.at(services_time_end - services_time_start).utc.strftime('%H:%M:%S')}"
      end

      services_time_start = Time.now
      lambdas.each { |lambda| lambda.deploy(environment_options, @lambda_client, @region) }
      services_time_end = Time.now

      unless lambdas.empty?
        puts "Deploying #{lambdas.length} Lambda(s) took: \
        #{Time.at(services_time_end - services_time_start).utc.strftime('%H:%M:%S')}"
      end

      services_time_start = Time.now
      api_gateways.each { |apig| apig.deploy(environment_options, @api_gateway_client, @region) }
      services_time_end = Time.now

      unless api_gateways.empty?
        puts "Deploying #{api_gateways.length} API Gateway(s) took: \
        #{Time.at(services_time_end - services_time_start).utc.strftime('%H:%M:%S')}"
      end

      total_time_end = Time.now

      puts "Total API Deployment took: \
      #{Time.at(total_time_end - total_time_start).utc.strftime('%H:%M:%S')}"
      puts "Successfully deployed API to #{environment_options.name}"

      true
    end

    # Tearsdown Environment for all services.
    #
    # @param [LambdaWrap::Environment] environment_options the Environment to teardown
    def teardown(environment_options)
      environment_parameter_guard(environment_options)
      if no_op?
        puts 'Nothing to teardown.'
        return
      end

      deployment_start_message = 'Tearing-down '
      deployment_start_message += "#{dynamo_tables.length} Dynamo Tables, " unless dynamo_tables.empty?
      deployment_start_message += "#{lambdas.length} Lambdas, " unless lambdas.empty?
      deployment_start_message += "#{api_gateways.length} API Gateways " unless api_gateways.empty?
      deployment_start_message += " Environment: #{environment_options.name}"
      puts deployment_start_message

      total_time_start = Time.now

      services_time_start = total_time_start
      dynamo_tables.each { |table| table.teardown(environment_options, @dynamo_client, @region) }
      services_time_end = Time.now

      unless dynamo_tables.empty?
        puts "Tearing-down #{dynamo_tables.length} Table(s) took: \
        #{Time.at(services_time_end - services_time_start).utc.strftime('%H:%M:%S')}"
      end

      services_time_start = Time.now
      lambdas.each { |lambda| lambda.teardown(environment_options, @lambda_client, @region) }
      services_time_end = Time.now

      unless lambdas.empty?
        puts "Tearing-down #{lambdas.length} Lambda(s) took: \
        #{Time.at(services_time_end - services_time_start).utc.strftime('%H:%M:%S')}"
      end

      services_time_start = Time.now
      api_gateways.each { |apig| apig.teardown(environment_options, @api_gateway_client, @region) }
      services_time_end = Time.now

      unless api_gateways.empty?
        puts "Tearing-down #{api_gateways.length} API Gateway(s) took: \
        #{Time.at(services_time_end - services_time_start).utc.strftime('%H:%M:%S')}"
      end

      total_time_end = Time.now

      puts "Total API Tear-down took: \
      #{Time.at(total_time_end - total_time_start).utc.strftime('%H:%M:%S')}"
      puts "Successful Teardown API to #{environment_options.name}"

      true
    end

    # Deletes all services from the cloud.
    def delete
      if dynamo_tables.empty? && lambdas.empty? && api_gateways.empty?
        puts 'Nothing to Deleting.'
        return
      end

      deployment_start_message = 'Deleting '
      deployment_start_message += "#{dynamo_tables.length} Dynamo Tables, " unless dynamo_tables.empty?
      deployment_start_message += "#{lambdas.length} Lambdas, " unless lambdas.empty?
      deployment_start_message += "#{api_gateways.length} API Gateways " unless api_gateways.empty?
      puts deployment_start_message

      total_time_start = Time.now

      services_time_start = total_time_start
      dynamo_tables.each { |table| table.delete(@dynamo_client, @region) }
      services_time_end = Time.now

      unless dynamo_tables.empty?
        puts "Deleting #{dynamo_tables.length} Table(s) took: \
        #{Time.at(services_time_end - services_time_start).utc.strftime('%H:%M:%S')}"
      end

      services_time_start = Time.now
      lambdas.each { |lambda| lambda.delete(@lambda_client, @region) }
      services_time_end = Time.now

      unless lambdas.empty?
        puts "Deleting #{lambdas.length} Lambda(s) took: \
        #{Time.at(services_time_end - services_time_start).utc.strftime('%H:%M:%S')}"
      end

      services_time_start = Time.now
      api_gateways.each { |apig| apig.delete(@api_gateway_client, @region) }
      services_time_end = Time.now

      unless api_gateways.empty?
        puts "Deleting #{api_gateways.length} API Gateway(s) took: \
        #{Time.at(services_time_end - services_time_start).utc.strftime('%H:%M:%S')}"
      end

      total_time_end = Time.now

      puts "Total API Deletion took: \
      #{Time.at(total_time_end - total_time_start).utc.strftime('%H:%M:%S')}"
      puts 'Successful Deletion of API'

      true
    end

    private

    def environment_parameter_guard(parameter)
      parameter_guard(parameter, LambdaWrap::Environment, 'LambdaWrap::Environment')
    end

    def parameter_guard(parameter, type, type_name)
      return if parameter.is_a?(type)
      raise ArgumentError, "Must pass a #{type_name} to the API Manager. Got: #{parameter}"
    end

    def no_op?
      dynamo_tables.empty? && lambdas.empty? && api_gateways.empty?
    end
  end
end