#!/usr/bin/env ruby # Copyright (C) 2011-2012 RightScale, Inc, All Rights Reserved Worldwide. # # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use, # reproduction, modification, or disclosure of this program is # strictly prohibited. Any use of this program by an authorized # licensee is strictly subject to the terms and conditions, # including confidentiality obligations, set forth in the applicable # License Agreement between RightScale.com, Inc. and # the licensee $stdout.sync = true require File.join(File.dirname(__FILE__), '..', 'lib', 'rconf') module RightConf class Configurer include ProgressReporter def self.run opts = Trollop::options do version "rconf #{VERSION} (c) 2011-2013 RightScale" banner <<-EOS #{DESCRIPTION} Usage: rconf [options] where [options] are: EOS opt :configurators, 'Show available configurators' opt :platform, 'Show current platform' opt :overrides, 'Show current overrides' opt :update, 'Update rconf to latest version' opt :remove, 'Remove rconf from all gemsets' opt :config, 'Set path to configuration file', :type => :string opt :output, 'Output file (output to STDOUT by default)', :type => :string opt :reconfigure, 'Bypass all checks and force configuration and ignore overrides' opt :force, 'Run rconf even if configuration file has not changed' opt :verbose,'Print debug output' end if opts[:config].nil? && !opts[:configurators] && !opts[:update] && !opts[:remove] search_dir = File.expand_path('..', Dir.pwd) opts[:config] = Dir["./*#{CONFIG_EXTENSION}"] while opts[:config].empty? && search_dir != '/' opts[:config] = Dir[search_dir + "/*#{CONFIG_EXTENSION}"] search_dir = File.expand_path('..', search_dir) end if opts[:config].empty? Trollop::die :config, "not used and could not find a '#{CONFIG_EXTENSION}' file in the working directory or any parent directory" else opts[:config] = opts[:config].first end end if opts[:output] begin FileUtils.mkdir_p(File.dirname(opts[:output])) rescue Exception => e Trollop::die :output, "Failed to initialize output file: #{e.message}" end end Command.set_verbose if opts[:verbose] if opts[:configurators] new.list_configurators elsif opts[:platform] new.show_platform elsif opts[:overrides] new.show_overrides elsif opts[:update] new.update elsif opts[:remove] new.remove else Dir.chdir(File.dirname(opts[:config])) { new.configure(opts) } end end # List all available configurators # # === Return # true:: Always return true def list_configurators puts "The following configurators are registered:\n\n" ConfiguratorRegistry.each do |key, configurator| puts "== #{key} ==".bold puts configurator.desc puts 'Settings:' max_size = configurator.all_settings.map { |s| s[:name] }.map(&:size).max configurator.all_settings.each do |setting| name = setting[:name] desc = setting[:description] options = setting[:options].keys.map { |k| "[#{k}]" }.join(' ') num_spaces = max_size - name.size + 1 print " - #{name.blue}:#{' ' * num_spaces}#{desc}" puts ' ' + options.green end puts end end # Show current platform as detected by rconf # # === Return # true:: Always return true def show_platform puts "rconf detected the following:\n\n" puts "Family: #{Platform.family}" puts "Flavor: #{Platform.flavor}" puts "Release: #{Platform.release}" end # List current overrides as defined in ~/.rconf_overrides # # === Return # true:: Always return true def show_overrides if !File.readable?(OverridesLanguage::OVERRIDES_FILE) puts "No rconf overrides file at #{OverridesLanguage::OVERRIDES_FILE}" else overrides = OverridesLanguage.load if overrides puts "Loading overrides from #{OverridesLanguage::OVERRIDES_FILE}" msg = '' overrides.each do |key, setting| msg += "* #{key}\n" + msg += setting.inject([]) do |m, (name, value)| m << " - #{name} = #{value}" m end.join("\n") end puts msg else puts "Failed to load overrides from #{OverridesLanguage::OVERRIDES_FILE}: #{OverridesLanguage.parse_error.error_message}" end end end # Update rconf to latest in all installed rubies # # === Return # true:: Always return true def update ProgressReporter.report_to_stdout report_check 'Retrieving latest rconf...' json = Command.execute('curl', '-s', 'https://rubygems.org/api/v1/gems/rconf.json').output json =~ /"version":"([^"]+)"/ version = Regexp.last_match(1) report_fatal 'Failed to retrieve rconf gem information, check network connectivity' unless version report_success report('Latest rconf version is ' + version.blue) update_rconf(version) end # Calls given block with all combination of installed rubies # # === Block # Given block should take one argument: # ruby(String):: Ruby version # # === Return # true:: Always return true def run_in_all_rubies(&callback) rubies = Command.execute('rbenv', 'versions').output rubies = rubies.split("\n").map { |r| r[2..-1] } if rubies report_fatal 'Failed to list install rubies (is rbenv in your path?)' unless rubies rubies.each { |r| callback.call(r) } end # Update rconf for given rubies if required # # === Parameters # version(String):: Latest version # # === Return # true:: Always return true def update_rconf(version) run_in_all_rubies do |ruby| report_check("Checking rconf for #{ruby}") rconf = Command.execute('gem', 'list', 'rconf', :env => { 'RBENV_VERSION' => ruby }).output if rconf =~ /^rconf / && rconf !~ /rconf \(#{version}/ report_failure report_check("Updating rconf for #{ruby}") res = Command.execute('gem', 'install', 'rconf', '-v', version, '--no-ri', '--no-rdoc', :env => { 'RBENV_VERSION' => ruby }) report_result(res.success?) report(' ' + res.output.red) unless res.success? else report_success end end end # Remove rconf from all rubies # # === Return # true:: Always return true def remove ProgressReporter.report_to_stdout run_in_all_rubies do |ruby| rconf = Command.execute('gem', 'list', 'rconf', :env => { 'RBENV_VERSION' => ruby }).output if rconf =~ /^rconf / report_check("Removing rconf from #{ruby}") res = Command.execute('gem', 'uninstall', '-a', '-x', 'rconf', :env => { 'RBENV_VERSION' => ruby }) report_result(res.success?) end end end # Actually configure environment # # === Parameters # options[:config](String):: Configuration file # options[:output](String):: Output file, optional # # === Return # true:: Always return true def configure(options) ProgressReporter.report_to_stdout ProgressReporter.report_to_file(options[:output]) if options[:output] Profile.force_check if options[:force] Profile.force_reconfigure if options[:reconfigure] begin report("rconf file: #{options[:config].blue}") lang = Language.load(options[:config]) report_fatal("Validation of configuration file failed:\n - #{lang.validation_errors.map(&:red).join("\n - ")}") unless lang.validation_errors.empty? report_error(lang.warnings.join(', ').green) unless lang.warnings.empty? aborted = false if Platform.release == 'unknown' report_fatal("Unable to determine platform flavor and release. You may need to install a package to enable platform detection by command line.") aborted = true else Dir.chdir(File.dirname(options[:config])) do lang.configurators.each do |c| c.run break if aborted = c.aborting c.post_process end end end project = File.basename(options[:config], CONFIG_EXTENSION).blue platform = Platform.family.to_s.blue if aborted report("Configuration of #{project} stopped, please follow instructions below to proceed") else report("Successfully configured #{project} for #{platform}") end rescue Exception => e raise if e.is_a?(SystemExit) report_fatal("Execution failed with exception '#{e.message.red}'\n#{e.backtrace.join("\n")}") ensure note = '----['.bold.white + 'NOTE'.bold.green + ']----'.bold.white lang.configurators.each { |c| report("\n#{note}\n#{c.post_note.green}") if c.post_note } end end end end # Yeeeehaaaa! trap("INT") { puts "\nAborted!".red; exit 1 } RightConf::Configurer.run