Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
uoy-faculty-rake / lib / faculty / rake / deploy.rb
Size: Mime:
# frozen_string_literal: true

require 'English'
require 'rake/tasklib'
require_relative 'helpers'

module Faculty
  module Rake
    # Functions for deploying to AWS from the local machine
    class Deploy < ::Rake::TaskLib # rubocop:disable Metrics/ClassLength
      include Helpers

      def initialize(stack_name: nil, tag_name: nil, parameter_overrides: {}, &block)
        super()
        @stack_name = stack_name || repository_name_from_git_origin
        @tag_name = tag_name || repository_name_from_git_origin
        @extra_task = block if block
        @app_specific_parameter_overrides = parameter_overrides
        initialize_task
      end

      def initialize_task # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
        namespace :deploy do
          desc 'Deploy to AWS'
          task :to, [:environment, :deployed_by, :deploy_role] do |_, args|
            @environment = args.fetch(:environment, 'dev').to_sym
            @deployed_by = args.fetch(:deployed_by, 'manual')
            @deploy_role = args.fetch(:deploy_role, 'CloudformationDeploymentRole')
            @account_id = ACCOUNT_IDS.fetch(@environment)
            @parameter_overrides = cloudformation_parameter_overrides.merge @app_specific_parameter_overrides
            abort bold 'You have uncommitted changes; please commit before deploying' if uncommitted_changes?
            update_aws_cli
            check_aws_credentials
            abort bold 'Stack does not exist. Please deploy through github first.' unless stack_exists?
            begin
              Dir.chdir tmp_dir do
                install_gems
                @extra_task&.call
                cloudformation_deploy
              end
            ensure
              tidy_up
            end
          end
        end
      end

      ACCOUNT_IDS = {
        dev: '496883770698',
        staging: '423014805995',
        prod: '542159367156'
      }.freeze

      AWS_CREDENTIAL_VARS = %w[
        AWS_ACCESS_KEY_ID
        AWS_SECRET_ACCESS_KEY
        AWS_SESSION_TOKEN
        AWS_SECURITY_TOKEN
        AWS_DEFAULT_REGION
        AWS_REGION
      ].freeze

      environment_aws_credentials = ENV.keys.intersection(AWS_CREDENTIAL_VARS).map { |var| "--env #{var}" }.join(' ')

      AWS_CLI_IMAGE = 'amazon/aws-cli'
      AWS_CLI = 'docker run --rm -v ~/.aws:/home/.aws -v $(pwd):/aws ' \
                "--user #{Process.uid}:#{Process.gid} --env HOME=/home " \
                "#{environment_aws_credentials} #{AWS_CLI_IMAGE}"
      OUTPUT_TEMPLATE_FILE = 'rake-deploy-template.yaml'
      S3_BUCKET = 'uk-ac-york-global-artifactstore'
      TEMPLATE_FILE = 'cloudformation.yaml'

      private

      def check_aws_credentials
        puts bold 'Checking AWS credentials...'
        until current_credentials == @account_id
          puts bold "Please login using saml2aws to the #{@environment} account"
          system 'saml2aws login --force'
        end
      end

      def cloudformation_deploy
        puts bold 'Deploying to cloudformation...'
        system "#{AWS_CLI} cloudformation package --output-template-file #{OUTPUT_TEMPLATE_FILE} " \
               "--template-file #{TEMPLATE_FILE} --s3-bucket #{S3_BUCKET}"
        system "#{AWS_CLI} cloudformation deploy " \
               "#{cloudformation_deploy_parameters.map { |k, v| "--#{k} #{v}" }.join(' ')} " \
               "--tags #{cloudformation_deploy_tags.map { |k, v| "\"#{k}=#{v}\"" }.join(' ')}" \
               "--parameter-overrides #{@parameter_overrides.map { |k, v| "\"#{k}=#{v}\"" }.join(' ')}"
      end

      def cloudformation_deploy_parameters
        {
          capabilities: 'CAPABILITY_IAM',
          'no-fail-on-empty-changeset': nil,
          'role-arn': "arn:aws:iam::#{@account_id}:role/#{@deploy_role}",
          'stack-name': @stack_name,
          'template-file': OUTPUT_TEMPLATE_FILE
        }
      end

      def cloudformation_parameter_overrides
        {
          AppName: @stack_name,
          Deployment: @environment
        }
      end

      def cloudformation_deploy_tags
        {
          'york/defined_in' => 'cloudformation',
          'york/group' => 'FACULTY-DEV',
          'york/name' => "Faculty Dev #{@tag_name}",
          'york/project' => repository_name_from_git_origin,
          'york/pushed_by' => @deployed_by,
          'york/status' => @environment,
          'york/policy_version' => 2,
          'york/repo_name' => repository_name_from_git_origin
        }
      end

      def current_credentials
        `#{AWS_CLI} sts get-caller-identity --query Account --output text`.strip
      end

      def install_gems
        puts bold 'Installing gems...'
        system "docker compose run --rm -u #{Process.uid}:#{Process.gid} builder " \
               'bundle install --deployment --path vendor/bundle --without test development'
      end

      def repository_name_from_git_origin
        repository_name_from_remote `git config --get remote.origin.url`.strip
      end

      def stack_exists?
        `#{AWS_CLI} cloudformation describe-stacks --stack-name #{@stack_name}`
        $CHILD_STATUS.success?
      end

      def tidy_up
        puts bold 'Tidying up...'
        Dir.chdir tmp_dir do
          system 'docker compose down'
        end
        system "rm -rf #{tmp_dir}"
      end

      def tmp_dir
        @tmp_dir ||= begin
          tmp_dir = `mktemp -d -t aws-deploy-XXXXX`.strip
          system "git clone --depth=1 file://#{Dir.pwd} #{tmp_dir}"
          tmp_dir
        end
      end

      def uncommitted_changes?
        !system 'git diff HEAD --quiet'
      end

      def update_aws_cli
        puts bold 'Updating aws cli...'
        system "docker pull #{AWS_CLI_IMAGE}"
      end
    end
  end
end