18F/identity-idp

View on GitHub
lib/deploy/activate.rb

Summary

Maintainability
A
0 mins
Test Coverage
D
62%
# frozen_string_literal: true

require 'active_support/core_ext/hash/deep_merge'
require 'logger'
require 'identity/hostdata'
require 'yaml'

module Deploy
  class Activate
    FILES_TO_LINK =
      %w[agencies iaa_gtcs iaa_orders iaa_statuses integration_statuses integrations
         partner_account_statuses partner_accounts service_providers].freeze

    attr_reader :logger, :s3_client

    def initialize(
      logger: default_logger,
      s3_client: nil,
      root: nil
    )
      @logger = logger
      @s3_client = s3_client
      @root = root
    end

    def run
      clone_idp_config
      setup_idp_config_symlinks

      download_from_secrets_s3_unless_exists(
        s3_path: '/common/GeoIP2-City.mmdb',
        local_path: geolocation_db_path,
      )
      download_from_secrets_s3_unless_exists(
        s3_path: '/common/pwned-passwords.txt',
        local_path: pwned_passwords_path,
      )
    end

    # Set up symlinks into identity-idp-config needed for the idp to make use
    # of relevant config and assets.
    #
    def setup_idp_config_symlinks
      FILES_TO_LINK.each do |file|
        symlink_verbose(
          File.join(root, idp_config_checkout_name, "#{file}.yml"),
          File.join(root, "config/#{file}.yml"),
        )
      end

      # Service provider public keys
      FileUtils.mkdir_p(File.join(root, 'certs'))
      symlink_verbose(
        File.join(root, idp_config_checkout_name, 'certs/sp'),
        File.join(root, 'certs/sp'),
      )

      FileUtils.mkdir_p(idp_logos_dir)

      # Invalid symlinks can cause issues in the build process, so this step iterates through the
      # sp-logos directory in the IDP to delete any broken symlinks.
      Dir.entries(idp_logos_dir).each do |name|
        next if name.start_with?('.')
        target = File.join(idp_logos_dir, name)
        FileUtils.rm(target) if File.symlink?(target) && !File.file?(target)
      end
      # Public assets: sp-logos
      # Inject the logo files into the app's asset folder. deploy/activate is
      # run before deploy/build-post-config, so these will be picked up by the
      # rails asset pipeline.
      Dir.entries(config_logos_dir).each do |name|
        next if name.start_with?('.')
        target = File.join(config_logos_dir, name)
        link = File.join(root, 'app/assets/images/sp-logos', name)
        symlink_verbose(target, link, force: true)
        link = File.join(root, 'public/assets/sp-logos', name)
        symlink_verbose(target, link, force: true)
      end
    end

    def root
      @root || File.expand_path('../../../', __FILE__)
    end

    def idp_logos_dir
      File.join(root, 'public/assets/sp-logos')
    end

    def config_logos_dir
      File.join(checkout_dir, 'public/assets/images/sp-logos')
    end

    def checkout_dir
      File.join(root, idp_config_checkout_name)
    end

    private

    # Clone the private-but-not-secret git repo
    def clone_idp_config
      private_git_repo_url = ENV.fetch(
        'IDP_private_config_repo',
        'git@github.com:18F/identity-idp-config.git',
      )

      cmd = ['git', 'clone', '--depth', '1', '--branch', 'main', private_git_repo_url, checkout_dir]
      logger.info('+ ' + cmd.join(' '))
      result = system(*cmd)
      raise "failed to execute command #{cmd.join(' ')}" if !result
    end

    def idp_config_checkout_name
      'identity-idp-config'
    end

    def symlink_verbose(dest, link, force: false)
      logger.info("symlink: #{link.inspect} => #{dest.inspect}")
      File.unlink(link) if force && File.exist?(link)
      File.symlink(dest, link)
    end

    def download_from_secrets_s3_unless_exists(s3_path:, local_path:)
      if File.exist?(local_path) || File.symlink?(local_path)
        logger.info("Skipping #{local_path}") && return
      end
      secrets_s3.download_file(
        s3_path: s3_path,
        local_path: local_path,
      )
    end

    def secrets_s3
      @secrets_s3 ||= Identity::Hostdata.secrets_s3(s3_client: s3_client, logger: logger)
    end

    def default_logger
      logger = Logger.new(STDOUT)
      logger.progname = 'deploy/activate'
      logger
    end

    def geolocation_db_path
      File.join(root, 'geo_data/GeoLite2-City.mmdb')
    end

    def pwned_passwords_path
      File.join(root, 'pwned_passwords/pwned_passwords.txt')
    end
  end
end