diff --git a/.gitignore b/.gitignore index ec6baee2..11fff976 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ tags # Locale files locale/*/*.edit.po locale/*/*.po.time_stamp +.idea/* diff --git a/bin/hammer b/bin/hammer index 1a158400..5f69500d 100755 --- a/bin/hammer +++ b/bin/hammer @@ -122,4 +122,4 @@ HammerCLI::I18n.domains.each do |domain| logger.debug "'#{domain.type}' files for locale domain '#{domain.domain_name}' loaded from '#{File.expand_path(domain.locale_dir)}'" end -exit HammerCLI::MainCommand.run || HammerCLI::EX_OK +exit HammerCLI::MainCommand.run(File.basename($0), ARGV, HammerCLI.context) || HammerCLI::EX_OK diff --git a/lib/hammer_cli.rb b/lib/hammer_cli.rb index 5bf9d7bc..2e2af588 100644 --- a/lib/hammer_cli.rb +++ b/lib/hammer_cli.rb @@ -13,9 +13,11 @@ require 'hammer_cli/option_builder' require 'hammer_cli/abstract' require 'hammer_cli/main' +require 'hammer_cli/context' require 'hammer_cli/apipie' # extend MainCommand require 'hammer_cli/shell' +require 'hammer_cli/defaults' diff --git a/lib/hammer_cli/abstract.rb b/lib/hammer_cli/abstract.rb index d9dedc9e..1688a61e 100644 --- a/lib/hammer_cli/abstract.rb +++ b/lib/hammer_cli/abstract.rb @@ -5,7 +5,6 @@ require 'hammer_cli/subcommand' require 'hammer_cli/options/matcher' require 'logging' - module HammerCLI class AbstractCommand < Clamp::Command @@ -234,10 +233,12 @@ def self.option(switches, type, description, opts = {}, &block) end def all_options - self.class.recognised_options.inject({}) do |h, opt| - h[opt.attribute_name] = send(opt.read_method) - h + @all_options ||= self.class.recognised_options.inject({}) do |hash, opt| + hash[opt.attribute_name] = send(opt.read_method) + hash[opt.attribute_name] = add_custom_defaults(opt.attribute_name) if hash[opt.attribute_name].nil? + hash end + @all_options end def options @@ -246,6 +247,14 @@ def options private + def add_custom_defaults(attr) + if context[:defaults] + value = context[:defaults].get_defaults(attr) + logger.info("Custom default value #{value} was used for attribute #{attr}") if value + value + end + end + def self.inherited_output_definition od = nil if superclass.respond_to? :output_definition diff --git a/lib/hammer_cli/context.rb b/lib/hammer_cli/context.rb new file mode 100644 index 00000000..d11776e2 --- /dev/null +++ b/lib/hammer_cli/context.rb @@ -0,0 +1,13 @@ +require 'hammer_cli/defaults' + +module HammerCLI + + def self.context + { + :defaults => HammerCLI.defaults + } + end + +end + + diff --git a/lib/hammer_cli/defaults.rb b/lib/hammer_cli/defaults.rb new file mode 100644 index 00000000..9db394a8 --- /dev/null +++ b/lib/hammer_cli/defaults.rb @@ -0,0 +1,83 @@ +require 'hammer_cli/defaults_commands' +module HammerCLI + DEFAULT_FILE = "#{Dir.home}/.hammer/defaults.yml" + + class Defaults + class DefaultsError < StandardError; end + class DefaultsPathError < DefaultsError; end + + attr_reader :defaults_settings + + def initialize(settings, file_path = nil) + + @defaults_settings = settings + @path = file_path || DEFAULT_FILE + end + + def register_provider(provider) + providers[provider.plugin_name.to_s] = provider + end + + def providers + @providers ||= {} + end + + def delete_default_from_conf(param) + conf_file = YAML.load_file(path) + conf_file[:defaults].delete(param) + write_to_file conf_file + conf_file + end + + def add_defaults_to_conf(default_options, provider) + create_default_file if defaults_settings.nil? + defaults = YAML.load_file(path) + defaults[:defaults] ||= {} + default_options.each do |key, value| + key = key.to_sym + defaults[:defaults][key] = value ? {:value => value,} : {:provider => provider} + end + write_to_file defaults + defaults + end + + def get_defaults(opt) + option = opt + option = opt.gsub("option_",'') if opt.include? "option_" + unless defaults_settings.nil? || defaults_settings[option.to_sym].nil? + if defaults_settings[option.to_sym][:provider] + providers[defaults_settings[option.to_sym][:provider]].get_defaults(option.to_sym) + else + defaults_settings[option.to_sym][:value] + end + end + end + + def write_to_file(defaults) + File.open(path,'w') do |h| + h.write defaults.to_yaml + end + end + + protected + + attr_reader :path + + def create_default_file + if Dir.exist?(File.dirname(@path)) + new_file = File.new(path, "w") + new_file.write ":defaults:" + new_file.close + else + raise DefaultsPathError.new(_("Couldn't create %s please create the path before defaults are enabled.") % path) + end + end + end + + def self.defaults + @defaults ||= Defaults.new(HammerCLI::Settings.settings[:defaults]) + + end + + HammerCLI::MainCommand.subcommand "defaults", _("Defaults management"), HammerCLI::DefaultsCommand +end diff --git a/lib/hammer_cli/defaults_commands.rb b/lib/hammer_cli/defaults_commands.rb new file mode 100644 index 00000000..9ab50470 --- /dev/null +++ b/lib/hammer_cli/defaults_commands.rb @@ -0,0 +1,163 @@ +require 'hammer_cli' +require 'yaml' +module HammerCLI + class BaseDefaultsProvider + def self.plugin_name + self.name.split('::').first.gsub(/^HammerCLI/, '').underscore + end + + def self.register_provider + HammerCLI.defaults.register_provider(self) + end + + def self.support? + raise NotImplementedError + end + + def self.supported_defaults + raise NotImplementedError + end + + def self.get_defaults + raise NotImplementedError + end + end + + class DefaultsCommand < HammerCLI::AbstractCommand + class ProvidersDefaultsCommand < HammerCLI::DefaultsCommand + command_name 'providers' + desc _('List all the providers') + + def execute + data = context[:defaults].providers.map do |key, val| + { + :provider => key.to_s, + :defaults => (val.supported_defaults || ['*']).map(&:to_s) + } + end + + fields = HammerCLI::Output::Dsl.new.build do + field :provider, _('Provider') + field :defaults, _('Supported defaults'), Fields::List + end + + definition = HammerCLI::Output::Definition.new + definition.append(fields) + + print_collection(definition, data) + HammerCLI::EX_OK + end + + def adapter + @context[:adapter] || :table + end + end + + class ListDefaultsCommand < HammerCLI::DefaultsCommand + command_name 'list' + desc _('List all the default parameters') + + def execute + data = context[:defaults].defaults_settings.map do |key, val| + { + :parameter => key.to_s, + :value => val[:provider] ? "Provided by: " + val[:provider].to_s.capitalize : val[:value] + } + end + + fields = HammerCLI::Output::Dsl.new.build do + field :parameter, _('Parameter') + field :value, _('Value'), Fields::List + end + + definition = HammerCLI::Output::Definition.new + definition.append(fields) + + print_collection(definition, data) + HammerCLI::EX_OK + end + + def adapter + @context[:adapter] || :table + end + end + + class DeleteDefaultsCommand < HammerCLI::DefaultsCommand + command_name 'delete' + + desc _('Delete a default param') + option "--param-name", "OPTION_NAME", _("The name of the default option"), :required => true + + def execute + if context[:defaults].defaults_settings && context[:defaults].defaults_settings[option_param_name.to_sym] + context[:defaults].delete_default_from_conf(option_param_name.to_sym) + param_deleted(option_param_name) + else + variable_not_found + end + HammerCLI::EX_OK + end + end + + class AddDefaultsCommand < HammerCLI::DefaultsCommand + command_name 'add' + + desc _('Add a default parameter to config') + option "--param-name", "OPTION_NAME", _("The name of the default option (e.g. organization_id)."), :required => true + option "--param-value", "OPTION_VALUE", _("The value for the default option") + option "--plugin-name", "OPTION_PLUGIN_NAME", _("The name of the provider providing the value. For list available providers see `hammer defaults providers`.") + + def execute + if option_plugin_name.nil? && option_param_value.nil? || !option_plugin_name.nil? && !option_param_value.nil? + bad_input + HammerCLI::EX_USAGE + else + if option_plugin_name + namespace = option_plugin_name + if !context[:defaults].providers.key?(namespace) + plugin_prob_message(namespace) + return HammerCLI::EX_USAGE + elsif !context[:defaults].providers[namespace].support?(option_param_name) + defaults_not_supported_by_provider + return HammerCLI::EX_CONFIG + end + end + context[:defaults].add_defaults_to_conf({option_param_name => option_param_value}, namespace) + added_default_message(option_param_name.to_s, option_param_value) + HammerCLI::EX_OK + end + rescue Defaults::DefaultsError, SystemCallError => e + print_message(e.message) + HammerCLI::EX_CONFIG + end + end + + def added_default_message(key, value) + print_message(_("Added %{key_val} default-option with value that will be generated from the server.") % {:key_val => key.to_s}) if value.nil? + print_message(_("Added %{key_val} default-option with value %{val_val}.") % {:key_val => key.to_s, :val_val => value.to_s}) unless value.nil? + end + + def plugin_prob_message(namespace) + print_message(_("Provider #{namespace} was not found. See `hammer defaults providers` for available providers.")) + end + + def defaults_not_supported_by_provider + print_message(_("The param name is not supported by provider. See `hammer defaults providers` for supported params.")) + end + + def param_deleted(param) + print_message(_("%{param} was deleted successfully.") % {:param => param.to_s}) + end + + def bad_input + print_message(_("You must specify value or a provider name, cant specify both.")) + end + + def variable_not_found + print_message(_("Couldn't find the requested param in %s.") % context[:defaults].send(:path)) + end + + autoload_subcommands + end +end + diff --git a/lib/hammer_cli/settings.rb b/lib/hammer_cli/settings.rb index 72dbd0c7..1f45c676 100644 --- a/lib/hammer_cli/settings.rb +++ b/lib/hammer_cli/settings.rb @@ -18,6 +18,7 @@ def self.load_from_paths(files) if File.directory? full_path # check for cli_config.yml load_from_file(File.join(full_path, 'cli_config.yml')) + load_from_file(File.join(full_path, 'defaults.yml')) # load config for modules Dir.glob(File.join(full_path, 'cli.modules.d/*.yml')).sort.each do |f| load_from_file(f) diff --git a/test/functional/defaults_test.rb b/test/functional/defaults_test.rb new file mode 100644 index 00000000..5165cd9e --- /dev/null +++ b/test/functional/defaults_test.rb @@ -0,0 +1,206 @@ +require File.join(File.dirname(__FILE__), '../unit/test_helper') +describe 'commands' do + + class TestProvider < HammerCLI::BaseDefaultsProvider + def self.support?(param) + param.to_s == 'organization_id' + end + + def self.get_defaults(param) + 32 + end + + def self.supported_defaults + ['organization_id'] + end + end + + before do + settings = YAML::load(File.open(FILEPATH)) + @defaults = HammerCLI::Defaults.new(settings[:defaults], FILEPATH) + @defaults.stubs(:write_to_file).returns true + @defaults.stubs(:providers).returns({ + 'foreman' => TestProvider + }) + + @context = { + :defaults => @defaults + } + end + + def expected_message(header, data = []) + (header.join("\n") + data.join("\n")) + end + + def run_cmd(cmd_class, options) + capture_io do + cmd_class.run('hammer', options, @context) + end + end + + describe 'defaults list' do + + it 'it prints all defaults' do + header = ['----------------|----------------------------------------', + 'PARAMETER | VALUE ', + '----------------|----------------------------------------', + '' + ] + default_values = { + :organization_id => { + :value => 3, + }, + :location_id => { + :provider => 'HammerCLIForeman::Defaults' + } + } + @defaults.stubs(:defaults_settings).returns(default_values) + data = [ + 'organization_id | 3 ', + 'location_id | Provided by: Hammercliforeman::defaults', + '----------------|----------------------------------------', + '' + ] + out, err = run_cmd(HammerCLI::DefaultsCommand::ListDefaultsCommand, []) + assert_equal "", err + assert_equal expected_message(header, data), out + end + + it 'prints empty defaults' do + header = ['----------|------', + 'PARAMETER | VALUE', + '----------|------', + '' + ] + @defaults.stubs(:defaults_settings).returns({}) + + out, err = run_cmd(HammerCLI::DefaultsCommand::ListDefaultsCommand, []) + assert_equal "", err + assert_equal expected_message(header), out + end + end + + describe 'defaults providers' do + header = ['---------|-------------------', + 'PROVIDER | SUPPORTED DEFAULTS', + '---------|-------------------', + '' + ] + + it 'prints all providers and their supported defaults' do + data = ['foreman | organization_id ', + '---------|-------------------', + '' + ] + out, err = run_cmd(HammerCLI::DefaultsCommand::ProvidersDefaultsCommand, []) + assert_equal "", err + assert_equal expected_message(header, data), out + end + + it 'prints empty providers' do + @defaults.stubs(:providers).returns({}) + out, err = run_cmd(HammerCLI::DefaultsCommand::ProvidersDefaultsCommand, []) + assert_equal "", err + assert_equal expected_message(header), out + end + + end + + + describe 'defaults add' do + it 'adds static default' do + options = ['--param-name=param', '--param-value=83'] + + @defaults.expects(:add_defaults_to_conf).with({'param' => '83'}, nil).once + + out, err = run_cmd(HammerCLI::DefaultsCommand::AddDefaultsCommand, options) + assert_equal "", err + assert_equal "Added param default-option with value 83.\n", out + end + + it 'adds default from provider' do + options = ['--param-name=organization_id', '--plugin-name=foreman'] + + @defaults.expects(:add_defaults_to_conf).with({'organization_id' => nil}, 'foreman').once + + out, err = run_cmd(HammerCLI::DefaultsCommand::AddDefaultsCommand, options) + assert_equal "", err + assert_equal "Added organization_id default-option with value that will be generated from the server.\n", out + end + + it 'reports unsupported option' do + options = ['--param-name=unsupported', '--plugin-name=foreman'] + + out, err = run_cmd(HammerCLI::DefaultsCommand::AddDefaultsCommand, options) + assert_equal "", err + assert_equal "The param name is not supported by provider. See `hammer defaults providers` for supported params.\n", out + end + + it 'reports missing parameter name' do + options = ['--param-value=83'] + + out, err = capture_io do + HammerCLI::DefaultsCommand::AddDefaultsCommand.run('hammer', options, @context) + end + assert_match "option '--param-name' is required", err + assert_equal "", out + end + + it 'reports missing parameter value or source' do + options = ['--param-name=organization_id'] + + out, err = run_cmd(HammerCLI::DefaultsCommand::AddDefaultsCommand, options) + assert_equal "", err + assert_match "You must specify value or a provider name, cant specify both.", out + end + + it 'reports unknown plugin' do + options = ['--param-name=organization_id', '--plugin-name=unknown'] + + out, err = run_cmd(HammerCLI::DefaultsCommand::AddDefaultsCommand, options) + assert_equal "", err + assert_equal "Provider unknown was not found. See `hammer defaults providers` for available providers.\n", out + end + + it 'reports IO errors' do + options = ['--param-name=param', '--param-value=83'] + + @defaults.expects(:add_defaults_to_conf).raises(Errno::ENOENT, '/unknown/path') + + out, err = run_cmd(HammerCLI::DefaultsCommand::AddDefaultsCommand, options) + assert_equal "", err + assert_equal "No such file or directory - /unknown/path\n", out + end + end + + describe 'defaults delete' do + it 'removes the defaults' do + default_values = { + :organization_id => { + :value => 3, + :from_server => false + } + } + @defaults.stubs(:defaults_settings).returns(default_values) + @defaults.expects(:delete_default_from_conf).once + + options = ['--param-name=organization_id'] + + out, err = run_cmd(HammerCLI::DefaultsCommand::DeleteDefaultsCommand, options) + assert_equal "", err + assert_equal "organization_id was deleted successfully.\n", out + end + + it 'reports when the variable was not found' do + @defaults.stubs(:defaults_settings).returns({}) + @defaults.stubs(:path).returns('/path/to/defaults.yml') + + options = ['--param-name=organization_id'] + out, err = run_cmd(HammerCLI::DefaultsCommand::DeleteDefaultsCommand, options) + assert_equal "", err + assert_equal "Couldn't find the requested param in /path/to/defaults.yml.\n", out + end + end + +end + diff --git a/test/unit/apipie/command_test.rb b/test/unit/apipie/command_test.rb index b64b37bf..a83efe8f 100644 --- a/test/unit/apipie/command_test.rb +++ b/test/unit/apipie/command_test.rb @@ -111,6 +111,7 @@ class CommandC < CommandA end it "should perform a call to api when resource is defined" do + ctx[:defaults] = stub(:get_defaults => {}) cmd_run.must_equal 0 end end diff --git a/test/unit/defaults_test.rb b/test/unit/defaults_test.rb new file mode 100644 index 00000000..7b3ba5d1 --- /dev/null +++ b/test/unit/defaults_test.rb @@ -0,0 +1,40 @@ +require File.join(File.dirname(__FILE__), 'test_helper') +describe HammerCLI::Defaults do + + FILEPATH = File.join(File.dirname(__FILE__), '/fixtures/defaults/defaults.yml') + + before(:all) do + settings = YAML::load(File.open(FILEPATH)) + + @defaults = HammerCLI::Defaults.new(settings[:defaults], FILEPATH) + @defaults.stubs(:write_to_file).returns true + end + + it "Should add a default param to defaults file, without a provider" do + defaults_result = @defaults.add_defaults_to_conf({"organization_id"=> 3}, nil) + assert_equal 3, defaults_result[:defaults][:organization_id][:value] + end + + it "Should add a default param to defaults file, with provider" do + defaults_result = @defaults.add_defaults_to_conf({"location_id"=>nil}, :foreman) + assert_equal :foreman, defaults_result[:defaults][:location_id][:provider] + end + + it "Should remove default param from defaults file" do + defaults_result = @defaults.delete_default_from_conf(:organization_id) + assert_nil defaults_result[:defaults][:organization_id] + end + + it "should get the default param, without provider" do + assert_equal 2, @defaults.get_defaults("location_id") + end + + it "should get the default param, with provider" do + fake_provider = mock() + fake_provider.stubs(:plugin_name).returns(:foreman) + fake_provider.expects(:get_defaults).with(:organization_id).returns(3) + @defaults.register_provider(fake_provider) + assert_equal 3, @defaults.get_defaults("organization_id") + end + +end diff --git a/test/unit/fixtures/defaults/defaults.yml b/test/unit/fixtures/defaults/defaults.yml new file mode 100644 index 00000000..d64148a3 --- /dev/null +++ b/test/unit/fixtures/defaults/defaults.yml @@ -0,0 +1,6 @@ +--- +:defaults: + :organization_id: + :provider: 'foreman' + :location_id: + :value: 2