hammer_cli 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cec260aec7ea81ebce48b8efe53cd738f8e63266
4
+ data.tar.gz: 1aa5bb89e0dc87783b4bd093a2b8dffb58ac9616
5
+ SHA512:
6
+ metadata.gz: 0ea5172e0d0b8d3fdb743a3f26d3cf1a5ad351360b607f1a4343b7a09303b8d77c9c417106027a5163f49ac4382a158f31cdf23501d47d230594e44d92fa3e47
7
+ data.tar.gz: 6637eb0f497677b719636ac5b91c12e5ac3da1b96b68e95b49f36831a0d69ed7e355ada97dbd79d4b0ff9b08b4765f725a25fe8e9e934238522b3de7065729cf
data/README.md CHANGED
@@ -1,27 +1,83 @@
1
- Hammer - the CLI tool for foreman
1
+ Hammer - the CLI tool for Foreman
2
2
  =================================
3
3
 
4
- ![Design draft](doc/design.png)
4
+ Hammer is a generic [clamp-based](https://github.com/mdub/clamp) CLI framework. Hammer-cli is just a core without any commands.
5
5
 
6
- As the diagram shows, the CLI consist of almost generic framework (shell-like environment, autocompletion, command help text, option evaluation and command invocation) and set of plugins defining the actual commands. This setup is flexible and allows us to easily install different sets of commands for different products. The plugins are independant and can implement any action as an command, so that besides commands calling Foreman API you can have commands calling varius admin tasks, etc.
6
+ The core can be extended with plugins and customized according to your application setup. Any Ruby script can be easily turned into a command so possibilities are wide.
7
7
 
8
+ Currently available plugins are:
9
+ - [hammer-cli-foreman](https://github.com/theforeman/hammer-cli-foreman) - commands corresponding to Foreman API
10
+ - [hammer-cli-katello-bridge](https://github.com/theforeman/hammer-cli-katello-bridge) - set of commands provided by Katello CLI
8
11
 
9
- The CLI has
12
+ You also can easily add custom commands specific for your use, such as various bulk actions or admin tasks.
10
13
 
11
- - Git-like subcomands
12
- - system shell autocompletion for commands and options
13
- - shell-like environment with autocompletion and history where the commands can be run directly
14
- - commands extensible via plugins
15
- - been implemented in Ruby
16
-
17
14
 
18
- If you are interested you can help us by sending patches or filing bugs and feature requests (there is CLI catgory in Redmine)
19
15
 
16
+ Installation instructions
17
+ -------------------------
20
18
 
21
- How to run
22
- ----------
19
+ Hammer CLI is packaged for the following RPM based distributions:
23
20
 
24
- The work is in progress and there are still no builds ready, but instaling from sources is easy. You will need rake, bundler.
21
+ - RHEL and derivatives, version 6
22
+ - Fedora 18, 19
23
+
24
+
25
+ #### Step 1: setup rpm repositories
26
+ Add the Foreman's nightly rpm repository to your yum repo files. For Fedora installations replace 'el6' with 'f18' or 'f19' as appropriate.
27
+
28
+ ```bash
29
+ http://yum.theforeman.org/nightly/el6/$basearch/
30
+ ```
31
+
32
+ On RHEL systems you will also have to add [EPEL repository](https://fedoraproject.org/wiki/EPEL) as it contains some of the required dependencies.
33
+
34
+
35
+ #### Step 2: install hammer core
36
+ ```
37
+ $ yum install rubygem-hammer_cli
38
+ ```
39
+
40
+
41
+ #### Step 3: install plugins
42
+ Currently, there are two plugins, both available as rpm packages.
43
+
44
+ - commands for managing foreman
45
+ ```
46
+ $ yum install rubygem-hammer_cli_foreman
47
+ ```
48
+
49
+ - 1:1 bridge to [katello cli](https://github.com/Katello/katello)
50
+ ```
51
+ $ yum install rubygem-hammer_cli_katello_bridge
52
+ ```
53
+
54
+ Plugins are disabled after the installation. You have to edit the config file and enable them manually.
55
+
56
+
57
+ #### Step 4: configuration
58
+
59
+ Edit ```/etc/foreman/cli_config.yml``` or ```~/.foreman/cli_config.yml``` and uncomment lines with names of modules you've just installed to enable them:
60
+
61
+ ```yaml
62
+ :modules:
63
+ # - hammer_cli_foreman
64
+ # - hammer_cli_katello_bridge
65
+ ```
66
+
67
+ Confirm your setup by running ```$ hammer -h``` and see if the desired commands are listed.
68
+
69
+ You will also most likely want to change the url of the Foreman server.
70
+
71
+ ```yaml
72
+ :host: 'https://localhost/'
73
+ :username: 'admin'
74
+ :password: 'changeme'
75
+ ```
76
+
77
+ Done. Your hammer client is configured and ready to use.
78
+
79
+ #### Git installation
80
+ Optionally you can install hammer from git checkouts. You will need ```rake``` and ```bundler```.
25
81
  Clone and install CLI core
26
82
 
27
83
  $ git clone git@github.com:theforeman/hammer-cli.git
@@ -36,47 +92,22 @@ clone plugin with foreman commands
36
92
  $ cd hammer-cli-foreman
37
93
  $ rake install
38
94
  $ cd ..
39
-
40
- and configure. Configuration is by default looked for in ~/.foreman/ or in /etc/foreman/.
41
- Optionally you can put your configuration in ./config/ or point hammer
42
- to some other location using -c CONF_FILE option
95
+
96
+ and configure. Configuration is by default looked for in ```~/.foreman/``` or in ```/etc/foreman/```.
97
+ Optionally you can put your configuration in ```./config/``` or point hammer
98
+ to some other location using ```-c CONF_FILE``` option
43
99
 
44
100
  You can start with config file template we created for you and update it to suit your needs. E.g.:
45
101
 
46
102
  $ cp hammer-cli/config/cli_config.template.yaml ~/.foreman/cli_config.yml
47
103
 
48
- and run
49
-
50
- $ hammer -h
51
- Usage:
52
- hammer [OPTIONS] SUBCOMMAND [ARG] ...
53
-
54
- Parameters:
55
- SUBCOMMAND subcommand
56
- [ARG] ... subcommand arguments
57
-
58
- Subcommands:
59
- shell Interactive Shell
60
- architecture Manipulate Foreman's architectures.
61
- compute_resource Manipulate Foreman's architectures.
62
- domain Manipulate Foreman's domains.
63
- organization Manipulate Foreman's organizations.
64
- subnet Manipulate Foreman's subnets.
65
- user Manipulate Foreman's users.
66
104
 
67
- Options:
68
- -v, --verbose be verbose
69
- -u, --username USERNAME username to access the remote system
70
- -p, --password PASSWORD password to access the remote system
71
- --version show version
72
- --autocomplete LINE Get list of possible endings
73
- -h, --help print help
74
105
 
75
106
 
76
107
  Autocompletion
77
108
  --------------
78
109
 
79
- It is necessary to copy script hammer_cli_complete to the bash_completion.d directory.
110
+ It is necessary to copy script hammer_cli_complete to the bash_completion.d directory.
80
111
 
81
112
  $ sudo cp hammer-cli/hammer_cli_complete /etc/bash_completion.d/
82
113
 
data/bin/hammer CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'clamp'
5
+ require 'highline'
6
+ HighLine.color_scheme = HighLine::SampleColorScheme.new
5
7
 
6
8
  # create fake command instance to use some global args before we start
7
9
  class PreParser < Clamp::Command
@@ -6,6 +6,8 @@ require 'logging'
6
6
 
7
7
  module HammerCLI
8
8
 
9
+ class CommandConflict < StandardError; end
10
+
9
11
  class AbstractCommand < Clamp::Command
10
12
 
11
13
  extend Autocompletion
@@ -14,7 +16,7 @@ module HammerCLI
14
16
  end
15
17
 
16
18
  def run(arguments)
17
- exit_code = super(arguments)
19
+ exit_code = super
18
20
  raise "exit code must be integer" unless exit_code.is_a? Integer
19
21
  return exit_code
20
22
  rescue => e
@@ -24,7 +26,7 @@ module HammerCLI
24
26
  end
25
27
 
26
28
  def parse(arguments)
27
- super(arguments)
29
+ super
28
30
  validate_options
29
31
  logger.info "Called with options: %s" % options.inspect
30
32
  rescue HammerCLI::Validator::ValidationError => e
@@ -35,7 +37,7 @@ module HammerCLI
35
37
  HammerCLI::EX_OK
36
38
  end
37
39
 
38
- def self.validate_options &block
40
+ def self.validate_options(&block)
39
41
  self.validation_block = block
40
42
  end
41
43
 
@@ -43,18 +45,47 @@ module HammerCLI
43
45
  validator.run &self.class.validation_block if self.class.validation_block
44
46
  end
45
47
 
46
-
47
48
  def output
48
- @output ||= HammerCLI::Output::Output.new
49
+ @output ||= HammerCLI::Output::Output.new(:context => context)
49
50
  end
50
51
 
51
52
  def exception_handler
52
53
  @exception_handler ||= exception_handler_class.new :output => output
53
54
  end
54
55
 
56
+ def initialize(*args)
57
+ super
58
+ context[:path] ||= []
59
+ context[:path] << self
60
+ end
61
+
62
+ def parent_command
63
+ context[:path][-2]
64
+ end
65
+
66
+ def self.subcommand!(name, description, subcommand_class = self, &block)
67
+ self.recognised_subcommands.delete_if do |sc|
68
+ if sc.is_called?(name)
69
+ Logging.logger[self].info "subcommand #{name} (#{sc.subcommand_class}) replaced with #{name} (#{subcommand_class})"
70
+ true
71
+ else
72
+ false
73
+ end
74
+ end
75
+ self.subcommand(name, description, subcommand_class, &block)
76
+ end
77
+
78
+ def self.subcommand(name, description, subcommand_class = self, &block)
79
+ existing = find_subcommand(name)
80
+ if existing
81
+ raise HammerCLI::CommandConflict, "can't replace subcommand #{name} (#{existing.subcommand_class}) with #{name} (#{subcommand_class})"
82
+ end
83
+ super
84
+ end
85
+
55
86
  protected
56
87
 
57
- def logger name=self.class
88
+ def logger(name=self.class)
58
89
  logger = Logging.logger[name]
59
90
  logger.extend(HammerCLI::Logger::Watch) if not logger.respond_to? :watch
60
91
  logger
@@ -65,7 +96,7 @@ module HammerCLI
65
96
  @validator ||= HammerCLI::Validator.new(options)
66
97
  end
67
98
 
68
- def handle_exception e
99
+ def handle_exception(e)
69
100
  exception_handler.handle_exception(e)
70
101
  end
71
102
 
@@ -80,6 +111,23 @@ module HammerCLI
80
111
  return HammerCLI::ExceptionHandler
81
112
  end
82
113
 
114
+ def self.desc(desc=nil)
115
+ @desc = desc if desc
116
+ @desc
117
+ end
118
+
119
+ def self.command_name(name=nil)
120
+ @name = name if name
121
+ @name
122
+ end
123
+
124
+ def self.autoload_subcommands
125
+ commands = constants.map { |c| const_get(c) }.select { |c| c <= HammerCLI::AbstractCommand }
126
+ commands.each do |cls|
127
+ subcommand cls.command_name, cls.desc, cls
128
+ end
129
+ end
130
+
83
131
  def all_options
84
132
  self.class.recognised_options.inject({}) do |h, opt|
85
133
  h[opt.attribute_name] = send(opt.read_method)
@@ -19,7 +19,7 @@ module HammerCLI::Apipie
19
19
  self.class.identifier_option(:label, "resource label")
20
20
  end
21
21
 
22
- def self.identifiers *keys
22
+ def self.identifiers(*keys)
23
23
  @identifiers ||= {}
24
24
  keys.each do |key|
25
25
  if key.is_a? Hash
@@ -35,6 +35,12 @@ module HammerCLI::Apipie
35
35
  validator.any(*self.class.declared_identifiers.values).required
36
36
  end
37
37
 
38
+ def self.desc(desc=nil)
39
+ super(desc) || resource.docs_for(action)["apis"][0]["short_description"]
40
+ rescue
41
+ " "
42
+ end
43
+
38
44
  protected
39
45
 
40
46
  def get_identifier
@@ -45,7 +51,7 @@ module HammerCLI::Apipie
45
51
  [nil, nil]
46
52
  end
47
53
 
48
- def self.identifier? key
54
+ def self.identifier?(key)
49
55
  if @identifiers
50
56
  return true if @identifiers.keys.include? key
51
57
  else
@@ -8,14 +8,14 @@ module HammerCLI::Apipie
8
8
  end
9
9
 
10
10
  def all_method_options
11
- method_options_for_params(self.class.method_doc["params"], true)
11
+ method_options_for_params(resource.docs_for(action)["params"], true)
12
12
  end
13
13
 
14
14
  def method_options
15
- method_options_for_params(self.class.method_doc["params"], false)
15
+ method_options_for_params(resource.docs_for(action)["params"], false)
16
16
  end
17
17
 
18
- def method_options_for_params params, include_nil=true
18
+ def method_options_for_params(params, include_nil=true)
19
19
  opts = {}
20
20
  params.each do |p|
21
21
  if p["expected_type"] == "hash"
@@ -32,18 +32,18 @@ module HammerCLI::Apipie
32
32
 
33
33
  module ClassMethods
34
34
 
35
- def apipie_options options={}
35
+ def apipie_options(options={})
36
36
  raise "Specify apipie resource first." unless resource_defined?
37
37
 
38
38
  filter = options[:without] || []
39
39
  filter = Array(filter)
40
40
 
41
- options_for_params(method_doc["params"], filter)
41
+ options_for_params(resource.docs_for(action)["params"], filter)
42
42
  end
43
43
 
44
44
  protected
45
45
 
46
- def options_for_params params, filter
46
+ def options_for_params(params, filter)
47
47
  params.each do |p|
48
48
  next if filter.include? p["name"].to_s or filter.include? p["name"].to_sym
49
49
  if p["expected_type"] == "hash"
@@ -54,7 +54,7 @@ module HammerCLI::Apipie
54
54
  end
55
55
  end
56
56
 
57
- def create_option param
57
+ def create_option(param)
58
58
  option(
59
59
  option_switches(param),
60
60
  option_type(param),
@@ -64,27 +64,27 @@ module HammerCLI::Apipie
64
64
  )
65
65
  end
66
66
 
67
- def option_switches param
67
+ def option_switches(param)
68
68
  '--' + param["name"].gsub('_', '-')
69
69
  end
70
70
 
71
- def option_type param
71
+ def option_type(param)
72
72
  param["name"].upcase.gsub('-', '_')
73
73
  end
74
74
 
75
- def option_desc param
75
+ def option_desc(param)
76
76
  desc = param["description"].gsub(/<\/?[^>]+?>/, "")
77
77
  return " " if desc.empty?
78
78
  return desc
79
79
  end
80
80
 
81
- def option_opts param
81
+ def option_opts(param)
82
82
  opts = {}
83
83
  opts[:required] = true if param["required"]
84
84
  return opts
85
85
  end
86
86
 
87
- def option_formatter param
87
+ def option_formatter(param)
88
88
  # FIXME: There is a bug in apipie, it does not produce correct expected type for Arrays
89
89
  # When it's fixed, we should test param["expected_type"] == "array"
90
90
  if param["validator"].include? "Array"
@@ -4,19 +4,14 @@ module HammerCLI::Apipie
4
4
 
5
5
  class ReadCommand < Command
6
6
 
7
- def self.output definition=nil, &block
7
+ def self.output(definition=nil, &block)
8
8
  dsl = HammerCLI::Output::Dsl.new
9
- dsl.build &block
9
+ dsl.build &block if block_given?
10
10
 
11
11
  output_definition.append definition.fields unless definition.nil?
12
12
  output_definition.append dsl.fields
13
13
  end
14
14
 
15
- def self.heading heading=nil
16
- @heading = heading if heading
17
- @heading
18
- end
19
-
20
15
  def output_definition
21
16
  self.class.output_definition
22
17
  end
@@ -40,11 +35,11 @@ module HammerCLI::Apipie
40
35
  protected
41
36
  def retrieve_data
42
37
  raise "resource or action not defined" unless self.class.resource_defined?
43
- resource.send(action, request_params)[0]
38
+ resource.call(action, request_params)[0]
44
39
  end
45
40
 
46
41
  def print_data(records)
47
- output.print_records(records, self.class.heading)
42
+ output.print_records(records)
48
43
  end
49
44
 
50
45
  def request_params
@@ -1,4 +1,49 @@
1
1
  module HammerCLI::Apipie
2
+
3
+ class ResourceDefinition
4
+
5
+ attr_reader :resource_class
6
+
7
+ def initialize(resource_class)
8
+ @resource_class = resource_class
9
+ end
10
+
11
+ def name
12
+ resource_class.name.split("::")[-1].downcase
13
+ end
14
+
15
+ def docs_for(method_name)
16
+ resource_class.doc["methods"].each do |method|
17
+ return method if method["name"] == method_name.to_s
18
+ end
19
+ raise "No method documentation found for #{resource_class}##{action}"
20
+ end
21
+
22
+ end
23
+
24
+
25
+ class ResourceInstance < ResourceDefinition
26
+
27
+ def initialize(resource_class, config)
28
+ super(resource_class)
29
+ @instance = resource_class.new(config)
30
+ end
31
+
32
+ def self.from_definition(definition, config)
33
+ self.new(definition.resource_class, config)
34
+ end
35
+
36
+ def call(method_name, params=nil)
37
+ instance.send(method_name, params)
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :instance
43
+
44
+ end
45
+
46
+
2
47
  module Resource
3
48
 
4
49
  def self.included(base)
@@ -6,8 +51,13 @@ module HammerCLI::Apipie
6
51
  end
7
52
 
8
53
  def resource
9
- @resource ||= self.class.resource.new resource_config
10
- @resource
54
+ # if the resource definition is not available in this command's class
55
+ # try to look it up in parent command's class
56
+ if self.class.resource
57
+ return ResourceInstance.from_definition(self.class.resource, resource_config)
58
+ else
59
+ return ResourceInstance.from_definition(self.parent_command.class.resource, resource_config)
60
+ end
11
61
  end
12
62
 
13
63
  def action
@@ -24,28 +74,28 @@ module HammerCLI::Apipie
24
74
 
25
75
  module ClassMethods
26
76
 
27
-
28
- def resource resource=nil, action=nil
29
- @api_resource = resource unless resource.nil?
77
+ def resource(resource_class=nil, action=nil)
78
+ @api_resource = ResourceDefinition.new(resource_class) unless resource_class.nil?
30
79
  @api_action = action unless action.nil?
31
80
  return @api_resource if @api_resource
32
- return superclass.resource
33
- end
34
81
 
35
- def action action=nil
36
- @api_action = action unless action.nil?
37
- @api_action
38
- end
82
+ # if the resource definition is not available in this class
83
+ # try to look it up in it's enclosing module/class
84
+ enclosing_module = self.name.split("::")[0..-2].inject(Object) { |mod, cls| mod.const_get cls }
39
85
 
40
- def method_doc
41
- @api_resource.doc["methods"].each do |method|
42
- return method if method["name"] == @api_action.to_s
86
+ if enclosing_module.respond_to? :resource
87
+ enclosing_module.resource
43
88
  end
44
- raise "No method documentation found for #{@api_resource}##{@api_action}"
89
+ end
90
+
91
+ def action(action=nil)
92
+ @api_action = action unless action.nil?
93
+ return @api_action if @api_action
94
+ return superclass.action if superclass.respond_to? :action
45
95
  end
46
96
 
47
97
  def resource_defined?
48
- not (@api_resource.nil? or @api_action.nil?)
98
+ not (resource.nil? or action.nil?)
49
99
  end
50
100
 
51
101
  end
@@ -21,7 +21,7 @@ module HammerCLI::Apipie
21
21
 
22
22
  def send_request
23
23
  raise "resource or action not defined" unless self.class.resource_defined?
24
- resource.send(action, request_params)[0]
24
+ resource.call(action, request_params)[0]
25
25
  end
26
26
 
27
27
  def request_params
@@ -4,7 +4,7 @@ require 'logging'
4
4
  module HammerCLI
5
5
  class ExceptionHandler
6
6
 
7
- def initialize options={}
7
+ def initialize(options={})
8
8
  @output = options[:output] or raise "Missing option output"
9
9
  @logger = Logging.logger['Exception']
10
10
  end
@@ -17,7 +17,7 @@ module HammerCLI
17
17
  ]
18
18
  end
19
19
 
20
- def handle_exception e, options={}
20
+ def handle_exception(e, options={})
21
21
  @options = options
22
22
  handler = mappings.reverse.find { |m| e.class.respond_to?(:"<=") ? e.class <= m[0] : false }
23
23
  return send(handler[1], e) if handler
@@ -26,7 +26,7 @@ module HammerCLI
26
26
 
27
27
  protected
28
28
 
29
- def print_error error
29
+ def print_error(error)
30
30
  error = error.join("\n") if error.kind_of? Array
31
31
  @logger.error error
32
32
 
@@ -37,26 +37,26 @@ module HammerCLI
37
37
  end
38
38
  end
39
39
 
40
- def log_full_error e
40
+ def log_full_error(e)
41
41
  backtrace = e.backtrace || []
42
- @logger.error "\n\n#{e.class} (#{e.message}):\n " +
42
+ @logger.error "\n\n#{e.class} (#{e.message}):\n " +
43
43
  backtrace.join("\n ")
44
44
  "\n\n"
45
- end
45
+ end
46
46
 
47
- def handle_general_exception e
47
+ def handle_general_exception(e)
48
48
  print_error "Error: " + e.message
49
49
  log_full_error e
50
50
  HammerCLI::EX_SOFTWARE
51
51
  end
52
52
 
53
- def handle_not_found e
53
+ def handle_not_found(e)
54
54
  print_error e.message
55
55
  log_full_error e
56
56
  HammerCLI::EX_NOT_FOUND
57
57
  end
58
58
 
59
- def handle_unauthorized e
59
+ def handle_unauthorized(e)
60
60
  print_error "Invalid username or password"
61
61
  log_full_error e
62
62
  HammerCLI::EX_UNAUTHORIZED
@@ -15,6 +15,8 @@ module HammerCLI
15
15
  exit(HammerCLI::EX_OK)
16
16
  end
17
17
 
18
+ option ["--show-ids"], :flag, "Show ids of associated resources"
19
+
18
20
  option ["-P", "--ask-pass"], :flag, "Ask for password" do
19
21
  context[:password] = get_password()
20
22
  ''
@@ -28,6 +30,10 @@ module HammerCLI
28
30
  exit(HammerCLI::EX_OK)
29
31
  end
30
32
 
33
+ def show_ids=(show_ids)
34
+ context[:show_ids] = show_ids
35
+ end
36
+
31
37
  def password=(password)
32
38
  context[:password] = password.nil? ? ENV['FOREMAN_PASSWORD'] : password
33
39
  end
@@ -6,7 +6,7 @@ module HammerCLI
6
6
  base.extend(ClassMethods)
7
7
  end
8
8
 
9
- def success_message_for action
9
+ def success_message_for(action)
10
10
  self.class.success_message_for action
11
11
  end
12
12
 
@@ -14,7 +14,7 @@ module HammerCLI
14
14
  self.class.success_message
15
15
  end
16
16
 
17
- def failure_message_for action
17
+ def failure_message_for(action)
18
18
  self.class.failure_message_for action
19
19
  end
20
20
 
@@ -22,28 +22,28 @@ module HammerCLI
22
22
  self.class.failure_message
23
23
  end
24
24
 
25
- def handle_exception e
25
+ def handle_exception(e)
26
26
  exception_handler.handle_exception e, :heading => failure_message
27
27
  end
28
28
 
29
29
  module ClassMethods
30
- def success_message_for action, msg=nil
30
+ def success_message_for(action, msg=nil)
31
31
  @success_message ||= {}
32
32
  @success_message[action] = msg unless msg.nil?
33
33
  @success_message[action]
34
34
  end
35
35
 
36
- def success_message msg=nil
36
+ def success_message(msg=nil)
37
37
  success_message_for :default, msg
38
38
  end
39
39
 
40
- def failure_message_for action, msg=nil
40
+ def failure_message_for(action, msg=nil)
41
41
  @failure_message ||= {}
42
42
  @failure_message[action] = msg unless msg.nil?
43
43
  @failure_message[action]
44
44
  end
45
45
 
46
- def failure_message msg=nil
46
+ def failure_message(msg=nil)
47
47
  failure_message_for :default, msg
48
48
  end
49
49
  end