Module: Morpheus::Cli::CliCommand

Overview

Module to be included by every CLI command so that commands get registered

Defined Under Namespace

Modules: ClassMethods

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#no_promptObject (readonly)

this setting makes it easy for the called to disable prompting



22
23
24
# File 'lib/morpheus/cli/cli_command.rb', line 22

def no_prompt
  @no_prompt
end

Class Method Details

.included(klass) ⇒ Object



13
14
15
16
17
# File 'lib/morpheus/cli/cli_command.rb', line 13

def self.included(klass)
  klass.send :include, Morpheus::Cli::PrintHelper
  klass.extend ClassMethods
  Morpheus::Cli::CliRegistry.add(klass, klass.command_name)
end

Instance Method Details

#build_common_options(opts, options, includes = []) ⇒ Object

appends to the passed OptionParser all the generic options

Parameters:

  • opts (OptionParser)

    the option parser object being constructed

  • options (Hash)

    the output Hash that is to being modified

  • includes (Array) (defaults to: [])

    which options to include eg. :options, :json, :remote

Returns:

  • opts



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/morpheus/cli/cli_command.rb', line 100

def build_common_options(opts, options, includes=[])
  #opts.separator ""
  opts.separator "Common options:"
  includes = includes.clone
  while (option_key = includes.shift) do
    case option_key.to_sym

    when :account
      opts.on('-a','--account ACCOUNT', "Account Name") do |val|
        options[:account_name] = val
      end
      opts.on('-A','--account-id ID', "Account ID") do |val|
        options[:account_id] = val
      end

    when :options
      options[:options] ||= {}
      opts.on( '-O', '--option OPTION', "Option in the format var=\"value\"" ) do |option|
        # todo: look ahead and parse ALL the option=value args after -O switch
        #custom_option_args = option.split('=')
        custom_option_args = option.sub(/\s?\=\s?/, '__OPTION_DELIM__').split('__OPTION_DELIM__')
        custom_options = options[:options]
        option_name_args = custom_option_args[0].split('.')
        if option_name_args.count > 1
          nested_options = custom_options
          option_name_args.each_with_index do |name_element,index|
            if index < option_name_args.count - 1
              nested_options[name_element] = nested_options[name_element] || {}
              nested_options = nested_options[name_element]
            else
              nested_options[name_element] = custom_option_args[1]
            end
          end
        else
          custom_options[custom_option_args[0]] = custom_option_args[1]
        end
        options[:options] = custom_options
      end
      opts.on('-N','--no-prompt', "Skip prompts. Use default values for all optional fields.") do |val|
        options[:no_prompt] = true
        # ew, stored in here for now because options[:options] is what is passed into OptionTypes.prompt() everywhere!
        options[:options] ||= {}
        options[:options][:no_prompt] = true
      end

    when :list
      opts.on( '-m', '--max MAX', "Max Results" ) do |max|
        options[:max] = max.to_i
      end

      opts.on( '-o', '--offset OFFSET', "Offset Results" ) do |offset|
        options[:offset] = offset.to_i
      end

      opts.on( '-s', '--search PHRASE', "Search Phrase" ) do |phrase|
        options[:phrase] = phrase
      end

      opts.on( '-S', '--sort ORDER', "Sort Order" ) do |v|
        options[:sort] = v
      end

      opts.on( '-D', '--desc', "Reverse Sort Order" ) do |v|
        options[:direction] = "desc"
      end

    when :remote

      # this is the only option now... 
      # first, you must do `remote use [appliance]`
      opts.on( '-r', '--remote REMOTE', "Remote Appliance Name to use for this command. The active appliance is used by default." ) do |val|
        options[:remote] = val
      end

      # todo: also require this for talking to plain old HTTP
      opts.on('-I','--insecure', "Allow for insecure HTTPS communication i.e. bad SSL certificate") do |val|
        Morpheus::RestClient.enable_ssl_verification = false
      end

      # skipping the rest of this for now..

      next

      # opts.on( '-r', '--remote REMOTE', "Remote Appliance" ) do |remote|
      #   options[:remote] = remote
      # end

      opts.on( '-U', '--url REMOTE', "API Url" ) do |remote|
        options[:remote_url] = remote
      end

      opts.on( '-u', '--username USERNAME', "Username" ) do |remote|
        options[:remote_username] = remote
      end

      opts.on( '-p', '--password PASSWORD', "Password" ) do |remote|
        options[:remote_password] = remote
      end

      opts.on( '-T', '--token ACCESS_TOKEN', "Access Token" ) do |remote|
        options[:remote_token] = remote
      end
      
    when :auto_confirm
      opts.on( '-y', '--yes', "Auto Confirm" ) do
        options[:yes] = true
      end
    
    when :json
      opts.on('-j','--json', "JSON Output") do
        options[:json] = true
      end

    when :dry_run
      opts.on('-d','--dry-run', "Dry Run, print the API request instead of executing it") do
        options[:dry_run] = true
      end

    when :quiet
      opts.on('-q','--quiet', "No Output, when successful") do
        options[:quiet] = true
      end

    else
      raise "Unknown common_option key: #{option_key}"
    end
  end

  # options that are always included

  # disable ANSI coloring
  opts.on('-C','--nocolor', "Disable ANSI coloring") do
    Term::ANSIColor::coloring = false
  end

  opts.on('-V','--debug', "Print extra output for debugging. ") do
    options[:debug] = true
    Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
    ::RestClient.log = Morpheus::Logging.debug? ? STDOUT : nil
    # perhaps...
    # create a new logger instance just for this command instance
    # this way we don't elevate the global level for subsequent commands in a shell
    # @logger = Morpheus::Logging::Logger.new(STDOUT)
    # if [email protected]?
    #   @logger.log_level = Morpheus::Logging::Logger::DEBUG
    # end
  end

  opts.on('-h', '--help', "Prints this help" ) do
    puts opts
    exit
  end
  opts
end

#build_option_type_options(opts, options, option_types = []) ⇒ Object

Appends Array of OptionType definitions to an OptionParser instance This adds an option like –fieldContext.fieldName=“VALUE”

Parameters:

  • opts (OptionParser)
  • options (Hash)

    output map that is being constructed

  • option_types (Array) (defaults to: [])

    list of OptionType definitions to add

Returns:

  • void, this modifies the opts in place.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/morpheus/cli/cli_command.rb', line 41

def build_option_type_options(opts, options, option_types=[])
  #opts.separator ""
  #opts.separator "Options:"
  options[:options] ||= {} # this is where these go..for now
  custom_options = options[:options]
  
  # add each one to the OptionParser
  option_types.each do |option_type|
    field_namespace = []
    field_name = option_type['fieldName'].to_s
    if field_name.empty?
      puts "Missing fieldName for option type: #{option_type}" if Morpheus::Logging.debug?
      next
    end
    
    if !option_type['fieldContext'].to_s.empty?
      option_type['fieldContext'].split(".").each do |ns|
        field_namespace << ns
      end
    end
    
    full_field_name = field_name
    if !field_namespace.empty?
      full_field_name = "#{field_namespace.join('.')}.#{field_name}"
    end

    description = "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{option_type['defaultValue'] ? ' Default: '+option_type['defaultValue'].to_s : ''}"
    # description = option_type['description'].to_s
    # if option_type['defaultValue']
    #   description = "#{description} Default: #{option_type['defaultValue']}"
    # end
    # if option_type['required'] == true
    #   description = "(Required) #{description}"
    # end
    
    opts.on("--#{full_field_name} VALUE", String, description) do |val|
      cur_namespace = custom_options
      field_namespace.each do |ns|
        next if ns.empty?
        cur_namespace[ns.to_s] ||= {}
        cur_namespace = cur_namespace[ns.to_s]
      end
      cur_namespace[field_name] = val
    end

    # todo: all the various types
    # number 
    # checkbox [on|off]
    # select for optionSource and selectOptions

  end
  opts
end

#command_nameObject



255
256
257
# File 'lib/morpheus/cli/cli_command.rb', line 255

def command_name
  self.class.command_name
end

#default_subcommandObject



267
268
269
# File 'lib/morpheus/cli/cli_command.rb', line 267

def default_subcommand
  self.class.default_subcommand
end

#establish_remote_appliance_connection(options) ⇒ Object

This supports the simple remote option eg. ‘instances add –remote “qa”` It will establish a connection to the pre-configured appliance named “qa” The calling command can populate @appliances and/or @appliance_name Otherwise, the current active appliance is used… This returns a new instance of Morpheus::APIClient (and sets @access_token, and @appliance) Your command should be ready to make api requests after this. todo: probably don’t exit here, just return nil or raise



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
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/morpheus/cli/cli_command.rb', line 341

def establish_remote_appliance_connection(options)
  @appliances ||= ::Morpheus::Cli::Remote.appliances
  if !@appliance_name
    @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
  end

  # todo: support old way of accepting --username and --password on the command line
  # it's probably better not to do that tho, just so it stays out of history files

  # use a specific remote appliance by name
  if options[:remote]
    @appliance_name = options[:remote].to_sym rescue nil
    if !@appliances
      print_red_alert "You have no appliances configured. See the `remote add` command."
      exit 1
    end
    found_appliance = @appliances[@appliance_name.to_sym]
    if !found_appliance
      print_red_alert "You have no appliance named '#{@appliance_name}' configured. See the `remote add` command."
      exit 1
    end
    @appliance = found_appliance
    @appliance_name = @appliance_name
    @appliance_url = @appliance[:host]
    if options[:debug]
      print dark,"# => Using remote appliance [#{@appliance_name}] #{@appliance_url}",reset,"\n" if !options[:quiet]
    end
  else
    #@appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
  end

  if !@appliance_name
    print_red_alert "You must specify a remote appliance with --remote, or see the `remote use` command."
    exit 1
  end

  puts "#{dark} #=> establishing connection to [#{@appliance_name}] #{@appliance_url}#{reset}\n" if options[:debug]

  # ok, get some credentials.
  # this prompts for username, password  without options[:no_prompt]
  # used saved credentials please
  @api_credentials = Morpheus::Cli::Credentials.new(@appliance_name, @appliance_url)
  @access_token = @api_credentials.load_saved_credentials()
  if @access_token.to_s.empty?
    unless options[:no_prompt]
      @access_token = @api_credentials.request_credentials(options)
    end
  end

  # bail if we got nothing still
  unless options[:skip_verify_access_token]
    verify_access_token!
  end

  # ok, connect to the appliance.. actually this just instantiates an ApiClient
  api_client = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url)
  @api_client = api_client # meh, just return w/o setting instance attrs
  return api_client
end

#handle(args) ⇒ Object



330
331
332
# File 'lib/morpheus/cli/cli_command.rb', line 330

def handle(args)
  raise "#{self} has not defined handle()!"
end

#handle_subcommand(args) ⇒ Object

a default handler



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/morpheus/cli/cli_command.rb', line 301

def handle_subcommand(args)
  commands = subcommands
  if subcommands.empty?
    raise "#{self.class} has no available subcommands"
  end
  # meh, could deprecate and make subcommand define handle() itself
  # if args.count == 0 && default_subcommand
  #   # p "using default subcommand #{default_subcommand}"
  #   return self.send(default_subcommand, args || [])
  # end
  subcommand_name = args[0]
  if args[0] == "-h" || args[0] == "--help" || args[0] == "help"
    print_usage
    exit
  end
  if subcommand_aliases[subcommand_name]
    subcommand_name = subcommand_aliases[subcommand_name]
  end
  cmd_method = subcommands[subcommand_name]
  if subcommand_name && !cmd_method
    puts "unknown command '#{self.command_name} #{subcommand_name}'"
  end
  if !cmd_method
    print_usage
    exit 127
  end
  self.send(cmd_method, args[1..-1])
end

#interactive?Boolean

whether to prompt or not, this is true by default.

Returns:

  • (Boolean)


31
32
33
# File 'lib/morpheus/cli/cli_command.rb', line 31

def interactive?
  @no_prompt != true
end

#noninteractiveObject

disabled prompting for this command



25
26
27
28
# File 'lib/morpheus/cli/cli_command.rb', line 25

def noninteractive()
  @no_prompt = true
  self
end


290
291
292
293
294
295
296
297
298
# File 'lib/morpheus/cli/cli_command.rb', line 290

def print_usage()
  puts usage
  if !subcommands.empty?
    puts "Commands:"
    subcommands.sort.each {|cmd, method|
      puts "\t#{cmd.to_s}"
    }
  end
end

#subcommand_aliasesObject



263
264
265
# File 'lib/morpheus/cli/cli_command.rb', line 263

def subcommand_aliases
  self.class.subcommand_aliases
end

#subcommand_usage(*extra) ⇒ Object



279
280
281
282
283
284
285
286
287
288
# File 'lib/morpheus/cli/cli_command.rb', line 279

def subcommand_usage(*extra)
  calling_method = caller[0][/`([^']*)'/, 1].to_s.sub('block in ', '')
  subcommand_name = subcommands.key(calling_method)
  extra = extra.flatten
  if !subcommand_name.empty? && extra.first == subcommand_name
    extra.shift
  end
  #extra = ["[options]"] if extra.empty?
  "Usage: morpheus #{command_name} #{subcommand_name} #{extra.join(' ')}".squeeze(' ').strip
end

#subcommandsObject



259
260
261
# File 'lib/morpheus/cli/cli_command.rb', line 259

def subcommands
  self.class.subcommands
end

#usageObject



271
272
273
274
275
276
277
# File 'lib/morpheus/cli/cli_command.rb', line 271

def usage
  if !subcommands.empty?
    "Usage: morpheus #{command_name} [command] [options]"
  else
    "Usage: morpheus #{command_name} [options]"
  end
end

#verify_access_token!Object



401
402
403
404
405
406
407
# File 'lib/morpheus/cli/cli_command.rb', line 401

def verify_access_token!
  if @access_token.empty?
    print_red_alert "Invalid Credentials. Unable to acquire access token. Please verify your credentials and try again."
    exit 1
  end
  true
end