# frozen_string_literal: true require 'highline' require 'fileutils' require 'json' require 'securerandom' module ConvoxInstaller class Config CONFIG_FILE = File.expand_path('./.installer_config.json').freeze attr_accessor :logger, :config, :prompts, :highline DEFAULT_PROMPTS = [ { key: :stack_name, title: 'Convox Stack Name', prompt: 'Please enter a name for your Convox installation', default: 'convox' }, { key: :aws_region, title: 'AWS Region', default: 'us-east-1' }, { key: :instance_type, title: 'EC2 Instance Type', default: 't3.medium' }, { section: 'Admin AWS Credentials' }, { key: :aws_access_key_id, title: 'AWS Access Key ID' }, { key: :aws_secret_access_key, title: 'AWS Secret Access Key' }, # Short random ID used to ensure that resources are always unique { key: :random_id, value: -> { SecureRandom.hex(4) }, hidden: true } ].freeze def initialize(options = {}) @logger = Logger.new($stdout) logger.level = options[:log_level] || Logger::INFO self.prompts = options[:prompts] || DEFAULT_PROMPTS self.config = {} load_config_from_file load_config_from_env self.config = config.merge((options[:config] || {}).symbolize_keys) self.highline = options[:highline] || HighLine.new end def config_keys prompts.map { |prompt| prompt[:key] }.compact.map(&:to_sym) end def prompt_for_config loop do prompts.each do |prompt| if prompt[:section] highline.say "\n#{prompt[:section]}" highline.say "============================================\n\n" end next unless prompt[:key] ask_prompt(prompt) end show_config_summary @completed_prompt = true highline.say 'Please double check all of these configuration details.' break if ENV['AUTOSTART_CONVOX_INSTALLATION'] agree = highline.agree( 'Would you like to start the Convox installation?' \ " (press 'n' to correct any settings)" ) break if agree highline.say "\n" end config end def show_config_summary highline.say "\n============================================" highline.say ' SUMMARY' highline.say "============================================\n\n" config_titles = prompts.map do |prompt| prompt[:title] || prompt[:key] end.compact max = config_titles.map(&:length).max prompts.each do |prompt| next if !prompt[:key] || prompt[:hidden] value = config[prompt[:key]] title = prompt[:title] || prompt[:key] padded_key = "#{title}:".ljust(max + 3) highline.say " #{padded_key} #{value}" end highline.say "\nWe've saved your configuration to: #{CONFIG_FILE}" highline.say 'If anything goes wrong during the installation, ' \ "you can restart the script to reload the config and continue.\n\n" end def self.config_file_exists? File.exist?(CONFIG_FILE) end def self.read_config_file File.read(CONFIG_FILE) end private def ask_prompt(prompt) key = prompt[:key] title = prompt[:title] || key # If looping through the config again, ask for all # the config with defaults. if config[key] && !@completed_prompt logger.debug "Found existing config for #{key} => #{config[key]}" return end # Used when we want to force a default value and not prompt the user. # (e.g. securely generated passwords) if prompt[:value] return if config[key] default = prompt[:value] config[key] = if default.is_a?(Proc) default.arity.zero? ? default.call : default.call(config) else default end save_config_to_file return end prompt_string = prompt[:prompt] || "Please enter your #{title}: " config[key] = highline.ask(prompt_string) do |q| if @completed_prompt q.default = config[key] elsif prompt[:default] q.default = prompt[:default] end q.validate = /.+/ end save_config_to_file end def load_config_from_file return unless Config.config_file_exists? logger.debug "Loading saved config from #{CONFIG_FILE}..." loaded_config = JSON.parse(Config.read_config_file)['config'].symbolize_keys self.config = config.merge(loaded_config) end def load_config_from_env config_keys.each do |key| env_key = key.to_s.upcase value = ENV[env_key] next unless value.present? logger.debug "Found value for #{key} in env var: #{env_key} => #{value}" config[key] = value end end def save_config_to_file # FileUtils.mkdir_p File.expand_path("~/.convox") File.open(CONFIG_FILE, 'w') do |f| f.puts(JSON.pretty_generate(config: config)) end end end end