Class: MCollective::Application

Inherits:
Object
  • Object
show all
Includes:
RPC
Defined in:
lib/mcollective/application.rb,
lib/mcollective/application/tasks.rb,
lib/mcollective/application/choria.rb,
lib/mcollective/application/playbook.rb,
lib/mcollective/application/federation.rb

Defined Under Namespace

Classes: Choria, Completion, Facts, Federation, Find, Help, Inventory, Ping, Playbook, Plugin, Rpc, Tasks

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from RPC

const_missing, discovered, #empty_filter?, #printrpc, #printrpcstats, #rpcoptions, stats

Instance Attribute Details

#optionsObject (readonly)

The active options hash used for MC::Client and other configuration



116
117
118
# File 'lib/mcollective/application.rb', line 116

def options
  @options
end

Class Method Details

.[](option) ⇒ Object

retrieves a specific option



22
23
24
25
# File 'lib/mcollective/application.rb', line 22

def [](option)
  intialize_application_options unless @application_options
  @application_options[option]
end

.[]=(option, value) ⇒ Object

set an option in the options hash



16
17
18
19
# File 'lib/mcollective/application.rb', line 16

def []=(option, value)
  intialize_application_options unless @application_options
  @application_options[option] = value
end

.application_optionsObject

Intialize a blank set of options if its the first time used else returns active options



10
11
12
13
# File 'lib/mcollective/application.rb', line 10

def application_options
  intialize_application_options unless @application_options
  @application_options
end

.description(descr) ⇒ Object

Sets the application description, there can be only one description per application so multiple calls will just change the description



30
31
32
# File 'lib/mcollective/application.rb', line 30

def description(descr)
  self[:description] = descr
end

.exclude_argument_sections(*sections) ⇒ Object



58
59
60
61
62
63
64
65
66
67
# File 'lib/mcollective/application.rb', line 58

def exclude_argument_sections(*sections)
  sections = [sections].flatten

  sections.each do |s|
    raise "Unknown CLI argument section #{s}" unless ["rpc", "common", "filter"].include?(s)
  end

  intialize_application_options unless @application_options
  self[:exclude_arg_sections] = sections
end

.external(command) ⇒ Object

Executes an external program instead of implement the logic in ruby

Parameters:

  • command (Hash)

    the command to run

Options Hash (command):

  • :command (String)

    the command to run

  • :args (Array)

    arguments to pass to the command



39
40
41
# File 'lib/mcollective/application.rb', line 39

def external(command)
  self[:external] = command
end

.external_help(command) ⇒ Object

Executes an external program to show help instead of supplying options

Parameters:

  • command (Hash)

    the command to run

Options Hash (command):

  • :command (String)

    the command to run

  • :args (Array)

    arguments to pass to the command



48
49
50
# File 'lib/mcollective/application.rb', line 48

def external_help(command)
  self[:external_help] = command
end

.intialize_application_optionsObject

Creates an empty set of options



99
100
101
102
103
104
105
106
# File 'lib/mcollective/application.rb', line 99

def intialize_application_options
  @application_options = {:description => nil,
                          :usage => [],
                          :cli_arguments => [],
                          :exclude_arg_sections => [],
                          :external => nil,
                          :external_help => nil}
end

.option(name, arguments) ⇒ Object

Wrapper to create command line options

- name: varaible name that will be used to access the option value
- description: textual info shown in --help
- arguments: a list of possible arguments that can be used
  to activate this option
- type: a data type that ObjectParser understand of :bool or :array
- required: true or false if this option has to be supplied
- validate: a proc that will be called with the value used to validate
  the supplied value

 option :foo,
        :description => "The foo option"
        :arguments   => ["--foo ARG"]

after this the value supplied will be in configuration



85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/mcollective/application.rb', line 85

def option(name, arguments)
  opt = {:name => name,
         :description => nil,
         :arguments => [],
         :type => String,
         :required => false,
         :validate => proc { true }}

  arguments.each_pair {|k, v| opt[k] = v}

  self[:cli_arguments] << opt
end

.usage(usage) ⇒ Object

Supplies usage information, calling multiple times will create multiple usage lines in –help output



54
55
56
# File 'lib/mcollective/application.rb', line 54

def usage(usage)
  self[:usage] << usage
end

Instance Method Details

#application_cli_argumentsObject

Returns an array of all the arguments built using calls to optin



260
261
262
# File 'lib/mcollective/application.rb', line 260

def application_cli_arguments
  application_options[:cli_arguments]
end

#application_descriptionObject

Retrieve the current application description



247
248
249
# File 'lib/mcollective/application.rb', line 247

def application_description
  application_options[:description]
end

#application_failure(err, err_dest = $stderr) ⇒ Object

Handles failure, if we’re far enough in the initialization phase it will log backtraces if its in verbose mode only



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/mcollective/application.rb', line 266

def application_failure(err, err_dest=$stderr)
  # peole can use exit() anywhere and not get nasty backtraces as a result
  if err.is_a?(SystemExit)
    disconnect
    raise(err)
  end

  if options[:verbose]
    err_dest.puts "\nThe %s application failed to run: %s\n" % [Util.colorize(:bold, $0), Util.colorize(:red, err.to_s)]
  else
    err_dest.puts "\nThe %s application failed to run, use -v for full error backtrace details: %s\n" % [Util.colorize(:bold, $0), Util.colorize(:red, err.to_s)]
  end

  if options.nil? || options[:verbose]
    err.backtrace.first << Util.colorize(:red, "  <----")
    err_dest.puts "\n%s %s" % [Util.colorize(:red, err.to_s), Util.colorize(:bold, "(#{err.class})")]
    err.backtrace.each {|l| err_dest.puts "\tfrom #{l}"}
  end

  disconnect

  exit 1
end

#application_optionsObject

Retrieves the full hash of application options



242
243
244
# File 'lib/mcollective/application.rb', line 242

def application_options
  self.class.application_options
end

#application_parse_options(help = false) ⇒ Object

Builds an ObjectParser config, parse the CLI options and validates based on the option config



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/mcollective/application.rb', line 153

def application_parse_options(help=false)
  @options ||= {:verbose => false}

  @options = clioptions(help) do |parser, _options|
    parser.define_head application_description if application_description
    parser.banner = ""

    if application_usage
      parser.separator ""

      application_usage.each do |u|
        parser.separator "Usage: #{u}"
      end

      parser.separator ""
    end

    parser.separator "Application Options" unless application_cli_arguments.empty?

    parser.define_tail ""
    parser.define_tail "The Marionette Collective #{MCollective.version}"

    application_cli_arguments.each do |carg|
      opts_array = []

      opts_array << :on

      # if a default is set from the application set it up front
      configuration[carg[:name]] = carg[:default] if carg.include?(:default)

      # :arguments are multiple possible ones
      if carg[:arguments].is_a?(Array)
        carg[:arguments].each {|a| opts_array << a}
      else
        opts_array << carg[:arguments]
      end

      # type was given and its not one of our special types, just pass it onto optparse
      opts_array << carg[:type] if carg[:type] && ![:boolean, :bool, :array].include?(carg[:type])

      opts_array << carg[:description]

      # Handle our special types else just rely on the optparser to handle the types
      if [:bool, :boolean].include?(carg[:type])
        parser.send(*opts_array) do |v|
          validate_option(carg[:validate], carg[:name], v)

          configuration[carg[:name]] = v
        end

      elsif carg[:type] == :array
        parser.send(*opts_array) do |v|
          validate_option(carg[:validate], carg[:name], v)

          configuration[carg[:name]] = [] unless configuration.include?(carg[:name])
          configuration[carg[:name]] << v
        end

      else
        parser.send(*opts_array) do |v|
          validate_option(carg[:validate], carg[:name], v)

          configuration[carg[:name]] = v
        end
      end
    end
  end
end

#application_usageObject

Return the current usage text false if nothing is set



252
253
254
255
256
# File 'lib/mcollective/application.rb', line 252

def application_usage
  usage = application_options[:usage]

  usage.empty? ? false : usage
end

#clioptions(help) ⇒ Object

Creates a standard options hash, pass in a block to add extra headings etc see Optionparser



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/mcollective/application.rb', line 131

def clioptions(help)
  oparser = Optionparser.new({:verbose => false, :progress_bar => true}, "filter", application_options[:exclude_arg_sections])

  options = oparser.parse do |parser, opts|
    yield(parser, opts) if block_given?

    RPC::Helpers.add_simplerpc_options(parser, opts) unless application_options[:exclude_arg_sections].include?("rpc")
  end

  return oparser.parser.help if help

  validate_cli_options

  post_option_parser(configuration) if respond_to?(:post_option_parser)

  options
rescue Exception # rubocop:disable Lint/RescueException
  application_failure($!)
end

#configurationObject

The application configuration built from CLI arguments



110
111
112
113
# File 'lib/mcollective/application.rb', line 110

def configuration
  @application_configuration ||= {}
  @application_configuration
end

#disconnectObject



320
321
322
323
# File 'lib/mcollective/application.rb', line 320

def disconnect
  MCollective::PluginManager["connector_plugin"].disconnect
rescue # rubocop:disable Lint/SuppressedException
end

#external_helpObject



290
291
292
293
# File 'lib/mcollective/application.rb', line 290

def external_help
  ext = application_options[:external_help]
  exec(ext[:command], ext[:args])
end

#external_mainObject



325
326
327
328
329
330
331
# File 'lib/mcollective/application.rb', line 325

def external_main
  ext = application_options[:external]
  args = ext[:args] || []
  args.concat(ARGV)

  exec(ext[:command], *args)
end

#halt(stats) ⇒ Object

A helper that creates a consistent exit code for applications by looking at an instance of MCollective::RPC::Stats

Exit with 0 if nodes were discovered and all passed Exit with 0 if no discovery were done and > 0 responses were received, all ok Exit with 1 if no nodes were discovered Exit with 2 if nodes were discovered but some RPC requests failed Exit with 3 if nodes were discovered, but no responses received Exit with 4 if no discovery were done and no responses were received



376
377
378
# File 'lib/mcollective/application.rb', line 376

def halt(stats)
  exit(halt_code(stats))
end

#halt_code(stats) ⇒ Object



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/mcollective/application.rb', line 340

def halt_code(stats)
  request_stats = {:discoverytime => 0,
                   :discovered => 0,
                   :okcount => 0,
                   :failcount => 0}.merge(stats.to_hash)

  return 4 if request_stats[:discoverytime] == 0 && request_stats[:responses] == 0

  if request_stats[:discovered] > 0
    if request_stats[:responses] == 0
      return 3
    elsif request_stats[:failcount] > 0
      return 2
    end
  end

  if request_stats[:discovered] == 0
    if request_stats[:responses] && request_stats[:responses] > 0
      return 0
    else
      return 1
    end
  end

  0
end

#helpObject



295
296
297
298
299
# File 'lib/mcollective/application.rb', line 295

def help
  return external_help if application_options[:external_help]

  application_parse_options(true)
end

#mainObject

Fake abstract class that logs if the user tries to use an application without supplying a main override method.



335
336
337
338
# File 'lib/mcollective/application.rb', line 335

def main
  warn "Applications need to supply a 'main' method"
  exit 1
end

#rpcclient(agent, flags = {}) ⇒ Object

Wrapper around MC::RPC#rpcclient that forcably supplies our options hash if someone forgets to pass in options in an application the filters and other cli options wouldnt take effect which could have a disasterous outcome



383
384
385
386
387
388
# File 'lib/mcollective/application.rb', line 383

def rpcclient(agent, flags={})
  flags[:options] = options unless flags.include?(:options)
  flags[:exit_on_failure] = false

  super
end

#runObject

The main logic loop, builds up the options, validate configuration and calls the main as supplied by the user. Disconnects when done and pass any exception onto the application_failure helper



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/mcollective/application.rb', line 304

def run
  return external_main if application_options[:external]

  application_parse_options

  validate_configuration(configuration) if respond_to?(:validate_configuration)

  Util.setup_windows_sleeper if Util.windows?

  main

  disconnect
rescue Exception # rubocop:disable Lint/RescueException
  application_failure($!)
end

#validate_cli_optionsObject



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/mcollective/application.rb', line 222

def validate_cli_options
  # Check all required parameters were set
  validation_passed = true
  application_cli_arguments.each do |carg|
    # Check for required arguments
    next unless carg[:required]

    unless configuration[carg[:name]]
      validation_passed = false
      warn "The #{carg[:name]} option is mandatory"
    end
  end

  unless validation_passed
    warn "\nPlease run with --help for detailed help"
    exit 1
  end
end

#validate_option(blk, name, value) ⇒ Object

Calls the supplied block in an option for validation, an error raised will log to STDERR and exit the application



120
121
122
123
124
125
126
127
# File 'lib/mcollective/application.rb', line 120

def validate_option(blk, name, value)
  validation_result = blk.call(value)

  unless validation_result == true
    warn "Validation of #{name} failed: #{validation_result}"
    exit 1
  end
end